Best practices
Here we lay out some best practices for writing smart contracts on NEAR, such as:- Keep state minimal
- Store Account IDs efficiently
- Enable overflow checks
- Use
require!early - Use
log! - Return
Promise - Reuse crates from
near-sdk - The difference between
std::panic!andenv::panic - Test with Sandbox
Keep state minimal
Only store the data your contract needs to enforce its rules. Data your app only displays, such as messages or history, is already recorded in the transactions that call your contract and can be read with an indexer. Never store values you can derive from other stored values, such as a premium flag next to the amount that defines it. See A contract is not a backend.Store Account IDs efficiently
You can save on smart contract storage if using NEAR Account IDs by encoding them using base32. Since they consist of[a-z.-_] characters with a maximum length of 64 characters, they can be encoded using 5 bits per character, with terminal \0. Going to a size of 65 * 5 = 325 bits from the original (64 + 4) * 8 = 544 bits. This is a 40% reduction in storage costs
Enable overflow checks
It’s usually helpful to panic on integer overflow. To enable it, add the following into yourCargo.toml file:
Use require! early
Try to validate the input, context, state and access using require! before taking any actions. The earlier you panic, the more gas you will save for the caller.
4.0.0-pre.2,
the Rust assert! macro can be used instead of require!.
Use log!
Use logging for debugging and notifying user.
When you need a formatted message, you can use the following macro:
Return Promise
If your method makes a cross-contract call, you probably want to return the newly created Promise.
This allows the caller (such as a near-cli or near-api-js call) to wait for the result of the promise instead of returning immediately.
Additionally, if the promise fails for some reason, returning it will let the caller know about the failure, as well as enabling NEAR Explorer and other tools to mark the whole transaction chain as failing.
This can prevent false-positives when the first or first few transactions in a chain succeed but a subsequent transaction fails.
E.g.
Reuse crates from near-sdk
near-sdk re-exports the following crates:
borshbase64bs58serdeserde_json
borsh which is needed for internal STATE serialization and
serde for external JSON serialization.
When marking structs with serde::Serialize you need to use #[serde(crate = "near_sdk::serde")]
to point serde to the correct base crate.
std::panic! vs env::panic
-
std::panic!panics the current thread. It usesformat!internally, so it can take arguments. SDK sets up a panic hook, which converts the generatedPanicInfofrompanic!into a string and usesenv::panicinternally to report it to Runtime. This may provide extra debugging information such as the line number of the source code where the panic happened. -
env::panicdirectly calls the host method to panic the contract. It doesn’t provide any other extra debugging information except for the passed message.
Test with Sandbox
Usenear-sandbox to run sandbox tests against a local NEAR node, and near-api to create accounts, deploy contracts, and send transactions. Test cross-contract calls, callback failures, refunds, and state changes as well as successful calls.
See the sandbox-testing guide for source-backed examples.