Collecting contract gas metrics
Gas consumption
When developing smart contracts for TON, it's essential to consider how efficiently your logic consumes gas. Unlike many other blockchains, TON also requires you to pay for storing contract data and forwarding messages between contracts.
As you develop and iterate on a contract, even small changes to its logic can affect both gas usage and data size. Monitoring these changes helps ensure that your contract remains efficient and cost-effective.
For a deeper breakdown of how fees work in TON, refer to:
Gas metrics reporting
To simplify tracking changes in gas usage and data size, we’ve introduced a reporting system that lets you collect and compare metrics across different versions of a contract.
To enable this, write test scenarios that cover the contract’s primary usage patterns and verify that its behavior is as expected. This approach is sufficient to gather relevant metrics, which you can later use to compare performance changes after updating the implementation.
Before running the tests, a store is created to collect metrics from all transactions generated during the tests. After test execution, the collected metrics are supplemented with ABI information from the snapshot, and a report is generated based on this data.
While more metrics are collected, the current report format includes only compute.phase
, state.code
, and state.data
.
Metrics comparison example
To see how gas metrics can be collected and compared in practice, let’s walk through a complete example.
Start by creating a new project using npm create ton@latest
:
npm create ton@latest -y -- sample --type func-counter --contractName Sample
cd sample
This command scaffolds a project with a basic counter contract at contracts/sample.fc
.
It defines a simple stateful contract that stores an id
and a counter
and supports an increase
operation.
#include "imports/stdlib.fc";
const op::increase = "op::increase"c;
global int ctx_id;
global int ctx_counter;
() load_data() impure {
var ds = get_data().begin_parse();
ctx_id = ds~load_uint(32);
ctx_counter = ds~load_uint(32);
ds.end_parse();
}
() save_data() impure {
set_data(
begin_cell()
.store_uint(ctx_id, 32)
.store_uint(ctx_counter, 32)
.end_cell()
);
}
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
if (in_msg_body.slice_empty?()) { ;; ignore all empty messages
return ();
}
slice cs = in_msg_full.begin_parse();
int flags = cs~load_uint(4);
if (flags & 1) { ;; ignore all bounced messages
return ();
}
load_data();
int op = in_msg_body~load_uint(32);
int query_id = in_msg_body~load_uint(64);
if (op == op::increase) {
int increase_by = in_msg_body~load_uint(32);
ctx_counter += increase_by;
save_data();
return ();
}
throw(0xffff);
}
int get_counter() method_id {
load_data();
return ctx_counter;
}
int get_id() method_id {
load_data();
return ctx_id;
}
Generate a gas report
Let’s now generate a gas usage report for the contract.
Run the following command:
npx blueprint test --gas-report
This runs your tests with gas tracking enabled and outputs a gas-report.json
with transaction metrics.
...
PASS Comparison metric mode: gas depth: 1
Gas report write in 'gas-report.json'
┌───────────┬──────────────┬───────────────────────────┐
│ │ │ current │
│ Contract │ Method ├──────────┬────────┬───────┤
│ │ │ gasUsed │ cells │ bits │
├───────────┼──────────────┼──────────┼────────┼───────┤
│ │ sendDeploy │ 1937 │ 11 │ 900 │
│ ├──────────────┼──────────┼────────┼───────┤
│ │ send │ 515 │ 11 │ 900 │
│ Sample ├──────────────┼──────────┼────────┼───────┤
│ │ sendIncrease │ 1937 │ 11 │ 900 │
│ ├──────────────┼──────────┼────────┼───────┤
│ │ 0x7e8764ef │ 2681 │ 11 │ 900 │
└───────────┴──────────────┴──────────┴────────┴───────┘
Storage fee calculation
You can use the cells
and bits
values from the report to estimate the storage fee for your contract.
Here’s the formula:
storage_fee = ceil(
(account.bits * bit_price
+ account.cells * cell_price)
* time_delta / 2 ^ 16)
To try this in practice, use the calculator example.
Regenerate the gas report
Note that the op::increase
method appears in the report as the raw opcode 0x7e8764ef
.
To display a human-readable name in the report, update the generated contract.abi.json
by replacing the raw opcode with the name increase in both the messages
and types
sections:
--- a/contract.abi.json
+++ b/contract.abi.json
@@ -6,13 +6,13 @@
"receiver": "internal",
"message": {
"kind": "typed",
- "type": "0x7e8764ef"
+ "type": "increase"
}
}
],
"types": [
{
- "name": "0x7e8764ef",
+ "name": "increase",
"header": 2122802415
}
],