From 40b7f82cae354f11f88c2c5e8f945a6a6831d6ed Mon Sep 17 00:00:00 2001 From: polyprogrammist Date: Thu, 21 Nov 2024 18:10:32 +0800 Subject: [PATCH 01/11] make external calls more clear for rust and words about key value storage --- .../anatomy/crosscontract.md | 18 ++++++++++++++- .../2.smart-contracts/anatomy/storage.md | 22 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/docs/2.build/2.smart-contracts/anatomy/crosscontract.md b/docs/2.build/2.smart-contracts/anatomy/crosscontract.md index c7ef0e2dd47..67b18abbb09 100644 --- a/docs/2.build/2.smart-contracts/anatomy/crosscontract.md +++ b/docs/2.build/2.smart-contracts/anatomy/crosscontract.md @@ -41,7 +41,16 @@ While making your contract, it is likely that you will want to query information - + + + +Note that in order for that to work in Rust, you have to implement the interface of the contract you call: + + + + @@ -66,6 +75,13 @@ Calling another contract passing information is also a common scenario. Below yo + + + + + + + diff --git a/docs/2.build/2.smart-contracts/anatomy/storage.md b/docs/2.build/2.smart-contracts/anatomy/storage.md index 3711ec87f64..f9ed8fac9cf 100644 --- a/docs/2.build/2.smart-contracts/anatomy/storage.md +++ b/docs/2.build/2.smart-contracts/anatomy/storage.md @@ -13,6 +13,28 @@ Smart contracts store data in their account's state, which is public on the chai It is important to know that the account's **code** and account's **storage** are **independent**. [Updating the code](../release/upgrade.md) does **not erase** the state. +## Key-value storage + +Behind the scenes, all data stored on the NEAR blockchain is saved in a key-value database. A storage key is a unique identifier used to access data stored in a smart contract’s persistent storage. The key-value storage model in NEAR allows smart contracts to manage and retrieve data efficiently along with minimizing costs for storage. + +Keys can be any binary sequence, but they are typically structured for ease of use (e.g., as human-readable strings). +Data associated with a key persists across contract calls and is stored on-chain until explicitly deleted. + +SDK collections are instantiated using a "prefix" so that the elements from different collections access different data. + +Here is an example: + +```ts +const tokenToOwner = new PersistentMap("t2o"); +``` + +That `'t2o'` variable that's passed to `PersistentMap` helps it keep track of all its values. If your account `example.near` owns token with ID `0`, then at the time of writing, here's the data that would get saved to the key-value database: + +- key: `t2o::0` +- value: `example.near` + +Storage key type should implement the trait `IntoStorageKey`. +
From 323551af6a7315f1e7d628d6b9f57370c1783908 Mon Sep 17 00:00:00 2001 From: polyprogrammist Date: Thu, 21 Nov 2024 19:16:01 +0800 Subject: [PATCH 02/11] update checklist, more info about promise return --- .../anatomy/crosscontract.md | 26 ++++++++++++++++++- .../anatomy/serialization.md | 4 +-- .../2.smart-contracts/security/checklist.md | 8 ++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/docs/2.build/2.smart-contracts/anatomy/crosscontract.md b/docs/2.build/2.smart-contracts/anatomy/crosscontract.md index 67b18abbb09..4aeb764894e 100644 --- a/docs/2.build/2.smart-contracts/anatomy/crosscontract.md +++ b/docs/2.build/2.smart-contracts/anatomy/crosscontract.md @@ -165,10 +165,34 @@ You can attach an unused GAS weight by specifying the `.with_unused_gas_weight() -:::info +:::caution If a function returns a promise, then it will delegate the return value and status of transaction execution, but if you return a value or nothing, then the `Promise` result will not influence the transaction status + + +Unless there is a very good reason not to, all the cross-contract calling functions should return the Promise as the result of its execution, otherwise, the cross-contract calls won't affect the result of the contract execution. + +For example: + + + + ```rust + fn destroy(&mut self) { + Promise::...; // notice the `;` here and the empty return value in the function signature + } + fn destroy(&mut self) -> Promise { + Promise::... // notice there is no `;` here and the return value in the function signature Promise + } + ``` + + + + +The difference between the two is that the first will schedule the cross-contract call, but its return value will be ignored (even if the call fails, the result of the `destroy` function will be OK), and transaction return value may even be returned earlier than all the in-flight cross-contract calls complete since they do not affect the execution result. + +Without linking the cross-contract call with the result of the `destroy` function, it may start removing itself earlier than the cross-contract call is finished and if it fails, it's state will become unreachable. + ::: :::caution diff --git a/docs/2.build/2.smart-contracts/anatomy/serialization.md b/docs/2.build/2.smart-contracts/anatomy/serialization.md index dc455fb26cf..5b5c0655d15 100644 --- a/docs/2.build/2.smart-contracts/anatomy/serialization.md +++ b/docs/2.build/2.smart-contracts/anatomy/serialization.md @@ -110,7 +110,8 @@ When returning the result, the contract will automatically encode the object `B{ into its JSON serialized value: `"{\"success\":true, \"other_number\":0}"` and return this string. -:::caution JSON Limitations +### JSON Limitations +:::caution Since JSON is limited to `52 bytes` numbers, you cannot use `u64`/`u128` as input or output. JSON simply cannot serialize them. Instead, you must use `Strings`. @@ -118,7 +119,6 @@ The `NEAR SDK RS` currently implements the `near_sdk::json_types::{U64, I64, U12 that you can use for input / output of data. ::: - --- ## Borsh: State Serialization diff --git a/docs/2.build/2.smart-contracts/security/checklist.md b/docs/2.build/2.smart-contracts/security/checklist.md index 61101e6d90d..828cd5dec7d 100644 --- a/docs/2.build/2.smart-contracts/security/checklist.md +++ b/docs/2.build/2.smart-contracts/security/checklist.md @@ -34,3 +34,11 @@ Check our [security articles](./welcome.md) to understand how to improve the sec 12. Callbacks are free of `panic!` 13. All the callbacks are given enough GAS to execute entirely 14. The contract is not left in an exploitable state between a cross-contract call and its callback + +## Cross-contract calls +15. Cross-contract calling functions [should](../anatomy/crosscontract#creating-a-cross-contract-call) return the Promise as the result of its execution + +## Types +16. Use `near_sdk::json_types::{U64, I64, U128, I128}` to avoid deserialization [problems](../anatomy/serialization#json-limitations) with `u64`, etc. +17. Use built in types like `AccountId`, `BlockHeight`, `NearToken` and other types from [near-sdk](https://docs.rs/near-sdk/latest/near_sdk/) +18. When dealing with balances, consider using `checked_mul`, `checked_sub`, etc \ No newline at end of file From dc00bd8b7a39d12d8ec03266150362c4dc86ad35 Mon Sep 17 00:00:00 2001 From: polyprogrammist Date: Thu, 21 Nov 2024 20:54:29 +0800 Subject: [PATCH 03/11] public key --- docs/1.concepts/protocol/access-keys.md | 3 ++ .../2.smart-contracts/anatomy/types.md | 36 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/docs/1.concepts/protocol/access-keys.md b/docs/1.concepts/protocol/access-keys.md index 2a87dd3d9c4..41ca4637145 100644 --- a/docs/1.concepts/protocol/access-keys.md +++ b/docs/1.concepts/protocol/access-keys.md @@ -11,6 +11,9 @@ NEAR accounts present the **unique** feature of being able to hold multiple [Acc 1. `Full-Access Keys`: Have full control over the account, and should **never be shared** 2. `Function-Call Keys`: Can sign calls to specific contract, and are **meant to be shared** + +Currently supported curve types are `ed25519` (default curve type used for public key cryptography in NEAR) and `secp256k1` (supported for compatibility with other blockchains). + --- ## Full-Access Keys {#full-access-keys} diff --git a/docs/2.build/2.smart-contracts/anatomy/types.md b/docs/2.build/2.smart-contracts/anatomy/types.md index acd4a3b994c..96aed05b0b3 100644 --- a/docs/2.build/2.smart-contracts/anatomy/types.md +++ b/docs/2.build/2.smart-contracts/anatomy/types.md @@ -113,3 +113,39 @@ To simplify development, the SDK provides the `U64` and `U128` types which are a start="2" end="84" /> + + +## PublicKey + +Public key is in a binary format with base58 string serialization with human-readable curve. +The key types currently supported are `secp256k1` and `ed25519`. + +Example: +```rust +use near_sdk::{PublicKey, CurveType}; + +pub fn stake_tokens( + &mut self, + amount: U128, + ) -> Promise { + // Compressed ed25519 key + let ed: PublicKey = "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp".parse() + .unwrap(); + + // Uncompressed secp256k1 key + let secp256k1: PublicKey = + "secp256k1:qMoRgcoXai4mBPsdbHi1wfyxF9TdbPCF4qSDQTRP3TfescSRoUdSx6nmeQoN3aiwGzwMyGXAb1gUjBTv5AY8DXj" + .parse().unwrap(); + + // Check the curve type of the public key + match ed.curve_type() { + near_sdk::CurveType::ED25519 => env::log_str("Using ED25519 curve."), + near_sdk::CurveType::SECP256K1 => env::log_str("Using SECP256K1 curve."), + _ => env::panic_str("Unsupported curve type!"), + } + + // Use the staking contract to stake the tokens (using NEAR's PromiseAction::Stake) + Promise::new(env::predecessor_account_id().clone()) + .stake(amount.0, public_key) // stake the tokens using the public key for verification + } +``` From 3ea1389fca5167dced95a7d3ff606f5d700fd190 Mon Sep 17 00:00:00 2001 From: polyprogrammist Date: Fri, 22 Nov 2024 18:22:36 +0800 Subject: [PATCH 04/11] borshserialize and storage_write --- .../anatomy/serialization.md | 37 ++++++++++++++++++- .../2.smart-contracts/anatomy/types.md | 4 +- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/docs/2.build/2.smart-contracts/anatomy/serialization.md b/docs/2.build/2.smart-contracts/anatomy/serialization.md index 5b5c0655d15..7f5f6110b01 100644 --- a/docs/2.build/2.smart-contracts/anatomy/serialization.md +++ b/docs/2.build/2.smart-contracts/anatomy/serialization.md @@ -127,7 +127,7 @@ Under the hood smart contracts store data using simple **key/value pairs**. This the contract needs to translate complex states into simple key-value pairs. For this, NEAR contracts use [borsh](https://borsh.io) which is optimized for (de)serializing -complex objects into smaller streams of bytes. +complex objects into smaller streams of bytes. Borsh is used to serialize and deserialize both keys and values. :::tip SDK-JS still uses json The JavaScript SDK uses JSON to serialize objects in the state, but the borsh implementation @@ -138,7 +138,7 @@ should arrive soon Let's look at this example, written only for educational purposes: ```rust -#[near(serializers = [json, borsh])] +#[near(contract_state, serializers = [json, borsh])] #[derive(PanicOnDefault)] pub struct Contract { string: String, @@ -211,6 +211,21 @@ If we initialize the state we can see how Borsh is used to serialize the state +Under the hood, every method in smart contract where state is mutated, finishes with writing state to storage, where key is "STATE" and value is contract state, serialized with Borsh. `storage_write` is used for that: + +```rust +pub fn init(...) -> Self { + ... + storage_write(b"STATE", &borsh::to_vec(&Self {string, vector})); +} +``` + +Also, when pushing to vector, another `storage_write` is used: +```rust + // here 0 is element index + storage_write(b"prefix:0", &borsh::to_vec(&element)) +``` +
Result @@ -287,6 +302,24 @@ the vector now has 2 elements. At the same time, a new key-value was added adding the new vector entry: the `1u8` we just added. +#### BorshSerialize +In order for state reading and writing to work, contract structure should be borsh serializable. That means every field and their fields should be borsh serializable as well. Otherwise, code is not going to compile. For that, `BorshSerialize` and `BorshDeserialize` macros can be used. But `#[near]` macro specifies it internally. So, all the structures and enums, that are going to be used as fields of contract, have to be wrapped with `#[near]` macro. + +Example: + +```rust +#[near] +pub struct InnerStruct { + name: String, + age: u8 +} + +#[near(contract_state)] +pub struct Contract { + person: InnerStruct +} +``` +
diff --git a/docs/2.build/2.smart-contracts/anatomy/types.md b/docs/2.build/2.smart-contracts/anatomy/types.md index 96aed05b0b3..7b1a8df6356 100644 --- a/docs/2.build/2.smart-contracts/anatomy/types.md +++ b/docs/2.build/2.smart-contracts/anatomy/types.md @@ -117,8 +117,8 @@ To simplify development, the SDK provides the `U64` and `U128` types which are a ## PublicKey -Public key is in a binary format with base58 string serialization with human-readable curve. -The key types currently supported are `secp256k1` and `ed25519`. +[`PublicKey`](https://docs.rs/near-sdk/latest/near_sdk/struct.PublicKey.html) is in a binary format with base58 string serialization with human-readable curve. +The curve types currently supported are `secp256k1` and `ed25519` under [`near_sdk::CurveType`](https://docs.rs/near-sdk/latest/near_sdk/struct.PublicKey.html). Example: ```rust From 2d39bca69b4ecfba08b073826fb918b643237337 Mon Sep 17 00:00:00 2001 From: polyprogrammist Date: Mon, 25 Nov 2024 21:32:49 +0800 Subject: [PATCH 05/11] macros explained --- .../2.smart-contracts/anatomy/macros.md | 149 ++++++++++++++++++ website/sidebars.js | 1 + 2 files changed, 150 insertions(+) create mode 100644 docs/2.build/2.smart-contracts/anatomy/macros.md diff --git a/docs/2.build/2.smart-contracts/anatomy/macros.md b/docs/2.build/2.smart-contracts/anatomy/macros.md new file mode 100644 index 00000000000..56306a6564c --- /dev/null +++ b/docs/2.build/2.smart-contracts/anatomy/macros.md @@ -0,0 +1,149 @@ +--- +id: macros +title: Internals of Rust macros +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Let's talk about what happens internally when `near` macro is used. Consider this example: + +```rust +#[near(contract_state)] +pub struct PersonContract { + name: String, + age: u8, +} + +#[near] +impl PersonContract { + pub fn set_person(&mut self, name: String, age: u8) { + self.name = name; + self.age = age; + } +} +``` + +The following is what `near` macro does. The generated code is not a real output, but a demonstration: +```rust +// Using Borsh so that it can be keeped in the blockchain storage. +#[derive(BorshSerialize, BorshDeserialize)] +#[borsh(crate = "::near_sdk::borsh")] +pub struct PersonContract { + name : String, + age : u8, +} + +// This function is executed when someone calls a contract method +pub extern "C" fn set_person() +{ + // Setting up panic hook so that when error appears, it is handled correctly (env::panic_str() is called) + setup_panic_hook(); + + // To accept a deposit, you have to mark the function as #[payable]. Otherwise, the execution has to be interrupted. + if env::attached_deposit().as_yoctonear() != 0 { + env::panic_str("Method set_person doesn't accept deposit"); + } + + // Creating a structure for arguments, so it can be deserialized with serde when the method is called + #[derive(Deserialize)] + #[serde(crate = "::near_sdk::serde")] + struct Input { + name: String, + age: u8, + } + + // Getting arguments + let Input { name, age, } = match env::input() { + Some(input) => match serde_json::from_slice(&input) { + Ok(deserialized) => deserialized, + Err(_) => env::panic_str("Failed to deserialize input from JSON.") + }, + None => env::panic_str("Expected input since method has arguments.") + }; + + // Reading contract state + let mut contract: PersonContract = env::state_read().unwrap_or_default(); + + // Calling contract method + contract.set_person(name, age); + + // Writing contract state + env::state_write(& contract); +} +``` + +In addition, a struct for cross contract calls is generated. It is done so that the contract can easily call itself. Similar code is generated when `#[ext_contract]` macro is used: +```rust +impl PersonContractExt { + // If you execute this method from another contract, it's going to call `pub extern "C" fn set_person()` + pub fn set_person(self, name : String, age : u8,) -> Promise { + let __args = + { + // Creating a structure for arguments, so it can be serialized with serde and passed + #[derive(serde::Serialize)] + #[serde(crate = "::near_sdk::serde")] + struct Input < 'nearinput > { + name : & 'nearinput String, + age : & 'nearinput u8, + } + + // Serializing arguments with serde + let __args = Input { name : & name, age : & age, }; + serde_json::to_vec(& __args) + }; + + // Actually calling other contract + Promise::new(self.account_id).function_call_weight( + String::from("set_person"), + __args, + self.deposit, + self.static_gas, + self.gas_weight, + ) + } +} +``` + +In addition, helpers are generated: +```rust +impl PersonContract +{ + pub fn ext(account_id : :: near_sdk :: AccountId) -> PersonContractExt + { + PersonContractExt + { + account_id, + deposit: from_near(0), + static_gas: from_gas(0), + gas_weight: default(), + } + } +} + + +pub struct PersonContractExt +{ + pub(crate) account_id: AccountId, + pub(crate) deposit: NearToken, + pub(crate) static_gas: Gas, + pub(crate) gas_weight: GasWeight, +} + +impl PersonContractExt +{ + pub fn with_attached_deposit(mut self, amount: NearToken) -> Self { + self.deposit = amount; + self + } + pub fn with_static_gas(mut self, static_gas: Gas) -> Self { + self.static_gas = static_gas; + self + } + pub fn with_unused_gas_weight(mut self, gas_weight : u64) -> Self { + self.gas_weight = GasWeight(gas_weight); + self + } +} + +``` \ No newline at end of file diff --git a/website/sidebars.js b/website/sidebars.js index 4c3538e9011..fa34745cdc1 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -139,6 +139,7 @@ const sidebar = { "build/smart-contracts/anatomy/serialization-protocols", "build/smart-contracts/anatomy/reduce-size", "build/smart-contracts/anatomy/reproducible-builds", + "build/smart-contracts/anatomy/macros", ] } ] From 851554b25ae01eb63b661b0d452f0083b7f7bbc5 Mon Sep 17 00:00:00 2001 From: polyprogrammist Date: Mon, 25 Nov 2024 22:05:05 +0800 Subject: [PATCH 06/11] more clear info in integration tests --- docs/2.build/2.smart-contracts/security/checklist.md | 1 + docs/2.build/2.smart-contracts/testing/integration-test.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/2.build/2.smart-contracts/security/checklist.md b/docs/2.build/2.smart-contracts/security/checklist.md index 828cd5dec7d..85cb8ef071d 100644 --- a/docs/2.build/2.smart-contracts/security/checklist.md +++ b/docs/2.build/2.smart-contracts/security/checklist.md @@ -37,6 +37,7 @@ Check our [security articles](./welcome.md) to understand how to improve the sec ## Cross-contract calls 15. Cross-contract calling functions [should](../anatomy/crosscontract#creating-a-cross-contract-call) return the Promise as the result of its execution +16. Use [integration tests](../testing/integration-test.md) to cover cross contract calls ## Types 16. Use `near_sdk::json_types::{U64, I64, U128, I128}` to avoid deserialization [problems](../anatomy/serialization#json-limitations) with `u64`, etc. diff --git a/docs/2.build/2.smart-contracts/testing/integration-test.md b/docs/2.build/2.smart-contracts/testing/integration-test.md index 4790b180b5e..3835318cabf 100644 --- a/docs/2.build/2.smart-contracts/testing/integration-test.md +++ b/docs/2.build/2.smart-contracts/testing/integration-test.md @@ -27,7 +27,7 @@ All of our [examples](https://github.com/near-examples/docs-examples) come with ## Snippet I: Testing Hello NEAR -Lets take a look at the test of our [Quickstart Project](../quickstart.md) [👋 Hello NEAR](https://github.com/near-examples/hello-near-examples), where we deploy the contract on an account and test it correctly retrieves and sets the greeting. +Lets take a look at the test of our [Quickstart Project](../quickstart.md) [👋 Hello NEAR](https://github.com/near-examples/hello-near-examples), where we deploy the contract on an account and test it correctly retrieves and sets the greeting. Here local `sandbox` is used. @@ -52,7 +52,7 @@ In most cases we will want to test complex methods involving multiple users and --- -## Sandbox Testing +## Sandbox Features NEAR Workspaces allows you to write tests once, and run them either on `testnet` or a local `Sandbox`. By **default**, Workspaces will start a **sandbox** and run your tests **locally**. Lets dive into the features of our framework and see how they can help you. From 10d46e3d1aa074cf652c8b71548840622c6475e3 Mon Sep 17 00:00:00 2001 From: polyprogrammist Date: Mon, 25 Nov 2024 23:00:08 +0800 Subject: [PATCH 07/11] environment docs --- .../2.smart-contracts/anatomy/environment.md | 56 +++++++++++++------ 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/docs/2.build/2.smart-contracts/anatomy/environment.md b/docs/2.build/2.smart-contracts/anatomy/environment.md index d3c20615037..5dd3c8eaff0 100644 --- a/docs/2.build/2.smart-contracts/anatomy/environment.md +++ b/docs/2.build/2.smart-contracts/anatomy/environment.md @@ -67,23 +67,23 @@ Every method execution has an environment associated with information such as: ## Who is Calling? Who am I? -The environment gives you access to 3 important users: the `current_account`, the `predecessor`, and the `signer`. +The environment gives you access to 3 important users: the `current_account_id()`, the `predecessor_account_id()`, and the `signer_account_id()`. ### Current Account -The `current_account` contains the address in which your contract is deployed. This is very useful to implement ownership, e.g. making a public method only callable by the contract itself. +The `current_account_id()` contains the address in which your contract is deployed. This is very useful to implement ownership, e.g. making a public method only callable by the contract itself. ### Predecessor and Signer -The `predecessor` is the account that called the method in the contract. Meanwhile, the `signer` is the account that _signed_ the initial transaction. +The `predecessor_account_id()` is the account that called the method in the contract. Meanwhile, the `signer_account_id()` is the account that _signed_ the initial transaction. -During a simple transaction (no [cross-contract calls](../anatomy/crosscontract.md)) the `predecessor` is the same as the `signer`. For example, if **alice.near** calls **contract.near**, from the contract's perspective, **alice.near** is both the `signer` and the `predecessor`. However, if **contract.near** creates a [cross-contract call](../anatomy/crosscontract.md), then the `predecessor` changes down the line. In the example below, when **pool.near** executes, it would see **contract.near** as the `predecessor` and **alice.near** as the `signer`. +During a simple transaction (no [cross-contract calls](../anatomy/crosscontract.md)) the `predecessor_account_id()` is the same as the `signer_account_id()`. For example, if **alice.near** calls **contract.near**, from the contract's perspective, **alice.near** is both the `signer_account_id()` and the `predecessor_account_id()`. However, if **contract.near** creates a [cross-contract call](../anatomy/crosscontract.md), then the `predecessor_account_id()` changes down the line. In the example below, when **pool.near** executes, it would see **contract.near** as the `predecessor_account_id()` and **alice.near** as the `signer_account_id()`. ![img](https://miro.medium.com/max/1400/1*LquSNOoRyXpITQF9ugsDpQ.png) *You can access information about the users interacting with your smart contract* :::tip -In most scenarios you will **only need to know the predecessor**. However, there are situations in which the signer is very useful. For example, when adding [NFTs](../../5.primitives/nft.md) into [this marketplace](https://github.com/near-examples/nft-tutorial/blob/7fb267b83899d1f65f1bceb71804430fab62c7a7/market-contract/src/nft_callbacks.rs#L42), the contract checks that the `signer`, i.e. the person who generated the transaction chain, is the NFT owner. +In most scenarios you will **only need to know the predecessor**. However, there are situations in which the signer is very useful. For example, when adding [NFTs](../../5.primitives/nft.md) into [this marketplace](https://github.com/near-examples/nft-tutorial/blob/7fb267b83899d1f65f1bceb71804430fab62c7a7/market-contract/src/nft_callbacks.rs#L42), the contract checks that the `signer_account_id()`, i.e. the person who generated the transaction chain, is the NFT owner. ::: --- @@ -92,9 +92,9 @@ In most scenarios you will **only need to know the predecessor**. However, there The environment gives you access to 3 token-related parameters, all expressed in yoctoNEAR (1 Ⓝ = 1024yⓃ): ### Attached Deposit -`attached_deposit` represents the amount of yoctoNEAR the predecessor attached to the call. +`attached_deposit()` represents the amount of yoctoNEAR the predecessor attached to the call. -This amount is **already deposited** in your contract's account, and is **automatically returned** to the `predecessor` if your **method panics**. +This amount is **already deposited** in your contract's account, and is **automatically returned** to the `predecessor_account_id()` if your **method panics**. :::warning If you make a [cross-contract call](../anatomy/crosscontract.md) and it panics, the funds are sent back to **your contract**. See how to handle this situation in the [callback section](../anatomy/crosscontract.md#what-happens-if-the-function-i-call-fails) @@ -102,17 +102,17 @@ If you make a [cross-contract call](../anatomy/crosscontract.md) and it panics, ### Account Balance -`account_balance` represents the balance of your contract (`current_account`). +`account_balance()` represents the balance of your contract (`current_account_id()`). -It includes the `attached_deposit`, since it was deposited when the method execution started. +It includes the `attached_deposit()`, since it was deposited when the method execution started. -If the contract has any locked $NEAR, it will appear in `account_locked_balance`. +If the contract has any locked $NEAR, it will appear in `account_locked_balance()`. --- ### Storage Used -`storage_used` represents the amount of [storage](../anatomy/storage.md) that is currently being used by your contract. +`storage_used()` represents the amount of [storage](../anatomy/storage.md) that is currently being used by your contract. :::tip If you want to know how much storage a structure uses, print the storage before and after storing it. @@ -126,15 +126,15 @@ The environment exposes three different ways to tell the pass of time, each repr ### Timestamp -The `timestamp` attribute represents the approximated **UNIX timestamp** in **nanoseconds** at which this call was executed. It quantifies time passing in a human way, enabling us to check if a specific date has passed or not. +The `block_timestamp()` attribute represents the approximated **UNIX timestamp** in **nanoseconds** at which this call was executed. It quantifies time passing in a human way, enabling us to check if a specific date has passed or not. ### Current Epoch -The NEAR blockchain groups blocks in [Epochs](../../../1.concepts/basics/epoch.md). The `current_epoch` attribute measures how many epochs have passed so far. It is very useful to coordinate with other contracts that measure time in epochs, such as the [validators](../../../1.concepts/basics/validators.md). +The NEAR blockchain groups blocks in [Epochs](../../../1.concepts/basics/epoch.md). The `current_epoch()` attribute measures how many epochs have passed so far. It is very useful to coordinate with other contracts that measure time in epochs, such as the [validators](../../../1.concepts/basics/validators.md). ### Block Index -The `block_index` represents the index of the block in which this transaction will be added to the blockchain. +The `block_index()` represents the index of the block in which this transaction will be added to the blockchain. --- @@ -146,13 +146,13 @@ Gas can be thought of as wall time, where 1 PetaGas (1_000 TGas) is ~1 second of Each code instruction costs a certain amount of Gas, and if you run out of it, the execution halts with the error message `Exceeded the prepaid gas`. -The environment gives you access to two gas-related arguments: `prepaid_gas` and `used_gas`. +The environment gives you access to two gas-related arguments: `prepaid_gas()` and `used_gas()`. ### Prepaid Gas -`prepaid_gas` represents the amount of Gas the `predecessor` attached to this call. It cannot exceed the limit 300TGas (300 * 1012 Gas). +`prepaid_gas` represents the amount of Gas the `predecessor_account_id()` attached to this call. It cannot exceed the limit 300TGas (300 * 1012 Gas). ### Used Gas -`used_gas` contains the amount of Gas that has been used so far. It is useful to estimate the Gas cost of running a method. +`used_gas()` contains the amount of Gas that has been used so far. It is useful to estimate the Gas cost of running a method. :::warning During [cross-contract calls](./crosscontract.md) always make sure the callback has enough Gas to fully execute. @@ -203,6 +203,7 @@ Besides environmental variables, the SDK also exposes some functions to perform | Function Name | SDK method | Description | |-----------------------|---------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | SHA 256 | `env::sha256(value)` | Hashes a sequence of bytes using sha256. | +| ED25519 verify | `env::ed25519_verify(signature, message, public_key)` | Verifies if the message was signed with correct public key | | Keccak 256 | `env::keccak256(value)` | Hashes a sequence of bytes using keccak256. | | Keccak 512 | `env::keccak512(value)` | Hashes a sequence of bytes using keccak512. | | SHA 256 (Array) | `env::sha256_array(value)` | Hashes the bytes using the SHA-256 hash function. This returns a 32 byte hash. | @@ -214,13 +215,32 @@ Besides environmental variables, the SDK also exposes some functions to perform | Log String | `env::log_str(message)` | Logs the string message. This message is stored on chain. | | Validator Stake | `env::validator_stake(account_id)` | For a given account return its current stake. If the account is not a validator, returns 0. | | Validator Total Stake | `env::validator_total_stake()` | Returns the total stake of validators in the current epoch. | - +| Random seed | `env::random_seed()` | Returns random number | +| Random seed array | `env::random_seed_array()` | Returns array of random numbers | +## Low-level functions for Rust sdk: + + +| Function Name | SDK method | Description | +|-----------------------|---------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Setup panic hook | `env::setup_panic_hook()` | Sets up `env::panic_str` method to call when unexpected error occurs. +| Contract call input | `env::input()` | Returns serde-serialized arguments to the method call | +| Create promise | `env::promise_create(account_id, function_name, arguments, amount, gas)` | Makes cross-contract call and returns promise | + | Read storage | `env::storage_read(key)` | Reads value for the specified key from storage | + | Write storage | `env::storage_write(key, value)` | Writes value for the specified key to storage | + | Remove storage | `env::storage_remove(key)` | Removes key with it's value from storage | + + + :::info In the JS SDK, `throw new Error("message")` mimics the behavior of Rust's `env::panic_str("message")`. ::: +:::info +More information on Rust sdk environment methods you can find in it's [documentation](https://docs.rs/near-sdk/latest/near_sdk/env/index.html) +::: + --- From 2037cb890e4cc5e207bfda9c401c492e68d0424b Mon Sep 17 00:00:00 2001 From: polyprogrammist Date: Mon, 25 Nov 2024 23:14:54 +0800 Subject: [PATCH 08/11] link to storage staking --- docs/1.concepts/protocol/gas.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/1.concepts/protocol/gas.md b/docs/1.concepts/protocol/gas.md index 8fb7993e2bd..92da4050593 100644 --- a/docs/1.concepts/protocol/gas.md +++ b/docs/1.concepts/protocol/gas.md @@ -100,7 +100,7 @@ The cost of calling a function will depend on how complex the function is, but w ::: :::tip Deploying a Contract** -Note that this covers the gas cost of uploading and writing bytes to storage, but does **not** cover the cost of holding them in storage (which is `1Ⓝ ~ 100kb`). +Note that this covers the gas cost of uploading and writing bytes to storage, but does **not** cover the cost of holding them in storage (which is `1Ⓝ ~ 100kb`). More information about storage staking [here](../storage/storage-staking.md) :::
From 477b5af51b6b6fff16e42e7c2bc5908792ec4b64 Mon Sep 17 00:00:00 2001 From: polyprogrammist Date: Tue, 26 Nov 2024 12:31:36 +0800 Subject: [PATCH 09/11] links to docs.rs and near.github.io/near-sdk-js --- docs/2.build/2.smart-contracts/anatomy/yield-resume.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/2.build/2.smart-contracts/anatomy/yield-resume.md b/docs/2.build/2.smart-contracts/anatomy/yield-resume.md index 432e3e2ad6e..812e7ec8dbd 100644 --- a/docs/2.build/2.smart-contracts/anatomy/yield-resume.md +++ b/docs/2.build/2.smart-contracts/anatomy/yield-resume.md @@ -29,9 +29,9 @@ Let's look at an example that takes a prompt from a user (e.g. "What is 2+2"), a #### Creating a Yielded Promise -In the example above, we are creating a [`Promise`](./crosscontract.md#promises) to call the contract's function `return_external_response`. +In the example above, we are creating a [`Promise`](./crosscontract.md#promises) [🦀](https://near.github.io/near-sdk-js/classes/promise.NearPromise.html) [🌐](https://docs.rs/near-sdk/latest/near_sdk/struct.Promise.html) to call the contract's function `return_external_response`. -Notice that we create the `Promise` using `env::promise_yield_create`, which will create an **identifier** for the yielded promise in the `YIELD_REGISTER`. +Notice that we create the `Promise` using `env::promise_yield_create` [🦀](https://docs.rs/near-sdk/latest/near_sdk/env/fn.promise_yield_create.html) , which will create an **identifier** for the yielded promise in the `YIELD_REGISTER`. #### Retrieving the Yielded Promise ID We read the `YIELD_REGISTER` to retrieve the `ID` of our yielded promise. We store the `yield_id` and the user's `prompt` so the external service query them (the contract exposes has a function to list all requests). @@ -88,7 +88,7 @@ The function being resumed will have access to all parameters passed to it, incl In the example above, the `return_external_response` receives two parameters: 1. A `request_id` - passed on [creation](#creating-a-yielded-promise) - which is used to remove the request from the state -2. A `response` - passed when [signaling to resume](#signaling-the-resume) - which contains the external response, or a `PromiseError` if the contract timed out while waiting +2. A `response` - passed when [signaling to resume](#signaling-the-resume) - which contains the external response, or a `PromiseError` [🦀](https://docs.rs/near-sdk/latest/near_sdk/enum.PromiseError.html) [🌐](https://near.github.io/near-sdk-js/enums/types_vm_types.PromiseError.html) if the contract timed out while waiting :::tip There's plenty of time From b1664107f88b7f8f8865b28661e959f7b1144bdb Mon Sep 17 00:00:00 2001 From: polyprogrammist Date: Fri, 29 Nov 2024 21:25:12 +0800 Subject: [PATCH 10/11] gas --- .../2.smart-contracts/anatomy/gascalc.md | 196 ++++++++++++++++++ website/sidebars.js | 1 + 2 files changed, 197 insertions(+) create mode 100644 docs/2.build/2.smart-contracts/anatomy/gascalc.md diff --git a/docs/2.build/2.smart-contracts/anatomy/gascalc.md b/docs/2.build/2.smart-contracts/anatomy/gascalc.md new file mode 100644 index 00000000000..9801ed3afed --- /dev/null +++ b/docs/2.build/2.smart-contracts/anatomy/gascalc.md @@ -0,0 +1,196 @@ +--- +id: gascalc +title: Gas estimation +hide_table_of_contents: true +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import {CodeTabs, Language, Github} from "@site/src/components/codetabs"; +import CodeBlock from '@theme/CodeBlock'; +import {ExplainCode, Block, File} from '@site/src/components/CodeExplainer/code-explainer'; + +This guide explains how to estimate, allocate, and check gas for cross-contract calls in NEAR. It includes practical examples, tools like near-cli, and API usage to streamline gas estimation. All examples are in Rust. + +## Overview of Gas in NEAR +Gas in NEAR is used to measure computational resources for transactions. When calling cross-contract functions, you need to allocate gas carefully to prevent failures or inefficiencies. Key components include: + + - Gas burned: Gas consumed during execution. + - Refunded gas: Unused prepaid gas returned to the caller. + - Used gas: The total amount of gas deducted from the transaction (includes burned and refunded gas) + - Prepaid Gas: Gas attached to a transaction upfront, allocated by the caller. + +## Cross-Contract Call Workflow +When invoking another contract: + +1. Attach prepaid gas to the transaction. +2. Allocate gas for: + - The called contract. + - Any callbacks or subsequent operations. + - The current function’s execution. +3. Test and benchmark, monitor gas usage (burned, used, refunded) to optimize allocation. + +Gas to Attach = Gas for Current Function + Gas for Target Contract + Gas for Callback + +:::note +Near sets a gas limit per transaction: 300 Tgas (300 × 10^12 gas) +::: + +### Example: Simple Cross-Contract Call + +```rust +use near_sdk::Promise; + +const TGAS: u64 = 1_000_000_000_000; // 1 TeraGas +const GAS_FOR_TARGET: u64 = 50 * TGAS; // Gas for the target contract +const GAS_FOR_CALLBACK: u64 = 20 * TGAS; // Gas reserved for the callback + +#[near] +impl MyContract { + /// Initiates a cross-contract call + pub fn call_another_contract(&self, target_contract: AccountId, args: Vec) { + // Ensure there's enough gas for the call and the callback + assert!( + env::prepaid_gas() > GAS_FOR_TARGET + GAS_FOR_CALLBACK, + "Not enough gas attached" + ); + + // Call the target contract + Promise::new(target_contract.clone()) + .function_call( + "target_function".to_string(), + args, + 0, // No attached deposit + GAS_FOR_TARGET, // Gas for this function + ) + // Add a callback to handle the response + .then( + Promise::new(env::current_account_id()) + .function_call( + "on_callback".to_string(), + vec![], + 0, // No attached deposit + GAS_FOR_CALLBACK, + ) + ); + } + + /// Callback handler + pub fn on_callback(&self) { + if let Some(result) = env::promise_result(0) { + match result { + PromiseResult::Successful(data) => { + env::log_str("Callback succeeded"); + // Process the data if needed + } + PromiseResult::Failed => { + env::log_str("Callback failed"); + } + _ => (), + } + } + } +} +``` + +## How to Check Gas Usage +### Testnet / mainnet +You can use `nearblocks` explorer to see gas usage. You may first check for existing transactions in mainnet or testnet and see how much gas did they use. Here is an example of a token swap transaction on a decentralized exchange. + +For example, for swap operation transaction `s5dL1DTiJ5pJugU2KpyTWLCbCT3pCXM3Pw8zSrTfqD4` in mainnet, go to https://nearblocks.io/txns/s5dL1DTiJ5pJugU2KpyTWLCbCT3pCXM3Pw8zSrTfqD4 + +There you can see `Usage by Txn: 37.97 Tgas` +That means that the transaction used 37.97 TGas. So the amount of gas you should attach to call the contract is 37.97 Tgas. + +### Sandbox +When developing contract, you may want to know how much gas is used every time you change the contract code. In order to do that, you can run your code in sandox and see how much gas is used. + +On integrations tests [page](../testing/integration-test.md) you can find how to run sandbox. + +Use `env::used_gas()` inside your contract to check how much gas is used +```rust +env::log_str(&format!("Gas used: {}", env::used_gas())); +``` + + +## Setting gas in cross-contract call + +The following are the two different ways to make a contract call. Take a look on how the way of setting gas changes. + +Example: Using `Promise::new().function_call` +```rust +use near_sdk::Promise; + +#[near] +impl Contract { + fn call_other_contract() { + Promise::new("receiver.testnet".to_string()) + .function_call( + "target_function".to_string(), // Method name to call on the contract + b"{}".to_vec(), // Arguments to pass as a byte array (empty in this case) + 0, // Attached deposit (in yoctoNEAR), set to 0 here + 10_000_000_000_000, // Gas attached for this call + ) + } +} +``` + +Example: Using `ext` structure and `with_static_gas` method +```rust +#[ext_contract(ext_target)] +pub trait TargetContract { + fn target_function(&self, arg1: String); +} + +#[near] +impl Contract { + pub fn call_with_static_gas(&self, target: AccountId) -> Promise { + ext_target::ext("receiver.testnet".to_string()) + .with_static_gas(10_000_000_000_000) + .target_fuction("hello") + } +} +``` + +## Using prepaid gas + +In order to check whether the gas required is attached to a call, you can use `env::prepaid_gas`. It allows to stop the execution in complex workflows where you know in advance that the gas attached may be insufficient: +```rust +#[near] +impl MyContract { + pub fn example_prepaid(&self, target: AccountId, args: Vec) { + assert!( + env::prepaid_gas() > 80 * TGAS, + "Insufficient prepaid gas" + ); + // ... + } +} +``` + +Sometimes, you just don't know how much gas will be used. It can happen when you make a cross-contract call to a contract that is not predefined and it's gas usage may be unpredictable. Also, if your contract's code is changing rapidly, it's gas usage may change rapidly as well. In order to make your code still work and make the user decide how much gas to attach, you can use a trick with `env::prepaid_gas` when making cross-contract call. + +Instead of estimating the gas for a cross-contract call, use a part of `prepaid_gas`, for example, one third: +```rust +#[near] +impl Contract { + pub fn call_with_static_gas(&self, target: AccountId) -> Promise { + ... + ext_target::ext("receiver.testnet".to_string()) + .with_static_gas(env::prepaid_gas() / 3) + .target_fuction("hello") + ... + } +} +``` +It will allow you to enable the user to execute contract method when the gas usage unexpectedly increases. + +## Common Pitfalls to Avoid +1. Running Out of Gas: + - Ensure that the total gas attached to the transaction is within the 300 Tgas limit. + - Always allocate some gas for callbacks if you expect them. + - Reserve extra gas for scenarios where the target contract performs unexpectedly resource-intensive operations. +2. Over-Allocation: + - While unused gas is refunded, attaching excessive gas may cause unnecessary delays in execution due to prioritization in the network. +3. Mismanaging Deposits: + - Ensure attached deposits are explicitly set to 0 if not required to avoid unexpected costs \ No newline at end of file diff --git a/website/sidebars.js b/website/sidebars.js index fa34745cdc1..e97baf0d59b 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -127,6 +127,7 @@ const sidebar = { "build/smart-contracts/anatomy/actions", "build/smart-contracts/anatomy/crosscontract", "build/smart-contracts/anatomy/yield-resume", + "build/smart-contracts/anatomy/gascalc", "build/smart-contracts/security/checklist", { "type": "html", From fed1610a7ee785e97b652c9c8a98760c081a0370 Mon Sep 17 00:00:00 2001 From: polyprogrammist Date: Fri, 29 Nov 2024 21:33:15 +0800 Subject: [PATCH 11/11] unordered and iterable map --- docs/2.build/2.smart-contracts/anatomy/collections.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/2.build/2.smart-contracts/anatomy/collections.md b/docs/2.build/2.smart-contracts/anatomy/collections.md index 00537099204..303d2027a8d 100644 --- a/docs/2.build/2.smart-contracts/anatomy/collections.md +++ b/docs/2.build/2.smart-contracts/anatomy/collections.md @@ -109,8 +109,8 @@ SDK collections are useful when you are planning to store large amounts of data |-----------------------------------------------|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `store::Vector` | `Vec` | A growable array type. The values are sharded in memory and can be used for iterable and indexable values that are dynamically sized. | | store::LookupMap`` | HashMap`` | This structure behaves as a thin wrapper around the key-value storage available to contracts. This structure does not contain any metadata about the elements in the map, so it is not iterable. | -| store::IterableMap`` | HashMap`` | Similar to `LookupMap`, except that it stores additional data to be able to iterate through elements in the data structure. | -| store::UnorderedMap`` | HashMap`` | Similar to `LookupMap`, except that it stores additional data to be able to iterate through elements in the data structure. | +| store::IterableMap`` | HashMap`` | Similar to `LookupMap`, except that it stores additional data to be able to iterate through elements in the data structure. To be used when remove is not a frequent operation. | +| store::UnorderedMap`` | HashMap`` | Similar to `LookupMap`, except that it stores additional data to be able to iterate through elements in the data structure. Optimized for fast removal. Other operations become slower. | | `store::LookupSet` | `HashSet` | A set, which is similar to `LookupMap` but without storing values, can be used for checking the unique existence of values. This structure is not iterable and can only be used for lookups. | | `store::IterableSet` | `HashSet` | An iterable equivalent of `LookupSet` which stores additional metadata for the elements contained in the set. | | `store::UnorderedSet` | `HashSet` | An iterable equivalent of `LookupSet` which stores additional metadata for the elements contained in the set. |