-
Notifications
You must be signed in to change notification settings - Fork 420
Fixes for rust sdk #2325
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
+542
−32
Closed
Fixes for rust sdk #2325
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
40b7f82
make external calls more clear for rust and words about key value sto…
PolyProgrammist 323551a
update checklist, more info about promise return
PolyProgrammist dc00bd8
public key
PolyProgrammist 3ea1389
borshserialize and storage_write
PolyProgrammist d1496c8
Merge branch 'master' into sdk-rs-fixes
bucanero 2d39bca
macros explained
PolyProgrammist 851554b
more clear info in integration tests
PolyProgrammist 10d46e3
environment docs
PolyProgrammist 2037cb8
link to storage staking
PolyProgrammist 477b5af
links to docs.rs and near.github.io/near-sdk-js
PolyProgrammist 01a8272
Merge branch 'master' into sdk-rs-fixes
bucanero b166410
gas
PolyProgrammist fed1610
unordered and iterable map
PolyProgrammist File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<u8>) { | ||
// 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<u8>) { | ||
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} | ||
|
||
``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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", | ||
|
@@ -139,6 +140,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", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good call adding this here |
||
] | ||
} | ||
] | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks more like a tutorial than a section for
Build
If you check all the other pages on
Smart Contract / Anatomy
, you will see that they only talk about the SDKHere, we mix multiple tools, therefore it is better suited as a tutorial