Measure gas before you optimise
Measure NEAR gas before optimising: read env::used_gas() and env::prepaid_gas(), check gas burnt per receipt, and print gas in near-workspaces sandbox tests.
Intermediate6 min read3-question check
Gas surprises people. The expensive part of a method is often not the logic you are staring at but loading and saving contract state, (de)serializing JSON arguments, or a cross-contract call you forgot was there. Measure first, change one thing, measure again with the same inputs.
Inside the contract#
Rust
use near_sdk::store::LookupMap;
use near_sdk::{env, log, near};
#[near(contract_state)]
pub struct Counter {
scores: LookupMap<String, u64>,
}
impl Default for Counter {
fn default() -> Self {
Self { scores: LookupMap::new(b"s") }
}
}
#[near]
impl Counter {
pub fn bump(&mut self, key: String) {
let start = env::used_gas();
let current = self.scores.get(&key).copied().unwrap_or(0);
self.scores.insert(key, current + 1);
log!(
"bump: {} Ggas so far, {} Tgas prepaid",
env::used_gas().saturating_sub(start).as_ggas(),
env::prepaid_gas().as_tgas()
);
}
}In sandbox tests#
Rust
#[tokio::test]
async fn bump_gas() -> anyhow::Result<()> {
let sandbox = near_workspaces::sandbox().await?;
let wasm = near_workspaces::compile_project("./").await?;
let contract = sandbox.dev_deploy(&wasm).await?;
let outcome = contract
.call("bump")
.args_json(serde_json::json!({ "key": "alice" }))
.transact()
.await?;
assert!(outcome.is_success());
println!("total: {} Ggas", outcome.total_gas_burnt.as_ggas());
for receipt in outcome.receipt_outcomes() {
println!("{}: {} Ggas", receipt.executor_id, receipt.gas_burnt.as_ggas());
}
println!("logs: {:?}", outcome.logs());
Ok(())
}- Run with
cargo test -- --nocaptureso theprintln!output is shown. - Test the worst case, not the happy path: a large collection, the longest allowed string, the account with the most entries. Gas that grows with input size is what eventually breaks a method.
- For transactions already on testnet or mainnet, the transaction outcome lists gas burnt and tokens burnt for the transaction and for every receipt it spawned. near-cli can print it, and explorers such as NearBlocks show it per receipt.
- Record the numbers before and after each change. An optimisation you cannot measure is a guess.
Check yourself
3 questions · progress saved in this browser