diff --git a/README.md b/README.md index 7bb392e38..6b951be44 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,8 @@ impl StatusMessage { } ``` -## Github Codespaces template +## Github Codespaces template + [an online ide](https://docs.github.com/en/codespaces/reference/using-the-vs-code-command-palette-in-codespaces) + * Quick start your project with the [template repository](https://github.com/near/cargo-near-new-project-template) generated by `cargo near new` command * The associated [template repository](https://github.com/near/cargo-near-new-project-template)'s README contains a `Code > Codespaces > Create codespace on main` screenshot. * Clicking the button will result in using [Github Codespaces devcontainer](https://docs.github.com/en/codespaces). The containers allow you to start building without the need to install any dependencies on your machine and provide the actual version. @@ -281,6 +282,14 @@ cargo near build Builds a NEAR smart contract along with its [ABI](https://github.com/near/abi) (while in the directory containing contract's Cargo.toml). +If you have problems/errors with schema/ABI during build that you cannot figure out quick, you can skip/circumvent them with: + +```sh +cargo near build non-reproducible-wasm --no-abi +``` + +And return to figuring how to resolve problems with generating ABI of your contract later. + ```sh cargo near create-dev-account ``` diff --git a/near-sdk/src/collections/mod.rs b/near-sdk/src/collections/mod.rs index 12a69df7c..c13c3ae04 100644 --- a/near-sdk/src/collections/mod.rs +++ b/near-sdk/src/collections/mod.rs @@ -1,3 +1,5 @@ +//! ## General description +//! //! Collections that offer an alternative to standard containers from `std::collections::*` by //! utilizing the underlying blockchain trie storage more efficiently. //! @@ -33,6 +35,15 @@ //! //! The efficiency of `LookupMap` comes at the cost, since it has fewer methods than `HashMap` and is not //! that seamlessly integrated with the rest of the Rust standard library. +//! +//! ## Calls to **host functions**, used in implementation: +//! +//! * [`near_sdk::env::storage_write`](crate::env::storage_write) +//! * [`near_sdk::env::storage_read`](crate::env::storage_read) +//! * [`near_sdk::env::storage_remove`](crate::env::storage_remove) +//! * [`near_sdk::env::storage_get_evicted`](crate::env::storage_get_evicted) +//! * [`near_sdk::env::storage_has_key`](crate::env::storage_has_key) +//! mod legacy_tree_map; #[allow(deprecated)] diff --git a/near-sdk/src/lib.rs b/near-sdk/src/lib.rs index 39dab8644..9ba162342 100644 --- a/near-sdk/src/lib.rs +++ b/near-sdk/src/lib.rs @@ -24,7 +24,7 @@ //! near-sdk = "5.6.0" //! ``` //! -//! ### Example: Counter Smart Contract. For more information, see the [near] documentation. +//! ### Example: Counter Smart Contract. For more information, see the [**near** macro](near) documentation. //! //! Below is an example of a simple counter contract that increments and retrieves a value: //! @@ -54,17 +54,27 @@ //! //! ### Compiling to WASM //! -//! Install cargo near in case if you don't have it: +//! Install `cargo-near` in case if you don't have it: //! ```bash //! cargo install --locked cargo-near //! ``` //! -//! Build your contract for the NEAR blockchain: +//! More installation methods on [cargo-near](https://github.com/near/cargo-near) +//! +//! Builds a NEAR smart contract along with its [ABI](https://github.com/near/abi) (while in the directory containing contract's Cargo.toml): //! //! ```bash //! cargo near build //! ``` //! +//! If you have problems/errors with schema/ABI during build that you cannot figure out quick, you can skip/circumvent them with: +//! +//! ```bash +//! cargo near build non-reproducible-wasm --no-abi +//! ``` +//! +//! And return to figuring how to resolve problems with generating ABI of your contract later. +//! //! ### Running Unit Tests //! //! Use the following testing setup: @@ -102,16 +112,20 @@ extern crate quickcheck; /// to generate the necessary code to expose `pub` methods from the contract as well /// as generating the glue code to be a valid NEAR contract. /// -/// The macro is a syntactic sugar for [near_bindgen] and expands to the [near_bindgen] macro invocations. -/// Both of them share the same attributes, except for those that are explicitly marked as specific to the [near] macro. ([1](near#nearserializers-annotates-structsenums), [2](near#nearcontract_state-annotates-structsenums)) +/// The macro is a syntactic sugar for [**near_bindgen**](near_bindgen) and expands to the [**near_bindgen**](near_bindgen) macro invocations. +/// Both of them share the same attributes, except for those that are explicitly marked as specific to the [**near**](near) macro. ([1](near#nearcontract_state-annotates-structsenums), [2](near#nearserializers-annotates-structsenums)) /// /// # Attributes /// /// ## `#[near(contract_state)]` (annotates structs/enums) /// /// The attribute prepares a struct/enum to be a contract state. Only one contract state is allowed per crate. +/// +/// A contract type is usually acompanied by an `impl` block, annotated with [`#[near]`](near#near-annotates-impl-blocks). +/// /// This attribute is also required to make the [`#[near(contract_metadata(...))]`](near#nearcontract_metadata-annotates-structsenums) attribute work. -/// **The attribute specific to the [near] macro only.** +/// +/// `contract_state` is specific to the [near] macro only, not available for [near_bindgen]. /// /// ### Basic example /// ```rust @@ -119,14 +133,161 @@ extern crate quickcheck; /// /// #[near(contract_state)] /// pub struct Contract { -/// data: i8, +/// greeting: String, +/// } +/// ``` +/// which usually comes paired with at least one **impl** block for the contract type, +/// annotated with a plain `#[near]` attribute: +/// +/// ### Using SDK collections for storage +/// +/// If contract state becomes large, collections from following modules can be used: +/// +/// #### [`store`] module: +/// +/// ```rust +/// # use near_sdk_macros::near; +/// use near_sdk::store::IterableMap; +/// +/// #[near(contract_state)] +/// pub struct StatusMessage { +/// records: IterableMap, +/// } +/// ``` +/// +/// * list of [**host functions**](store#calls-to-host-functions-used-in-implementation) used for [`store`] implementation +/// * **FAQ**: mutating state of collections from [`store`] module is only finally persisted on running [`Drop`/`flush`](store#faq-collections-of-this-module-only-persist-on-drop-and-flush) +/// +/// #### [`collections`] module: +/// +/// ```rust +/// # use near_sdk_macros::near; +/// use near_sdk::collections::LookupMap; +/// +/// #[near(contract_state)] +/// pub struct StatusMessage { +/// records: LookupMap, +/// } +/// ``` +/// +/// * list of [**host functions**](collections#calls-to-host-functions-used-in-implementation) used for [`collections`] implementation +/// +/// ### Implementation details of `#[near(contract_state)]` macro and **host functions** calls used +/// +/// If the details of [ABI](https://github.com/near/abi) generation layer are put aside, then the macro performs roughly the following: +/// +/// ```rust +/// # use near_sdk::near; +/// #[near(contract_state)] +/// pub struct Contract { /* .. */ } +/// ``` +/// +/// 1. Macro adds derived implementations of [`borsh::BorshSerialize`]/[`borsh::BorshSerialize`] for `Contract` type +/// 2. Macro defines a global `CONTRACT_SOURCE_METADATA` variable, which is a string of json serialization of [`near_contract_standards::contract_metadata::ContractSourceMetadata`](https://docs.rs/near-contract-standards/latest/near_contract_standards/contract_metadata/struct.ContractSourceMetadata.html). +/// 3. Macro defines `contract_source_metadata` function: +/// ```rust,no_run +/// #[no_mangle] +/// pub extern "C" fn contract_source_metadata() { /* .. */ } +/// ``` +/// which +/// * calls [`env::setup_panic_hook`] host function +/// * calls [`env::value_return`] host function with bytes of `CONTRACT_SOURCE_METADATA` from step 2. +/// +/// ##### using [cargo-expand](https://crates.io/crates/cargo-expand) to view actual macro results +/// +/// The above is an approximate description of what macro performs. +/// +/// Running the following in a contract's crate is a way to introspect more details of its operation: +/// +/// ```bash,ignore +/// cargo expand --lib --target wasm32-unknown-unknown +/// # this has additional code generated for ABI layer +/// cargo expand --lib --features near-sdk/__abi-generate +/// ``` +/// --- +/// +/// ## `#[near]` (annotates impl blocks) +/// +/// This macro is used to define the code for view-only and mutating methods for contract types, +/// annotated by [`#[near(contract_state)]`](near#nearcontract_state-annotates-structsenums). +/// +/// ### Basic example +/// ```rust +/// use near_sdk::{near, log}; +/// +/// # #[near(contract_state)] +/// # pub struct Contract { +/// # greeting: String, +/// # } +/// #[near] +/// impl Contract { +/// // view method +/// pub fn get_greeting(&self) -> String { +/// self.greeting.clone() +/// } +/// +/// // mutating method +/// pub fn set_greeting(&mut self, greeting: String) { +/// log!("Saving greeting: {greeting}"); +/// self.greeting = greeting; +/// } /// } /// ``` /// +/// ### Implementation details of `#[near]` macro and **host functions** calls used +/// +/// ```rust +/// # use near_sdk::near; +/// # #[near(contract_state)] +/// # pub struct Contract { /* .. */ } +/// #[near] +/// impl Contract { +/// pub fn view_method(&self) -> String { todo!("method body") } +/// +/// pub fn mutating_method(&mut self, argument: String) { /* .. */ } +/// } +/// ``` +/// +/// ##### for above **view** method macro defines the following function: +/// +/// ```rust,no_run +/// #[no_mangle] +/// pub extern "C" fn view_method() { /* .. */ } +/// ``` +/// which +/// +/// 1. calls [`env::setup_panic_hook`] host function +/// 2. calls [`env::state_read`] host function to load `Contract` into a `state` variable +/// 3. calls original `Contract::view_method(&state)` as defined in `#[near]` annotated [impl block](near#implementation-details-of-near-macro-and-host-functions-calls-used) and saves +/// the returned value into a `result` variable +/// 4. calls [`serde_json::to_vec`] on obtained `result` and saves returned value to `serialized_result` variable +/// * `json` format can be changed to serializing with [`borsh::to_vec`] by using [`#[result_serializer(...)]`](`near#result_serializer-annotates-methods-of-a-type-in-its-impl-block`) +/// 5. if the `serialized_result` is an [`Result::Err`] error, then [`env::panic_str`] host function is called to signal result serialization error +/// 6. otherwise, if the `serialized_result` is a [`Result::Ok`], then [`env::value_return`] host function is called with unwrapped `serialized_result` +/// +/// ##### for above **mutating** method macro defines the following function: +/// ```rust,no_run +/// #[no_mangle] +/// pub extern "C" fn mutating_method() { /* ..*/ } +/// ``` +/// which +/// +/// 1. calls [`env::setup_panic_hook`] host function +/// 2. calls [`env::input`] host function and saves it to `input` variable +/// 3. deserializes `Contract::mutating_method` arguments by calling [`serde_json::from_slice`] on `input` variable and saves it to `deserialized_input` variable +/// * `json` format can be changed to deserializing with [`borsh::from_slice`] by using [`#[serializer(...)]`](`near#serializer-annotates-function-arguments`) +/// 4. if the `deserialized_input` is an [`Result::Err`] error, then [`env::panic_str`] host function is called to signal input deserialization error +/// 5. otherwise, if the `deserialized_input` is a [`Result::Ok`], `deserialized_input` is unwrapped and saved to `deserialized_input_success` variable +/// 6. calls [`env::state_read`] host function to load `Contract` into a `state` variable +/// 7. calls original `Contract::mutating_method(&mut state, deserialized_input_success.argument)` as defined in `#[near]` annotated [impl block](near#implementation-details-of-near-macro-and-host-functions-calls-used) +/// 8. calls [`env::state_write`] with `&state` as argument. +/// --- +/// /// ## `#[near(serializers=[...])` (annotates structs/enums) /// /// The attribute makes the struct or enum serializable with either json or borsh. By default, borsh is used. -/// **The attribute specific to the [near] macro only.** +/// +/// `serializers` is specific to the [near] macro only, not available for [near_bindgen]. /// /// ### Make struct/enum serializable with borsh /// @@ -176,8 +337,10 @@ extern crate quickcheck; /// /// ## `#[serializer(...)]` (annotates function arguments) /// -/// The attribute makes the function argument serializable with either json or borsh. By default, json is used. -/// Please, note that all the arguments of the function should be using the same serializer. +/// The attribute makes the function argument deserializable from [`Vec`]<[`u8`]> with either json or borsh. By default, json is used. +/// Please, note that all the arguments of the function should be using the same deserializer. +/// +/// NOTE: a more correct name for the attribute would be `argument_deserializer`, but it's `serializer` for historic reasons. /// /// ### Basic example /// @@ -418,7 +581,7 @@ extern crate quickcheck; /// All fields(version, link) are optional and will be populated with defaults from the Cargo.toml file if not specified. /// The `standard` will be populated with `nep330` by default. /// -/// Any additional standards can be added and should be specified using the `standard` attribute. +/// **Any additional standards can be added and should be specified using the `standard` attribute.** /// /// The `contract_source_metadata()` view function will be added and can be used to retrieve the source metadata. /// Also, the source metadata will be stored as a constant, `CONTRACT_SOURCE_METADATA`, in the contract code. diff --git a/near-sdk/src/store/mod.rs b/near-sdk/src/store/mod.rs index 4a3453a76..61621141d 100644 --- a/near-sdk/src/store/mod.rs +++ b/near-sdk/src/store/mod.rs @@ -1,5 +1,65 @@ //! Collections and types used when interacting with storage. //! +//! ## Benchmarks of comparison with [`std::collections`]: +//! +//! To help you understand how cost-effective near collections are in terms of gas usage compared to native ones, +//! take a look at this investigation: [Near benchmarking github](https://github.com/volodymyr-matselyukh/near-benchmarking). +//! +//! The results of the investigation can be found here: [Results](https://docs.google.com/spreadsheets/d/1ThsBlNR6_Ol9K8cU7BRXNN73PblkTbx0VW_njrF633g/edit?gid=0#gid=0). +//! +//! If your collection has up to 100 entries, it's acceptable to use the native collection, as it might be simpler +//! since you don't have to manage prefixes as we do with near collections. +//! However, if your collection has 1,000 or more entries, it's better to use a near collection. The investigation +//! mentioned above shows that running the contains method on a native [`std::collections::HashSet`] **consumes 41% more gas** +//! compared to a near [`crate::store::IterableSet`]. +//! +//! ## FAQ: collections of this [`module`](self) only persist on `Drop` and `flush` +//! Unlike containers in [`near_sdk::collections`](crate::collections) module, most containers in current [`module`](self) will cache all changes +//! and loads and only update values that are changed in storage after it’s dropped through it’s [`Drop`] implementation. +//! +//! These changes can be updated in storage before the container variable is dropped by using +//! the container's `flush` method, e.g. [`IterableMap::flush`](crate::store::IterableMap::flush) ([`IterableMap::drop`](crate::store::IterableMap::drop) uses it in implementation too). +//! +//! ```rust,no_run +//! # use near_sdk::{log, near}; +//! use near_sdk::store::IterableMap; +//! +//! #[near(contract_state)] +//! #[derive(Debug)] +//! pub struct Contract { +//! greeting_map: IterableMap, +//! } +//! +//! # impl Default for Contract { +//! # fn default() -> Self { +//! # let prefix = b"gr_pr"; +//! # Self { +//! # greeting_map: IterableMap::new(prefix.as_slice()), +//! # } +//! # } +//! # } +//! +//! #[near] +//! impl Contract { +//! pub fn mutating_method(&mut self, argument: String) { +//! self.greeting_map.insert("greeting".into(), argument); + +//! near_sdk::env::log_str(&format!("State of contract mutated: {:#?}", self)); +//! } +//! } +//! // expanded #[near] macro call on a contract method definition: +//! // ... +//! # let argument = "hello world".to_string(); +//! let mut contract: Contract = ::near_sdk::env::state_read().unwrap_or_default(); +//! // call of the original `mutating_method` as defined in source code prior to expansion +//! Contract::mutating_method(&mut contract, argument); +//! ::near_sdk::env::state_write(&contract); +//! // Drop on `contract` is called! `IterableMap` is only `flush`-ed here <==== +//! // ... +//! ``` +//! +//! ## General description +//! //! These collections are more scalable versions of [`std::collections`] when used as contract //! state because it allows values to be lazily loaded and stored based on what is actually //! interacted with. @@ -14,15 +74,6 @@ //! For example, a store::Vector is stored as several key-value pairs, where indices are the keys. //! So, accessing a single element would only load this specific element. //! -//! To help you understand how cost-effective near collections are in terms of gas usage compared to native ones, -//! take a look at this investigation: [Near benchmarking github](https://github.com/volodymyr-matselyukh/near-benchmarking). -//! The results of the investigation can be found here: [Results](https://docs.google.com/spreadsheets/d/1ThsBlNR6_Ol9K8cU7BRXNN73PblkTbx0VW_njrF633g/edit?gid=0#gid=0). -//! If your collection has up to 100 entries, it's acceptable to use the native collection, as it might be simpler -//! since you don't have to manage prefixes as we do with near collections. -//! However, if your collection has 1,000 or more entries, it's better to use a near collection. The investigation -//! mentioned above shows that running the contains method on a native [`std::collections::HashSet`] consumes 41% more gas -//! compared to a near [`crate::store::IterableSet`]. -//! //! It's also a bad practice to have a native collection properties as a top level properties of your contract. //! The contract will load all the properties before the contract method invocation. That means that all your native //! collections will be fully loaded into memory even if they are not used in the method you invoke. @@ -37,6 +88,15 @@ //! collections to be able to access all values. Because only metadata is serialized, these //! structures should not be used as a borsh return value from a function. //! +//! ## Calls to **host functions**, used in implementation: +//! +//! * [`near_sdk::env::storage_write`](crate::env::storage_write) +//! * [`near_sdk::env::storage_read`](crate::env::storage_read) +//! * [`near_sdk::env::storage_remove`](crate::env::storage_remove) +//! * [`near_sdk::env::storage_has_key`](crate::env::storage_has_key) +//! +//! ## Module's glossary: +//! //! The collections are as follows: //! //! Sequences: @@ -51,6 +111,7 @@ //! //! - [`UnorderedMap`]: Storage version of [`std::collections::HashMap`]. No ordering //! guarantees. +//! - [`IterableMap`]: a replacement with better iteration performance for [`UnorderedMap`], which is being deprecated. //! //! - [`TreeMap`] (`unstable`): Storage version of [`std::collections::BTreeMap`]. Ordered by key, //! which comes at the cost of more expensive lookups and iteration. @@ -61,6 +122,7 @@ //! //! - [`UnorderedSet`]: Analogous to [`std::collections::HashSet`], and is an iterable //! version of [`LookupSet`] and persisted to storage. +//! - [`IterableSet`]: a replacement with better iteration performance for [`UnorderedSet`], which is being deprecated. //! //! Basic Types: //!