From dcfb69b4c0e8253db141395efbe415ebe455402c Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Mon, 15 Dec 2025 10:33:00 +0000 Subject: [PATCH 1/6] Initial version of gas cost estimation scripts --- .../src/gas-costs/01_create_update_destroy.ts | 96 +++++++++++++++++++ .../notarization_wasm/examples/src/main.ts | 3 + 2 files changed, 99 insertions(+) create mode 100644 bindings/wasm/notarization_wasm/examples/src/gas-costs/01_create_update_destroy.ts diff --git a/bindings/wasm/notarization_wasm/examples/src/gas-costs/01_create_update_destroy.ts b/bindings/wasm/notarization_wasm/examples/src/gas-costs/01_create_update_destroy.ts new file mode 100644 index 0000000..2e66cb3 --- /dev/null +++ b/bindings/wasm/notarization_wasm/examples/src/gas-costs/01_create_update_destroy.ts @@ -0,0 +1,96 @@ +// Copyright 2025 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { State } from "@iota/notarization/node"; +import { strict as assert } from "assert"; +import { getFundedClient } from "../util"; +import {IotaTransactionBlockResponse} from "@iota/iota-sdk/client"; + +const STATE_DATA = "1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"; //"This is some state data"; +const STATE_METADATA: string | null = null; // "State metadata example"; +const IMMUTABLE_DESCRIPTION: string | null = null; // "This metadata will not change"; + + +const BILLION = 1000000000; + +function print_gas_cost(transaction_type: String, flexDataSize: number, response: IotaTransactionBlockResponse) { + const gasUsed = response.effects?.gasUsed; + + if (gasUsed != undefined) { + const totalGasCost = parseInt(gasUsed.computationCost) + parseInt(gasUsed.storageCost) - parseInt(gasUsed.storageRebate); + const storageCost = parseInt(gasUsed.storageCost) / BILLION; + const storageCostAboveMin = storageCost - 0.0029488; + console.log("-------------------------------------------------------------------------------------------------------"); + console.log(`--- Gas cost for '${transaction_type}' transaction`); + console.log("-------------------------------------------------------------------------------------------------------"); + console.log(`computationCost: ${parseInt(gasUsed.computationCost) / BILLION}`); + console.log(`storageCost: ${storageCost}`); + console.log(`flexDataSize: ${flexDataSize}`); + console.log(`storageCost above minimum (0.0029488): ${storageCostAboveMin}`); + console.log(`storageCostAboveMin per flexDataSize: ${storageCostAboveMin / flexDataSize}`); + console.log(`storageRebate: ${parseInt(gasUsed.storageRebate) / BILLION}`); + console.log(`totalGasCost (calculated): ${totalGasCost / BILLION}`); + console.log("-------------------------------------------------------------------------------------------------------"); + } else { + console.log("Gas used information is not available."); + } +} + +function randomString(length = 50) { + return [...Array(length + 10)].map((value) => (Math.random() * 1000000).toString(36).replace('.', '')).join('').substring(0, length); +}; + +/** Create, update and destroy a Dynamic Notarization to estimate gas cost */ +export async function createUpdateDestroy(): Promise { + console.log("Create, update and destroy a Dynamic Notarization to estimate gas cost"); + + const notarizationClient = await getFundedClient(); + + console.log("Creating a dynamic notarization for state updates..."); + + // Create a dynamic notarization + const { output: notarization, response: response } = await notarizationClient + .createDynamic() + .withStringState(STATE_DATA, STATE_METADATA) + .withImmutableDescription(IMMUTABLE_DESCRIPTION) + .finish() + .buildAndExecute(notarizationClient); + + console.log("āœ… Created dynamic notarization:", notarization.id); + const flexDataSize = STATE_DATA.length + (STATE_METADATA ? STATE_METADATA.length : 0) + (IMMUTABLE_DESCRIPTION ? IMMUTABLE_DESCRIPTION.length : 0); + print_gas_cost("Create", flexDataSize, response); + + // Perform multiple state updates + console.log("\nšŸ”„ Performing state updates..."); + + for (let i = 1; i <= 3; i++) { + console.log(`\n--- Update ${i} ---`); + + // Create new state with updated content and metadata + const newContent = randomString(i * 50); + const newMetadata = `Version ${i + 1}.0 - Update ${i}`; + + // Update the state + const { output: _, response: response } = await notarizationClient + .updateState( + State.fromString(newContent, newMetadata), + notarization.id, + ) + .buildAndExecute(notarizationClient); + + console.log(`āœ… State update ${i} completed`); + const flexDataSize = newContent.length + (STATE_METADATA ? newMetadata.length : 0); + print_gas_cost("Update", flexDataSize, response); + } + + // Destroy the dynamic notarization + try { + const { output: _, response: response } = await notarizationClient + .destroy(notarization.id) + .buildAndExecute(notarizationClient); + console.log("āœ… Successfully destroyed unlocked dynamic notarization"); + print_gas_cost("Destroy", 1, response); + } catch (e) { + console.log("āŒ Failed to destroy:", e); + } +} diff --git a/bindings/wasm/notarization_wasm/examples/src/main.ts b/bindings/wasm/notarization_wasm/examples/src/main.ts index 0f78ce9..436e4b9 100644 --- a/bindings/wasm/notarization_wasm/examples/src/main.ts +++ b/bindings/wasm/notarization_wasm/examples/src/main.ts @@ -11,6 +11,7 @@ import { transferNotarization } from "./07_transfer_notarization"; import { accessReadOnlyMethods } from "./08_access_read_only_methods"; import { iotWeatherStation } from "./real-world/01_iot_weather_station"; import { legalContract } from "./real-world/02_legal_contract"; +import { createUpdateDestroy } from "./gas-costs/01_create_update_destroy"; export async function main(example?: string) { // Extract example name. @@ -40,6 +41,8 @@ export async function main(example?: string) { return await iotWeatherStation(); case "02_real_world_legal_contract": return await legalContract(); + case "01_gas_costs_create_update_destroy": + return await createUpdateDestroy(); default: throw "Unknown example name: '" + argument + "'"; } From 63bb9759c1e75e6467d28e4e8f842e4a5dbbcc84 Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Tue, 16 Dec 2025 18:44:16 +0000 Subject: [PATCH 2/6] Slightly enhanced version of gas cost estimation script --- .../src/gas-costs/01_create_update_destroy.ts | 50 +++++++++++++------ .../wasm/notarization_wasm/package-lock.json | 34 ++++++++----- bindings/wasm/notarization_wasm/package.json | 2 +- 3 files changed, 57 insertions(+), 29 deletions(-) diff --git a/bindings/wasm/notarization_wasm/examples/src/gas-costs/01_create_update_destroy.ts b/bindings/wasm/notarization_wasm/examples/src/gas-costs/01_create_update_destroy.ts index 2e66cb3..477b9cb 100644 --- a/bindings/wasm/notarization_wasm/examples/src/gas-costs/01_create_update_destroy.ts +++ b/bindings/wasm/notarization_wasm/examples/src/gas-costs/01_create_update_destroy.ts @@ -1,20 +1,20 @@ // Copyright 2025 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { State } from "@iota/notarization/node"; -import { strict as assert } from "assert"; +import { State, NotarizationClient } from "@iota/notarization/node"; import { getFundedClient } from "../util"; -import {IotaTransactionBlockResponse} from "@iota/iota-sdk/client"; +import {EpochInfo, IotaTransactionBlockResponse} from "@iota/iota-sdk/client"; -const STATE_DATA = "1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"; //"This is some state data"; const STATE_METADATA: string | null = null; // "State metadata example"; const IMMUTABLE_DESCRIPTION: string | null = null; // "This metadata will not change"; +let EPOCH_INFO: EpochInfo | null = null; const BILLION = 1000000000; function print_gas_cost(transaction_type: String, flexDataSize: number, response: IotaTransactionBlockResponse) { const gasUsed = response.effects?.gasUsed; + const referenceGasPrice = EPOCH_INFO ? EPOCH_INFO.referenceGasPrice ? parseInt(EPOCH_INFO.referenceGasPrice) : 1000 : 1000; // Fallback to 1000 if EpochInfo is not available if (gasUsed != undefined) { const totalGasCost = parseInt(gasUsed.computationCost) + parseInt(gasUsed.storageCost) - parseInt(gasUsed.storageRebate); @@ -23,11 +23,12 @@ function print_gas_cost(transaction_type: String, flexDataSize: number, response console.log("-------------------------------------------------------------------------------------------------------"); console.log(`--- Gas cost for '${transaction_type}' transaction`); console.log("-------------------------------------------------------------------------------------------------------"); + console.log(`referenceGasPrice: ${referenceGasPrice / BILLION}`); console.log(`computationCost: ${parseInt(gasUsed.computationCost) / BILLION}`); console.log(`storageCost: ${storageCost}`); console.log(`flexDataSize: ${flexDataSize}`); console.log(`storageCost above minimum (0.0029488): ${storageCostAboveMin}`); - console.log(`storageCostAboveMin per flexDataSize: ${storageCostAboveMin / flexDataSize}`); + console.log(`storageCostAboveMin per flexDataSize: ${storageCostAboveMin / (flexDataSize - 1)}`); console.log(`storageRebate: ${parseInt(gasUsed.storageRebate) / BILLION}`); console.log(`totalGasCost (calculated): ${totalGasCost / BILLION}`); console.log("-------------------------------------------------------------------------------------------------------"); @@ -40,26 +41,45 @@ function randomString(length = 50) { return [...Array(length + 10)].map((value) => (Math.random() * 1000000).toString(36).replace('.', '')).join('').substring(0, length); }; -/** Create, update and destroy a Dynamic Notarization to estimate gas cost */ -export async function createUpdateDestroy(): Promise { - console.log("Create, update and destroy a Dynamic Notarization to estimate gas cost"); - - const notarizationClient = await getFundedClient(); +async function create_dynamic_notarization(notarizationClient: NotarizationClient, stateDataSize: number): Promise<{notarization: any, response: IotaTransactionBlockResponse}> { + console.log(`Creating a dynamic notarization for state updates with ${stateDataSize} bytes of state data`); - console.log("Creating a dynamic notarization for state updates..."); + let stateData = randomString(stateDataSize) - // Create a dynamic notarization - const { output: notarization, response: response } = await notarizationClient + const {output: notarization, response: response} = await notarizationClient .createDynamic() - .withStringState(STATE_DATA, STATE_METADATA) + .withStringState(stateData, STATE_METADATA) .withImmutableDescription(IMMUTABLE_DESCRIPTION) .finish() .buildAndExecute(notarizationClient); console.log("āœ… Created dynamic notarization:", notarization.id); - const flexDataSize = STATE_DATA.length + (STATE_METADATA ? STATE_METADATA.length : 0) + (IMMUTABLE_DESCRIPTION ? IMMUTABLE_DESCRIPTION.length : 0); + const flexDataSize = stateData.length + (STATE_METADATA ? STATE_METADATA.length : 0) + (IMMUTABLE_DESCRIPTION ? IMMUTABLE_DESCRIPTION.length : 0); print_gas_cost("Create", flexDataSize, response); + return {notarization, response}; +} + +/** Create, update and destroy a Dynamic Notarization to estimate gas cost */ +export async function createUpdateDestroy(): Promise { + console.log("Create, update and destroy a Dynamic Notarization to estimate gas cost"); + + const notarizationClient = await getFundedClient(); + + const iotaClient = notarizationClient.iotaClient(); + EPOCH_INFO = await iotaClient.getCurrentEpoch(); + console.log("Successfully fetched the EpochInfo to evaluate the referenceGasPrice: ", EPOCH_INFO != null ? EPOCH_INFO.referenceGasPrice : "Not Available"); + + let notarization; + + // Create several dynamic notarizations with different initial state sizes. The notarization with the largest state size will be used for updates. + console.log("\nšŸ†• Creating dynamic notarizations with different initial state sizes..."); + for (let i = 1; i <= 4; i++) { + const result= await create_dynamic_notarization(notarizationClient, 10 * i*i); // 10, 40, 90, 160 bytes + notarization = result.notarization; + } + + // Perform multiple state updates console.log("\nšŸ”„ Performing state updates..."); diff --git a/bindings/wasm/notarization_wasm/package-lock.json b/bindings/wasm/notarization_wasm/package-lock.json index 74198c9..9ce7c74 100644 --- a/bindings/wasm/notarization_wasm/package-lock.json +++ b/bindings/wasm/notarization_wasm/package-lock.json @@ -35,7 +35,7 @@ "node": ">=20" }, "peerDependencies": { - "@iota/iota-sdk": "^1.7.1" + "@iota/iota-sdk": "^1.9.1" } }, "node_modules/@0no-co/graphql.web": { @@ -302,9 +302,9 @@ } }, "node_modules/@iota/bcs": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@iota/bcs/-/bcs-1.3.0.tgz", - "integrity": "sha512-R99rbggmRuRIhfPpmmceo87gRrVrfM1oZRhGhK5dmbOk5wooo3rMYzN/a0KzQ9Ieig/FisuV5uDEkFheQXr7WA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@iota/bcs/-/bcs-1.4.0.tgz", + "integrity": "sha512-Bpg8uPB3UTweJyFS3G+aycGcTCxaJQi2a9bEy2QXWMBM8a/tLN1KCg4IzKWkAJ4FyMNrZZrdXiBqAzmNK6xdVQ==", "license": "Apache-2.0", "peer": true, "dependencies": { @@ -324,14 +324,14 @@ } }, "node_modules/@iota/iota-sdk": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@iota/iota-sdk/-/iota-sdk-1.7.1.tgz", - "integrity": "sha512-ZUGPGldQPI2j9twUkylRuE/JkP9U4T7eOFdid4G8UufCs5VdepTISv76IoTSgn/K27xQhVjULsqx4rhlUWSXeA==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@iota/iota-sdk/-/iota-sdk-1.9.1.tgz", + "integrity": "sha512-tsJJ6fJHPHTYxXX0BlRYjerKhaSH0XhBNkVGQRCCOSnunJGsB5ivG3ijQHygyA0yV+QOQ237bkhijmHAKZWGkw==", "license": "Apache-2.0", "peer": true, "dependencies": { "@graphql-typed-document-node/core": "^3.2.0", - "@iota/bcs": "1.3.0", + "@iota/bcs": "1.4.0", "@noble/curves": "^1.4.2", "@noble/hashes": "^1.4.0", "@scure/bip32": "^1.4.0", @@ -342,7 +342,7 @@ "gql.tada": "^1.8.2", "graphql": "^16.9.0", "tweetnacl": "^1.0.3", - "valibot": "^0.36.0" + "valibot": "^1.2.0" }, "engines": { "node": ">=20" @@ -7325,11 +7325,19 @@ "license": "MIT" }, "node_modules/valibot": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.36.0.tgz", - "integrity": "sha512-CjF1XN4sUce8sBK9TixrDqFM7RwNkuXdJu174/AwmQUB62QbCQADg5lLe8ldBalFgtj1uKj+pKwDJiNo4Mn+eQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz", + "integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==", "license": "MIT", - "peer": true + "peer": true, + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } }, "node_modules/verror": { "version": "1.10.0", diff --git a/bindings/wasm/notarization_wasm/package.json b/bindings/wasm/notarization_wasm/package.json index fbc9450..754707f 100644 --- a/bindings/wasm/notarization_wasm/package.json +++ b/bindings/wasm/notarization_wasm/package.json @@ -75,7 +75,7 @@ "@iota/iota-interaction-ts": "^0.9.0" }, "peerDependencies": { - "@iota/iota-sdk": "^1.7.1" + "@iota/iota-sdk": "^1.9.1" }, "engines": { "node": ">=20" From 0f93b3b8addb30ec0284064da3b4e9d887888a97 Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Thu, 18 Dec 2025 09:31:22 +0000 Subject: [PATCH 3/6] First experimental but useful version of an example to estimate the gas cost --- .../src/gas-costs/01_create_update_destroy.ts | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/bindings/wasm/notarization_wasm/examples/src/gas-costs/01_create_update_destroy.ts b/bindings/wasm/notarization_wasm/examples/src/gas-costs/01_create_update_destroy.ts index 477b9cb..daba4ce 100644 --- a/bindings/wasm/notarization_wasm/examples/src/gas-costs/01_create_update_destroy.ts +++ b/bindings/wasm/notarization_wasm/examples/src/gas-costs/01_create_update_destroy.ts @@ -8,23 +8,26 @@ import {EpochInfo, IotaTransactionBlockResponse} from "@iota/iota-sdk/client"; const STATE_METADATA: string | null = null; // "State metadata example"; const IMMUTABLE_DESCRIPTION: string | null = null; // "This metadata will not change"; -let EPOCH_INFO: EpochInfo | null = null; +let REFERENCE_GAS_PRICE: bigint | null = null; const BILLION = 1000000000; +const MINIMUM_STORAGE_COST = 0.0029488; // Unit is IOTA not Nanos function print_gas_cost(transaction_type: String, flexDataSize: number, response: IotaTransactionBlockResponse) { const gasUsed = response.effects?.gasUsed; - const referenceGasPrice = EPOCH_INFO ? EPOCH_INFO.referenceGasPrice ? parseInt(EPOCH_INFO.referenceGasPrice) : 1000 : 1000; // Fallback to 1000 if EpochInfo is not available + const referenceGasPrice = REFERENCE_GAS_PRICE ? Number(REFERENCE_GAS_PRICE) : -1; // Fallback to -1 if EpochInfo is not available if (gasUsed != undefined) { const totalGasCost = parseInt(gasUsed.computationCost) + parseInt(gasUsed.storageCost) - parseInt(gasUsed.storageRebate); const storageCost = parseInt(gasUsed.storageCost) / BILLION; - const storageCostAboveMin = storageCost - 0.0029488; + const computationCostNanos = parseInt(gasUsed.computationCost); + const storageCostAboveMin = storageCost - MINIMUM_STORAGE_COST; console.log("-------------------------------------------------------------------------------------------------------"); console.log(`--- Gas cost for '${transaction_type}' transaction`); console.log("-------------------------------------------------------------------------------------------------------"); - console.log(`referenceGasPrice: ${referenceGasPrice / BILLION}`); - console.log(`computationCost: ${parseInt(gasUsed.computationCost) / BILLION}`); + console.log(`referenceGasPrice: ${referenceGasPrice}`); + console.log(`computationCost: ${computationCostNanos / BILLION}`); + console.log(`Computation Units: ${computationCostNanos / referenceGasPrice}`); console.log(`storageCost: ${storageCost}`); console.log(`flexDataSize: ${flexDataSize}`); console.log(`storageCost above minimum (0.0029488): ${storageCostAboveMin}`); @@ -67,8 +70,8 @@ export async function createUpdateDestroy(): Promise { const notarizationClient = await getFundedClient(); const iotaClient = notarizationClient.iotaClient(); - EPOCH_INFO = await iotaClient.getCurrentEpoch(); - console.log("Successfully fetched the EpochInfo to evaluate the referenceGasPrice: ", EPOCH_INFO != null ? EPOCH_INFO.referenceGasPrice : "Not Available"); + REFERENCE_GAS_PRICE = await iotaClient.getReferenceGasPrice(); + console.log("Successfully fetched the referenceGasPrice: ", REFERENCE_GAS_PRICE != null ? REFERENCE_GAS_PRICE : "Not Available"); let notarization; @@ -87,7 +90,7 @@ export async function createUpdateDestroy(): Promise { console.log(`\n--- Update ${i} ---`); // Create new state with updated content and metadata - const newContent = randomString(i * 50); + const newContent = randomString(i * 50); // Set this size to 138 bytes to keep total flex data size equal to the latest created notarization const newMetadata = `Version ${i + 1}.0 - Update ${i}`; // Update the state @@ -99,7 +102,7 @@ export async function createUpdateDestroy(): Promise { .buildAndExecute(notarizationClient); console.log(`āœ… State update ${i} completed`); - const flexDataSize = newContent.length + (STATE_METADATA ? newMetadata.length : 0); + const flexDataSize = newContent.length + newMetadata.length; print_gas_cost("Update", flexDataSize, response); } From 757237a43065b54170b2a76340a41c16ff4f584e Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Thu, 18 Dec 2025 18:49:32 +0000 Subject: [PATCH 4/6] Readme for gas-cost-estimation example --- .../examples/src/gas-costs/README.md | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 bindings/wasm/notarization_wasm/examples/src/gas-costs/README.md diff --git a/bindings/wasm/notarization_wasm/examples/src/gas-costs/README.md b/bindings/wasm/notarization_wasm/examples/src/gas-costs/README.md new file mode 100644 index 0000000..336539e --- /dev/null +++ b/bindings/wasm/notarization_wasm/examples/src/gas-costs/README.md @@ -0,0 +1,53 @@ +# Gas Cost Estimation Example for Notarization + +This folder contains an example to estimate the gas cost for Notarization object creation , update anf destroy operations. + +It can be run like any other example. + +The log output of the example is optimized to evaluate variables and constants needed to calculate gas cost as being +described in the following sections. + +## Creating Notarizations + +The cost for creating a Notarization object can roughly be calculated by the following equation: + + `TotalCost` = `FlexDataSize` * `FlexDataByteCost` + `MinimumStorageCost` + `ComputationCost` + + `TotalCost` = F [Byte] * 0.0000076 [IOTA/Byte] + 0.00295 [IOTA] + 0.001 [IOTA] + +Where: + +| Parameter | Description | +|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `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. | +| `FlexDataByteCost` | A constant value of 0.0000076 IOTA/Byte
This value denotes (`StorageCost` - `MinimumStorageCost`) divided by `FlexDataSize`. | +| `MinimumStorageCost` | A constant value of 0.00295 IOTA.
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. | +| `ComputationCost` | A constant value of 0.001 IOTA.
Given the Gas Price is 1000 nano, the `ComputationCost` will always be 0.001 IOTA as creating Notarizations always consume 1000 Computation Units. | +| `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. | + +Examples: + +| `FlexDataSize` | `TotalCost` (Storage Rebate not taken into account) | +|----------------|-----------------------------------------------------| +| 10 | 0.004026 IOTA | +| 100 | 0.00471 IOTA | +| 1000 | 0.01155 IOTA | + +## Updating Dynamic Notarizations + +The `TotalCost` for updating a Dynamic Notarization can roughly be calculated using the same equation used for creating +Notarization objects (see above). + +The value for `FlexDataByteCost` should be set to 0.00000769 IOTA/Byte. + +If the new Notarization State results in the same `FlexDataSize` as the overwritten old Notarization State, the Storage +Rebate will compensate the Storage Cost so that the real gas cost to be paid will be more or less the Computation Cost, +which is always 0.001 IOTA (presumed the Gas Price is 1000 nano). + +## Destroying a Notarization + +The `TotalCost` for destroying a Notarization is the Computation Cost which is 0.001 IOTA (presumed the Gas Price is 1000 nano). + +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. + +The Storage Rebate can roughly be calculated using the below equation. See above for more details about the used variables and constants. \ No newline at end of file From 0274437fbb934abec62bc200b78408a42e66dcb2 Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Fri, 19 Dec 2025 10:49:54 +0000 Subject: [PATCH 5/6] Fix dprint issues --- .../src/gas-costs/01_create_update_destroy.ts | 50 ++++++++++++------- .../examples/src/gas-costs/README.md | 12 ++--- .../notarization_wasm/examples/src/main.ts | 2 +- 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/bindings/wasm/notarization_wasm/examples/src/gas-costs/01_create_update_destroy.ts b/bindings/wasm/notarization_wasm/examples/src/gas-costs/01_create_update_destroy.ts index daba4ce..fa716d3 100644 --- a/bindings/wasm/notarization_wasm/examples/src/gas-costs/01_create_update_destroy.ts +++ b/bindings/wasm/notarization_wasm/examples/src/gas-costs/01_create_update_destroy.ts @@ -1,12 +1,12 @@ // Copyright 2025 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { State, NotarizationClient } from "@iota/notarization/node"; +import { EpochInfo, IotaTransactionBlockResponse } from "@iota/iota-sdk/client"; +import { NotarizationClient, State } from "@iota/notarization/node"; import { getFundedClient } from "../util"; -import {EpochInfo, IotaTransactionBlockResponse} from "@iota/iota-sdk/client"; const STATE_METADATA: string | null = null; // "State metadata example"; -const IMMUTABLE_DESCRIPTION: string | null = null; // "This metadata will not change"; +const IMMUTABLE_DESCRIPTION: string | null = null; // "This metadata will not change"; let REFERENCE_GAS_PRICE: bigint | null = null; @@ -18,13 +18,18 @@ function print_gas_cost(transaction_type: String, flexDataSize: number, response const referenceGasPrice = REFERENCE_GAS_PRICE ? Number(REFERENCE_GAS_PRICE) : -1; // Fallback to -1 if EpochInfo is not available if (gasUsed != undefined) { - const totalGasCost = parseInt(gasUsed.computationCost) + parseInt(gasUsed.storageCost) - parseInt(gasUsed.storageRebate); + const totalGasCost = parseInt(gasUsed.computationCost) + parseInt(gasUsed.storageCost) + - parseInt(gasUsed.storageRebate); const storageCost = parseInt(gasUsed.storageCost) / BILLION; const computationCostNanos = parseInt(gasUsed.computationCost); const storageCostAboveMin = storageCost - MINIMUM_STORAGE_COST; - console.log("-------------------------------------------------------------------------------------------------------"); + console.log( + "-------------------------------------------------------------------------------------------------------", + ); console.log(`--- Gas cost for '${transaction_type}' transaction`); - console.log("-------------------------------------------------------------------------------------------------------"); + console.log( + "-------------------------------------------------------------------------------------------------------", + ); console.log(`referenceGasPrice: ${referenceGasPrice}`); console.log(`computationCost: ${computationCostNanos / BILLION}`); console.log(`Computation Units: ${computationCostNanos / referenceGasPrice}`); @@ -34,22 +39,28 @@ function print_gas_cost(transaction_type: String, flexDataSize: number, response console.log(`storageCostAboveMin per flexDataSize: ${storageCostAboveMin / (flexDataSize - 1)}`); console.log(`storageRebate: ${parseInt(gasUsed.storageRebate) / BILLION}`); console.log(`totalGasCost (calculated): ${totalGasCost / BILLION}`); - console.log("-------------------------------------------------------------------------------------------------------"); + console.log( + "-------------------------------------------------------------------------------------------------------", + ); } else { console.log("Gas used information is not available."); } } function randomString(length = 50) { - return [...Array(length + 10)].map((value) => (Math.random() * 1000000).toString(36).replace('.', '')).join('').substring(0, length); -}; + return [...Array(length + 10)].map((value) => (Math.random() * 1000000).toString(36).replace(".", "")).join("") + .substring(0, length); +} -async function create_dynamic_notarization(notarizationClient: NotarizationClient, stateDataSize: number): Promise<{notarization: any, response: IotaTransactionBlockResponse}> { +async function create_dynamic_notarization( + notarizationClient: NotarizationClient, + stateDataSize: number, +): Promise<{ notarization: any; response: IotaTransactionBlockResponse }> { console.log(`Creating a dynamic notarization for state updates with ${stateDataSize} bytes of state data`); - let stateData = randomString(stateDataSize) + let stateData = randomString(stateDataSize); - const {output: notarization, response: response} = await notarizationClient + const { output: notarization, response: response } = await notarizationClient .createDynamic() .withStringState(stateData, STATE_METADATA) .withImmutableDescription(IMMUTABLE_DESCRIPTION) @@ -57,10 +68,11 @@ async function create_dynamic_notarization(notarizationClient: NotarizationClien .buildAndExecute(notarizationClient); console.log("āœ… Created dynamic notarization:", notarization.id); - const flexDataSize = stateData.length + (STATE_METADATA ? STATE_METADATA.length : 0) + (IMMUTABLE_DESCRIPTION ? IMMUTABLE_DESCRIPTION.length : 0); + const flexDataSize = stateData.length + (STATE_METADATA ? STATE_METADATA.length : 0) + + (IMMUTABLE_DESCRIPTION ? IMMUTABLE_DESCRIPTION.length : 0); print_gas_cost("Create", flexDataSize, response); - return {notarization, response}; + return { notarization, response }; } /** Create, update and destroy a Dynamic Notarization to estimate gas cost */ @@ -71,18 +83,20 @@ export async function createUpdateDestroy(): Promise { const iotaClient = notarizationClient.iotaClient(); REFERENCE_GAS_PRICE = await iotaClient.getReferenceGasPrice(); - console.log("Successfully fetched the referenceGasPrice: ", REFERENCE_GAS_PRICE != null ? REFERENCE_GAS_PRICE : "Not Available"); + console.log( + "Successfully fetched the referenceGasPrice: ", + REFERENCE_GAS_PRICE != null ? REFERENCE_GAS_PRICE : "Not Available", + ); let notarization; // Create several dynamic notarizations with different initial state sizes. The notarization with the largest state size will be used for updates. console.log("\nšŸ†• Creating dynamic notarizations with different initial state sizes..."); for (let i = 1; i <= 4; i++) { - const result= await create_dynamic_notarization(notarizationClient, 10 * i*i); // 10, 40, 90, 160 bytes + const result = await create_dynamic_notarization(notarizationClient, 10 * i * i); // 10, 40, 90, 160 bytes notarization = result.notarization; } - // Perform multiple state updates console.log("\nšŸ”„ Performing state updates..."); @@ -94,7 +108,7 @@ export async function createUpdateDestroy(): Promise { const newMetadata = `Version ${i + 1}.0 - Update ${i}`; // Update the state - const { output: _, response: response } = await notarizationClient + const { output: _, response: response } = await notarizationClient .updateState( State.fromString(newContent, newMetadata), notarization.id, diff --git a/bindings/wasm/notarization_wasm/examples/src/gas-costs/README.md b/bindings/wasm/notarization_wasm/examples/src/gas-costs/README.md index 336539e..f3d0c4f 100644 --- a/bindings/wasm/notarization_wasm/examples/src/gas-costs/README.md +++ b/bindings/wasm/notarization_wasm/examples/src/gas-costs/README.md @@ -18,7 +18,7 @@ The cost for creating a Notarization object can roughly be calculated by the fol Where: | Parameter | Description | -|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `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. | | `FlexDataByteCost` | A constant value of 0.0000076 IOTA/Byte
This value denotes (`StorageCost` - `MinimumStorageCost`) divided by `FlexDataSize`. | | `MinimumStorageCost` | A constant value of 0.00295 IOTA.
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. | @@ -28,20 +28,20 @@ Where: Examples: | `FlexDataSize` | `TotalCost` (Storage Rebate not taken into account) | -|----------------|-----------------------------------------------------| +| -------------- | --------------------------------------------------- | | 10 | 0.004026 IOTA | | 100 | 0.00471 IOTA | | 1000 | 0.01155 IOTA | ## Updating Dynamic Notarizations -The `TotalCost` for updating a Dynamic Notarization can roughly be calculated using the same equation used for creating +The `TotalCost` for updating a Dynamic Notarization can roughly be calculated using the same equation used for creating Notarization objects (see above). The value for `FlexDataByteCost` should be set to 0.00000769 IOTA/Byte. -If the new Notarization State results in the same `FlexDataSize` as the overwritten old Notarization State, the Storage -Rebate will compensate the Storage Cost so that the real gas cost to be paid will be more or less the Computation Cost, +If the new Notarization State results in the same `FlexDataSize` as the overwritten old Notarization State, the Storage +Rebate will compensate the Storage Cost so that the real gas cost to be paid will be more or less the Computation Cost, which is always 0.001 IOTA (presumed the Gas Price is 1000 nano). ## Destroying a Notarization @@ -50,4 +50,4 @@ The `TotalCost` for destroying a Notarization is the Computation Cost which is 0 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. -The Storage Rebate can roughly be calculated using the below equation. See above for more details about the used variables and constants. \ No newline at end of file +The Storage Rebate can roughly be calculated using the below equation. See above for more details about the used variables and constants. diff --git a/bindings/wasm/notarization_wasm/examples/src/main.ts b/bindings/wasm/notarization_wasm/examples/src/main.ts index 436e4b9..b86467f 100644 --- a/bindings/wasm/notarization_wasm/examples/src/main.ts +++ b/bindings/wasm/notarization_wasm/examples/src/main.ts @@ -9,9 +9,9 @@ import { updateState } from "./05_update_state"; import { updateMetadata } from "./06_update_metadata"; import { transferNotarization } from "./07_transfer_notarization"; import { accessReadOnlyMethods } from "./08_access_read_only_methods"; +import { createUpdateDestroy } from "./gas-costs/01_create_update_destroy"; import { iotWeatherStation } from "./real-world/01_iot_weather_station"; import { legalContract } from "./real-world/02_legal_contract"; -import { createUpdateDestroy } from "./gas-costs/01_create_update_destroy"; export async function main(example?: string) { // Extract example name. From 6ac8a5f0d881bdf04a59046904858d4e932a426d Mon Sep 17 00:00:00 2001 From: chrisgitiota Date: Fri, 19 Dec 2025 11:08:16 +0000 Subject: [PATCH 6/6] Method specific gas cost comment added --- .../examples/src/gas-costs/README.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/bindings/wasm/notarization_wasm/examples/src/gas-costs/README.md b/bindings/wasm/notarization_wasm/examples/src/gas-costs/README.md index f3d0c4f..5449be1 100644 --- a/bindings/wasm/notarization_wasm/examples/src/gas-costs/README.md +++ b/bindings/wasm/notarization_wasm/examples/src/gas-costs/README.md @@ -1,13 +1,22 @@ # Gas Cost Estimation Example for Notarization -This folder contains an example to estimate the gas cost for Notarization object creation , update anf destroy operations. +This folder contains an example to estimate the gas cost for Notarization object creation, update and destroy operations. It can be run like any other example. The log output of the example is optimized to evaluate variables and constants needed to calculate gas cost as being described in the following sections. -## Creating Notarizations +## Results of the Gas Cost Estimation + +The gas cost for creating Dynamic and Locked Notarizations only differ in the amount of needed Storage Cost. +The mimimum Byte size of a Locked Notarization is 19 bytes larger than the one of a Dynamic Notarization due to the additional +lock information stored in the Notarization object. This results in a slightly higher Storage Cost (0.0001425 IOTA) for +Locked Notarizations compared to Dynamic Notarizations when they are created with the same amount of State Data, Metadata, etc. + +**For the sake of simplicity, the following sections only describe the gas cost estimation for Dynamic Notarizations.** + +### Creating Notarizations The cost for creating a Notarization object can roughly be calculated by the following equation: @@ -33,7 +42,7 @@ Examples: | 100 | 0.00471 IOTA | | 1000 | 0.01155 IOTA | -## Updating Dynamic Notarizations +### Updating Dynamic Notarizations The `TotalCost` for updating a Dynamic Notarization can roughly be calculated using the same equation used for creating Notarization objects (see above). @@ -44,7 +53,7 @@ If the new Notarization State results in the same `FlexDataSize` as the overwrit Rebate will compensate the Storage Cost so that the real gas cost to be paid will be more or less the Computation Cost, which is always 0.001 IOTA (presumed the Gas Price is 1000 nano). -## Destroying a Notarization +### Destroying a Notarization The `TotalCost` for destroying a Notarization is the Computation Cost which is 0.001 IOTA (presumed the Gas Price is 1000 nano).