From 8197d0ac7eb3d4b7af0f36eb0dd5efd8b22f9462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?dj8yf0=CE=BCl?= Date: Fri, 18 Aug 2023 22:04:07 +0300 Subject: [PATCH] doc: `near-sdk-rs` migration guide to `1.0.0` --- .../v0.9_to_v1.0.0_near_sdk_rs.md | 573 ++++++++++++++++++ 1 file changed, 573 insertions(+) create mode 100644 docs/migration_guides/v0.9_to_v1.0.0_near_sdk_rs.md diff --git a/docs/migration_guides/v0.9_to_v1.0.0_near_sdk_rs.md b/docs/migration_guides/v0.9_to_v1.0.0_near_sdk_rs.md new file mode 100644 index 000000000..8c8a21d3b --- /dev/null +++ b/docs/migration_guides/v0.9_to_v1.0.0_near_sdk_rs.md @@ -0,0 +1,573 @@ +# upgrade `v0.9` -> `v1.0.0` `near-sdk-rs` upgrade `migration guide` + +The link to `nearcore` pr is [chore: borsh version update](https://github.com/near/near-sdk-rs/pull/1075) + +Steps: + +## 1. update dependencies in `near-sdk/Cargo.toml`. + +First we update to `1.0.0-alpha.5` version, which contains [deprecation](https://github.com/near/borsh-rs/pull/206) of `BorshSerialize::try_to_vec` method. + +We enable `derive` feature by default, and make `schema` feature optional, enabled +depending on whether `abi` feature of `near-sdk` package is enabled or not. + +```diff +diff --git a/near-sdk/Cargo.toml b/near-sdk/Cargo.toml +index a015a64..e6099d4 100644 +--- a/near-sdk/Cargo.toml ++++ b/near-sdk/Cargo.toml +@@ -26,3 +26,3 @@ near-sys = { path = "../near-sys", version = "0.2" } + base64 = "0.13" +-borsh = { version = "0.9", features = ["const-generics"] } ++borsh = { version = "1.0.0-alpha.5", features = ["derive"] } + bs58 = "0.4" +@@ -35,3 +35,4 @@ once_cell = { version = "1.17", default-features = false } +@@ -58,3 +59,3 @@ unstable = [] + legacy = [] +-abi = ["near-abi", "schemars", "near-sdk-macros/abi"] ++abi = ["borsh/schema", "near-abi", "schemars", "near-sdk-macros/abi"] + unit-testing = ["near-vm-logic", "near-primitives-core", "near-primitives", "near-crypto"] +``` + +## 2. We receive a great number of deprecation warnings of `borsh::BorshSerialize::try_to_vec` method (`near-sdk` package): + +```bash + 2 warning: use of deprecated method `borsh::BorshSerialize::try_to_vec`: use `borsh::to_vec(&object)` instead + --> near-sdk/src/store/lazy/mod.rs:43:28 + | + 43 | let serialized = value.try_to_vec().unwrap_or_else(|_| env::panic_str(ERR_VALUE_SERIALIZATION)); + | ^^^^^^^^^^ + | + = note: `#[warn(deprecated)]` on by default +``` + +We choose to fix it at once, as this method is removed in `1.0.0-alpha.5` -> `1.0.0` transition completely +with following diff: + +```diff +diff --git a/near-sdk/src/store/lazy/mod.rs b/near-sdk/src/store/lazy/mod.rs +index 7df7ee4..42112ea 100644 +--- a/near-sdk/src/store/lazy/mod.rs ++++ b/near-sdk/src/store/lazy/mod.rs +@@ -8,3 +8,3 @@ mod impls; + +-use borsh::{BorshDeserialize, BorshSerialize}; ++use borsh::{BorshDeserialize, BorshSerialize, to_vec}; + use once_cell::unsync::OnceCell; +@@ -42,3 +42,3 @@ where + { +- let serialized = value.try_to_vec().unwrap_or_else(|_| env::panic_str(ERR_VALUE_SERIALIZATION)); ++ let serialized = to_vec(value).unwrap_or_else(|_| env::panic_str(ERR_VALUE_SERIALIZATION)); + env::storage_write(key, &serialized); +... +... +``` + +where `value` is `&T`, where `T: BorshSerialize`. + +## 3. We replace the usage of `BorshSchema::schema_container` method (`near-sdk-macros` package) + +To prevent compilation errors in the future, we grep for `schema_container` string. +`schema_container` was changed from being a `BorshSchema` trait method to being a function, external +to the trait in [chore!: make BorshSchema::{add_definition,schema_container} free-standing funcs](https://github.com/near/borsh-rs/pull/204) + +We fix code, generated with `near_bindgen` procedural macro, with following diff: + + +```diff +diff --git a/near-sdk-macros/src/core_impl/abi/abi_generator.rs b/near-sdk-macros/src/core_impl/abi/abi_generator.rs +index cbe659a..994e63c 100644 +--- a/near-sdk-macros/src/core_impl/abi/abi_generator.rs ++++ b/near-sdk-macros/src/core_impl/abi/abi_generator.rs +@@ -239,21 +239,21 @@ impl ImplItemMethodInfo { + } + } + } + + fn generate_schema(ty: &Type, serializer_type: &SerializerType) -> TokenStream2 { + match serializer_type { + SerializerType::JSON => quote! { + gen.subschema_for::<#ty>() + }, + SerializerType::Borsh => quote! { +- <#ty as ::near_sdk::borsh::BorshSchema>::schema_container() ++ ::near_sdk::borsh::schema_container_of::<#ty>() + }, + } + } + + fn generate_abi_type(ty: &Type, serializer_type: &SerializerType) -> TokenStream2 { + let schema = generate_schema(ty, serializer_type); + match serializer_type { + SerializerType::JSON => quote! { + ::near_sdk::__private::AbiType::Json { + type_schema: #schema, +``` + + + +## 4. next we encounter error with `#[borsh(use_discriminant=)]` (`near-sdk` package): + +```bash + 1 error: You have to specify `#[borsh(use_discriminant=true)]` or `#[borsh(use_discriminant=false)]` for all enums with explicit discriminant + --> near-sdk/src/types/public_key.rs:8:10 + | + 8 | pub enum CurveType { + | ^^^^^^^^^ + +``` +on + +```rust +/// PublicKey curve +#[derive(Debug, Clone, Copy, PartialOrd, Ord, Eq, PartialEq, BorshDeserialize, BorshSerialize)] +#[repr(u8)] +pub enum CurveType { + ED25519 = 0, + SECP256K1 = 1, +} +``` + +We fix it with `#[borsh(use_discriminant=true)]`, which will behave the same as `#[borsh(use_discriminant=false)]` +in this particular case, where `false` preserves the behaviour of borsh before 1.0 release +(borsh 0.10 and older [ignored explicit discriminant values in enum definitions](https://github.com/near/borsh-rs/issues/137)): + +```diff +diff --git a/near-sdk/src/types/public_key.rs b/near-sdk/src/types/public_key.rs +index 30ebd43..b539ddd 100644 +--- a/near-sdk/src/types/public_key.rs ++++ b/near-sdk/src/types/public_key.rs +@@ -7,2 +7,3 @@ use std::convert::TryFrom; + #[repr(u8)] ++#[borsh(use_discriminant=true)] + pub enum CurveType { +@@ -144,4 +145,4 @@ impl serde::Serialize for PublicKey { +``` + + +## 5. next we encounter errors with `borsh::maybestd` imports (`near-sdk` package): + +```bash + 1 error[E0432]: unresolved import `borsh::maybestd` + --> near-sdk/src/types/public_key.rs:1:13 + | + 1 | use borsh::{maybestd::io, BorshDeserialize, BorshSerialize}; + | ^^^^^^^^ could not find `maybestd` in `borsh` + + 2 error[E0432]: unresolved import `borsh::maybestd` + --> near-sdk/src/types/account_id.rs:1:13 + | + 1 | use borsh::{maybestd::io, BorshDeserialize, BorshSchema, BorshSerialize}; + | ^^^^^^^^ could not find `maybestd` in `borsh` + +``` +```rust +// near-sdk/src/types/public_key.rs +impl BorshDeserialize for PublicKey { + fn deserialize(buf: &mut &[u8]) -> io::Result { + as BorshDeserialize>::deserialize(buf).and_then(|s| { + Self::try_from(s).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) + }) + } +} +``` + +`maybestd` [has moved](https://github.com/near/borsh-rs/pull/171) to a `__private` package in `borsh`, and is not supposed to be +accessed directly now besides from within code, derived in `borsh` traits implementations. + +As `near-sdk` crate is not supposed to be used in `no_std` context, we can +replace imports with `std::io`: + +```diff +diff --git a/near-sdk/src/types/account_id.rs b/near-sdk/src/types/account_id.rs +index a338b5c..7876d77 100644 +--- a/near-sdk/src/types/account_id.rs ++++ b/near-sdk/src/types/account_id.rs +@@ -1,5 +1,5 @@ +-use borsh::{maybestd::io, BorshDeserialize, BorshSchema, BorshSerialize}; ++use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; + use serde::{de, Deserialize, Serialize}; + use std::convert::TryFrom; +-use std::fmt; ++use std::{fmt, io}; + +diff --git a/near-sdk/src/types/public_key.rs b/near-sdk/src/types/public_key.rs +index 10175a0..4280f70 100644 +--- a/near-sdk/src/types/public_key.rs ++++ b/near-sdk/src/types/public_key.rs +@@ -1,4 +1,4 @@ +-use borsh::{maybestd::io, BorshDeserialize, BorshSerialize}; ++use borsh::{BorshDeserialize, BorshSerialize}; + use bs58::decode::Error as B58Error; +-use std::convert::TryFrom; ++use std::{convert::TryFrom, io}; +``` + +Otherwise, if we intended to support [both `std` and `no_std`](https://github.com/near/borsh-rs/pull/212), we would've imported from `borsh::io`: + +```diff +-use borsh::{maybestd::io, BorshDeserialize, BorshSerialize}; ++use borsh::{BorshDeserialize, BorshSerialize}; ++use borsh::io; +``` + +## 6. next we encounter a large number of similar syntax errors with `borsh_skip` (`near-sdk` package): + +```bash + 1 error: cannot find attribute `borsh_skip` in this scope + --> near-sdk/src/store/lookup_map/mod.rs:89:7 + | + 89 | #[borsh_skip] + | ^^^^^^^^^^ + +``` + +We change all of these occurencies according to [new](https://github.com/near/borsh-rs/pull/192) +`#[borsh(skip)]` syntax. The following diff is shortened to first and last +occurencies: + +```diff +diff --git a/near-sdk/src/collections/lazy_option.rs b/near-sdk/src/collections/lazy_option.rs +index 04e79fb..f4ea0dc 100644 +--- a/near-sdk/src/collections/lazy_option.rs ++++ b/near-sdk/src/collections/lazy_option.rs +@@ -19,3 +19,3 @@ pub struct LazyOption { + storage_key: Vec, +- #[borsh_skip] ++ #[borsh(skip)] + el: PhantomData, +... +diff --git a/near-sdk/src/store/lookup_set/mod.rs b/near-sdk/src/store/lookup_set/mod.rs +index 762956a..b2d1ac0 100644 +--- a/near-sdk/src/store/lookup_set/mod.rs ++++ b/near-sdk/src/store/lookup_set/mod.rs +@@ -54,3 +54,3 @@ where + +- #[borsh_skip] ++ #[borsh(skip)] + hasher: PhantomData (T, H)>, +``` + +## 7. next there's a bunch of similar errors with `borsh::maybestd::io` imports (`near-sdk` package): + +They're fixed in a similar way as in 5. + +## 8. next there's a bunch of similar errors due to `BorshDeserialize` trait signature change (`near-sdk` package): + +```bash + 1 error[E0046]: not all trait items implemented, missing: `deserialize_reader` + --> near-sdk/src/store/vec/mod.rs:138:1 + | + 138 | impl BorshDeserialize for Vector + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `deserialize_reader` in implementation + + = help: implement the missing item: `fn deserialize_reader(_: &mut R) -> std::result::Result where R: std::io::Read { todo!() } +``` + +The signature of trait has changed on 0.9.3 -> 0.10.0 transition in [implement deserialize_reader](https://github.com/near/borsh-rs/pull/116). +We fix it the following way: + +```diff +diff --git a/near-sdk/src/store/free_list/mod.rs b/near-sdk/src/store/free_list/mod.rs +index 43d8908..20a1cc7 100644 +--- a/near-sdk/src/store/free_list/mod.rs ++++ b/near-sdk/src/store/free_list/mod.rs +@@ -47,7 +47,7 @@ where + { +- fn deserialize(buf: &mut &[u8]) -> Result { ++ fn deserialize_reader(reader: &mut R) -> Result { + Ok(Self { +- first_free: BorshDeserialize::deserialize(buf)?, +- occupied_count: BorshDeserialize::deserialize(buf)?, +- elements: BorshDeserialize::deserialize(buf)?, ++ first_free: BorshDeserialize::deserialize_reader(reader)?, ++ occupied_count: BorshDeserialize::deserialize_reader(reader)?, ++ elements: BorshDeserialize::deserialize_reader(reader)?, + }) +diff --git a/near-sdk/src/store/unordered_map/mod.rs b/near-sdk/src/store/unordered_map/mod.rs +index 5decc60..d82a8aa 100644 +--- a/near-sdk/src/store/unordered_map/mod.rs ++++ b/near-sdk/src/store/unordered_map/mod.rs +@@ -117,6 +117,6 @@ where + { +- fn deserialize(buf: &mut &[u8]) -> Result { ++ fn deserialize_reader(reader: &mut R) -> Result { + Ok(Self { +- keys: BorshDeserialize::deserialize(buf)?, +- values: BorshDeserialize::deserialize(buf)?, ++ keys: BorshDeserialize::deserialize_reader(reader)?, ++ values: BorshDeserialize::deserialize_reader(reader)?, + }) +diff --git a/near-sdk/src/store/vec/mod.rs b/near-sdk/src/store/vec/mod.rs +index 9d19614..94127ba 100644 +--- a/near-sdk/src/store/vec/mod.rs ++++ b/near-sdk/src/store/vec/mod.rs +@@ -141,6 +141,6 @@ where + { +- fn deserialize(buf: &mut &[u8]) -> Result { ++ fn deserialize_reader(reader: &mut R) -> Result { + Ok(Self { +- len: BorshDeserialize::deserialize(buf)?, +- values: BorshDeserialize::deserialize(buf)?, ++ len: BorshDeserialize::deserialize_reader(reader)?, ++ values: BorshDeserialize::deserialize_reader(reader)?, + }) +diff --git a/near-sdk/src/types/account_id.rs b/near-sdk/src/types/account_id.rs +index 7876d77..3da417a 100644 +--- a/near-sdk/src/types/account_id.rs ++++ b/near-sdk/src/types/account_id.rs +@@ -88,4 +88,4 @@ impl<'de> Deserialize<'de> for AccountId { + impl BorshDeserialize for AccountId { +- fn deserialize(buf: &mut &[u8]) -> io::Result { +- ::deserialize(buf).and_then(|s| { ++ fn deserialize_reader(reader: &mut R) -> io::Result { ++ ::deserialize_reader(reader).and_then(|s| { + Self::try_from(s).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) +diff --git a/near-sdk/src/types/public_key.rs b/near-sdk/src/types/public_key.rs +index 4280f70..b539ddd 100644 +--- a/near-sdk/src/types/public_key.rs ++++ b/near-sdk/src/types/public_key.rs +@@ -145,4 +145,4 @@ impl serde::Serialize for PublicKey { + impl BorshDeserialize for PublicKey { +- fn deserialize(buf: &mut &[u8]) -> io::Result { +- as BorshDeserialize>::deserialize(buf).and_then(|s| { ++ fn deserialize_reader(reader: &mut R) -> io::Result { ++ as BorshDeserialize>::deserialize_reader(reader).and_then(|s| { + Self::try_from(s).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) +diff --git a/near-sdk/src/collections/unordered_map/mod.rs b/near-sdk/src/collections/unordered_map/mod.rs +index d3ba8d5..aab31a4 100644 +--- a/near-sdk/src/collections/unordered_map/mod.rs ++++ b/near-sdk/src/collections/unordered_map/mod.rs +@@ -512,5 +512,5 @@ mod tests { + impl BorshDeserialize for DeserializeCounter { +- fn deserialize(buf: &mut &[u8]) -> std::io::Result { ++ fn deserialize_reader(reader: &mut R) -> std::io::Result { + DES_COUNT.fetch_add(1, Ordering::SeqCst); +- u64::deserialize(buf).map(DeserializeCounter) ++ u64::deserialize_reader(reader).map(DeserializeCounter) + } +``` + +## 9. next we encounter an error with `BorshDeserialize` trait derivation (`near-sdk` package): + +```bash + 6 error[E0277]: the trait bound `T: Default` is not satisfied + --> near-sdk/src/store/vec/mod.rs:145:21 + | + 145 | values: BorshDeserialize::deserialize_reader(reader)?, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Default` is not implemented for `T` + | + note: required for `IndexMap` to implement `BorshDeserialize` + --> near-sdk/src/store/index_map.rs:12:26 + | + 12 | #[derive(BorshSerialize, BorshDeserialize)] + | ^^^^^^^^^^^^^^^^ unsatisfied trait bound introduced in this `derive` macro + 13 | pub(crate) struct IndexMap + | ^^^^^^^^^^^ + = note: this error originates in the derive macro `BorshDeserialize` (in Nightly builds, run with -Z macro-backtrace for more info) + help: consider further restricting this bound + | + 140 | T: BorshSerialize + std::default::Default, + | +++++++++++++++++++++++ +``` + +where `IndexMap` looks like the following: + +```rust +#[derive(BorshSerialize, BorshDeserialize)] +pub(crate) struct IndexMap +where + T: BorshSerialize, +{ + pub(crate) prefix: Box<[u8]>, + /// Cache for loads and intermediate changes to the underlying index map. + /// The cached entries are wrapped in a [`Box`] to avoid existing pointers from being + /// invalidated. + /// + /// Note: u32 indices are used over usize to have consistent functionality across architectures. + /// Some functionality would be different from tests to Wasm if exceeding 32-bit length. + #[borsh(skip)] + pub(crate) cache: StableMap>>, +} +``` + +On version change `v0.9` -> `v1.0.0-alpha.5` bounds derivation in `borsh` [has changed](https://github.com/near/borsh-rs/pull/178): + +From bounds on the types of the fields: + +```rust +// cd near-sdk; cargo expand ::store::index_map +impl borsh::de::BorshDeserialize for IndexMap +where + T: BorshSerialize, + Box<[u8]>: borsh::BorshDeserialize, +{ + fn deserialize( + buf: &mut &[u8], + ) -> ::core::result::Result { + Ok(Self { + prefix: borsh::BorshDeserialize::deserialize(buf)?, + cache: Default::default(), + }) + } +} +``` + +to bounds on type parameters, encountered in fields. `borsh::de::BorshDeserialize` bound +for parameters in non-skipped fields, `core::default::Default` bound - otherwise: + +```rust +impl borsh::de::BorshDeserialize for IndexMap +where + T: BorshSerialize, + T: core::default::Default, +{ + fn deserialize_reader( + reader: &mut R, + ) -> ::core::result::Result { + Ok(Self { + prefix: borsh::BorshDeserialize::deserialize_reader(reader)?, + cache: core::default::Default::default(), + }) + } +} +``` + +We can instruct `borsh` to [replace automatically derived bound](https://github.com/near/borsh-rs/pull/180) with nothing, as `StableMap` has a `impl Default for StableMap` +implementation of its own, as it will be used when deserializing skipped field, irrelevant of bounds on `V`: + +```diff +diff --git a/near-sdk/src/store/index_map.rs b/near-sdk/src/store/index_map.rs +index 834fc98..7d1df75 100644 +--- a/near-sdk/src/store/index_map.rs ++++ b/near-sdk/src/store/index_map.rs +@@ -23,3 +23,3 @@ where + /// Some functionality would be different from tests to Wasm if exceeding 32-bit length. +- #[borsh(skip)] ++ #[borsh(skip, bound(deserialize = ""))] + pub(crate) cache: StableMap>>, +``` + +which would transform into following bould on trait's implementation: + +```rust +// line with `T: core::default::Default,` dissappeared +impl borsh::de::BorshDeserialize for IndexMap +where + T: BorshSerialize, +{ +... +``` + +Similar diffs were also applied here: + +```diff +diff --git a/near-sdk/src/store/lookup_map/mod.rs b/near-sdk/src/store/lookup_map/mod.rs +index 0b20345..927b2d6 100644 +--- a/near-sdk/src/store/lookup_map/mod.rs ++++ b/near-sdk/src/store/lookup_map/mod.rs +@@ -88,3 +88,3 @@ where + /// invalidated. +- #[borsh(skip)] ++ #[borsh(skip, bound(deserialize = ""))] + cache: StableMap>, +``` + +```diff +diff --git a/near-sdk/src/store/unordered_set/mod.rs b/near-sdk/src/store/unordered_set/mod.rs +index 4504580..77621b9 100644 +--- a/near-sdk/src/store/unordered_set/mod.rs ++++ b/near-sdk/src/store/unordered_set/mod.rs +@@ -83,9 +83,11 @@ pub struct UnorderedSet + where + T: BorshSerialize + Ord, + H: ToKey, + { ++ #[borsh(bound(serialize = "", deserialize = ""))] + elements: FreeList, ++ #[borsh(bound(serialize = "", deserialize = ""))] + index: LookupMap, + } +``` + + +## 10. next we encounter an error with `BorshSchema` trait derivation (`near-sdk` package): + +```bash + 4 error[E0053]: method `add_definitions_recursively` has an incompatible type for trait + --> near-sdk/src/promise.rs:232:22 + | + 232 | definitions: &mut HashMap, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | expected `BTreeMap`, found `HashMap` + | help: change the parameter type to match the trait: `&mut BTreeMap` + | + = note: expected signature `fn(&mut BTreeMap)` + found signature `fn(&mut HashMap)` +``` + +Signature in trait's method [has changed](https://github.com/near/borsh-rs/pull/165/): + +```diff +diff --git a/near-sdk/src/promise.rs b/near-sdk/src/promise.rs +index f8afe56..a430568 100644 +--- a/near-sdk/src/promise.rs ++++ b/near-sdk/src/promise.rs +@@ -2,3 +2,3 @@ use borsh::BorshSchema; + use std::cell::RefCell; +-use std::collections::HashMap; ++use std::collections::BTreeMap; + use std::io::{Error, Write}; +@@ -231,3 +231,3 @@ impl BorshSchema for Promise { + fn add_definitions_recursively( +- definitions: &mut HashMap, ++ definitions: &mut BTreeMap, + ) { +@@ -576,3 +576,3 @@ where + fn add_definitions_recursively( +- definitions: &mut HashMap, ++ definitions: &mut BTreeMap, + ) { +``` + +## 11. next we encounter an error with both `BorshSerialize` and `BorshDeserialize` traits' derivation (`near-contract-standards` package): + +```bash + 1 error: proc-macro derive panicked + --> near-contract-standards/src/fungible_token/core_impl.rs:27:10 + | + 27 | #[derive(BorshDeserialize, BorshSerialize)] + | ^^^^^^^^^^^^^^^^ + | + = help: message: called `Result::unwrap()` on an `Err` value: CrateNotFound { crate_name: "borsh", path: "/home/user/Documents/code/near-sdk-rs/near-contract-standards/Cargo.toml" } + + 2 error: proc-macro derive panicked + --> near-contract-standards/src/fungible_token/core_impl.rs:27:28 + | + 27 | #[derive(BorshDeserialize, BorshSerialize)] + | ^^^^^^^^^^^^^^ + | + = help: message: called `Result::unwrap()` on an `Err` value: CrateNotFound { crate_name: "borsh", path: "/home/user/Documents/code/near-sdk-rs/near-contract-standards/Cargo.toml" } +``` + +Thing is, `borsh` [has started getting into a `panic`](https://github.com/near/borsh-rs/pull/149) when using [proc-macro-crate](https://crates.io/crates/proc-macro-crate) dependency for derives, +in the cases when `borsh` is not imported as direct dependency in the crate, which attempts to use its derive macros. +`near-contract-standards` wasn't importing `borsh` directly, just using `near-sdk`'s reexports. + +We may instruct `BorshSerialize` and `BorshDeserialize` derives to skip this check of direct import +and to [use a reexported version](https://github.com/near/borsh-rs/pull/210) of `borsh` via following diff: + +```diff +diff --git a/near-contract-standards/src/fungible_token/core_impl.rs b/near-contract-standards/src/fungible_token/core_impl.rs +index d61ee8e..cae776c 100644 +--- a/near-contract-standards/src/fungible_token/core_impl.rs ++++ b/near-contract-standards/src/fungible_token/core_impl.rs +@@ -27,2 +27,3 @@ const ERR_TOTAL_SUPPLY_OVERFLOW: &str = "Total supply overflow"; + #[derive(BorshDeserialize, BorshSerialize)] ++#[borsh(crate = "::near_sdk::borsh")] + pub struct FungibleToken { + ``` + + +