Skip to content

Commit f7c1ed2

Browse files
authored
Merge pull request #171 from iotaledger/feat/estimate-gas-cost
feat(docs): Example to estimate gas cost
2 parents e2fd14f + 6ac8a5f commit f7c1ed2

File tree

3 files changed

+198
-0
lines changed

3 files changed

+198
-0
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright 2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { EpochInfo, IotaTransactionBlockResponse } from "@iota/iota-sdk/client";
5+
import { NotarizationClient, State } from "@iota/notarization/node";
6+
import { getFundedClient } from "../util";
7+
8+
const STATE_METADATA: string | null = null; // "State metadata example";
9+
const IMMUTABLE_DESCRIPTION: string | null = null; // "This metadata will not change";
10+
11+
let REFERENCE_GAS_PRICE: bigint | null = null;
12+
13+
const BILLION = 1000000000;
14+
const MINIMUM_STORAGE_COST = 0.0029488; // Unit is IOTA not Nanos
15+
16+
function print_gas_cost(transaction_type: String, flexDataSize: number, response: IotaTransactionBlockResponse) {
17+
const gasUsed = response.effects?.gasUsed;
18+
const referenceGasPrice = REFERENCE_GAS_PRICE ? Number(REFERENCE_GAS_PRICE) : -1; // Fallback to -1 if EpochInfo is not available
19+
20+
if (gasUsed != undefined) {
21+
const totalGasCost = parseInt(gasUsed.computationCost) + parseInt(gasUsed.storageCost)
22+
- parseInt(gasUsed.storageRebate);
23+
const storageCost = parseInt(gasUsed.storageCost) / BILLION;
24+
const computationCostNanos = parseInt(gasUsed.computationCost);
25+
const storageCostAboveMin = storageCost - MINIMUM_STORAGE_COST;
26+
console.log(
27+
"-------------------------------------------------------------------------------------------------------",
28+
);
29+
console.log(`--- Gas cost for '${transaction_type}' transaction`);
30+
console.log(
31+
"-------------------------------------------------------------------------------------------------------",
32+
);
33+
console.log(`referenceGasPrice: ${referenceGasPrice}`);
34+
console.log(`computationCost: ${computationCostNanos / BILLION}`);
35+
console.log(`Computation Units: ${computationCostNanos / referenceGasPrice}`);
36+
console.log(`storageCost: ${storageCost}`);
37+
console.log(`flexDataSize: ${flexDataSize}`);
38+
console.log(`storageCost above minimum (0.0029488): ${storageCostAboveMin}`);
39+
console.log(`storageCostAboveMin per flexDataSize: ${storageCostAboveMin / (flexDataSize - 1)}`);
40+
console.log(`storageRebate: ${parseInt(gasUsed.storageRebate) / BILLION}`);
41+
console.log(`totalGasCost (calculated): ${totalGasCost / BILLION}`);
42+
console.log(
43+
"-------------------------------------------------------------------------------------------------------",
44+
);
45+
} else {
46+
console.log("Gas used information is not available.");
47+
}
48+
}
49+
50+
function randomString(length = 50) {
51+
return [...Array(length + 10)].map((value) => (Math.random() * 1000000).toString(36).replace(".", "")).join("")
52+
.substring(0, length);
53+
}
54+
55+
async function create_dynamic_notarization(
56+
notarizationClient: NotarizationClient,
57+
stateDataSize: number,
58+
): Promise<{ notarization: any; response: IotaTransactionBlockResponse }> {
59+
console.log(`Creating a dynamic notarization for state updates with ${stateDataSize} bytes of state data`);
60+
61+
let stateData = randomString(stateDataSize);
62+
63+
const { output: notarization, response: response } = await notarizationClient
64+
.createDynamic()
65+
.withStringState(stateData, STATE_METADATA)
66+
.withImmutableDescription(IMMUTABLE_DESCRIPTION)
67+
.finish()
68+
.buildAndExecute(notarizationClient);
69+
70+
console.log("✅ Created dynamic notarization:", notarization.id);
71+
const flexDataSize = stateData.length + (STATE_METADATA ? STATE_METADATA.length : 0)
72+
+ (IMMUTABLE_DESCRIPTION ? IMMUTABLE_DESCRIPTION.length : 0);
73+
print_gas_cost("Create", flexDataSize, response);
74+
75+
return { notarization, response };
76+
}
77+
78+
/** Create, update and destroy a Dynamic Notarization to estimate gas cost */
79+
export async function createUpdateDestroy(): Promise<void> {
80+
console.log("Create, update and destroy a Dynamic Notarization to estimate gas cost");
81+
82+
const notarizationClient = await getFundedClient();
83+
84+
const iotaClient = notarizationClient.iotaClient();
85+
REFERENCE_GAS_PRICE = await iotaClient.getReferenceGasPrice();
86+
console.log(
87+
"Successfully fetched the referenceGasPrice: ",
88+
REFERENCE_GAS_PRICE != null ? REFERENCE_GAS_PRICE : "Not Available",
89+
);
90+
91+
let notarization;
92+
93+
// Create several dynamic notarizations with different initial state sizes. The notarization with the largest state size will be used for updates.
94+
console.log("\n🆕 Creating dynamic notarizations with different initial state sizes...");
95+
for (let i = 1; i <= 4; i++) {
96+
const result = await create_dynamic_notarization(notarizationClient, 10 * i * i); // 10, 40, 90, 160 bytes
97+
notarization = result.notarization;
98+
}
99+
100+
// Perform multiple state updates
101+
console.log("\n🔄 Performing state updates...");
102+
103+
for (let i = 1; i <= 3; i++) {
104+
console.log(`\n--- Update ${i} ---`);
105+
106+
// Create new state with updated content and metadata
107+
const newContent = randomString(i * 50); // Set this size to 138 bytes to keep total flex data size equal to the latest created notarization
108+
const newMetadata = `Version ${i + 1}.0 - Update ${i}`;
109+
110+
// Update the state
111+
const { output: _, response: response } = await notarizationClient
112+
.updateState(
113+
State.fromString(newContent, newMetadata),
114+
notarization.id,
115+
)
116+
.buildAndExecute(notarizationClient);
117+
118+
console.log(`✅ State update ${i} completed`);
119+
const flexDataSize = newContent.length + newMetadata.length;
120+
print_gas_cost("Update", flexDataSize, response);
121+
}
122+
123+
// Destroy the dynamic notarization
124+
try {
125+
const { output: _, response: response } = await notarizationClient
126+
.destroy(notarization.id)
127+
.buildAndExecute(notarizationClient);
128+
console.log("✅ Successfully destroyed unlocked dynamic notarization");
129+
print_gas_cost("Destroy", 1, response);
130+
} catch (e) {
131+
console.log("❌ Failed to destroy:", e);
132+
}
133+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Gas Cost Estimation Example for Notarization
2+
3+
This folder contains an example to estimate the gas cost for Notarization object creation, update and destroy operations.
4+
5+
It can be run like any other example.
6+
7+
The log output of the example is optimized to evaluate variables and constants needed to calculate gas cost as being
8+
described in the following sections.
9+
10+
## Results of the Gas Cost Estimation
11+
12+
The gas cost for creating Dynamic and Locked Notarizations only differ in the amount of needed Storage Cost.
13+
The mimimum Byte size of a Locked Notarization is 19 bytes larger than the one of a Dynamic Notarization due to the additional
14+
lock information stored in the Notarization object. This results in a slightly higher Storage Cost (0.0001425 IOTA) for
15+
Locked Notarizations compared to Dynamic Notarizations when they are created with the same amount of State Data, Metadata, etc.
16+
17+
**For the sake of simplicity, the following sections only describe the gas cost estimation for Dynamic Notarizations.**
18+
19+
### Creating Notarizations
20+
21+
The cost for creating a Notarization object can roughly be calculated by the following equation:
22+
23+
`TotalCost` = `FlexDataSize` * `FlexDataByteCost` + `MinimumStorageCost` + `ComputationCost`
24+
25+
`TotalCost` = F [Byte] * 0.0000076 [IOTA/Byte] + 0.00295 [IOTA] + 0.001 [IOTA]
26+
27+
Where:
28+
29+
| Parameter | Description |
30+
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
31+
| `FlexDataSize` | Sum of the byte sizes of State Data, State Metadata, Updatable Metadata and Immutable Metadata. The value must be reduced by 1 as the `MinimumStorageCost` uses 1 byte of State Data. |
32+
| `FlexDataByteCost` | A constant value of 0.0000076 IOTA/Byte <br> This value denotes (`StorageCost` - `MinimumStorageCost`) divided by `FlexDataSize`. |
33+
| `MinimumStorageCost` | A constant value of 0.00295 IOTA. <br> This value denotes the `StorageCost` for a Notarization with 1 Byte of `FlexDataSize` meaning a Notarization with 1 Byte of State Data, no meta data and no optional locks. |
34+
| `ComputationCost` | A constant value of 0.001 IOTA. <br> Given the Gas Price is 1000 nano, the `ComputationCost` will always be 0.001 IOTA as creating Notarizations always consume 1000 Computation Units. |
35+
| `TotalCost` | The amount of IOTA that would need to be paid for gas when Storage Rebate is not taken into account. The real gas cost will be lower, due to Storage Rebate, which is usually -0.0009804 IOTA when a Notarization object is created. |
36+
37+
Examples:
38+
39+
| `FlexDataSize` | `TotalCost` (Storage Rebate not taken into account) |
40+
| -------------- | --------------------------------------------------- |
41+
| 10 | 0.004026 IOTA |
42+
| 100 | 0.00471 IOTA |
43+
| 1000 | 0.01155 IOTA |
44+
45+
### Updating Dynamic Notarizations
46+
47+
The `TotalCost` for updating a Dynamic Notarization can roughly be calculated using the same equation used for creating
48+
Notarization objects (see above).
49+
50+
The value for `FlexDataByteCost` should be set to 0.00000769 IOTA/Byte.
51+
52+
If the new Notarization State results in the same `FlexDataSize` as the overwritten old Notarization State, the Storage
53+
Rebate will compensate the Storage Cost so that the real gas cost to be paid will be more or less the Computation Cost,
54+
which is always 0.001 IOTA (presumed the Gas Price is 1000 nano).
55+
56+
### Destroying a Notarization
57+
58+
The `TotalCost` for destroying a Notarization is the Computation Cost which is 0.001 IOTA (presumed the Gas Price is 1000 nano).
59+
60+
Due to the Storage Rebate, which depends on the size of the stored Notarization object, the real gas cost to be paid will often be negative.
61+
62+
The Storage Rebate can roughly be calculated using the below equation. See above for more details about the used variables and constants.

bindings/wasm/notarization_wasm/examples/src/main.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { updateState } from "./05_update_state";
99
import { updateMetadata } from "./06_update_metadata";
1010
import { transferNotarization } from "./07_transfer_notarization";
1111
import { accessReadOnlyMethods } from "./08_access_read_only_methods";
12+
import { createUpdateDestroy } from "./gas-costs/01_create_update_destroy";
1213
import { iotWeatherStation } from "./real-world/01_iot_weather_station";
1314
import { legalContract } from "./real-world/02_legal_contract";
1415

@@ -40,6 +41,8 @@ export async function main(example?: string) {
4041
return await iotWeatherStation();
4142
case "02_real_world_legal_contract":
4243
return await legalContract();
44+
case "01_gas_costs_create_update_destroy":
45+
return await createUpdateDestroy();
4346
default:
4447
throw "Unknown example name: '" + argument + "'";
4548
}

0 commit comments

Comments
 (0)