├── .gitignore ├── contracts ├── solidity │ ├── .gitignore │ ├── secrets.json │ ├── evm │ │ └── contracts │ │ │ ├── Storage.sol │ │ │ ├── Storage.dbg.json │ │ │ └── Storage.json │ │ │ ├── flipper.sol │ │ │ ├── flipper.dbg.json │ │ │ └── flipper.json │ │ │ ├── BenchERC20.sol │ │ │ └── BenchERC20.dbg.json │ │ │ ├── BenchERC1155.sol │ │ │ └── BenchERC1155.dbg.json │ │ │ ├── BenchERC721.sol │ │ │ └── BenchERC721.dbg.json │ │ │ ├── Computation.sol │ │ │ ├── Computation.dbg.json │ │ │ └── Computation.json │ │ │ └── incrementer.sol │ │ │ ├── incrementer.dbg.json │ │ │ └── incrementer.json │ ├── package.json │ ├── contracts │ │ ├── BenchERC20.sol │ │ ├── BenchERC721.sol │ │ ├── incrementer.sol │ │ ├── flipper.sol │ │ ├── BenchERC1155.sol │ │ ├── Computation.sol │ │ └── Storage.sol │ ├── scripts │ │ └── deploy.js │ ├── hardhat.config.js │ ├── openzeppelin_solang.patch │ └── wasm │ │ ├── flipper.contract │ │ ├── Computation.contract │ │ └── incrementer.contract └── ink │ ├── storage │ ├── .gitignore │ ├── Cargo.toml │ └── lib.rs │ ├── computation │ ├── .gitignore │ ├── Cargo.toml │ └── lib.rs │ ├── incrementer.contract │ ├── computation.contract │ ├── flipper.contract │ └── storage.contract ├── metadata ├── moonbeam.scale └── contracts-node.scale ├── launch ├── .gitignore ├── smart_bench.Dockerfile.dockerignore ├── configs │ ├── network_native_wasm.toml │ └── network_native_moonbeam.toml ├── build.sh ├── entrypoint.sh ├── moonbeam.patch ├── smart_bench.Dockerfile ├── download-bins.sh ├── run.sh └── README.md ├── stats ├── panel_id_2_example.png ├── .env ├── grafana-provisioning │ ├── dashboards │ │ ├── dashboard.yml │ │ └── tps.json │ └── datasources │ │ └── datasource.yml ├── docker-compose.yml ├── smart_bench_to_csv.sh ├── README.md └── get_graph.sh ├── scripts └── ci │ └── build-contract.sh ├── macro ├── Cargo.toml └── src │ └── lib.rs ├── CODEOWNERS ├── .github ├── actions │ └── get-contract-language │ │ └── action.yml └── workflows │ ├── ci.yaml │ └── benchmark.yml ├── Cargo.toml ├── src ├── main.rs ├── evm │ ├── xts.rs │ ├── transaction.rs │ └── mod.rs ├── stats.rs └── wasm │ ├── xts.rs │ └── runner.rs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.log 3 | *-local.json 4 | *-local-raw.json 5 | -------------------------------------------------------------------------------- /contracts/solidity/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | package-lock.json -------------------------------------------------------------------------------- /metadata/moonbeam.scale: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/smart-bench/HEAD/metadata/moonbeam.scale -------------------------------------------------------------------------------- /launch/.gitignore: -------------------------------------------------------------------------------- 1 | *.wasm 2 | *.log 3 | rococo-local.json 4 | rococo-local-raw.json 5 | configLocal.json 6 | bin -------------------------------------------------------------------------------- /metadata/contracts-node.scale: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/smart-bench/HEAD/metadata/contracts-node.scale -------------------------------------------------------------------------------- /stats/panel_id_2_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paritytech/smart-bench/HEAD/stats/panel_id_2_example.png -------------------------------------------------------------------------------- /contracts/solidity/secrets.json: -------------------------------------------------------------------------------- 1 | { 2 | "alith": "0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133" 3 | } -------------------------------------------------------------------------------- /contracts/solidity/evm/contracts/Storage.sol/Storage.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../build-info/5ac40253a83a817bc0a7c2fb8e7914b6.json" 4 | } 5 | -------------------------------------------------------------------------------- /contracts/solidity/evm/contracts/flipper.sol/flipper.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../build-info/5ac40253a83a817bc0a7c2fb8e7914b6.json" 4 | } 5 | -------------------------------------------------------------------------------- /launch/smart_bench.Dockerfile.dockerignore: -------------------------------------------------------------------------------- 1 | doc 2 | **target* 3 | .idea/ 4 | .git/ 5 | .github/ 6 | Dockerfile 7 | .dockerignore 8 | .local 9 | .env* 10 | launch/run.sh 11 | -------------------------------------------------------------------------------- /contracts/solidity/evm/contracts/BenchERC20.sol/BenchERC20.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../build-info/5ac40253a83a817bc0a7c2fb8e7914b6.json" 4 | } 5 | -------------------------------------------------------------------------------- /contracts/solidity/evm/contracts/BenchERC1155.sol/BenchERC1155.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../build-info/5ac40253a83a817bc0a7c2fb8e7914b6.json" 4 | } 5 | -------------------------------------------------------------------------------- /contracts/solidity/evm/contracts/BenchERC721.sol/BenchERC721.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../build-info/5ac40253a83a817bc0a7c2fb8e7914b6.json" 4 | } 5 | -------------------------------------------------------------------------------- /contracts/solidity/evm/contracts/Computation.sol/Computation.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../build-info/5ac40253a83a817bc0a7c2fb8e7914b6.json" 4 | } 5 | -------------------------------------------------------------------------------- /contracts/solidity/evm/contracts/incrementer.sol/incrementer.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../build-info/5ac40253a83a817bc0a7c2fb8e7914b6.json" 4 | } 5 | -------------------------------------------------------------------------------- /stats/.env: -------------------------------------------------------------------------------- 1 | INFLUXDB_BUCKET=bucket 2 | INFLUXDB_TOKEN=token 3 | INFLUXDB_ORG=org 4 | GRAFANA_USERNAME=admin 5 | GRAFANA_PASSWORD=admin 6 | GRAFANA_PORT=3000 7 | RENDERER_PORT=8081 8 | INFLUXDB_PORT=8086 9 | -------------------------------------------------------------------------------- /contracts/solidity/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@nomiclabs/hardhat-ethers": "^2.0.6", 4 | "@openzeppelin/contracts": "^4.6.0", 5 | "ethers": "^5.6.8" 6 | }, 7 | "devDependencies": { 8 | "hardhat": "^2.18.2" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /stats/grafana-provisioning/dashboards/dashboard.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | providers: 3 | - name: InfluxDB 4 | folder: '' 5 | type: file 6 | disableDeletion: false 7 | editable: false 8 | options: 9 | path: /etc/grafana/provisioning/dashboards 10 | -------------------------------------------------------------------------------- /contracts/solidity/contracts/BenchERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 4 | 5 | contract BenchERC20 is ERC20 { 6 | constructor(uint256 initialSupply) ERC20("BenchERC20", "CAN") { 7 | _mint(msg.sender, initialSupply); 8 | } 9 | } -------------------------------------------------------------------------------- /contracts/ink/storage/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore build artifacts from the local tests sub-crate. 2 | /target/ 3 | 4 | # Ignore backup files creates by cargo fmt. 5 | **/*.rs.bk 6 | 7 | # Remove Cargo.lock when creating an executable, leave it for libraries 8 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 9 | Cargo.lock 10 | -------------------------------------------------------------------------------- /contracts/ink/computation/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore build artifacts from the local tests sub-crate. 2 | /target/ 3 | 4 | # Ignore backup files creates by cargo fmt. 5 | **/*.rs.bk 6 | 7 | # Remove Cargo.lock when creating an executable, leave it for libraries 8 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 9 | Cargo.lock 10 | -------------------------------------------------------------------------------- /contracts/solidity/contracts/BenchERC721.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 4 | 5 | contract BenchERC721 is ERC721 { 6 | constructor() ERC721("BenchERC721", "CANFT") { 7 | } 8 | 9 | function mint(uint256 tokenId) public { 10 | _safeMint(msg.sender, tokenId); 11 | } 12 | } -------------------------------------------------------------------------------- /stats/grafana-provisioning/datasources/datasource.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | datasources: 3 | - name: InfluxDB 4 | type: influxdb 5 | access: proxy 6 | url: http://influxdb:8086 7 | jsonData: 8 | dbName: bucket 9 | httpMode: GET 10 | httpHeaderName1: 'Authorization' 11 | secureJsonData: 12 | httpHeaderValue1: 'Token token' 13 | editable: true 14 | isDefault: true 15 | -------------------------------------------------------------------------------- /launch/configs/network_native_wasm.toml: -------------------------------------------------------------------------------- 1 | [settings] 2 | timeout = 1000 3 | 4 | [relaychain] 5 | command = "polkadot" 6 | chain = "rococo-local" 7 | 8 | [[relaychain.nodes]] 9 | name = "alice" 10 | 11 | [[relaychain.nodes]] 12 | name = "bob" 13 | 14 | [[parachains]] 15 | id = 1002 16 | chain="contracts-rococo-dev" 17 | 18 | [parachains.collator] 19 | name = "contracts" 20 | command = "polkadot-parachain" 21 | ws_port = 9988 22 | args = ["-lparachain=debug"] 23 | -------------------------------------------------------------------------------- /launch/configs/network_native_moonbeam.toml: -------------------------------------------------------------------------------- 1 | [settings] 2 | timeout = 1000 3 | 4 | [relaychain] 5 | command = "polkadot" 6 | chain = "rococo-local" 7 | 8 | [[relaychain.nodes]] 9 | name = "alice" 10 | 11 | [[relaychain.nodes]] 12 | name = "bob" 13 | 14 | [[parachains]] 15 | id = 1281 16 | chain = "moonbase-local" 17 | 18 | [parachains.collator] 19 | name = "alith" 20 | command = "moonbeam" 21 | ws_port = 9988 22 | args = ["-lparachain=debug --force-authoring"] 23 | -------------------------------------------------------------------------------- /contracts/solidity/scripts/deploy.js: -------------------------------------------------------------------------------- 1 | async function main() { 2 | // We get the contract to deploy 3 | const BenchERC20 = await ethers.getContractFactory("BenchERC20"); 4 | const erc20 = await BenchERC20.deploy(1000000); 5 | 6 | await erc20.deployed(); 7 | 8 | console.log("BenchERC20 deployed to:", erc20.address); 9 | } 10 | 11 | main() 12 | .then(() => process.exit(0)) 13 | .catch((error) => { 14 | console.error(error); 15 | process.exit(1); 16 | }); -------------------------------------------------------------------------------- /scripts/ci/build-contract.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Write the original and optimized sizes of a Wasm file as 4 | # a CSV format line to stdout. 5 | # 6 | # Usage: `./build-contract.sh ` 7 | 8 | set -eux 9 | set -o pipefail 10 | 11 | CONTRACT=$(basename $1) 12 | SIZE_OUT=$(cargo +nightly contract build --release --manifest-path $1/Cargo.toml --output-json) || exit $? 13 | OPTIMIZED_SIZE=$(echo $SIZE_OUT | jq '.optimization_result.optimized_size') 14 | 15 | echo -n "${CONTRACT}, ${OPTIMIZED_SIZE}" 16 | -------------------------------------------------------------------------------- /contracts/solidity/contracts/incrementer.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | contract incrementer { 4 | uint32 private value; 5 | 6 | /// Constructor that initializes the `int32` value to the given `init_value`. 7 | constructor(uint32 initvalue) { 8 | value = initvalue; 9 | } 10 | 11 | /// This increments the value by `by`. 12 | function inc(uint32 by) public { 13 | value += by; 14 | } 15 | 16 | /// Simply returns the current value of our `uint32`. 17 | function get() public view returns (uint32) { 18 | return value; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/solidity/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('@nomiclabs/hardhat-ethers'); 2 | 3 | const { alith } = require('./secrets.json'); 4 | 5 | /** 6 | * @type import('hardhat/config').HardhatUserConfig 7 | */ 8 | module.exports = { 9 | defaultNetwork: "dev", 10 | networks: { 11 | dev: { 12 | url: "http://127.0.0.1:9933", 13 | chainId: 1281, 14 | accounts: [alith] 15 | }, 16 | }, 17 | solidity: "0.8.1", 18 | paths: { 19 | sources: "./contracts", 20 | tests: "./test", 21 | cache: "./cache", 22 | artifacts: "./evm" 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "smart-bench-macro" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | heck = "0.4.0" 11 | subxt-codegen = "0.32.0" 12 | ink_metadata = "4.2.0" 13 | contract-metadata = "3.2.0" 14 | proc-macro2 = "1.0.56" 15 | proc-macro-error = { version = "1.0.4", default-features = false } 16 | serde = "1.0.137" 17 | serde_json = "1.0.81" 18 | syn = "2.0.37" 19 | quote = "1.0.33" 20 | 21 | [dev_dependencies] 22 | ink_primitives = "4.2.0" 23 | scale-info = { version = "2.5", default-features = false } 24 | -------------------------------------------------------------------------------- /launch/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | SCRIPT_NAME="${BASH_SOURCE[0]}" 5 | SCRIPT_PATH=$(dirname "$(realpath -s "${BASH_SOURCE[0]}")") 6 | THIS_GIT_REPOSITORY_ROOT=$(git rev-parse --show-toplevel) 7 | VERSION=${VERSION:-latest} 8 | NAME=smart-bench 9 | IMAGE="${NAME}:${VERSION}" 10 | 11 | ./download-bins.sh 12 | 13 | (cd "${THIS_GIT_REPOSITORY_ROOT}" && 14 | DOCKER_BUILDKIT=1 docker build --platform linux/amd64 \ 15 | --build-arg DOCKERFILE_DIR="$(realpath --relative-to="${THIS_GIT_REPOSITORY_ROOT}" "${SCRIPT_PATH}")" \ 16 | -f "${SCRIPT_PATH}/smart_bench.Dockerfile" -t "${IMAGE}" . 17 | ) 18 | -------------------------------------------------------------------------------- /contracts/solidity/contracts/flipper.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | contract flipper { 4 | bool private value; 5 | 6 | /// Constructor that initializes the `bool` value to the given `init_value`. 7 | constructor(bool initvalue) { 8 | value = initvalue; 9 | } 10 | 11 | /// A message that can be called on instantiated contracts. 12 | /// This one flips the value of the stored `bool` from `true` 13 | /// to `false` and vice versa. 14 | function flip() public { 15 | value = !value; 16 | } 17 | 18 | /// Simply returns the current value of our `bool`. 19 | function get() public view returns (bool) { 20 | return value; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /contracts/solidity/contracts/BenchERC1155.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; 4 | import "@openzeppelin/contracts/utils/Counters.sol"; 5 | 6 | contract BenchERC1155 is ERC1155 { 7 | using Counters for Counters.Counter; 8 | Counters.Counter private _tokenIds; 9 | 10 | constructor() ERC1155("BenchERC1155") { 11 | } 12 | 13 | function create(uint256 balance) public returns (uint256) { 14 | _tokenIds.increment(); 15 | 16 | uint256 newItemId = _tokenIds.current(); 17 | _mint(msg.sender, newItemId, balance, ""); 18 | 19 | return newItemId; 20 | } 21 | } -------------------------------------------------------------------------------- /contracts/ink/storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | [package] 4 | name = "storage" 5 | version = "0.2.0" 6 | authors = ["[your_name] <[your_email]>"] 7 | edition = "2021" 8 | 9 | [dependencies] 10 | ink = { version = "4.3", default-features = false } 11 | 12 | scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } 13 | scale-info = { version = "2.5", default-features = false, features = ["derive"], optional = true } 14 | 15 | [lib] 16 | name = "storage" 17 | path = "lib.rs" 18 | 19 | [features] 20 | default = ["std"] 21 | std = [ 22 | "ink/std", 23 | "scale/std", 24 | "scale-info/std", 25 | ] 26 | ink-as-dependency = [] 27 | -------------------------------------------------------------------------------- /contracts/ink/computation/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | [package] 4 | name = "computation" 5 | version = "0.2.0" 6 | authors = ["[your_name] <[your_email]>"] 7 | edition = "2021" 8 | 9 | [dependencies] 10 | ink = { version = "4.3", default-features = false } 11 | 12 | scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } 13 | scale-info = { version = "2.5", default-features = false, features = ["derive"], optional = true } 14 | 15 | [lib] 16 | name = "computation" 17 | path = "lib.rs" 18 | 19 | [features] 20 | default = ["std"] 21 | std = [ 22 | "ink/std", 23 | "scale/std", 24 | "scale-info/std", 25 | ] 26 | ink-as-dependency = [] 27 | -------------------------------------------------------------------------------- /contracts/ink/computation/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std, no_main)] 2 | 3 | #[ink::contract] 4 | mod computation { 5 | #[ink(storage)] 6 | pub struct Computation {} 7 | 8 | impl Computation { 9 | #[ink(constructor)] 10 | pub fn new() -> Self { 11 | Self {} 12 | } 13 | 14 | #[ink(message)] 15 | pub fn triangle_number(&self, n: i32) -> i64 { 16 | (1..=n as i64).fold(0, |sum, x| sum.wrapping_add(x)) 17 | } 18 | 19 | #[ink(message)] 20 | pub fn odd_product(&self, n: i32) -> i64 { 21 | (1..=n as i64).fold(1, |prod, x| prod.wrapping_mul(2 * x - 1)) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/solidity/contracts/Computation.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | contract Computation { 4 | 5 | constructor() { } 6 | 7 | function oddProduct(int32 x) public pure returns (int64) { 8 | int64 prod = 1; 9 | for (int32 counter = 1; counter <= x; counter++) { 10 | unchecked { 11 | prod *= 2 * counter - 1; 12 | } 13 | } 14 | return prod; 15 | } 16 | 17 | function triangleNumber(int32 x) public pure returns (int64) { 18 | int64 sum = 0; 19 | for (int32 counter = 1; counter <= x; counter++) { 20 | unchecked { 21 | sum += counter; 22 | } 23 | } 24 | return sum; 25 | } 26 | } -------------------------------------------------------------------------------- /contracts/solidity/contracts/Storage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | contract Storage { 4 | 5 | mapping(address => uint256) private _balances; 6 | 7 | constructor() { } 8 | 9 | function read(address account, int32 count) public view { 10 | for (int32 counter = 1; counter <= count; counter++) { 11 | _balances[account]; 12 | } 13 | } 14 | 15 | function write(address account, int32 count) public { 16 | for (int32 counter = 1; counter <= count; counter++) { 17 | _balances[account] = 1000000; 18 | } 19 | } 20 | 21 | function readWrite(address account, int32 count) public { 22 | for (int32 counter = 1; counter <= count; counter++) { 23 | _balances[account] += 1; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lists some code owners. 2 | # 3 | # A codeowner just oversees some part of the codebase. If an owned file is changed then the 4 | # corresponding codeowner receives a review request. An approval of the codeowner might be 5 | # required for merging a PR (depends on repository settings). 6 | # 7 | # For details about syntax, see: 8 | # https://help.github.com/en/articles/about-code-owners 9 | # But here are some important notes: 10 | # 11 | # - Glob syntax is git-like, e.g. `/core` means the core directory in the root, unlike `core` 12 | # which can be everywhere. 13 | # - Multiple owners are supported. 14 | # - Either handle (e.g, @github_user or @github_org/team) or email can be used. Keep in mind, 15 | # that handles might work better because they are more recognizable on GitHub, 16 | # eyou can use them for mentioning unlike an email. 17 | # - The latest matching rule, if multiple, takes precedence. 18 | 19 | # CI 20 | /.github/ @paritytech/ci @ascjones @cmichi 21 | -------------------------------------------------------------------------------- /launch/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | exec 3>&1 5 | exec 2>&3 6 | exec > /dev/null 7 | 8 | if echo "${@}" | grep -q evm; then 9 | zombienet_config=$(realpath -s "${CONFIGS_DIR}/network_native_moonbeam.toml") 10 | elif echo "${@}" | grep -q wasm; then 11 | zombienet_config=$(realpath -s "${CONFIGS_DIR}/network_native_wasm.toml") 12 | else 13 | exit 1 14 | fi 15 | 16 | parachain_ws_port=$(grep ws_port "${zombienet_config}" | tr -d '[:space:]' | cut -f2 -d'=') 17 | PATH="${BINARIES_DIR}:${PATH}" zombienet -p native spawn "${zombienet_config}" & 18 | zombienet_pid=$! 19 | 20 | wait_for_parachain_node() { 21 | local pid=$1 22 | local port=$2 23 | while ! echo q | nc localhost "${port}"; do 24 | if ! kill -0 "${pid}"; then 25 | exit 1 26 | fi 27 | sleep 1; 28 | done 29 | } 30 | wait_for_parachain_node "${zombienet_pid}" "${parachain_ws_port}" 31 | 32 | PATH="${BINARIES_DIR}:${PATH}" CARGO_MANIFEST_DIR=$(dirname "${CONTRACTS_DIR}") smart-bench "${@}" --url "ws://localhost:${parachain_ws_port}" 1>&3 33 | -------------------------------------------------------------------------------- /launch/moonbeam.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node/service/src/rpc.rs b/node/service/src/rpc.rs 2 | index 3c088865..2c27fe58 100644 3 | --- a/node/service/src/rpc.rs 4 | +++ b/node/service/src/rpc.rs 5 | @@ -45,6 +45,7 @@ use sc_consensus_manual_seal::rpc::{EngineCommand, ManualSeal, ManualSealApiServ 6 | use sc_network::NetworkService; 7 | use sc_network_sync::SyncingService; 8 | use sc_rpc::SubscriptionTaskExecutor; 9 | +use sc_rpc::dev::{Dev, DevApiServer}; 10 | use sc_rpc_api::DenyUnsafe; 11 | use sc_service::TaskManager; 12 | use sc_transaction_pool::{ChainApi, Pool}; 13 | @@ -182,6 +183,7 @@ where 14 | BE::State: StateBackend, 15 | BE::Blockchain: BlockchainBackend, 16 | C: ProvideRuntimeApi + StorageProvider + AuxStore, 17 | + C: sc_client_api::BlockBackend, 18 | C: BlockchainEvents, 19 | C: HeaderBackend + HeaderMetadata + 'static, 20 | C: CallApiAt, 21 | @@ -323,6 +325,7 @@ where 22 | .into_rpc(), 23 | )?; 24 | } 25 | + io.merge(Dev::new(Arc::clone(&client), deny_unsafe).into_rpc())?; 26 | 27 | if let Some(tracing_config) = maybe_tracing_config { 28 | if let Some(trace_filter_requester) = tracing_config.tracing_requesters.trace { 29 | -------------------------------------------------------------------------------- /launch/smart_bench.Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1: builder 2 | FROM docker.io/paritytech/ci-unified:bullseye-1.74.0 as builder 3 | 4 | COPY . /smart-bench 5 | WORKDIR /smart-bench 6 | RUN cargo install --root /usr/local/ --locked --path . \ 7 | && cargo clean \ 8 | && rm -rf $CARGO_HOME/registry \ 9 | && rm -rf $CARGO_HOME/git 10 | 11 | # Stage 2: smart-bench 12 | FROM docker.io/library/ubuntu:20.04 13 | LABEL description="Multistage Docker image for smart-bench" 14 | ARG DOCKERFILE_DIR=launch 15 | RUN echo "deb http://security.ubuntu.com/ubuntu focal-security main" | tee /etc/apt/sources.list.d/focal-security.list 16 | RUN apt-get update \ 17 | && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ 18 | libssl1.1 \ 19 | netcat=1.206-1ubuntu1 \ 20 | && apt-get clean \ 21 | && rm -rf /var/lib/apt/lists/* 22 | 23 | COPY --from=builder /usr/local/bin/smart-bench /usr/local/bin 24 | 25 | ENV CONTRACTS_DIR /usr/local/smart-bench/contracts 26 | ENV CONFIGS_DIR /usr/local/smart-bench/config 27 | ENV BINARIES_DIR /usr/local/smart-bench/bin 28 | 29 | COPY $DOCKERFILE_DIR/bin/* $BINARIES_DIR/ 30 | COPY $DOCKERFILE_DIR/../contracts $CONTRACTS_DIR 31 | COPY $DOCKERFILE_DIR/configs/* $CONFIGS_DIR/ 32 | COPY $DOCKERFILE_DIR/entrypoint.sh /entrypoint.sh 33 | 34 | ENTRYPOINT [ "./entrypoint.sh" ] 35 | -------------------------------------------------------------------------------- /.github/actions/get-contract-language/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Get Contract Language Version' 2 | description: 'Checks if all contracts have the same language version.' 3 | 4 | inputs: 5 | contracts-directory: 6 | description: 'Path to the directory containing contracts.' 7 | required: true 8 | outputs: 9 | language: 10 | description: "Language version" 11 | value: ${{ steps.language.outputs.language }} 12 | 13 | runs: 14 | using: 'composite' 15 | steps: 16 | - name: Check contract language version 17 | id: language 18 | run: | 19 | language="" 20 | # Iterate over each contract in the directory 21 | for contract in "${{ inputs.contracts-directory }}"/*.contract; do 22 | current_language=$(jq -r '.source.language' "$contract") 23 | 24 | # Check if the current language is different from the previous one 25 | if [ -n "$language" ] && [ "$current_language" != "$language" ]; then 26 | echo "Error: Different language detected in contract $contract: $language != $current_language" 27 | exit 1 28 | fi 29 | 30 | language="$current_language" 31 | 32 | echo "Contract: $contract, Language: $current_language" 33 | done 34 | echo "language=$language" >> $GITHUB_OUTPUT 35 | shell: bash 36 | -------------------------------------------------------------------------------- /stats/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | influxdb: 4 | image: influxdb:2.7.5 5 | ports: 6 | - ${INFLUXDB_PORT}:8086 7 | volumes: 8 | - influxdb-storage:/var/lib/influxdb 9 | environment: 10 | - DOCKER_INFLUXDB_INIT_MODE=setup 11 | - DOCKER_INFLUXDB_INIT_USERNAME=admin 12 | - DOCKER_INFLUXDB_INIT_PASSWORD=adminadmin 13 | - DOCKER_INFLUXDB_INIT_ORG=${INFLUXDB_ORG} 14 | - DOCKER_INFLUXDB_INIT_BUCKET=${INFLUXDB_BUCKET} 15 | - DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=${INFLUXDB_TOKEN} 16 | grafana: 17 | image: grafana/grafana:10.4.0 18 | ports: 19 | - ${GRAFANA_PORT}:3000 20 | volumes: 21 | - grafana-storage:/var/lib/grafana 22 | - ./grafana-provisioning/:/etc/grafana/provisioning 23 | depends_on: 24 | - influxdb 25 | - renderer 26 | environment: 27 | - GF_SECURITY_ADMIN_USER=${GRAFANA_USERNAME} 28 | - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD} 29 | - GF_RENDERING_SERVER_URL=http://renderer:${RENDERER_PORT}/render 30 | - GF_RENDERING_CALLBACK_URL=http://grafana:${GRAFANA_PORT} 31 | renderer: 32 | image: grafana/grafana-image-renderer:3.10.1 33 | ports: 34 | - ${RENDERER_PORT}:8081 35 | volumes: 36 | influxdb-storage: 37 | grafana-storage: 38 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [".", "macro"] 3 | 4 | [package] 5 | name = "smart-bench" 6 | version = "0.1.0" 7 | edition = "2021" 8 | 9 | [dependencies] 10 | smart-bench-macro = { version = "0.1.0", path = "./macro" } 11 | tokio = { version = "1.20.4", features = ["rt-multi-thread"] } 12 | color-eyre = "0.6.1" 13 | codec = { package = "parity-scale-codec", version = "3.4", default-features = false } 14 | contract-metadata = "3.2.0" 15 | impl-serde = { version = "0.4.0", default-features = false } 16 | serde = { version = "1.0.137", default-features = false, features = ["derive"] } 17 | serde_json = "1.0.81" 18 | clap = { version = "3.1.17", features = ["derive"] } 19 | subxt = { version = "0.33", features = ["substrate-compat"] } 20 | futures = "0.3.21" 21 | parity-wasm = "0.45" 22 | blockstats = { git = "https://github.com/paritytech/blockstats", branch = "master" } 23 | tracing = "0.1.34" 24 | tracing-subscriber = "0.3.11" 25 | primitive-types = { version = "0.12.1", features = ["fp-conversion"] } 26 | 27 | # ethereum 28 | sha3 = "0.10.8" 29 | libsecp256k1 = { version = "0.7", default-features = false, features = [ "hmac" ] } 30 | web3 = { git = "https://github.com/tomusdrw/rust-web3", version = "0.20.0", features = ["signing"], rev = "65c9d0c4" } 31 | rlp = "0.5.2" 32 | secp256k1 = { version = "0.27.0", features = ["recovery"] } 33 | 34 | # Substrate 35 | pallet-contracts-primitives = "29.0.0" 36 | sp-core = "26.0.0" 37 | sp-weights = "25.0.0" 38 | sp-runtime = "29.0.0" 39 | sp-keyring = "29.0.0" 40 | 41 | 42 | [dev-dependencies] 43 | serial_test = "2.0.0" 44 | assert_cmd = "2.0.11" 45 | regex = "1.7.3" 46 | tempfile = "3.5.0" 47 | async-std = { version = "1.12.0", features = ["attributes", "tokio1"] } 48 | anyhow = "1.0.70" 49 | 50 | [features] 51 | default = ["integration-tests"] 52 | 53 | integration-tests = [] 54 | -------------------------------------------------------------------------------- /contracts/solidity/evm/contracts/flipper.sol/flipper.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "flipper", 4 | "sourceName": "contracts/flipper.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "bool", 10 | "name": "initvalue", 11 | "type": "bool" 12 | } 13 | ], 14 | "stateMutability": "nonpayable", 15 | "type": "constructor" 16 | }, 17 | { 18 | "inputs": [], 19 | "name": "flip", 20 | "outputs": [], 21 | "stateMutability": "nonpayable", 22 | "type": "function" 23 | }, 24 | { 25 | "inputs": [], 26 | "name": "get", 27 | "outputs": [ 28 | { 29 | "internalType": "bool", 30 | "name": "", 31 | "type": "bool" 32 | } 33 | ], 34 | "stateMutability": "view", 35 | "type": "function" 36 | } 37 | ], 38 | "bytecode": "0x608060405234801561001057600080fd5b506040516101c13803806101c183398181016040528101906100329190610066565b806000806101000a81548160ff021916908315150217905550506100b2565b6000815190506100608161009b565b92915050565b60006020828403121561007857600080fd5b600061008684828501610051565b91505092915050565b60008115159050919050565b6100a48161008f565b81146100af57600080fd5b50565b610100806100c16000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c80636d4ce63c146037578063cde4efa9146051575b600080fd5b603d6059565b6040516048919060a5565b60405180910390f35b6057606f565b005b60008060009054906101000a900460ff16905090565b60008054906101000a900460ff16156000806101000a81548160ff021916908315150217905550565b609f8160be565b82525050565b600060208201905060b860008301846098565b92915050565b6000811515905091905056fea2646970667358221220839b7127425cadc978fdae85c236303c56c2ce2e9ea2c4048fe9488c6187b87964736f6c63430008010033", 39 | "deployedBytecode": "0x6080604052348015600f57600080fd5b506004361060325760003560e01c80636d4ce63c146037578063cde4efa9146051575b600080fd5b603d6059565b6040516048919060a5565b60405180910390f35b6057606f565b005b60008060009054906101000a900460ff16905090565b60008054906101000a900460ff16156000806101000a81548160ff021916908315150217905550565b609f8160be565b82525050565b600060208201905060b860008301846098565b92915050565b6000811515905091905056fea2646970667358221220839b7127425cadc978fdae85c236303c56c2ce2e9ea2c4048fe9488c6187b87964736f6c63430008010033", 40 | "linkReferences": {}, 41 | "deployedLinkReferences": {} 42 | } 43 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod evm; 2 | #[cfg(test)] 3 | #[cfg(feature = "integration-tests")] 4 | mod integration_tests; 5 | mod stats; 6 | mod wasm; 7 | 8 | use std::fmt::Display; 9 | 10 | // export for use by contract! macro 11 | use clap::Parser; 12 | pub use stats::{collect_block_stats, print_block_info, BlockInfo}; 13 | pub use wasm::{InkConstructor, InkMessage}; 14 | 15 | #[derive(Debug, Parser)] 16 | #[clap(version)] 17 | pub struct Cli { 18 | /// the url of the substrate node for submitting the extrinsics. 19 | #[clap(name = "url", long, default_value = "ws://localhost:9944")] 20 | url: String, 21 | /// the smart contract platform to benchmark. 22 | #[clap(arg_enum)] 23 | chain: TargetPlatform, 24 | /// the list of contracts to benchmark with. 25 | #[clap(arg_enum)] 26 | contracts: Vec, 27 | /// the number of each contract to instantiate. 28 | #[clap(long, short)] 29 | instance_count: u32, 30 | /// the number of calls to make to each contract. 31 | #[clap(long, short)] 32 | call_count: u32, 33 | } 34 | 35 | #[derive(clap::ArgEnum, Debug, Clone)] 36 | pub enum TargetPlatform { 37 | InkWasm, 38 | SolWasm, 39 | Evm, 40 | } 41 | 42 | #[derive(clap::ArgEnum, Debug, Clone, Eq, PartialEq)] 43 | pub enum Contract { 44 | Erc20, 45 | Flipper, 46 | Incrementer, 47 | Erc721, 48 | Erc1155, 49 | OddProduct, 50 | TriangleNumber, 51 | StorageRead, 52 | StorageWrite, 53 | StorageReadWrite, 54 | } 55 | 56 | impl Display for TargetPlatform { 57 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 58 | write!(f, "{}", clap::ArgEnum::to_possible_value(self).unwrap_or("unknown".into()).get_name()) 59 | } 60 | } 61 | 62 | impl Display for Contract { 63 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 64 | write!(f, "{}", clap::ArgEnum::to_possible_value(self).unwrap_or("unknown".into()).get_name()) 65 | } 66 | } 67 | 68 | #[tokio::main] 69 | async fn main() -> color_eyre::Result<()> { 70 | color_eyre::install()?; 71 | let cli = Cli::parse(); 72 | tracing_subscriber::fmt::init(); 73 | 74 | println!("Smart-bench run parameters:"); 75 | println!("Platform: {}", cli.chain); 76 | println!("Contracts: {}", cli.contracts.iter().map(|arg| arg.to_string()).collect::>().join("+")); 77 | 78 | match cli.chain { 79 | TargetPlatform::InkWasm => wasm::exec(cli).await, 80 | TargetPlatform::SolWasm => wasm::exec(cli).await, 81 | TargetPlatform::Evm => evm::exec(&cli).await, 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Build examples 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: [opened, synchronize, reopened, ready_for_review] 9 | 10 | jobs: 11 | build: 12 | name: Cargo Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions-rs/toolchain@v1 17 | with: 18 | toolchain: stable 19 | - uses: actions-rs/cargo@v1 20 | with: 21 | command: build 22 | args: --release --all-features 23 | 24 | quick_check: 25 | strategy: 26 | matrix: 27 | os: ["ubuntu-latest"] 28 | runs-on: ${{ matrix.os }} 29 | steps: 30 | - name: Install Rust toolchain 31 | uses: actions-rs/toolchain@b2417cde72dcf67f306c0ae8e0828a81bf0b189f # v1.0.7 32 | with: 33 | profile: minimal 34 | toolchain: stable 35 | override: true 36 | 37 | - name: Install Rust nightly toolchain 38 | uses: actions-rs/toolchain@b2417cde72dcf67f306c0ae8e0828a81bf0b189f # v1.0.7 39 | with: 40 | profile: minimal 41 | toolchain: nightly 42 | override: true 43 | components: rust-src 44 | 45 | - name: Cache Dependencies & Build Outputs 46 | uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11 47 | with: 48 | path: | 49 | ~/.cargo/registry 50 | ~/.cargo/git 51 | target 52 | key: ${{ runner.os }}-${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 53 | 54 | - name: Install cargo-dylint 55 | uses: baptiste0928/cargo-install@bf6758885262d0e6f61089a9d8c8790d3ac3368f #v1.3.0 56 | with: 57 | crate: cargo-dylint 58 | 59 | - name: Install dylint-link 60 | uses: baptiste0928/cargo-install@bf6758885262d0e6f61089a9d8c8790d3ac3368f #v1.3.0 61 | with: 62 | crate: dylint-link 63 | 64 | - name: Install cargo-contract 65 | uses: baptiste0928/cargo-install@bf6758885262d0e6f61089a9d8c8790d3ac3368f #v1.3.0 66 | with: 67 | crate: cargo-contract 68 | version: "2.0.0-rc" 69 | 70 | - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 71 | 72 | - name: Install cargo-contract and build contract 73 | shell: bash 74 | run: | 75 | git clone --verbose --depth 1 https://github.com/paritytech/ink.git 76 | mv Cargo.toml _Cargo.toml 77 | ./scripts/ci/build-contract.sh ./ink/integration-tests/public/erc20 78 | mv _Cargo.toml Cargo.toml 79 | -------------------------------------------------------------------------------- /contracts/ink/storage/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std, no_main)] 2 | 3 | #[ink::contract] 4 | mod storage { 5 | use ink::storage::Mapping; 6 | 7 | #[ink(storage)] 8 | #[derive(Default)] 9 | pub struct Storage { 10 | balances: Mapping, 11 | } 12 | 13 | impl Storage { 14 | #[ink(constructor)] 15 | pub fn new() -> Self { 16 | Self { 17 | balances: Mapping::default() 18 | } 19 | } 20 | 21 | #[ink(message)] 22 | pub fn read(&self, account: AccountId, count: u32) { 23 | for _ in 0..count { 24 | let _ = self.balances.get(account); 25 | } 26 | } 27 | 28 | #[ink(message)] 29 | pub fn write(&mut self, account: AccountId, count: u32) { 30 | for _ in 0..count { 31 | self.balances.insert(account, &1_000_000); 32 | } 33 | } 34 | 35 | #[ink(message)] 36 | pub fn read_write(&mut self, account: AccountId, count: u32) { 37 | for _ in 0..count { 38 | let x = self.balances.get(account).unwrap_or(0); 39 | self.balances.insert(account, &(x + 1)); 40 | } 41 | } 42 | 43 | #[ink(message)] 44 | pub fn read_write_raw(&mut self, account: AccountId, count: u32) { 45 | let account_key = ink::primitives::KeyComposer::from_bytes(account.as_ref()); 46 | for _ in 0..count { 47 | let x = ink::env::get_contract_storage(&account_key).unwrap().unwrap_or(0); 48 | ink::env::set_contract_storage(&account_key, &(x + 1)); 49 | } 50 | } 51 | } 52 | 53 | /// Unit tests in Rust are normally defined within such a `#[cfg(test)]` 54 | /// module and test functions are marked with a `#[test]` attribute. 55 | /// The below code is technically just normal Rust code. 56 | #[cfg(test)] 57 | mod tests { 58 | /// Imports all the definitions from the outer scope so we can use them here. 59 | use super::*; 60 | 61 | /// We test if the default constructor does its job. 62 | #[ink::test] 63 | fn default_works() { 64 | let storage = Storage::default(); 65 | assert_eq!(storage.get(), false); 66 | } 67 | 68 | /// We test a simple use case of our contract. 69 | #[ink::test] 70 | fn it_works() { 71 | let mut storage = Storage::new(false); 72 | assert_eq!(storage.get(), false); 73 | storage.flip(); 74 | assert_eq!(storage.get(), true); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /launch/download-bins.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | cumulus_version=1.0.0 5 | polkadot_version=1.0.0 6 | zombienet_version=1.3.58 7 | moonbeam_version=13_12_2023 8 | 9 | packages_to_download=$(cat << EOF 10 | [ 11 | { 12 | "target":"polkadot-parachain", 13 | "url": "https://github.com/paritytech/cumulus/releases/download/v${cumulus_version}/polkadot-parachain", 14 | "format": "bin", 15 | "sha256": "https://github.com/paritytech/cumulus/releases/download/v${cumulus_version}/polkadot-parachain.sha256" 16 | }, 17 | { 18 | "target":"polkadot", 19 | "url": "https://github.com/paritytech/polkadot/releases/download/v$polkadot_version/polkadot", 20 | "format": "bin", 21 | "sha256": "https://github.com/paritytech/polkadot/releases/download/v$polkadot_version/polkadot.sha256" 22 | }, 23 | { 24 | "target":"zombienet", 25 | "url": "https://github.com/paritytech/zombienet/releases/download/v$zombienet_version/zombienet-linux-x64", 26 | "format": "bin", 27 | "sha256": "e49b6f15c8aa304e38ad8819c853d721f2f580f3906e6e03601b6824de6964fc" 28 | }, 29 | { 30 | "target":"moonbeam", 31 | "url": "https://github.com/karolk91/moonbeam/releases/download/$moonbeam_version/moonbeam.gz", 32 | "format": "gz", 33 | "sha256": "8402a01a0fdadaf7ffbae59e84982f3186e73de8d3ea9a0cb67aaa81b90a7f48" 34 | } 35 | ] 36 | EOF 37 | ) 38 | 39 | download_package() { 40 | local package_json=$1 41 | local url; url=$(echo "${package_json}" | jq -r '.url') 42 | local target; target=$(echo "${package_json}" | jq -r '.target') 43 | local format; format=$(echo "${package_json}" | jq -r '.format') 44 | local sha256; sha256=$(echo "${package_json}" | jq -r '.sha256') 45 | 46 | case "${sha256}" in 47 | "http"*) 48 | curl --location "${sha256}" --output "${target}.sha256" 49 | sha256=$(cut -d' ' -f1 "${target}.sha256") 50 | rm "${target}.sha256" 51 | ;; 52 | esac 53 | 54 | curl --location "${url}" --output "_${target}" 55 | echo "${sha256} _${target}" | sha256sum --check 56 | 57 | if [ "${format}" = "gz" ]; then 58 | tar xzf "_${target}" 59 | rm "_${target}" 60 | else 61 | mv "_${target}" "${target}" 62 | fi 63 | 64 | chmod +x "${target}" 65 | } 66 | 67 | 68 | mkdir -p bin 69 | 70 | item_count=$(echo "${packages_to_download}" | jq length) 71 | (cd bin && for i in $(seq 0 $((item_count-1)) ); do 72 | item=$(echo "$packages_to_download" | jq -r ".[$i]") 73 | target=$(echo "${item}" | jq -r '.target') 74 | if [ -f "${target}" ]; then 75 | echo "[${target}] already exists" 76 | else 77 | echo "[${target}] downloading" 78 | download_package "${item}" 79 | echo "[${target}] downloaded" 80 | fi 81 | done) 82 | -------------------------------------------------------------------------------- /stats/smart_bench_to_csv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | SCRIPT_NAME="${BASH_SOURCE[0]}" 6 | SCRIPT_PATH=$(dirname "$(realpath -s "${BASH_SOURCE[0]}")") 7 | TIMESTAMP=$(date +%s) 8 | 9 | if [ -p /dev/stdin ]; then 10 | STATS=$(&2; } 16 | 17 | function usage { 18 | cat << EOF 19 | Script to translate smart bench stdout data into csv formatted benchmarking results and append to given output file 20 | Script expects data to be piped from smart-bench application into this script 21 | 22 | Usage: smart-bench ..... | ${SCRIPT_NAME} ARGS 23 | 24 | ARGS 25 | -o, --csv-output (Required) CSV formatted output of smart-bench 26 | -t, --timestamp (Optional) Timestamp to use for benchmark results - if not provided, current time is used 27 | -h, --help Print this help message 28 | 29 | EXAMPLE 30 | cargo run --release -- evm flipper --instance-count 1 --call-count 1500 --url ws://localhost:9988 | ${SCRIPT_NAME} --csv-output=benchmark-result.csv --timestamp=1714515934 31 | EOF 32 | } 33 | 34 | function parse_args { 35 | function needs_arg { 36 | if [ -z "${OPTARG}" ]; then 37 | echoerr "No arg for --${OPT} option" 38 | exit 2 39 | fi 40 | } 41 | 42 | # shellcheck disable=SC2214 43 | while getopts o:t:h-: OPT; do 44 | # support long options: https://stackoverflow.com/a/28466267/519360 45 | if [ "$OPT" = "-" ]; then # long option: reformulate OPT and OPTARG 46 | OPT="${OPTARG%%=*}" # extract long option name 47 | OPTARG="${OPTARG#"$OPT"}" # extract long option argument (may be empty) 48 | OPTARG="${OPTARG#=}" # if long option argument, remove assigning `=` 49 | fi 50 | case "$OPT" in 51 | o | csv-output) needs_arg && CSV_OUTPUT="${OPTARG}";; 52 | t | timestamp ) needs_arg && TIMESTAMP="${OPTARG}";; 53 | h | help ) usage; exit 0;; 54 | ??* ) echoerr "Illegal option --$OPT"; exit 2;; # bad long option 55 | ? ) exit 2 ;; # bad short option (error reported via getopts) 56 | esac 57 | done 58 | shift $((OPTIND-1)) # remove parsed options and args from $@ list 59 | 60 | [ -n "${CSV_OUTPUT-}" ] || { 61 | echoerr "missing -c/--csv-output arg" 62 | echoerr "" 63 | usage 64 | exit 2 65 | } 66 | } 67 | 68 | parse_args "$@" 69 | 70 | 71 | platform=$(echo ${STATS} | grep -o 'Platform: [a-z0-9-]*' | awk '{print $2}') 72 | contract_types=$(echo ${STATS} | grep -o 'Contracts: [+a-z0-9-]*' | awk '{print $2}') 73 | tps=$(echo ${STATS} | grep -o 'sTPS: [0-9]\+\.[0-9]\{2\}' | awk '{print $2}') 74 | 75 | echo "${TIMESTAMP}, ${platform}, n/a, ${contract_types}, ${tps}, n/a, n/a" >> "${CSV_OUTPUT}" 76 | -------------------------------------------------------------------------------- /contracts/solidity/evm/contracts/incrementer.sol/incrementer.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "incrementer", 4 | "sourceName": "contracts/incrementer.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "uint32", 10 | "name": "initvalue", 11 | "type": "uint32" 12 | } 13 | ], 14 | "stateMutability": "nonpayable", 15 | "type": "constructor" 16 | }, 17 | { 18 | "inputs": [], 19 | "name": "get", 20 | "outputs": [ 21 | { 22 | "internalType": "uint32", 23 | "name": "", 24 | "type": "uint32" 25 | } 26 | ], 27 | "stateMutability": "view", 28 | "type": "function" 29 | }, 30 | { 31 | "inputs": [ 32 | { 33 | "internalType": "uint32", 34 | "name": "by", 35 | "type": "uint32" 36 | } 37 | ], 38 | "name": "inc", 39 | "outputs": [], 40 | "stateMutability": "nonpayable", 41 | "type": "function" 42 | } 43 | ], 44 | "bytecode": "0x608060405234801561001057600080fd5b506040516102c93803806102c98339818101604052810190610032919061006d565b806000806101000a81548163ffffffff021916908363ffffffff160217905550506100bd565b600081519050610067816100a6565b92915050565b60006020828403121561007f57600080fd5b600061008d84828501610058565b91505092915050565b600063ffffffff82169050919050565b6100af81610096565b81146100ba57600080fd5b50565b6101fd806100cc6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80636d4ce63c1461003b578063dd5d521114610059575b600080fd5b610043610075565b604051610050919061011c565b60405180910390f35b610073600480360381019061006e91906100e4565b61008e565b005b60008060009054906101000a900463ffffffff16905090565b806000808282829054906101000a900463ffffffff166100ae9190610137565b92506101000a81548163ffffffff021916908363ffffffff16021790555050565b6000813590506100de816101b0565b92915050565b6000602082840312156100f657600080fd5b6000610104848285016100cf565b91505092915050565b61011681610171565b82525050565b6000602082019050610131600083018461010d565b92915050565b600061014282610171565b915061014d83610171565b92508263ffffffff0382111561016657610165610181565b5b828201905092915050565b600063ffffffff82169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6101b981610171565b81146101c457600080fd5b5056fea264697066735822122060097c0a9d7bdc18ea8694ba137807985328dd8a3229367d23b991605a78d06f64736f6c63430008010033", 45 | "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80636d4ce63c1461003b578063dd5d521114610059575b600080fd5b610043610075565b604051610050919061011c565b60405180910390f35b610073600480360381019061006e91906100e4565b61008e565b005b60008060009054906101000a900463ffffffff16905090565b806000808282829054906101000a900463ffffffff166100ae9190610137565b92506101000a81548163ffffffff021916908363ffffffff16021790555050565b6000813590506100de816101b0565b92915050565b6000602082840312156100f657600080fd5b6000610104848285016100cf565b91505092915050565b61011681610171565b82525050565b6000602082019050610131600083018461010d565b92915050565b600061014282610171565b915061014d83610171565b92508263ffffffff0382111561016657610165610181565b5b828201905092915050565b600063ffffffff82169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6101b981610171565b81146101c457600080fd5b5056fea264697066735822122060097c0a9d7bdc18ea8694ba137807985328dd8a3229367d23b991605a78d06f64736f6c63430008010033", 46 | "linkReferences": {}, 47 | "deployedLinkReferences": {} 48 | } 49 | -------------------------------------------------------------------------------- /contracts/solidity/evm/contracts/Computation.sol/Computation.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "Computation", 4 | "sourceName": "contracts/Computation.sol", 5 | "abi": [ 6 | { 7 | "inputs": [], 8 | "stateMutability": "nonpayable", 9 | "type": "constructor" 10 | }, 11 | { 12 | "inputs": [ 13 | { 14 | "internalType": "int32", 15 | "name": "x", 16 | "type": "int32" 17 | } 18 | ], 19 | "name": "oddProduct", 20 | "outputs": [ 21 | { 22 | "internalType": "int64", 23 | "name": "", 24 | "type": "int64" 25 | } 26 | ], 27 | "stateMutability": "pure", 28 | "type": "function" 29 | }, 30 | { 31 | "inputs": [ 32 | { 33 | "internalType": "int32", 34 | "name": "x", 35 | "type": "int32" 36 | } 37 | ], 38 | "name": "triangleNumber", 39 | "outputs": [ 40 | { 41 | "internalType": "int64", 42 | "name": "", 43 | "type": "int64" 44 | } 45 | ], 46 | "stateMutability": "pure", 47 | "type": "function" 48 | } 49 | ], 50 | "bytecode": "0x608060405234801561001057600080fd5b5061024c806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80631ad730ef1461003b5780637b96f0e01461006b575b600080fd5b61005560048036038101906100509190610136565b61009b565b604051610062919061016e565b60405180910390f35b61008560048036038101906100809190610136565b6100db565b604051610092919061016e565b60405180910390f35b600080600090506000600190505b8360030b8160030b136100d1578060030b8201915080806100c9906101a3565b9150506100a9565b5080915050919050565b600080600190506000600190505b8360030b8160030b13610117576001816002020360030b82029150808061010f906101a3565b9150506100e9565b5080915050919050565b600081359050610130816101ff565b92915050565b60006020828403121561014857600080fd5b600061015684828501610121565b91505092915050565b61016881610196565b82525050565b6000602082019050610183600083018461015f565b92915050565b60008160030b9050919050565b60008160070b9050919050565b60006101ae82610189565b9150637fffffff8214156101c5576101c46101d0565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b61020881610189565b811461021357600080fd5b5056fea26469706673582212204bfdf671dae3b63cc429a2f6ad926040d0c4f9504dccb337496531097a81bc7264736f6c63430008010033", 51 | "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80631ad730ef1461003b5780637b96f0e01461006b575b600080fd5b61005560048036038101906100509190610136565b61009b565b604051610062919061016e565b60405180910390f35b61008560048036038101906100809190610136565b6100db565b604051610092919061016e565b60405180910390f35b600080600090506000600190505b8360030b8160030b136100d1578060030b8201915080806100c9906101a3565b9150506100a9565b5080915050919050565b600080600190506000600190505b8360030b8160030b13610117576001816002020360030b82029150808061010f906101a3565b9150506100e9565b5080915050919050565b600081359050610130816101ff565b92915050565b60006020828403121561014857600080fd5b600061015684828501610121565b91505092915050565b61016881610196565b82525050565b6000602082019050610183600083018461015f565b92915050565b60008160030b9050919050565b60008160070b9050919050565b60006101ae82610189565b9150637fffffff8214156101c5576101c46101d0565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b61020881610189565b811461021357600080fd5b5056fea26469706673582212204bfdf671dae3b63cc429a2f6ad926040d0c4f9504dccb337496531097a81bc7264736f6c63430008010033", 52 | "linkReferences": {}, 53 | "deployedLinkReferences": {} 54 | } 55 | -------------------------------------------------------------------------------- /stats/README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This folder contains utilities to create visual graphs based on smart-bench measurements 4 | 5 | Solution is based upon Grafana and InfluxDB software. 6 | 7 | ### Theory of operation 8 | 9 | 1. Gather smart-bench benchmarking results in CSV format 10 | Make use of an utility script `smart_bench_to_csv.sh`. Run it against smart-bench software multiple times to gather statistics data. Make sure to use meaningful timestamps. For example (any method to run smart-bench): 11 | 12 | ``` 13 | cargo run --release -- evm flipper --instance-count 1 --call-count 1500 --url ws://localhost:9988 | ./smart_bench_to_csv.sh --csv-output=benchmark-result.csv --timestamp=1714515934 14 | 15 | cargo run --release -- ink-wasm flipper --instance-count 1 --call-count 1500 --url ws://localhost:9988 | ./smart_bench_to_csv.sh --csv-output=benchmark-result.csv --timestamp=1714515934 16 | 17 | cargo run --release -- sol-wasm flipper --instance-count 1 --call-count 1500 --url ws://localhost:9988 | ./smart_bench_to_csv.sh --csv-output=benchmark-result.csv --timestamp=1714515934 18 | ``` 19 | 20 | or 21 | 22 | ``` 23 | ../launch/run.sh -- evm flipper --instance-count 1 --call-count 1500 | ./smart_bench_to_csv.sh --csv-output=benchmark-result.csv --timestamp=1714515934 24 | 25 | ../launch/run.sh -- ink-wasm flipper --instance-count 1 --call-count 1500 | ./smart_bench_to_csv.sh --csv-output=benchmark-result.csv --timestamp=1714515934 26 | 27 | ../launch/run.sh -- sol-wasm flipper --instance-count 1 --call-count 1500 | ./smart_bench_to_csv.sh --csv-output=benchmark-result.csv --timestamp=1714515934 28 | ``` 29 | 30 | above will create `benchmark-result.csv` file with all `3` results appended 31 | 32 | or get existing csv results from [gh-pages branch](https://github.com/paritytech/smart-bench/tree/gh-pages) 33 | 2. Make use of `get_graph.sh` to generate graph as PNG image 34 | - script is spinning up ephemeral environemnt with Grafana, Grafana Renderer and InfluxDB services running by utilizing docker-compose.yml configuration 35 | - translates benchmarking data provided in CSV format into Line Protocol format supported by InfluxDB, then uploads it to the InfluxDB service 36 | - script is downloading given Grafana panel id (see supported ids beloew) as PNG image by utlizing Grafana plugin pre-configured in the environemnt 37 | 38 | ### Currently supported panel ids with examples: 39 | - `--panel-id=2` - panel to display transactions per seconds (TPS) measurements per platform, per contract type 40 | ![Example graphs](./panel_id_2_example.png) 41 | 42 | ## Usage 43 | ### `get_graph.sh` help screen: 44 | ``` 45 | Script to generate PNG graphs out of CSV formatted data from smart-bench via ephemeral Grafana+InfluxDB environment 46 | 47 | Usage: ./get_graph.sh ARGS 48 | 49 | ARGS 50 | -p, --panel-id (Required) ID of the panel within Grafana dashboard to render as PNG 51 | -c, --csv-data (Required) CSV formatted output of smart-bench 52 | -o, --output (Required) Path to file where output PNG image will be stored 53 | -h, --help Print this help message 54 | 55 | EXAMPLE 56 | ./get_graph.sh --panel-id=2 --csv-data=benchmark-result.csv --output=tps.png 57 | ``` 58 | 59 | ### `smart_bench_to_csv.sh` help screen: 60 | ``` 61 | Script to translate smart bench stdout data into csv formatted benchmarking results 62 | Script expects data to be piped from smart-bench application into this script 63 | 64 | Usage: smart-bench ..... | ./smart_bench_to_csv.sh ARGS 65 | 66 | ARGS 67 | -o, --csv-output (Required) CSV formatted output of smart-bench 68 | -t, --timestamp (Optional) Timestamp to use for benchmark results - if not provided, current time is used 69 | -h, --help Print this help message 70 | 71 | EXAMPLE 72 | smart-bench evm flipper --instance-count 1 --call-count 1500 --url ws://localhost:9988 | ./smart_bench_to_csv.sh --csv-output=benchmark-result.csv --timestamp=1714515934 73 | ``` 74 | 75 | -------------------------------------------------------------------------------- /launch/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | SCRIPT_NAME="${BASH_SOURCE[0]}" 5 | SCRIPT_PATH=$(dirname "$(realpath -s "${BASH_SOURCE[0]}")") 6 | VERSION=${VERSION:-latest} 7 | NAME=smart-bench 8 | IMAGE="${NAME}:${VERSION}" 9 | BINARIES_DIR="" 10 | CONTRACTS_DIR="" 11 | CONFIGS_DIR="" 12 | 13 | function echoerr() { echo "$@" 1>&2; } 14 | 15 | function usage { 16 | cat << EOF 17 | Usage: ${SCRIPT_NAME} OPTION -- ARGUMENTS_TO_SMART_BENCH 18 | 19 | OPTION 20 | -b, --binaries-dir (Optional) Path to directory that contains binaries to mount into the container (eg. polkadot, zombienet, moonbeam) 21 | List of binaries being used depends on config provided. Default set of binaries is available within the image 22 | -t, --contracts-dir (Optional) Path to directory that contains compiled smart contracts. Default set of compiled smart contracts is available within the image 23 | -u, --configs-dir (Optional) Path to directory that contains zombienet config files. Default set of configs files is available within the image 24 | -h, --help Print this help message 25 | 26 | ARGUMENTS_TO_SMART_BENCH 27 | smart-bench specific parameters (NOTE: do not provide --url param as it is managed by this tool) 28 | 29 | EXAMPLES 30 | ${SCRIPT_NAME} -- evm erc20 --instance-count 1 --call-count 10 31 | ${SCRIPT_NAME} -- ink-wasm erc20 --instance-count 1 --call-count 10 32 | ${SCRIPT_NAME} -- sol-wasm erc20 --instance-count 1 --call-count 10 33 | ${SCRIPT_NAME} --binaries-dir=./bin -- sol-wasm erc20 --instance-count 1 --call-count 10 34 | ${SCRIPT_NAME} --contracts-dir=../contracts -- sol-wasm erc20 --instance-count 1 --call-count 10 35 | ${SCRIPT_NAME} --configs-dir=./configs -- sol-wasm erc20 --instance-count 1 --call-count 10 36 | 37 | EOF 38 | } 39 | 40 | function parse_args { 41 | function needs_arg { 42 | if [ -z "${OPTARG}" ]; then 43 | echoerr "No arg for --${OPT} option" 44 | exit 2 45 | fi 46 | } 47 | 48 | function check_args { 49 | [ -z "${BINARIES_DIR}" ] || BINARIES_DIR=$(realpath -qe "${BINARIES_DIR}") || { 50 | echoerr "BINARIES_DIR path=[${BINARIES_DIR}] doesn't exist" 51 | exit 2 52 | } 53 | [ -z "${CONTRACTS_DIR}" ] || CONTRACTS_DIR=$(realpath -qe "${CONTRACTS_DIR}") || { 54 | echoerr "CONTRACTS_DIR path=[${CONTRACTS_DIR}] doesn't exist" 55 | exit 2 56 | } 57 | [ -z "${CONFIGS_DIR}" ] || CONFIGS_DIR=$(realpath -qe "${CONFIGS_DIR}") || { 58 | echoerr "CONTRACTS_DIR path=[${CONTRACTS_DIR}] doesn't exist" 59 | exit 2 60 | } 61 | } 62 | 63 | # shellcheck disable=SC2214 64 | while getopts b:c:t:u:h:-: OPT; do 65 | # support long options: https://stackoverflow.com/a/28466267/519360 66 | if [ "$OPT" = "-" ]; then # long option: reformulate OPT and OPTARG 67 | OPT="${OPTARG%%=*}" # extract long option name 68 | OPTARG="${OPTARG#"$OPT"}" # extract long option argument (may be empty) 69 | OPTARG="${OPTARG#=}" # if long option argument, remove assigning `=` 70 | fi 71 | case "$OPT" in 72 | b | binaries-dir) BINARIES_DIR="${OPTARG}";; 73 | t | contracts-dir) CONTRACTS_DIR="${OPTARG}";; 74 | u | configs-dir) CONFIGS_DIR="${OPTARG}";; 75 | h | help ) usage; exit 0;; 76 | ??* ) echoerr "Illegal option --$OPT"; exit 2;; # bad long option 77 | ? ) exit 2 ;; # bad short option (error reported via getopts) 78 | esac 79 | done 80 | shift $((OPTIND-1)) # remove parsed options and args from $@ list 81 | check_args 82 | OTHERARGS=("$@") 83 | } 84 | 85 | parse_args "$@" 86 | 87 | container_dir="/usr/local" 88 | container_zombienet_configs="${container_dir}/smart-bench/config" 89 | container_contracts="${container_dir}/smart-bench/contracts" 90 | container_binaries="${container_dir}/smart-bench/bin" 91 | 92 | volume_args="" 93 | if [ -n "${CONFIGS_DIR}" ]; then 94 | volume_args="${volume_args} -v ${CONFIGS_DIR}:${container_zombienet_configs}" 95 | fi 96 | 97 | if [ -n "${CONTRACTS_DIR}" ]; then 98 | volume_args="${volume_args} -v ${CONTRACTS_DIR}:${container_contracts}" 99 | fi 100 | 101 | if [ -n "${BINARIES_DIR}" ]; then 102 | volume_args="${volume_args} -v ${BINARIES_DIR}:${container_binaries}" 103 | fi 104 | 105 | # shellcheck disable=SC2086 106 | (set -x; docker run --platform linux/amd64 --rm -t --init \ 107 | ${volume_args} \ 108 | "${IMAGE}" \ 109 | "${OTHERARGS[@]}" 110 | ) 111 | -------------------------------------------------------------------------------- /stats/grafana-provisioning/dashboards/tps.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": { 7 | "type": "grafana", 8 | "uid": "-- Grafana --" 9 | }, 10 | "enable": true, 11 | "hide": true, 12 | "iconColor": "rgba(0, 211, 255, 1)", 13 | "name": "Annotations & Alerts", 14 | "type": "dashboard" 15 | } 16 | ] 17 | }, 18 | "editable": true, 19 | "fiscalYearStartMonth": 0, 20 | "graphTooltip": 0, 21 | "links": [], 22 | "liveNow": false, 23 | "panels": [ 24 | { 25 | "aliasColors": {}, 26 | "bars": false, 27 | "dashLength": 10, 28 | "dashes": false, 29 | "datasource": { 30 | "type": "influxdb", 31 | "uid": "P951FEA4DE68E13C5" 32 | }, 33 | "fieldConfig": { 34 | "defaults": { 35 | "links": [] 36 | }, 37 | "overrides": [] 38 | }, 39 | "fill": 1, 40 | "fillGradient": 0, 41 | "gridPos": { 42 | "h": 8, 43 | "w": 12, 44 | "x": 0, 45 | "y": 0 46 | }, 47 | "hiddenSeries": false, 48 | "id": 2, 49 | "legend": { 50 | "avg": false, 51 | "current": false, 52 | "max": false, 53 | "min": false, 54 | "show": true, 55 | "total": false, 56 | "values": false 57 | }, 58 | "lines": true, 59 | "linewidth": 1, 60 | "links": [], 61 | "nullPointMode": "null", 62 | "options": { 63 | "alertThreshold": true 64 | }, 65 | "percentage": false, 66 | "percentiles": [], 67 | "pluginVersion": "10.2.3", 68 | "pointradius": 2, 69 | "points": true, 70 | "renderer": "flot", 71 | "seriesOverrides": [], 72 | "spaceLength": 10, 73 | "stack": false, 74 | "steppedLine": false, 75 | "targets": [ 76 | { 77 | "datasource": { 78 | "uid": "influxdb" 79 | }, 80 | "groupBy": [ 81 | { 82 | "params": [ 83 | "$interval" 84 | ], 85 | "type": "time" 86 | }, 87 | { 88 | "params": [ 89 | "contract_type::tag" 90 | ], 91 | "type": "tag" 92 | }, 93 | { 94 | "params": [ 95 | "platform::tag" 96 | ], 97 | "type": "tag" 98 | } 99 | ], 100 | "measurement": "tps", 101 | "orderByTime": "ASC", 102 | "policy": "default", 103 | "refId": "A", 104 | "resultFormat": "time_series", 105 | "select": [ 106 | [ 107 | { 108 | "params": [ 109 | "tx_per_sec" 110 | ], 111 | "type": "field" 112 | }, 113 | { 114 | "params": [], 115 | "type": "distinct" 116 | } 117 | ] 118 | ], 119 | "tags": [] 120 | } 121 | ], 122 | "thresholds": [], 123 | "timeRegions": [], 124 | "title": "Transactions Per Second", 125 | "tooltip": { 126 | "shared": true, 127 | "sort": 0, 128 | "value_type": "individual" 129 | }, 130 | "type": "graph", 131 | "xaxis": { 132 | "mode": "time", 133 | "show": true, 134 | "values": [] 135 | }, 136 | "yaxes": [ 137 | { 138 | "format": "short", 139 | "logBase": 1, 140 | "show": true 141 | }, 142 | { 143 | "format": "short", 144 | "logBase": 1, 145 | "show": true 146 | } 147 | ], 148 | "yaxis": { 149 | "align": false 150 | } 151 | } 152 | ], 153 | "refresh": "", 154 | "schemaVersion": 39, 155 | "tags": [], 156 | "templating": { 157 | "list": [] 158 | }, 159 | "time": { 160 | "from": "now-24h", 161 | "to": "now" 162 | }, 163 | "timepicker": { 164 | "refresh_intervals": [ 165 | "5s", 166 | "10s", 167 | "30s", 168 | "1m", 169 | "5m", 170 | "15m", 171 | "30m", 172 | "1h", 173 | "2h", 174 | "1d" 175 | ], 176 | "time_options": [ 177 | "5m", 178 | "15m", 179 | "1h", 180 | "6h", 181 | "12h", 182 | "24h", 183 | "2d", 184 | "7d", 185 | "30d" 186 | ] 187 | }, 188 | "timezone": "", 189 | "title": "transactions-dashboard", 190 | "uid": "e2f936b3-fffc-443a-837b-e719581a031d", 191 | "version": 3, 192 | "weekStart": "" 193 | } 194 | -------------------------------------------------------------------------------- /contracts/solidity/openzeppelin_solang.patch: -------------------------------------------------------------------------------- 1 | diff --git a/contracts/token/ERC721/ERC721.sol b/contracts/token/ERC721/ERC721.sol 2 | index 7942e6f..0756c90 100644 3 | --- a/contracts/token/ERC721/ERC721.sol 4 | +++ b/contracts/token/ERC721/ERC721.sol 5 | @@ -409,10 +409,10 @@ contract ERC721 is Context, ERC165, IERC721, IERC721Metadata { 6 | if (reason.length == 0) { 7 | revert("ERC721: transfer to non ERC721Receiver implementer"); 8 | } else { 9 | - /// @solidity memory-safe-assembly 10 | - assembly { 11 | - revert(add(32, reason), mload(reason)) 12 | - } 13 | + // /// @solidity memory-safe-assembly 14 | + // assembly { 15 | + // revert(add(32, reason), mload(reason)) 16 | + // } 17 | } 18 | } 19 | } else { 20 | diff --git a/contracts/utils/Address.sol b/contracts/utils/Address.sol 21 | index 0791a66..0e373de 100644 22 | --- a/contracts/utils/Address.sol 23 | +++ b/contracts/utils/Address.sol 24 | @@ -2,6 +2,7 @@ 25 | // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) 26 | 27 | pragma solidity ^0.8.1; 28 | +import "polkadot"; 29 | 30 | /** 31 | * @dev Collection of functions related to the address type 32 | @@ -41,8 +42,7 @@ library Address { 33 | // This method relies on extcodesize/address.code.length, which returns 0 34 | // for contracts in construction, since the code is only stored at the end 35 | // of the constructor execution. 36 | - 37 | - return account.code.length > 0; 38 | + return is_contract(account); 39 | } 40 | 41 | /** 42 | @@ -142,7 +142,7 @@ library Address { 43 | * 44 | * _Available since v3.3._ 45 | */ 46 | - function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { 47 | + function functionStaticCall(address target, bytes memory data) internal returns (bytes memory) { 48 | return functionStaticCall(target, data, "Address: low-level static call failed"); 49 | } 50 | 51 | @@ -156,8 +156,8 @@ library Address { 52 | address target, 53 | bytes memory data, 54 | string memory errorMessage 55 | - ) internal view returns (bytes memory) { 56 | - (bool success, bytes memory returndata) = target.staticcall(data); 57 | + ) internal returns (bytes memory) { 58 | + (bool success, bytes memory returndata) = target.call(data); 59 | return verifyCallResultFromTarget(target, success, returndata, errorMessage); 60 | } 61 | 62 | @@ -230,15 +230,15 @@ library Address { 63 | 64 | function _revert(bytes memory returndata, string memory errorMessage) private pure { 65 | // Look for revert reason and bubble it up if present 66 | - if (returndata.length > 0) { 67 | - // The easiest way to bubble the revert reason is using memory via assembly 68 | - /// @solidity memory-safe-assembly 69 | - assembly { 70 | - let returndata_size := mload(returndata) 71 | - revert(add(32, returndata), returndata_size) 72 | - } 73 | - } else { 74 | + //if (returndata.length > 0) { 75 | + // // The easiest way to bubble the revert reason is using memory via assembly 76 | + // /// @solidity memory-safe-assembly 77 | + // assembly { 78 | + // let returndata_size := mload(returndata) 79 | + // revert(add(32, returndata), returndata_size) 80 | + // } 81 | + //} else { 82 | revert(errorMessage); 83 | - } 84 | + //} 85 | } 86 | } 87 | diff --git a/contracts/utils/Strings.sol b/contracts/utils/Strings.sol 88 | index 657ebd6..8a9e7e8 100644 89 | --- a/contracts/utils/Strings.sol 90 | +++ b/contracts/utils/Strings.sol 91 | @@ -17,25 +17,7 @@ library Strings { 92 | * @dev Converts a `uint256` to its ASCII `string` decimal representation. 93 | */ 94 | function toString(uint256 value) internal pure returns (string memory) { 95 | - unchecked { 96 | - uint256 length = Math.log10(value) + 1; 97 | - string memory buffer = new string(length); 98 | - uint256 ptr; 99 | - /// @solidity memory-safe-assembly 100 | - assembly { 101 | - ptr := add(buffer, add(32, length)) 102 | - } 103 | - while (true) { 104 | - ptr--; 105 | - /// @solidity memory-safe-assembly 106 | - assembly { 107 | - mstore8(ptr, byte(mod(value, 10), _SYMBOLS)) 108 | - } 109 | - value /= 10; 110 | - if (value == 0) break; 111 | - } 112 | - return buffer; 113 | - } 114 | + return "{}".format(value); 115 | } 116 | 117 | /** 118 | -------------------------------------------------------------------------------- /src/evm/xts.rs: -------------------------------------------------------------------------------- 1 | use super::transaction::Transaction; 2 | use impl_serde::serialize::to_hex; 3 | use subxt::{OnlineClient, PolkadotConfig as DefaultConfig}; 4 | use web3::{ 5 | signing::Key, 6 | transports::ws, 7 | types::{Address, CallRequest, H256, U256}, 8 | Web3, 9 | }; 10 | 11 | #[subxt::subxt( 12 | runtime_metadata_path = "metadata/moonbeam.scale", 13 | substitute_type( 14 | path = "primitive_types::H160", 15 | with = "::subxt::utils::Static<::sp_core::H160>" 16 | ) 17 | )] 18 | pub mod api {} 19 | 20 | pub struct MoonbeamApi { 21 | web3: Web3, 22 | pub client: OnlineClient, 23 | chain_id: U256, 24 | } 25 | 26 | impl MoonbeamApi { 27 | pub async fn new(url: &str) -> color_eyre::Result { 28 | let transport = ws::WebSocket::new(url).await?; 29 | let client = OnlineClient::from_url(url).await?; 30 | let web3 = Web3::new(transport); 31 | let chain_id = web3.eth().chain_id().await?; 32 | Ok(Self { 33 | web3, 34 | client, 35 | chain_id, 36 | }) 37 | } 38 | 39 | pub fn client(&self) -> &OnlineClient { 40 | &self.client 41 | } 42 | 43 | pub async fn fetch_nonce(&self, address: Address) -> color_eyre::Result { 44 | self.web3 45 | .eth() 46 | .transaction_count(address, None) 47 | .await 48 | .map_err(Into::into) 49 | } 50 | 51 | /// Estimate gas price for transaction 52 | /// 53 | /// In Moonbeam, the gas price is subject to change from block to block. 54 | /// In smart-bench, contract deployment transactions are sent at once and spread over many blocks. 55 | /// The same is for contract call extrinsic. 56 | /// The gas price estimated at the beginning may not be valid for transactions processed in subsequent blocks. 57 | /// Considering that the base fee can increase by a maximum of 12.5% per block if the target block size is exceeded, 58 | /// the returned gas price is enlarged by 12.5%. 59 | pub async fn get_gas_price(&self) -> color_eyre::Result { 60 | let gas_price = self.web3.eth().gas_price().await?.to_f64_lossy(); 61 | Ok(U256::from_f64_lossy(gas_price * 1.125)) 62 | } 63 | 64 | pub async fn estimate_gas( 65 | &self, 66 | from: Address, 67 | contract: Option
, 68 | data: &[u8], 69 | ) -> color_eyre::Result { 70 | let call_request = CallRequest { 71 | from: Some(from), 72 | to: contract, 73 | gas: None, 74 | gas_price: None, 75 | value: None, 76 | data: Some(data.into()), 77 | transaction_type: None, 78 | access_list: None, 79 | max_fee_per_gas: None, 80 | max_priority_fee_per_gas: None, 81 | }; 82 | self.web3 83 | .eth() 84 | .estimate_gas(call_request, None) 85 | .await 86 | .map_err(Into::into) 87 | } 88 | 89 | pub async fn deploy( 90 | &self, 91 | data: &[u8], 92 | signer: impl Key, 93 | nonce: U256, 94 | gas: U256, 95 | gas_price: U256, 96 | ) -> color_eyre::Result { 97 | self.sign_and_submit_tx(data, signer, nonce, None, gas, gas_price) 98 | .await 99 | } 100 | 101 | pub async fn call( 102 | &self, 103 | contract: Address, 104 | data: &[u8], 105 | signer: impl Key, 106 | nonce: U256, 107 | gas: U256, 108 | gas_price: U256, 109 | ) -> color_eyre::Result { 110 | self.sign_and_submit_tx(data, signer, nonce, Some(contract), gas, gas_price) 111 | .await 112 | } 113 | 114 | pub async fn sign_and_submit_tx( 115 | &self, 116 | data: &[u8], 117 | signer: impl Key, 118 | nonce: U256, 119 | to: Option
, 120 | gas: U256, 121 | gas_price: U256, 122 | ) -> color_eyre::Result { 123 | let tx = Transaction { 124 | nonce, 125 | to, 126 | gas, 127 | gas_price, 128 | value: 0u32.into(), 129 | data: data.into(), 130 | transaction_type: None, 131 | access_list: Default::default(), 132 | max_priority_fee_per_gas: gas_price, 133 | }; 134 | 135 | let signed_tx = tx.sign(signer, self.chain_id.as_u64()); 136 | 137 | tracing::debug!("data: {}", to_hex(data, false)); 138 | tracing::debug!( 139 | "signed_tx.raw_transaction: {}", 140 | to_hex(&signed_tx.raw_transaction.0, false) 141 | ); 142 | tracing::debug!("signed_tx.message_hash: {:?}", signed_tx.message_hash); 143 | tracing::debug!( 144 | "signed_tx.transaction_hash: {:?}", 145 | signed_tx.transaction_hash 146 | ); 147 | let hash = self 148 | .web3 149 | .eth() 150 | .send_raw_transaction(signed_tx.raw_transaction) 151 | .await?; 152 | 153 | Ok(hash) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /contracts/ink/incrementer.contract: -------------------------------------------------------------------------------- 1 | {"source":{"hash":"0x4f57203a8265872fa5ce1d9383c6b5e139024382c08651b0a1f70f7c9957e669","language":"ink! 4.3.0","compiler":"rustc 1.74.1","wasm":"0x0061736d01000000011f0660027f7f0060000060047f7f7f7f017f60037f7f7f006000017f60017f00027406057365616c310b6765745f73746f726167650002057365616c301176616c75655f7472616e736665727265640000057365616c3005696e7075740000057365616c320b7365745f73746f726167650002057365616c300b7365616c5f72657475726e000303656e76066d656d6f72790201021003090804010005000001010608017f01418080040b0711020463616c6c000b066465706c6f79000c0af707084d02017f027e230041206b2200240020004200370308200042003703002000411036021c20002000411c6a10012000290308210120002903002102200041206a2400410541042001200284501b0b12004180800441003b010041004102100a000b3c01027f027f200145044041808004210141010c010b410121024180800441013a000041818004210141020b2103200120023a000020002003100a000b8d0101047f230041106b22012400200142808001370208200141808004360204200141046a41001009024020012802082204200128020c2202490d00200128020421032001410036020c2001200420026b3602082001200220036a360204200141046a20001009200128020c220020012802084b0d00200320022001280204200010031a200141106a24000f0b000b4a01037f024002402000280208220241046a22032002490d00200320002802044b0d00200320026b4104470d012000280200210420002003360208200220046a20013600000f0b000b000b0d0020004180800420011004000ba00301077f230041106b220024000240100541ff01714105470d0020004180800136020441808004200041046a100220002802042201418180014f0d00024020014104490d00418380042d00002103418280042d00002104418180042d000021020240418080042d00002205412f4704402001417c714104462005411d47722002413247200441ff017141e1004772722003419f0147720d02418480042802002104410121060c010b200241860147200441ff017141db004772200341d90147720d010b200042808001370208200041808004360204200041046a4100100920002802082202200028020c2201490d01200028020421032000200220016b220236020420032001200120036a2201200041046a100020022000280204220049722000410449720d01200128000021012006044020044100482001200120046a22004a470d0220001008410041001007000b230041106b220024002000418080043602044180800441003a00002000428080818010370208200041046a20011009200028020c2200418180014f0440000b41002000100a000b410141011007000b000bcc0101057f230041106b2200240002400240100541ff01714105470d0020004180800136020c418080042000410c6a1002200028020c2200418180014f0d00024020004104490d00418380042d00002101418280042d00002102418180042d00002103418080042d0000220441e1004704402000417c714104462004419b014772200341ae01472002419d01477272200141de0047720d014184800428020010081006000b200341ef0147200241fe0047720d002001413e460d020b410141011007000b000b410010081006000b","build_info":{"build_mode":"Release","cargo_contract_version":"3.2.0","rust_toolchain":"stable-x86_64-unknown-linux-gnu","wasm_opt_settings":{"keep_debug_symbols":false,"optimization_passes":"Z"}}},"contract":{"name":"incrementer","version":"4.3.0","authors":["Parity Technologies "]},"spec":{"constructors":[{"args":[{"label":"init_value","type":{"displayName":["i32"],"type":0}}],"default":false,"docs":[],"label":"new","payable":false,"returnType":{"displayName":["ink_primitives","ConstructorResult"],"type":1},"selector":"0x9bae9d5e"},{"args":[],"default":false,"docs":[],"label":"new_default","payable":false,"returnType":{"displayName":["ink_primitives","ConstructorResult"],"type":1},"selector":"0x61ef7e3e"}],"docs":[],"environment":{"accountId":{"displayName":["AccountId"],"type":5},"balance":{"displayName":["Balance"],"type":8},"blockNumber":{"displayName":["BlockNumber"],"type":11},"chainExtension":{"displayName":["ChainExtension"],"type":12},"hash":{"displayName":["Hash"],"type":9},"maxEventTopics":4,"timestamp":{"displayName":["Timestamp"],"type":10}},"events":[],"lang_error":{"displayName":["ink","LangError"],"type":3},"messages":[{"args":[{"label":"by","type":{"displayName":["i32"],"type":0}}],"default":false,"docs":[],"label":"inc","mutates":true,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":1},"selector":"0x1d32619f"},{"args":[],"default":false,"docs":[],"label":"get","mutates":false,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":4},"selector":"0x2f865bd9"}]},"storage":{"root":{"layout":{"struct":{"fields":[{"layout":{"leaf":{"key":"0x00000000","ty":0}},"name":"value"}],"name":"Incrementer"}},"root_key":"0x00000000"}},"types":[{"id":0,"type":{"def":{"primitive":"i32"}}},{"id":1,"type":{"def":{"variant":{"variants":[{"fields":[{"type":2}],"index":0,"name":"Ok"},{"fields":[{"type":3}],"index":1,"name":"Err"}]}},"params":[{"name":"T","type":2},{"name":"E","type":3}],"path":["Result"]}},{"id":2,"type":{"def":{"tuple":[]}}},{"id":3,"type":{"def":{"variant":{"variants":[{"index":1,"name":"CouldNotReadInput"}]}},"path":["ink_primitives","LangError"]}},{"id":4,"type":{"def":{"variant":{"variants":[{"fields":[{"type":0}],"index":0,"name":"Ok"},{"fields":[{"type":3}],"index":1,"name":"Err"}]}},"params":[{"name":"T","type":0},{"name":"E","type":3}],"path":["Result"]}},{"id":5,"type":{"def":{"composite":{"fields":[{"type":6,"typeName":"[u8; 32]"}]}},"path":["ink_primitives","types","AccountId"]}},{"id":6,"type":{"def":{"array":{"len":32,"type":7}}}},{"id":7,"type":{"def":{"primitive":"u8"}}},{"id":8,"type":{"def":{"primitive":"u128"}}},{"id":9,"type":{"def":{"composite":{"fields":[{"type":6,"typeName":"[u8; 32]"}]}},"path":["ink_primitives","types","Hash"]}},{"id":10,"type":{"def":{"primitive":"u64"}}},{"id":11,"type":{"def":{"primitive":"u32"}}},{"id":12,"type":{"def":{"variant":{}},"path":["ink_env","types","NoChainExtension"]}}],"version":"4"} -------------------------------------------------------------------------------- /contracts/ink/computation.contract: -------------------------------------------------------------------------------- 1 | {"source":{"hash":"0x61b4dfed53e3295630bb03426470f4ef175fa4564b2e292d63a95dc151fa8bf3","language":"ink! 4.3.0","compiler":"rustc 1.74.1","wasm":"0x0061736d01000000011f0660027f7f0060000060047f7f7f7f017f60037f7f7f006000017f60017e00027406057365616c310b6765745f73746f726167650002057365616c301176616c75655f7472616e736665727265640000057365616c3005696e7075740000057365616c320b7365745f73746f726167650002057365616c300b7365616c5f72657475726e000303656e76066d656d6f727902010210030807040105000301010608017f01418080040b0711020463616c6c000a066465706c6f79000b0aeb08074d02017f027e230041206b2200240020004200370308200042003703002000411036021c20002000411c6a10012000290308210120002903002102200041206a2400410541042001200284501b0b1300418080044181023b0100410141021008000b5601017f230041206b2201240020014180800436020c4180800441003a00002001428080818010370210200120003703182001410c6a200141186a4108100920012802142201418180014f0440000b410020011008000b0d0020004180800420011004000b6f01037f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a210541002103037f2002200346047f200505200320056a200120036a2d00003a0000200341016a21030c010b0b1a200020043602080f0b000b000bdd0402077f077e230041106b22002400024002400240100541ff01714105470d00200041808001360200418080042000100220002802002201418180014f0d0020014104490d02200141046b2101418380042d00002102418280042d00002104418180042d000021030240418080042d00002205412a4704402005418e0147200341f0004772200441880147200241d1004772720d0441012106200141044f0d010c040b200341c60147200441d2004772200241850147200141044972720d030b4184800428020021012000428080013702042000418080043602002000410036020c20002000410c6a410410092000280204220320002802082202490d00200028020021042000200320026b220336020020042002200220046a200010002003200028020049720d0020060d01200141004c047e4201052001ac21094201210820014101470440200942017d210a420121070340200720087e2108200742027c2107200a42017d220a4200520d000b0b2008200942018642017d7e0b1007000b000b230041106b22002400200141004c047e4200052001ad220842017d2107230041106b220124002001200842027d220942ffffffff0f83220a200742ffffffff0f83220b7e220c200b200942208822097e220b200a2007422088220d7e7c22074220867c220a3703002001200a200c54ad2009200d7e2007200b54ad4220862007422088847c7c370308200129030021072000200141086a29030037030820002007370300200141106a2400200041086a290300423f8620002903004201888420084201867c42017d0b2108200041106a240020081007000b1006000bd20101027f230041106b220024000240100541ff01714105470d00200041808001360200418080042000100220002802002201418180014f0d00024020014104490d00418080042d0000419b01470d00418180042d000041ae01470d00418280042d0000419d01470d00418380042d000041de00470d002000428080013702042000418080043602002000410036020c20002000410c6a410410092000280208220120002802044b0d01200028020022002001200020016a410010031a4180800441003b0100410041021008000b1006000b000b","build_info":{"build_mode":"Release","cargo_contract_version":"3.2.0","rust_toolchain":"stable-x86_64-unknown-linux-gnu","wasm_opt_settings":{"keep_debug_symbols":false,"optimization_passes":"Z"}}},"contract":{"name":"computation","version":"0.2.0","authors":["[your_name] <[your_email]>"]},"spec":{"constructors":[{"args":[],"default":false,"docs":[],"label":"new","payable":false,"returnType":{"displayName":["ink_primitives","ConstructorResult"],"type":0},"selector":"0x9bae9d5e"}],"docs":[],"environment":{"accountId":{"displayName":["AccountId"],"type":6},"balance":{"displayName":["Balance"],"type":9},"blockNumber":{"displayName":["BlockNumber"],"type":12},"chainExtension":{"displayName":["ChainExtension"],"type":13},"hash":{"displayName":["Hash"],"type":10},"maxEventTopics":4,"timestamp":{"displayName":["Timestamp"],"type":11}},"events":[],"lang_error":{"displayName":["ink","LangError"],"type":2},"messages":[{"args":[{"label":"n","type":{"displayName":["i32"],"type":3}}],"default":false,"docs":[],"label":"triangle_number","mutates":false,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":4},"selector":"0x8e708851"},{"args":[{"label":"n","type":{"displayName":["i32"],"type":3}}],"default":false,"docs":[],"label":"odd_product","mutates":false,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":4},"selector":"0x2ac65285"}]},"storage":{"root":{"layout":{"struct":{"fields":[],"name":"Computation"}},"root_key":"0x00000000"}},"types":[{"id":0,"type":{"def":{"variant":{"variants":[{"fields":[{"type":1}],"index":0,"name":"Ok"},{"fields":[{"type":2}],"index":1,"name":"Err"}]}},"params":[{"name":"T","type":1},{"name":"E","type":2}],"path":["Result"]}},{"id":1,"type":{"def":{"tuple":[]}}},{"id":2,"type":{"def":{"variant":{"variants":[{"index":1,"name":"CouldNotReadInput"}]}},"path":["ink_primitives","LangError"]}},{"id":3,"type":{"def":{"primitive":"i32"}}},{"id":4,"type":{"def":{"variant":{"variants":[{"fields":[{"type":5}],"index":0,"name":"Ok"},{"fields":[{"type":2}],"index":1,"name":"Err"}]}},"params":[{"name":"T","type":5},{"name":"E","type":2}],"path":["Result"]}},{"id":5,"type":{"def":{"primitive":"i64"}}},{"id":6,"type":{"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}},"path":["ink_primitives","types","AccountId"]}},{"id":7,"type":{"def":{"array":{"len":32,"type":8}}}},{"id":8,"type":{"def":{"primitive":"u8"}}},{"id":9,"type":{"def":{"primitive":"u128"}}},{"id":10,"type":{"def":{"composite":{"fields":[{"type":7,"typeName":"[u8; 32]"}]}},"path":["ink_primitives","types","Hash"]}},{"id":11,"type":{"def":{"primitive":"u64"}}},{"id":12,"type":{"def":{"primitive":"u32"}}},{"id":13,"type":{"def":{"variant":{}},"path":["ink_env","types","NoChainExtension"]}}],"version":"4"} -------------------------------------------------------------------------------- /contracts/solidity/evm/contracts/Storage.sol/Storage.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "Storage", 4 | "sourceName": "contracts/Storage.sol", 5 | "abi": [ 6 | { 7 | "inputs": [], 8 | "stateMutability": "nonpayable", 9 | "type": "constructor" 10 | }, 11 | { 12 | "inputs": [ 13 | { 14 | "internalType": "address", 15 | "name": "account", 16 | "type": "address" 17 | }, 18 | { 19 | "internalType": "int32", 20 | "name": "count", 21 | "type": "int32" 22 | } 23 | ], 24 | "name": "read", 25 | "outputs": [], 26 | "stateMutability": "view", 27 | "type": "function" 28 | }, 29 | { 30 | "inputs": [ 31 | { 32 | "internalType": "address", 33 | "name": "account", 34 | "type": "address" 35 | }, 36 | { 37 | "internalType": "int32", 38 | "name": "count", 39 | "type": "int32" 40 | } 41 | ], 42 | "name": "readWrite", 43 | "outputs": [], 44 | "stateMutability": "nonpayable", 45 | "type": "function" 46 | }, 47 | { 48 | "inputs": [ 49 | { 50 | "internalType": "address", 51 | "name": "account", 52 | "type": "address" 53 | }, 54 | { 55 | "internalType": "int32", 56 | "name": "count", 57 | "type": "int32" 58 | } 59 | ], 60 | "name": "write", 61 | "outputs": [], 62 | "stateMutability": "nonpayable", 63 | "type": "function" 64 | } 65 | ], 66 | "bytecode": "0x608060405234801561001057600080fd5b506103be806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c806391f05dd514610046578063a7269efc14610062578063a8a4189d1461007e575b600080fd5b610060600480360381019061005b9190610223565b61009a565b005b61007c60048036038101906100779190610223565b61010c565b005b61009860048036038101906100939190610223565b61018e565b005b6000600190505b8160030b8160030b1361010757620f42406000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555080806100ff906102fe565b9150506100a1565b505050565b6000600190505b8160030b8160030b136101895760016000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461016f919061025f565b925050819055508080610181906102fe565b915050610113565b505050565b6000600190505b8160030b8160030b136101f4576000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205080806101ec906102fe565b915050610195565b505050565b6000813590506102088161035a565b92915050565b60008135905061021d81610371565b92915050565b6000806040838503121561023657600080fd5b6000610244858286016101f9565b92505060206102558582860161020e565b9150509250929050565b600061026a826102f4565b9150610275836102f4565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156102aa576102a961032b565b5b828201905092915050565b60006102c0826102d4565b9050919050565b60008160030b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b6000610309826102c7565b9150637fffffff8214156103205761031f61032b565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b610363816102b5565b811461036e57600080fd5b50565b61037a816102c7565b811461038557600080fd5b5056fea264697066735822122018f41360e7424f8634c357d99252fd69c1de9a98fa64d0a6da240ac231afa25364736f6c63430008010033", 67 | "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100415760003560e01c806391f05dd514610046578063a7269efc14610062578063a8a4189d1461007e575b600080fd5b610060600480360381019061005b9190610223565b61009a565b005b61007c60048036038101906100779190610223565b61010c565b005b61009860048036038101906100939190610223565b61018e565b005b6000600190505b8160030b8160030b1361010757620f42406000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555080806100ff906102fe565b9150506100a1565b505050565b6000600190505b8160030b8160030b136101895760016000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461016f919061025f565b925050819055508080610181906102fe565b915050610113565b505050565b6000600190505b8160030b8160030b136101f4576000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205080806101ec906102fe565b915050610195565b505050565b6000813590506102088161035a565b92915050565b60008135905061021d81610371565b92915050565b6000806040838503121561023657600080fd5b6000610244858286016101f9565b92505060206102558582860161020e565b9150509250929050565b600061026a826102f4565b9150610275836102f4565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156102aa576102a961032b565b5b828201905092915050565b60006102c0826102d4565b9050919050565b60008160030b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b6000610309826102c7565b9150637fffffff8214156103205761031f61032b565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b610363816102b5565b811461036e57600080fd5b50565b61037a816102c7565b811461038557600080fd5b5056fea264697066735822122018f41360e7424f8634c357d99252fd69c1de9a98fa64d0a6da240ac231afa25364736f6c63430008010033", 68 | "linkReferences": {}, 69 | "deployedLinkReferences": {} 70 | } 71 | -------------------------------------------------------------------------------- /contracts/ink/flipper.contract: -------------------------------------------------------------------------------- 1 | {"source":{"hash":"0xf340e04d09bb3da9bd64f3b4c1ecfa1091f352da2bd664d4e7432413f2c11f4e","language":"ink! 4.3.0","compiler":"rustc 1.74.1","wasm":"0x0061736d0100000001240760027f7f0060000060047f7f7f7f017f60037f7f7f006000017f60017f017f60017f00027406057365616c310b6765745f73746f726167650002057365616c301176616c75655f7472616e736665727265640000057365616c3005696e7075740000057365616c320b7365745f73746f726167650002057365616c300b7365616c5f72657475726e000303656e76066d656d6f727902010210030b0a000304050100060001010608017f01418080040b0711020463616c6c000d066465706c6f79000e0a9a090a2601017f230041106b22022400200220003a000f20012002410f6a41011006200241106a24000b6f01037f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a210541002103037f2002200346047f200505200320056a200120036a2d00003a0000200341016a21030c010b0b1a200020043602080f0b000b000b4d02017f027e230041206b2200240020004200370308200042003703002000411036021c20002000411c6a10012000290308210120002903002102200041206a2400410541042001200284501b0b3f01017f2000280204220145044041020f0b2000200141016b36020420002000280200220041016a3602004101410220002d000022004101461b410020001b0b12004180800441003b010041004102100c000b3c01027f027f200145044041808004210141010c010b410121024180800441013a000041818004210141020b2103200120023a000020002003100c000b960101047f230041106b2201240020014280800137020820014180800436020420014100360200200141046a200141041006024020012802082204200128020c2202490d00200128020421032001410036020c2001200420026b3602082001200220036a3602042000200141046a1005200128020c220020012802084b0d00200320022001280204200010031a200141106a24000f0b000b0d0020004180800420011004000b970301067f230041106b22002400024002400240100741ff01714105470d00200041808001360200418080042000100220002802002201418180014f0d0020014104490d02418380042d00002101418280042d00002102418180042d000021030240418080042d00002204412f470440200441e300472003413a4772200241a50147720d0441012105200141d100460d010c040b200341860147200241db004772200141d90147720d030b2000428080013702042000418080043602002000410036020c20002000410c6a410410062000280204220320002802082201490d00200028020021022000200320016b220336020020022001200120026a22012000100020032000280200220249720d00200020023602042000200136020020001008220141ff01714102460d0020050d01230041106b220024002000418080043602044180800441003a00002000428080818010370208200141ff0171410047200041046a1005200028020c2200418180014f0440000b41002000100c000b000b200141ff017145100b41004100100a000b41014101100a000be30101057f230041106b2200240002400240100741ff01714105470d0020004180800136020c418080042000410c6a1002200028020c2201418180014f0d00024020014104490d002000418480043602042000200141046b360208418380042d00002101418280042d00002102418180042d00002103418080042d0000220441e1004704402004419b0147200341ae0147722002419d0147200141de004772720d01200041046a1008220041ff01714102460d012000100b1009000b200341ef0147200241fe0047720d002001413e460d020b41014101100a000b000b4100100b1009000b","build_info":{"build_mode":"Release","cargo_contract_version":"3.2.0","rust_toolchain":"stable-x86_64-unknown-linux-gnu","wasm_opt_settings":{"keep_debug_symbols":false,"optimization_passes":"Z"}}},"contract":{"name":"flipper","version":"4.3.0","authors":["Parity Technologies "]},"spec":{"constructors":[{"args":[{"label":"init_value","type":{"displayName":["bool"],"type":0}}],"default":false,"docs":["Creates a new flipper smart contract initialized with the given value."],"label":"new","payable":false,"returnType":{"displayName":["ink_primitives","ConstructorResult"],"type":1},"selector":"0x9bae9d5e"},{"args":[],"default":false,"docs":["Creates a new flipper smart contract initialized to `false`."],"label":"new_default","payable":false,"returnType":{"displayName":["ink_primitives","ConstructorResult"],"type":1},"selector":"0x61ef7e3e"}],"docs":[],"environment":{"accountId":{"displayName":["AccountId"],"type":5},"balance":{"displayName":["Balance"],"type":8},"blockNumber":{"displayName":["BlockNumber"],"type":11},"chainExtension":{"displayName":["ChainExtension"],"type":12},"hash":{"displayName":["Hash"],"type":9},"maxEventTopics":4,"timestamp":{"displayName":["Timestamp"],"type":10}},"events":[],"lang_error":{"displayName":["ink","LangError"],"type":3},"messages":[{"args":[],"default":false,"docs":[" Flips the current value of the Flipper's boolean."],"label":"flip","mutates":true,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":1},"selector":"0x633aa551"},{"args":[],"default":false,"docs":[" Returns the current value of the Flipper's boolean."],"label":"get","mutates":false,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":4},"selector":"0x2f865bd9"}]},"storage":{"root":{"layout":{"struct":{"fields":[{"layout":{"leaf":{"key":"0x00000000","ty":0}},"name":"value"}],"name":"Flipper"}},"root_key":"0x00000000"}},"types":[{"id":0,"type":{"def":{"primitive":"bool"}}},{"id":1,"type":{"def":{"variant":{"variants":[{"fields":[{"type":2}],"index":0,"name":"Ok"},{"fields":[{"type":3}],"index":1,"name":"Err"}]}},"params":[{"name":"T","type":2},{"name":"E","type":3}],"path":["Result"]}},{"id":2,"type":{"def":{"tuple":[]}}},{"id":3,"type":{"def":{"variant":{"variants":[{"index":1,"name":"CouldNotReadInput"}]}},"path":["ink_primitives","LangError"]}},{"id":4,"type":{"def":{"variant":{"variants":[{"fields":[{"type":0}],"index":0,"name":"Ok"},{"fields":[{"type":3}],"index":1,"name":"Err"}]}},"params":[{"name":"T","type":0},{"name":"E","type":3}],"path":["Result"]}},{"id":5,"type":{"def":{"composite":{"fields":[{"type":6,"typeName":"[u8; 32]"}]}},"path":["ink_primitives","types","AccountId"]}},{"id":6,"type":{"def":{"array":{"len":32,"type":7}}}},{"id":7,"type":{"def":{"primitive":"u8"}}},{"id":8,"type":{"def":{"primitive":"u128"}}},{"id":9,"type":{"def":{"composite":{"fields":[{"type":6,"typeName":"[u8; 32]"}]}},"path":["ink_primitives","types","Hash"]}},{"id":10,"type":{"def":{"primitive":"u64"}}},{"id":11,"type":{"def":{"primitive":"u32"}}},{"id":12,"type":{"def":{"variant":{}},"path":["ink_env","types","NoChainExtension"]}}],"version":"4"} -------------------------------------------------------------------------------- /src/stats.rs: -------------------------------------------------------------------------------- 1 | use futures::{future, stream::poll_fn, Future, TryStream, TryStreamExt}; 2 | use std::task::Poll; 3 | 4 | use std::collections::HashSet; 5 | use std::sync::{Arc, Mutex}; 6 | pub struct BlockInfo { 7 | // block time stamp 8 | pub time_stamp: u64, 9 | pub stats: blockstats::BlockStats, 10 | // list of hashes to look for 11 | pub contract_call_hashes: Vec, 12 | } 13 | 14 | /// Subscribes to block stats. Completes once *all* hashes in `remaining_hashes` have been received. 15 | pub fn collect_block_stats( 16 | block_stats: impl TryStream + Unpin, 17 | remaining_hashes: HashSet, 18 | get_block_details: F, 19 | ) -> impl TryStream 20 | where 21 | Fut: Future)>>, 22 | F: Fn(sp_core::H256) -> Fut + Copy, 23 | { 24 | let block_stats_arc = Arc::new(Mutex::new(block_stats)); 25 | let remaining_hashes_arc = Arc::new(Mutex::new(remaining_hashes)); 26 | 27 | let remaining_hashes = remaining_hashes_arc.clone(); 28 | let stream = poll_fn(move |_| -> Poll>> { 29 | let remaining_hashes = remaining_hashes.lock().unwrap(); 30 | 31 | if !remaining_hashes.is_empty() { 32 | Poll::Ready(Some(Ok(()))) 33 | } else { 34 | Poll::Ready(None) 35 | } 36 | }); 37 | 38 | stream.and_then(move |_| { 39 | let remaining_hashes = remaining_hashes_arc.clone(); 40 | let block_stats = block_stats_arc.clone(); 41 | async move { 42 | let stats = block_stats.lock().unwrap().try_next().await?.unwrap(); 43 | tracing::debug!("{stats:?}"); 44 | let (time_stamp, hashes) = get_block_details(stats.hash).await?; 45 | let mut remaining_hashes = remaining_hashes.lock().unwrap(); 46 | for xt in &hashes { 47 | remaining_hashes.remove(xt); 48 | } 49 | Ok(BlockInfo { 50 | time_stamp, 51 | contract_call_hashes: hashes, 52 | stats, 53 | }) 54 | } 55 | }) 56 | } 57 | 58 | /// This function prints statistics to the standard output. 59 | 60 | /// The TPS calculation is based on the following assumptions about smart-bench: 61 | /// - smart-bench instantiates smart contracts on the chain and waits for the completion of these transactions. 62 | /// - Starting from some future block (after creation), smart-bench uploads transactions related to contract calls to the node. 63 | /// - Sending contract call transactions to the node is continuous once started and is not mixed with any other type of transactions. 64 | /// - Smart-bench finishes benchmarking at the block that contains the last contract call from the set. 65 | 66 | /// TPS calculation is exclusively concerned with contract calls, disregarding any system or contract-creating transactions. 67 | 68 | /// TPS calculation excludes the last block of the benchmark, as its full utilization is not guaranteed. In other words, only blocks in the middle will consist entirely of contract calls. 69 | pub async fn print_block_info( 70 | block_info: impl TryStream, 71 | ) -> color_eyre::Result<()> { 72 | let mut call_extrinsics_per_block: Vec = Vec::new(); 73 | let mut call_block_expected = false; 74 | let mut time_stamp_first = None; 75 | let mut time_stamp_last = None; 76 | println!(); 77 | block_info 78 | .try_for_each(|block| { 79 | println!("{}", block.stats); 80 | let contract_calls_count = block.contract_call_hashes.len() as u64; 81 | // Skip blocks at the beggining until we see first call related transaction 82 | // Once first call is seen, we expect all further blocks to contain calls until all calls are covered 83 | if !call_block_expected && contract_calls_count > 0 { 84 | call_block_expected = true; 85 | } 86 | if call_block_expected { 87 | time_stamp_last = Some(block.time_stamp); 88 | if time_stamp_first.is_none() { 89 | time_stamp_first = time_stamp_last; 90 | } 91 | 92 | call_extrinsics_per_block.push(contract_calls_count); 93 | } 94 | 95 | future::ready(Ok(())) 96 | }) 97 | .await?; 98 | 99 | // Skip the last block as it's not stressed to its full capabilities, 100 | // since there is a very low chance of hitting that exact amount of transactions 101 | // (it will contain as many transactions as there are left to execute). 102 | let call_extrinsics_per_block = 103 | &call_extrinsics_per_block[0..call_extrinsics_per_block.len() - 1]; 104 | 105 | let tps_blocks = call_extrinsics_per_block.len(); 106 | let tps_total_extrinsics = call_extrinsics_per_block.iter().sum::(); 107 | let time_diff = time_stamp_first.and_then(|first| { 108 | if let Some(last) = time_stamp_last { 109 | return Some(last - first) 110 | } 111 | None 112 | }).filter(|&d| d == 0).map(|d| d as f64 / (tps_blocks - 1) as f64 / 1000.0 ).unwrap_or(12.0); 113 | 114 | println!("\nSummary:"); 115 | println!("Total Blocks: {tps_blocks}"); 116 | println!("Total Extrinsics: {tps_total_extrinsics}"); 117 | println!("Block Build Time: {time_diff}"); 118 | if tps_blocks > 0 { 119 | println!("sTPS - Standard Transaction Per Second"); 120 | println!( 121 | "sTPS: {:.2}", 122 | tps_total_extrinsics as f64 / (tps_blocks as f64 * time_diff) 123 | ); 124 | } else { 125 | println!("sTPS - Error - not enough data to calculate sTPS, consider increasing --call-count value") 126 | } 127 | Ok(()) 128 | } 129 | -------------------------------------------------------------------------------- /src/evm/transaction.rs: -------------------------------------------------------------------------------- 1 | use rlp::RlpStream; 2 | use web3::{ 3 | signing::{self, Signature}, 4 | types::{AccessList, Address, SignedTransaction, U256, U64}, 5 | }; 6 | 7 | pub const LEGACY_TX_ID: u64 = 0; 8 | pub const ACCESSLISTS_TX_ID: u64 = 1; 9 | pub const EIP1559_TX_ID: u64 = 2; 10 | 11 | /// A transaction used for RLP encoding, hashing and signing. 12 | #[derive(Debug)] 13 | pub struct Transaction { 14 | pub to: Option
, 15 | pub nonce: U256, 16 | pub gas: U256, 17 | pub gas_price: U256, 18 | pub value: U256, 19 | pub data: Vec, 20 | pub transaction_type: Option, 21 | pub access_list: AccessList, 22 | pub max_priority_fee_per_gas: U256, 23 | } 24 | 25 | impl Transaction { 26 | fn rlp_append_legacy(&self, stream: &mut RlpStream) { 27 | stream.append(&self.nonce); 28 | stream.append(&self.gas_price); 29 | stream.append(&self.gas); 30 | if let Some(to) = self.to { 31 | stream.append(&to); 32 | } else { 33 | stream.append(&""); 34 | } 35 | stream.append(&self.value); 36 | stream.append(&self.data); 37 | } 38 | 39 | fn encode_legacy(&self, chain_id: u64, signature: Option<&Signature>) -> RlpStream { 40 | let mut stream = RlpStream::new(); 41 | stream.begin_list(9); 42 | 43 | self.rlp_append_legacy(&mut stream); 44 | 45 | if let Some(signature) = signature { 46 | self.rlp_append_signature(&mut stream, signature); 47 | } else { 48 | stream.append(&chain_id); 49 | stream.append(&0u8); 50 | stream.append(&0u8); 51 | } 52 | 53 | stream 54 | } 55 | 56 | fn encode_access_list_payload( 57 | &self, 58 | chain_id: u64, 59 | signature: Option<&Signature>, 60 | ) -> RlpStream { 61 | let mut stream = RlpStream::new(); 62 | 63 | let list_size = if signature.is_some() { 11 } else { 8 }; 64 | stream.begin_list(list_size); 65 | 66 | // append chain_id. from EIP-2930: chainId is defined to be an integer of arbitrary size. 67 | stream.append(&chain_id); 68 | 69 | self.rlp_append_legacy(&mut stream); 70 | self.rlp_append_access_list(&mut stream); 71 | 72 | if let Some(signature) = signature { 73 | self.rlp_append_signature(&mut stream, signature); 74 | } 75 | 76 | stream 77 | } 78 | 79 | fn encode_eip1559_payload(&self, chain_id: u64, signature: Option<&Signature>) -> RlpStream { 80 | let mut stream = RlpStream::new(); 81 | 82 | let list_size = if signature.is_some() { 12 } else { 9 }; 83 | stream.begin_list(list_size); 84 | 85 | // append chain_id. from EIP-2930: chainId is defined to be an integer of arbitrary size. 86 | stream.append(&chain_id); 87 | 88 | stream.append(&self.nonce); 89 | stream.append(&self.max_priority_fee_per_gas); 90 | stream.append(&self.gas_price); 91 | stream.append(&self.gas); 92 | if let Some(to) = self.to { 93 | stream.append(&to); 94 | } else { 95 | stream.append(&""); 96 | } 97 | stream.append(&self.value); 98 | stream.append(&self.data); 99 | 100 | self.rlp_append_access_list(&mut stream); 101 | 102 | if let Some(signature) = signature { 103 | self.rlp_append_signature(&mut stream, signature); 104 | } 105 | 106 | stream 107 | } 108 | 109 | fn rlp_append_signature(&self, stream: &mut RlpStream, signature: &Signature) { 110 | stream.append(&signature.v); 111 | stream.append(&U256::from_big_endian(signature.r.as_bytes())); 112 | stream.append(&U256::from_big_endian(signature.s.as_bytes())); 113 | } 114 | 115 | fn rlp_append_access_list(&self, stream: &mut RlpStream) { 116 | stream.begin_list(self.access_list.len()); 117 | for access in self.access_list.iter() { 118 | stream.begin_list(2); 119 | stream.append(&access.address); 120 | stream.begin_list(access.storage_keys.len()); 121 | for storage_key in access.storage_keys.iter() { 122 | stream.append(storage_key); 123 | } 124 | } 125 | } 126 | 127 | fn encode(&self, chain_id: u64, signature: Option<&Signature>) -> Vec { 128 | match self.transaction_type.map(|t| t.as_u64()) { 129 | Some(LEGACY_TX_ID) | None => { 130 | let stream = self.encode_legacy(chain_id, signature); 131 | stream.out().to_vec() 132 | } 133 | 134 | Some(ACCESSLISTS_TX_ID) => { 135 | let tx_id: u8 = ACCESSLISTS_TX_ID as u8; 136 | let stream = self.encode_access_list_payload(chain_id, signature); 137 | [&[tx_id], stream.as_raw()].concat() 138 | } 139 | 140 | Some(EIP1559_TX_ID) => { 141 | let tx_id: u8 = EIP1559_TX_ID as u8; 142 | let stream = self.encode_eip1559_payload(chain_id, signature); 143 | [&[tx_id], stream.as_raw()].concat() 144 | } 145 | 146 | _ => { 147 | panic!("Unsupported transaction type"); 148 | } 149 | } 150 | } 151 | 152 | /// Sign and return a raw signed transaction. 153 | pub fn sign(self, sign: impl signing::Key, chain_id: u64) -> SignedTransaction { 154 | let adjust_v_value = matches!( 155 | self.transaction_type.map(|t| t.as_u64()), 156 | Some(LEGACY_TX_ID) | None 157 | ); 158 | 159 | let encoded = self.encode(chain_id, None); 160 | 161 | let hash = signing::keccak256(encoded.as_ref()); 162 | 163 | let signature = if adjust_v_value { 164 | sign.sign(&hash, Some(chain_id)) 165 | .expect("hash is non-zero 32-bytes; qed") 166 | } else { 167 | sign.sign_message(&hash) 168 | .expect("hash is non-zero 32-bytes; qed") 169 | }; 170 | 171 | let signed = self.encode(chain_id, Some(&signature)); 172 | let transaction_hash = signing::keccak256(signed.as_ref()).into(); 173 | 174 | SignedTransaction { 175 | message_hash: hash.into(), 176 | v: signature.v, 177 | r: signature.r, 178 | s: signature.s, 179 | raw_transaction: signed.into(), 180 | transaction_hash, 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/evm/mod.rs: -------------------------------------------------------------------------------- 1 | mod runner; 2 | mod transaction; 3 | mod xts; 4 | 5 | use crate::{ 6 | evm::{runner::MoonbeamRunner, xts::MoonbeamApi}, 7 | Cli, Contract, 8 | }; 9 | use web3::{contract::tokens::Tokenize, signing::Key, types::U256}; 10 | 11 | pub async fn exec(cli: &Cli) -> color_eyre::Result<()> { 12 | let api = MoonbeamApi::new(&cli.url).await?; 13 | 14 | let mut runner = MoonbeamRunner::new(cli.url.to_string(), keyring::alith(), api); 15 | 16 | for contract in &cli.contracts { 17 | match contract { 18 | Contract::Erc20 => { 19 | let transfer_to = (&keyring::balthazar()).address(); 20 | let ctor_params = (1_000_000u32,).into_tokens(); 21 | let transfer_params = || (transfer_to, 1000u32).into_tokens(); 22 | runner 23 | .prepare_contract( 24 | "BenchERC20", 25 | cli.instance_count, 26 | &ctor_params, 27 | "transfer", 28 | &transfer_params, 29 | ) 30 | .await?; 31 | } 32 | Contract::Flipper => { 33 | let ctor_params = (true,).into_tokens(); 34 | let flip_params = || Vec::new(); 35 | runner 36 | .prepare_contract( 37 | "flipper", 38 | cli.instance_count, 39 | &ctor_params, 40 | "flip", 41 | &flip_params, 42 | ) 43 | .await?; 44 | } 45 | Contract::Incrementer => { 46 | let ctor_params = (1u32,).into_tokens(); 47 | let inc_params = || (1u32,).into_tokens(); 48 | runner 49 | .prepare_contract( 50 | "incrementer", 51 | cli.instance_count, 52 | &ctor_params, 53 | "inc", 54 | inc_params, 55 | ) 56 | .await?; 57 | } 58 | Contract::Erc721 => { 59 | let ctor_params = ().into_tokens(); 60 | let mut token_id = 0; 61 | let mint_params = || { 62 | let mint = (U256::from(token_id),).into_tokens(); 63 | token_id += 1; 64 | mint 65 | }; 66 | runner 67 | .prepare_contract( 68 | "BenchERC721", 69 | cli.instance_count, 70 | &ctor_params, 71 | "mint", 72 | mint_params, 73 | ) 74 | .await?; 75 | } 76 | Contract::Erc1155 => { 77 | let ctor_params = ().into_tokens(); 78 | let create_params = || (U256::from(1_000_000),).into_tokens(); 79 | runner 80 | .prepare_contract( 81 | "BenchERC1155", 82 | cli.instance_count, 83 | &ctor_params, 84 | "create", 85 | create_params, 86 | ) 87 | .await?; 88 | } 89 | Contract::OddProduct => { 90 | let ctor_params = ().into_tokens(); 91 | let call_params = || (1000i32,).into_tokens(); 92 | runner 93 | .prepare_contract( 94 | "Computation", 95 | cli.instance_count, 96 | &ctor_params, 97 | "oddProduct", 98 | call_params, 99 | ) 100 | .await?; 101 | } 102 | Contract::TriangleNumber => { 103 | let ctor_params = ().into_tokens(); 104 | let call_params = || (1000i32,).into_tokens(); 105 | runner 106 | .prepare_contract( 107 | "Computation", 108 | cli.instance_count, 109 | &ctor_params, 110 | "triangleNumber", 111 | call_params, 112 | ) 113 | .await?; 114 | } 115 | Contract::StorageRead => { 116 | let address = (&keyring::balthazar()).address(); 117 | let ctor_params = ().into_tokens(); 118 | let call_params = || (address, 10).into_tokens(); 119 | runner 120 | .prepare_contract( 121 | "Storage", 122 | cli.instance_count, 123 | &ctor_params, 124 | "read", 125 | call_params, 126 | ) 127 | .await?; 128 | } 129 | Contract::StorageWrite => { 130 | let address = (&keyring::balthazar()).address(); 131 | let ctor_params = ().into_tokens(); 132 | let call_params = || (address, 10).into_tokens(); 133 | runner 134 | .prepare_contract( 135 | "Storage", 136 | cli.instance_count, 137 | &ctor_params, 138 | "write", 139 | call_params, 140 | ) 141 | .await?; 142 | } 143 | Contract::StorageReadWrite => { 144 | let address = (&keyring::balthazar()).address(); 145 | let ctor_params = ().into_tokens(); 146 | let call_params = || (address, 10).into_tokens(); 147 | runner 148 | .prepare_contract( 149 | "Storage", 150 | cli.instance_count, 151 | &ctor_params, 152 | "readWrite", 153 | call_params, 154 | ) 155 | .await?; 156 | } 157 | } 158 | } 159 | 160 | let result = runner.run(cli.call_count).await?; 161 | crate::print_block_info(result).await?; 162 | 163 | Ok(()) 164 | } 165 | 166 | mod keyring { 167 | use secp256k1::SecretKey; 168 | use std::str::FromStr as _; 169 | 170 | pub fn alith() -> SecretKey { 171 | SecretKey::from_str("5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133") 172 | .unwrap() 173 | } 174 | 175 | pub fn balthazar() -> SecretKey { 176 | SecretKey::from_str("8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b") 177 | .unwrap() 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /launch/README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This directory contains docker related scripts and configurations to compile and run smart-bench as Docker Image. Resulting image and toolings available here are meant to be used in scenarios where consistent results are required such as CI/CD systems, but also for rapid testing on environments where there is no interest of setting up rust or any other compile prerequisites. Clients of the image will also benefit from not being required to track current recommendations for toolchain configurations or version changes and any compatibility issues that can arise from that, it can be expected that project will compile and run out of the box. 4 | 5 | The environment is created as Docker Image (by default named and tagged as `smart-bench:latest`) which leverages multi-stage Docker builds. Helper scripts available in this directory streamline operations to build ([build.sh](./build.sh)) and run ([run.sh](./run.sh)) of the image. 6 | 7 | At the first stage of docker build, based upon `docker.io/paritytech/ci-unified:bullseye-1.74.0` as rust enabled compilation env, smart-bench software is being compiled from current sources (this git repository). Resulting binary is copied into second stage / final docker image based on `ubuntu:20.04` image where also other dependencies are configured and installed. 8 | 9 | Final image consists of few pieces that are integrated into it: 10 | - `polkadot`, `polkadot-parachain` and `zombienet` pre-compiled binaries taken from offical releases 11 | - `moonbeam` binary provided by custom release created on [this fork](https://github.com/karolk91/moonbeam) (custom fork was required to enable dev rpc module which is not supported by offical releases) 12 | - pre-compiled smart contracts available within this repository at [contracts](../contracts/) dir 13 | - [entrypoint.sh](./entrypoint.sh) script that orchestrates running of the above to eventually receive benchmarking results from `smart-bench` itself. The script accepts set of parameters to be passed as-is to `smart-bench` app. It runs zombienet as a background job and starts smart-bench while providing it with correct parameters to reach to zombienet nodes. 14 | 15 | Please refer to the [download-bin.sh](./download-bin.sh) script for detailed info about source of download for binary components 16 | 17 | It is possible to run `smart-bench:latest` using raw `docker run` command with options (see examples below) but usage of `run.sh` can abstract away some of the internals of the image and also result in better compatibility with future image versions. The `run.sh` currently helps with following usage scenarios: 18 | 1. use `smart-bench:latest` image as is, composed of specific versions of nodes and contracts 19 | 1. use `smart-bench:latest` for what it consists of but provide custom set of pre-compiled contracts 20 | 1. use `smart-bench:latest` but provide custom set of node binaries (`polkadot`, `zombienet` `polkadot-parachain`, `moonbeam`) 21 | 1. use `smart-bench:latest` but provide custom zombienet config 22 | 23 | Usage scenarios above are possible by providing volume mounts arguments to `docker run`, to override specific locations within the resulting container filesystem. And this is exactly what `run.sh` is doing under the hood for you when providing optional parameters (see usage below) to the script. 24 | 25 | 26 | ## Usage 27 | ### Step 1. Build image 28 | 29 | a) downloads dependencies 30 | ``` 31 | ./download-bins.sh 32 | ``` 33 | 34 | b) build smart-bench:latest image 35 | ``` 36 | ./build.sh 37 | ``` 38 | 39 | or build using manual VERSION, eg. smart-bench:1.0 image 40 | ``` 41 | VERSION=1.0 ./build.sh 42 | ``` 43 | 44 | 45 | ### Step 2. Run image 46 | #### `run.sh` help screen: 47 | ``` 48 | Usage: ./run.sh OPTION -- ARGUMENTS_TO_SMART_BENCH 49 | 50 | OPTION 51 | -b, --binaries-dir (Optional) Path to directory that contains binaries to mount into the container (eg. polkadot, zombienet, moonbeam) 52 | List of binaries being used depends on config provided. Default set of binaries is available within the image 53 | -t, --contracts-dir (Optional) Path to directory that contains compiled smart contracts. Default set of compiled smart contracts is available within the image 54 | -u, --configs-dir (Optional) Path to directory that contains zombienet config files. Default set of configs files is available within the image 55 | -h, --help Print this help message 56 | 57 | ARGUMENTS_TO_SMART_BENCH 58 | smart-bench specific parameters (NOTE: do not provide --url param as it is managed by this tool) 59 | 60 | EXAMPLES 61 | ./run.sh -- evm erc20 --instance-count 1 --call-count 10 62 | ./run.sh -- ink-wasm erc20 --instance-count 1 --call-count 10 63 | ./run.sh -- sol-wasm erc20 --instance-count 1 --call-count 10 64 | ./run.sh --binaries-dir=./bin -- sol-wasm erc20 --instance-count 1 --call-count 10 65 | ./run.sh --contracts-dir=../contracts -- sol-wasm erc20 --instance-count 1 --call-count 10 66 | ./run.sh --configs-dir=./configs -- sol-wasm erc20 --instance-count 1 --call-count 10 67 | ``` 68 | 69 | ## run examples 70 | Following are examples of running `smart-bench:latest` image using either `run.sh` wrapper script or raw `docker run` commands equivalents (see expandable sections) 71 | 72 | --- 73 | 74 | ### Scenario 1. Running smart-bench without overriding any binaries or configurations: 75 | 76 | ``` 77 | ./run.sh -- evm erc20 --instance-count 1 --call-count 10 78 | ``` 79 |
80 | expand 81 | 82 | ``` 83 | docker run --rm -it --init smart-bench:latest evm erc20 --instance-count 1 --call-count 10 84 | ``` 85 |
86 | 87 | --- 88 | ### Scenario 2. Override binaries: 89 | ``` 90 | ./run.sh --binaries-dir=./bin -- sol-wasm erc20 --instance-count 1 --call-count 10 91 | ``` 92 |
93 | expand 94 | 95 | ``` 96 | docker run --rm -it --init -v $PWD/bin:/usr/local/smart-bench/bin smart-bench:latest sol-wasm erc20 --instance-count 1 --call-count 10 97 | ``` 98 |
99 | 100 | --- 101 | 102 | ### Scenario 3. Override contracts 103 | 104 | NOTE: please note that smart-bench application expects some particular hierarchy of files in context of contracts directory and it must be preserved when creating other one to override. Please consult hierarchy available at [contracts](../contracts/) which is also part of this git repository. It might be easier to create copy of the one mentioned and modify only the parts of interest / to override. 105 | ``` 106 | ./run.sh --contracts-dir=../contracts -- sol-wasm erc20 --instance-count 1 --call-count 10 107 | ``` 108 |
109 | expand 110 | 111 | ``` 112 | docker run --rm -it --init -v $PWD/../contracts:/usr/local/smart-bench/contracts smart-bench:latest sol-wasm erc20 --instance-count 1 --call-count 10 113 | ``` 114 |
115 | 116 | --- 117 | 118 | ### Scenario 4. Override configs 119 | ``` 120 | ./run.sh --configs-dir=./configs -- sol-wasm erc20 --instance-count 1 --call-count 10 121 | ``` 122 |
123 | expand 124 | 125 | ``` 126 | docker run --rm -it --init -v $PWD/configs:/usr/local/smart-bench/config smart-bench:latest sol-wasm erc20 --instance-count 1 --call-count 10 127 | ``` 128 |
129 | 130 | --- 131 | 132 | ## Miscellaneous 133 | 134 | ### Moonbeam with Dev RPC module enabled build recipe 135 | 136 | Following is an example recipe how to build moonbeam binary with Dev RPC module enabled 137 | ``` 138 | git clone https://github.com/PureStake/moonbeam.git 139 | cd moonbeam && git fetch https://github.com/karolk91/moonbeam master && git cherry-pick decd877 140 | docker run -it --rm -v $PWD:/moonbeam docker.io/paritytech/ci-unified:bullseye-1.74.0 /bin/bash 141 | cd /moonbeam 142 | rustup toolchain install 1.74 143 | rustup default 1.74 144 | rustup override set 1.74 145 | rustup target add wasm32-unknown-unknown --toolchain 1.74 146 | cargo build --release 147 | ``` 148 | -------------------------------------------------------------------------------- /stats/get_graph.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | SCRIPT_NAME="${BASH_SOURCE[0]}" 5 | SCRIPT_PATH=$(dirname "$(realpath -s "${BASH_SOURCE[0]}")") 6 | VERSION=${VERSION:-latest} 7 | DOCKER_COMPOSE_FILE="docker-compose.yml" 8 | HOST="localhost" 9 | GRAFANA_DASHBOARD_JSON="grafana-provisioning/dashboards/tps.json" 10 | 11 | check_command() { 12 | command -v "$1" >/dev/null 2>&1 && { $1 --version >/dev/null 2>&1; } 13 | } 14 | 15 | if check_command "docker compose"; then 16 | DOCKER_COMPOSE="docker compose" 17 | elif check_command "docker-compose"; then 18 | DOCKER_COMPOSE="docker-compose" 19 | else 20 | echo "Neither 'docker compose' nor 'docker-compose' could be found or are functional on your system." 21 | exit 1 22 | fi 23 | 24 | function echoerr() { echo "$@" 1>&2; } 25 | 26 | function usage { 27 | cat << EOF 28 | Script to generate PNG graphs out of CSV formatted data from smart-bench via ephemeral Grafana+InfluxDB environment 29 | 30 | Usage: ${SCRIPT_NAME} ARGS 31 | 32 | ARGS 33 | -p, --panel-id (Required) ID of the panel within Grafana dashboard to render as PNG 34 | -c, --csv-data (Required) CSV formatted output of smart-bench 35 | -o, --output (Required) Path to file where output PNG image will be stored 36 | -h, --help Print this help message 37 | 38 | EXAMPLE 39 | ${SCRIPT_NAME} --panel-id=2 --csv-data=benchmark-result.csv --output=tps.png 40 | EOF 41 | } 42 | 43 | function parse_args { 44 | function needs_arg { 45 | if [ -z "${OPTARG}" ]; then 46 | echoerr "No arg for --${OPT} option" 47 | exit 2 48 | fi 49 | } 50 | 51 | # shellcheck disable=SC2214 52 | while getopts p:c:o:h-: OPT; do 53 | # support long options: https://stackoverflow.com/a/28466267/519360 54 | if [ "$OPT" = "-" ]; then # long option: reformulate OPT and OPTARG 55 | OPT="${OPTARG%%=*}" # extract long option name 56 | OPTARG="${OPTARG#"$OPT"}" # extract long option argument (may be empty) 57 | OPTARG="${OPTARG#=}" # if long option argument, remove assigning `=` 58 | fi 59 | case "$OPT" in 60 | p | panel-id) needs_arg && PANEL_ID="${OPTARG}";; 61 | c | csv-data) needs_arg && CSV_DATA="${OPTARG}";; 62 | o | output) needs_arg && OUTPUT="${OPTARG}";; 63 | h | help ) usage; exit 0;; 64 | ??* ) echoerr "Illegal option --$OPT"; exit 2;; # bad long option 65 | ? ) exit 2 ;; # bad short option (error reported via getopts) 66 | esac 67 | done 68 | shift $((OPTIND-1)) # remove parsed options and args from $@ list 69 | 70 | [ -n "${PANEL_ID-}" ] || { 71 | echoerr "missing -p/--panel-id arg" 72 | usage 73 | exit 2 74 | } 75 | [ -n "${CSV_DATA-}" ] || { 76 | echoerr "missing -c/--csv-data arg" 77 | usage 78 | exit 2 79 | } 80 | [ -n "${OUTPUT-}" ] || { 81 | echoerr "missing -o/--output arg" 82 | usage 83 | exit 2 84 | } 85 | } 86 | 87 | retry_command() { 88 | local max_retries="$1" 89 | local retry_interval="$2" 90 | 91 | # Shift to remove the first two parameters (max_retries and retry_interval) 92 | shift 2 93 | local command=("$@") 94 | 95 | for ((i=1; i<=max_retries; i++)); do 96 | if "${command[@]}"; then 97 | return 0 98 | else 99 | echo "Retrying in $retry_interval seconds (Attempt $i/$max_retries)..." 100 | sleep "$retry_interval" 101 | fi 102 | done 103 | 104 | return 1 105 | } 106 | 107 | wait_for_service() { 108 | local host="$1" 109 | local port="$2" 110 | local max_retries=5 111 | local retry_interval=1 112 | 113 | echo "Waiting for TCP service at $host:$port..." 114 | retry_command "$max_retries" "$retry_interval" nc -z "$host" "$port" 115 | } 116 | 117 | check_influxdb() { 118 | local max_retries=5 119 | local retry_interval=1 120 | 121 | wait_for_service "${HOST}" "${INFLUXDB_PORT}" 122 | echo "Checking InfluxDB is responsive" 123 | retry_command "$max_retries" "$retry_interval" curl -s -o /dev/null -w "%{http_code}" "${HOST}:${INFLUXDB_PORT}/ping" 124 | } 125 | 126 | check_grafana() { 127 | local max_retries=5 128 | local retry_interval=5 129 | 130 | wait_for_service "${HOST}" "${RENDERER_PORT}" 131 | wait_for_service "${HOST}" "${GRAFANA_PORT}" 132 | echo "Checking Grafana is responsive" 133 | retry_command "$max_retries" "$retry_interval" curl -s -o /dev/null -w "%{http_code}" "${HOST}:${GRAFANA_PORT}/api/health" 134 | } 135 | 136 | wait_for_containers() { 137 | local max_retries=5 138 | local retry_interval=1 139 | 140 | echo "Waiting for all Docker containers to be running..." 141 | retry_command "$max_retries" "$retry_interval" ${DOCKER_COMPOSE} -f "$DOCKER_COMPOSE_FILE" \ 142 | ps --services --filter "status=running" | grep -qvx " " 143 | } 144 | 145 | start_containers() { 146 | echo "Starting docker containers" 147 | ${DOCKER_COMPOSE} -f "$DOCKER_COMPOSE_FILE" up -d 148 | wait_for_containers 149 | } 150 | 151 | stop_containers() { 152 | echo "Stopping docker containers" 153 | ${DOCKER_COMPOSE} -f "$DOCKER_COMPOSE_FILE" down 154 | } 155 | 156 | print_containers_logs() { 157 | echo "Container logs -- START" 158 | ${DOCKER_COMPOSE} -f "$DOCKER_COMPOSE_FILE" logs 159 | echo "Container logs -- END" 160 | } 161 | 162 | convert_csv_to_line_protocol() { 163 | local csv_file=$1 164 | local lp_file=$2 165 | 166 | awk -F, 'function trim(field){ 167 | gsub(/^ +| +$/,"", field); 168 | return field 169 | }; 170 | function escape(field){ 171 | gsub(/ /,"\\ ", field); 172 | return field 173 | }{ 174 | printf "%s,platform=\"%s\",parachain_ver=\"%s\",contract_type=\"%s\",contract_compiler_ver=\"%s\" tx_per_sec=%s %s\n", 175 | "tps", 176 | trim($2), 177 | trim($3), 178 | trim($4), 179 | escape(trim($6)), 180 | trim($5), 181 | trim($1) 182 | }' "${csv_file}" > "${lp_file}" 183 | } 184 | 185 | populate_influxdb() { 186 | local csv_file=$1 187 | local tmpdir=$2 188 | echo "Populating InfluxDB with data" 189 | 190 | temp_file="${tmpdir}/line_proto.txt" 191 | convert_csv_to_line_protocol "${csv_file}" "${temp_file}" 192 | 193 | echo "Excerpt from data to be uploaded" 194 | tail -10 "${temp_file}" 195 | 196 | check_influxdb 197 | curl -i -XPOST "http://${HOST}:${INFLUXDB_PORT}/api/v2/write?org=${INFLUXDB_ORG}&bucket=${INFLUXDB_BUCKET}&precision=s" \ 198 | -H "Authorization: Token ${INFLUXDB_TOKEN}" \ 199 | --data-binary "@${temp_file}" 200 | 201 | echo "Data population complete." 202 | } 203 | 204 | create_grafana_snapshot() { 205 | echo "Creating Grafana snapshot..." 206 | local dashboard_id=$1 207 | local panel_id=$2 208 | local csv_file=$3 209 | local output_png=$4 210 | local tmpdir=$5 211 | 212 | sort -k1,1 "${csv_file}" > "${tmpdir}/sorted_csv" 213 | # extend with 000 as grafana requires nanosecond precision 214 | timestamp_start="$(head -1 "${tmpdir}/sorted_csv" | cut -f1 -d"," )000" 215 | timestamp_end="$(tail -1 "${tmpdir}/sorted_csv" | cut -f1 -d"," )000" 216 | 217 | check_grafana 218 | curl -u"${GRAFANA_USERNAME}:${GRAFANA_PASSWORD}" \ 219 | "${HOST}:${GRAFANA_PORT}/render/d-solo/${dashboard_id}?orgId=1&from=${timestamp_start}&to=${timestamp_end}&panelId=${panel_id}&width=1000&height=500" \ 220 | -o "${output_png}" 221 | echo "Grafana snapshot created." 222 | } 223 | 224 | cleanup() { 225 | [ -z "${tmpdir-}" ] || rm -rf -- "${tmpdir}" 226 | stop_containers 227 | echo "Workspace cleanup completed successfully." 228 | } 229 | 230 | parse_args "$@" 231 | source .env 232 | 233 | trap 'cleanup' EXIT 234 | tmpdir="$(mktemp -d)" 235 | 236 | start_containers 237 | populate_influxdb "${CSV_DATA}" "${tmpdir}" 238 | 239 | dasboard_id=$(jq -r '.uid' "${GRAFANA_DASHBOARD_JSON}") 240 | dashboard_title=$(jq -r '.title' "${GRAFANA_DASHBOARD_JSON}") 241 | create_grafana_snapshot "${dasboard_id}/${dashboard_title}" "${PANEL_ID}" "${CSV_DATA}" "${OUTPUT}" "${tmpdir}" 242 | 243 | # check if file seem to contain png data 244 | if file "${OUTPUT}" | grep -q PNG; then 245 | exit 0 246 | else 247 | print_containers_logs 248 | echoerr "Output file doesn't seem to be a PNG image" 249 | echo "File dump -- START" 250 | hexdump -C "${OUTPUT}" 251 | echo "File dump -- END" 252 | exit 1 253 | fi 254 | -------------------------------------------------------------------------------- /contracts/ink/storage.contract: -------------------------------------------------------------------------------- 1 | {"source":{"hash":"0x2f1a78565b8233b7bbc1f6ae200d8dbf671bb524c50eb7c4efcc165c0edc7cfe","language":"ink! 4.3.0","compiler":"rustc 1.74.1","wasm":"0x0061736d0100000001280760027f7f0060000060047f7f7f7f017f60037f7f7f0060037f7f7f017f60037f7e7e006000017f027406057365616c310b6765745f73746f726167650002057365616c301176616c75655f7472616e736665727265640000057365616c3005696e7075740000057365616c320b7365745f73746f726167650002057365616c300b7365616c5f72657475726e000303656e76066d656d6f727902010210030c0b04000003050600010001010608017f01418080040b0711020463616c6c000e066465706c6f79000f0ae0130b2b01017f037f2002200346047f200005200020036a200120036a2d00003a0000200341016a21030c010b0b0b980202047f037e230041306b220224002002410c6a200141086a290000370200200241146a200141106a2900003702002002411c6a200141186a2900003702002002418080043602002002200129000037020420024280800137022820024184800436022441c29ef6d77b200241246a220110072001200241046a41201008024020022802282204200228022c2201490d00200228022421032002200420016b220436022420032001200120036a2201200241246a10002103200420022802242205490d00027e0240024020030e0400030301030b20054110490d022001290000210642012107200141086a2900000c010b42000b21082000200637030820002007370300200041106a2008370300200241306a24000f0b000b2601017f230041106b220224002002200036020c20012002410c6a41041008200241106a24000b4801027f024002402000280208220320026a22042003490d00200420002802044b0d00200420036b2002470d01200028020020036a2001200210051a200020043602080f0b000b000bf40101037f230041406a220324002003410c6a200041086a290000370200200341146a200041106a2900003702002003411c6a200041186a2900003702002003418080043602002003200029000037020420034280800137023420034184800436023041c29ef6d77b200341306a220010072000200341046a4120100802402003280234220420032802382200490d00200328023021052003410036022c2003200420006b3602282003200020056a3602242003200237033820032001370330200341246a200341306a41101008200328022c220420032802284b0d00200520002003280224200410031a200341406b24000f0b000b4d02017f027e230041206b2200240020004200370308200042003703002000411036021c20002000411c6a10012000290308210120002903002102200041206a2400410541042001200284501b0b3c01027f027f200145044041848004210141010c010b410121024184800441013a000041858004210141020b2103200120023a000020002003100d000b5301037f230041106b220024002000428080013702082000418480043602044100200041046a1007200028020c220120002802084b0440000b200028020422022001200120026a410010031a200041106a24000b0d0020004184800420011004000baa0b02087f027e230041d0006b22002400024002400240100a41ff01714105470d0020004180800136020841848004200041086a100220002802082201418180014f0d0020014104490d01200141046b2102418780042d00002104418680042d00002103418580042d00002105024002400240418480042d0000220641194704402006419a01460d02200641c100460d012006412147200541f4004772200341e60047200441df014772722002412049720d05410021022001417c714124460d050c030b2005418e0147200341344772200441a30147200241204972720d04410121022001417c714124460d040c020b200541c70147200341e1014772200441810147200241204972720d03410221022001417c714124470d010c030b2005410247200341c00147722004412247200241204972720d02410321022001417c714124460d020b200041406b220141998004290000370300200041c7006a220441a08004290000370000200041156a419180042900003700002000411d6a2001290300370000200041246a200429000037000020004189800429000037000d200041a8800428020036022c200020023602082000418880042d00003a000c2000428080013702342000418480043602304100200041306a10072000280234220320002802382201490d00200028023021042000200320016b220336023020042001200120046a200041306a10002003200028023049720d002000410c6a21040240024002400240200241016b0e03010203000b230041406a220124002001200041306a3602182001411c6a20044124100521022001413c6a280200220004400340200120021006200041016b22000d000b0b200141406b240041004100100b000b230041306b220124002001200041306a3602082001410c6a20044124100521022001412c6a280200220004400340200242c0843d42001009200041016b22000d000b0b200141306a24000c040b230041406a220124002001200041306a3602182001411c6a2004412410052100024002402001413c6a28020022020440200141106a2104034020012000100620012903084200200128020022031b42017c2208200850ad2004290300420020031b7c220984500d022000200820091009200241016b22020d000b0b200141406b24000c010b000b0c030b4100210141002102230041406a220024002000410c6a2004412410051a2000412c6a280200210441a8888da102210341cf8ca28e06210541f794afaf7821060340200041086a20016a220741046a28020041f794afaf786c20036a410d7741b1f3ddf1796c2103200741086a28020041f794afaf786c20066a410d7741b1f3ddf1796c21062007410c6a28020041f794afaf786c20026a410d7741b1f3ddf1796c2102200741106a28020041f794afaf786c20056a410d7741b1f3ddf1796c2105200141106a22014120470d000b024002402004044020054112772002410c776a20064107776a20034101776a41206a2201410f7620017341f794afaf786c2201410d7620017341bddcca957c6c2201411076200173210203402000428080013702342000418480043602302002200041306a10072000280234220520002802382201490d02200028023021032000200520016b220536023020032001200120036a2201200041306a10002103200520002802302206490d02027f0240024020030e0400050501050b20064104490d0420012800000c010b41000b2201200141016a22054a0d022000428080013702342000418480043602302002200041306a10072000280234220620002802382201490d0220002802302103200041003602382000200620016b3602342000200120036a3602302000200536023c200041306a2000413c6a410410082000280238220520002802344b0d02200320012000280230200510031a200441016b22040d000b0b200041406b24000c010b000b0c020b000b41014101100b000b100c41004100100b000b980101017f230041106b2200240002400240100a41ff01714105470d0020004180800136020c418480042000410c6a1002200028020c2200418180014f0d00024020004104490d00418480042d0000419b01470d00418580042d000041ae01470d00418680042d0000419d01470d00418780042d000041de00460d020b41014101100b000b000b100c4184800441003b010041004102100d000b0b0c0100418080040b04428ffdba","build_info":{"build_mode":"Release","cargo_contract_version":"3.2.0","rust_toolchain":"stable-x86_64-unknown-linux-gnu","wasm_opt_settings":{"keep_debug_symbols":false,"optimization_passes":"Z"}}},"contract":{"name":"storage","version":"0.2.0","authors":["[your_name] <[your_email]>"]},"spec":{"constructors":[{"args":[],"default":false,"docs":[],"label":"new","payable":false,"returnType":{"displayName":["ink_primitives","ConstructorResult"],"type":1},"selector":"0x9bae9d5e"}],"docs":[],"environment":{"accountId":{"displayName":["AccountId"],"type":4},"balance":{"displayName":["Balance"],"type":0},"blockNumber":{"displayName":["BlockNumber"],"type":7},"chainExtension":{"displayName":["ChainExtension"],"type":10},"hash":{"displayName":["Hash"],"type":8},"maxEventTopics":4,"timestamp":{"displayName":["Timestamp"],"type":9}},"events":[],"lang_error":{"displayName":["ink","LangError"],"type":3},"messages":[{"args":[{"label":"account","type":{"displayName":["AccountId"],"type":4}},{"label":"count","type":{"displayName":["u32"],"type":7}}],"default":false,"docs":[],"label":"read","mutates":false,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":1},"selector":"0x217466df"},{"args":[{"label":"account","type":{"displayName":["AccountId"],"type":4}},{"label":"count","type":{"displayName":["u32"],"type":7}}],"default":false,"docs":[],"label":"write","mutates":true,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":1},"selector":"0x198e34a3"},{"args":[{"label":"account","type":{"displayName":["AccountId"],"type":4}},{"label":"count","type":{"displayName":["u32"],"type":7}}],"default":false,"docs":[],"label":"read_write","mutates":true,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":1},"selector":"0x41c7e181"},{"args":[{"label":"account","type":{"displayName":["AccountId"],"type":4}},{"label":"count","type":{"displayName":["u32"],"type":7}}],"default":false,"docs":[],"label":"read_write_raw","mutates":true,"payable":false,"returnType":{"displayName":["ink","MessageResult"],"type":1},"selector":"0x9a02c022"}]},"storage":{"root":{"layout":{"struct":{"fields":[{"layout":{"root":{"layout":{"leaf":{"key":"0xbafd8f42","ty":0}},"root_key":"0xbafd8f42"}},"name":"balances"}],"name":"Storage"}},"root_key":"0x00000000"}},"types":[{"id":0,"type":{"def":{"primitive":"u128"}}},{"id":1,"type":{"def":{"variant":{"variants":[{"fields":[{"type":2}],"index":0,"name":"Ok"},{"fields":[{"type":3}],"index":1,"name":"Err"}]}},"params":[{"name":"T","type":2},{"name":"E","type":3}],"path":["Result"]}},{"id":2,"type":{"def":{"tuple":[]}}},{"id":3,"type":{"def":{"variant":{"variants":[{"index":1,"name":"CouldNotReadInput"}]}},"path":["ink_primitives","LangError"]}},{"id":4,"type":{"def":{"composite":{"fields":[{"type":5,"typeName":"[u8; 32]"}]}},"path":["ink_primitives","types","AccountId"]}},{"id":5,"type":{"def":{"array":{"len":32,"type":6}}}},{"id":6,"type":{"def":{"primitive":"u8"}}},{"id":7,"type":{"def":{"primitive":"u32"}}},{"id":8,"type":{"def":{"composite":{"fields":[{"type":5,"typeName":"[u8; 32]"}]}},"path":["ink_primitives","types","Hash"]}},{"id":9,"type":{"def":{"primitive":"u64"}}},{"id":10,"type":{"def":{"variant":{}},"path":["ink_env","types","NoChainExtension"]}}],"version":"4"} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # smart-bench :brain: 2 | 3 | > Measure the end-to-end throughput of smart contracts. 4 | 5 | ## Usage 6 | 7 | If you're not interested into compilation of this software natively on your PC but would like to leverage dockerized solution please find more information within [launch](./launch/README.md) subdirectory of this project. 8 | 9 | ### Installation 10 | 11 | Currently, this tool must be run directly via cargo, because it requires access to the predefined wasm contract binaries in the `contracts` directory. 12 | 13 | So first clone this repo and run: `cargo run --release -- --help` which should give info for the args: 14 | 15 | ``` 16 | USAGE: 17 | smart-bench [OPTIONS] --instance-count --call-count [CONTRACTS]... 18 | 19 | ARGS: 20 | the smart contract platform to benchmark [possible values: ink-wasm, sol-wasm, evm] 21 | ... the list of contracts to benchmark with [possible values: erc20, flipper, 22 | incrementer, erc721, erc1155, odd-product, triangle-number, storage-read, 23 | storage-write, storage-read-write] 24 | 25 | OPTIONS: 26 | -c, --call-count 27 | the number of calls to make to each contract 28 | 29 | -h, --help 30 | Print help information 31 | 32 | -i, --instance-count 33 | the number of each contract to instantiate 34 | 35 | --url 36 | the url of the substrate node for submitting the extrinsics [default: 37 | ws://localhost:9944] 38 | 39 | -V, --version 40 | Print version information 41 | 42 | ``` 43 | 44 | ### Node binaries 45 | 46 | - To quickly download node binaries, consider using helper [download](./launch/download-bins.sh) script. It will download all required files with predefined versions in the `launch/bin` directory. 47 | ``` 48 | (cd launch && ./download-bins.sh) 49 | ``` 50 | - You can also use binaries provided on your own as long as these are to be found using standard PATH variable based mechanism. In such case you must also consider possible compatibility issues between various versions of binaries used. 51 | - Please NOTE: For a local `moonbeam` parachain setup for EVM contracts, `moonbeam` node is required to have dev RPC endpoint enabled. This feature is not part of an official release and requires source code change and compilation (find example changes avaiable at [commit](https://github.com/PureStake/moonbeam/commit/decd8774bdc100670f86f293d8f145720290faef)). This will also require building the `polkadot` relay-chain node from source at the same commit as the polkadot dependencies for the `moonbeam` binary. 52 | 53 | ### Launching the local test network 54 | 55 | Eventually, `smart-bench` requires to provide it with URL address of web socket port for running pallet-contracts compatible node. It should work fine as long as you have such node started by any means. 56 | 57 | Following are example steps to start the network from scratch using `zombienet` project: 58 | 1. Make sure you have `zombienet` binary available on your local machine. It will be already available at `launch/bin` directory if you decided to use [download](./launch/download-bins.sh) script mentioned above. You could also download existing release or compile from sources by following offical [zombienet](https://github.com/paritytech/zombienet) github page documentation. 59 | 2. Launch the local network ***(consider changing `PATH` accordingly for any custom usage scenarios)*** 60 | - Wasm contracts with pallet-contracts: 61 | ``` 62 | PATH="launch/bin:$PATH" zombienet -p native spawn launch/configs/network_native_wasm.toml 63 | ``` 64 | - EVM contracts on a moonbeam node: 65 | ``` 66 | PATH="launch/bin:$PATH" zombienet -p native spawn launch/configs/network_native_moonbeam.toml 67 | ``` 68 | 3. Wait for `Network launched 🚀🚀` message 69 | 4. Node is now available at `ws://localhost:9988` (TCP port numer is defined as part of config file) 70 | 71 | ### Running benchmarks 72 | 73 | `smart-bench` works on a pre-defined set of contracts, and the user can specify which contract(s) should be tested, and how many instances and number of calls should be executed. e.g. 74 | 75 | `cargo run --release -- ink-wasm erc20 erc1155 --instance-count 10 --call-count 20 --url ws://localhost:9988` 76 | 77 | The above will create 10 instances of each of the `erc20` and `erc1155` contracts, and call each of those instances 20 times (400 total calls). Once all the calls have been submitted, the block stats should appear on the console e.g. 78 | 79 | ``` 80 | 0005: PoV Size=0130KiB(005%) Weight RefTime=0000088ms(017%) Weight ProofSize=3277KiB(064%) Witness=0119KiB Block=0011KiB NumExtrinsics=0048 81 | 0006: PoV Size=0130KiB(005%) Weight RefTime=0000088ms(017%) Weight ProofSize=3277KiB(064%) Witness=0118KiB Block=0011KiB NumExtrinsics=0048 82 | 0007: PoV Size=0130KiB(005%) Weight RefTime=0000088ms(017%) Weight ProofSize=3277KiB(064%) Witness=0119KiB Block=0011KiB NumExtrinsics=0048 83 | 0008: PoV Size=0130KiB(005%) Weight RefTime=0000088ms(017%) Weight ProofSize=3277KiB(064%) Witness=0118KiB Block=0011KiB NumExtrinsics=0048 84 | ``` 85 | One row per block, showing the % usage of the PoV size and the block weight, as well as the number of extrinsics executed per block. Note the Weight % is expected to max out at 75%, since that is the ratio of the total block weight assigned to "normal" i.e. the user submitted/non-operational class of extrinsics. 86 | 87 | #### Ink!/Wasm contracts 88 | 89 | Currently the Wasm contracts are the `contracts/ink/*.contract` files, some of which have been compiled from https://github.com/paritytech/ink/tree/master/examples and committed to this repository. So in order to modify these they can compiled locally and copied over to the `contracts/ink` dir. There are also two locally defined custom contracts in the `contracts/ink` folder: `computation` and `storage` for testing pure computation and storage operations. 90 | 91 | #### Solidity/EVM contracts 92 | 93 | Before running the benchmarks against a `pallet-evm` enabled network, the solidity contracts must first be compiled: 94 | 95 | - Install hardhat https://hardhat.org/getting-started 96 | - `cd contracts/solidity && npx hardhat compile` 97 | 98 | Now make sure the target EVM enabled network is up and running as specified above, and this time change the value of the first argument to `evm`: 99 | 100 | `cargo run --release -- evm erc20 erc1155 --instance-count 10 --call-count 20 --url ws://localhost:9988` 101 | 102 | #### Solang - Solidity/Wasm contracts 103 | 104 | Before running benchmark against a `pallet-contract` enabled network, Solang contract needs to be compiled. 105 | The easiest way to compile the contracts is to do this having Solidity/EVM compiled first. 106 | After this the `openzeppelin_solang.patch` needs to be applied: 107 | `cd contracts/solidity/node_modules/@openzeppelin && patch -p1 < ../../openzeppelin_solang.patch` 108 | Finally a Solang contract can be compiled using command: 109 | `cd contracts/solidity/wasm/ && solang compile --release --wasm-opt z --target polkadot --importmap @openzeppelin=../node_modules/@openzeppelin/ ./../contracts/BenchERC1155.sol` 110 | Currently [`solang`](https://github.com/hyperledger/solang) compiler needs to be built from sources including [`U256 type fix commit`](https://github.com/smiasojed/solang/commit/467b25ab3d44884e643e3217ac16c56c5788dccc) 111 | 112 | 113 | ### Integration tests 114 | 115 | Smart-bench contains integrations tests, which can be run using command `cargo test`. 116 | Before running tests, smart-bench needs to be build using `cargo build` command. 117 | Integration tests requires two types of nodes to be installed and available on `PATH`. 118 | - [`moonbeam`](https://github.com/PureStake/moonbeam/) with enabled [`dev RPC`](https://github.com/paritytech/substrate-contracts-node/blob/539cf0271090f406cb3337e4d97680a6a63bcd2f/node/src/rpc.rs#L60) for Solidity/EVM contracts 119 | - [`substrate-contracts-node`](https://github.com/paritytech/substrate-contracts-node/) for Ink! and Solang (Solidity/Wasm) contracts 120 | 121 | ### Benchmarks 122 | 123 | ## Erc20 124 | ![Erc20](https://github.com/paritytech/smart-bench/blob/gh-pages/stps_erc20.png?raw=true) 125 | 126 | ## Flipper 127 | ![Flipper](https://github.com/paritytech/smart-bench/blob/gh-pages/stps_flipper.png?raw=true) 128 | 129 | ## Storage Read 130 | ![Storage Read](https://github.com/paritytech/smart-bench/blob/gh-pages/stps_storage-read.png?raw=true) 131 | 132 | ## Storage Write 133 | ![Storage Write](https://github.com/paritytech/smart-bench/blob/gh-pages/stps_storage-write.png?raw=true) 134 | 135 | ## Triangle Number 136 | ![Triangle Number](https://github.com/paritytech/smart-bench/blob/gh-pages/stps_triangle-number.png?raw=true) 137 | -------------------------------------------------------------------------------- /src/wasm/xts.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::RefCell, 3 | collections::{hash_map::Entry, HashMap}, 4 | }; 5 | 6 | use super::*; 7 | use codec::{Decode, Encode, MaxEncodedLen}; 8 | use pallet_contracts_primitives::{ContractExecResult, ContractInstantiateResult}; 9 | use serde::{Deserialize, Serialize}; 10 | use sp_core::{Pair, H256}; 11 | use subxt::{ 12 | backend::{legacy::LegacyRpcMethods, rpc::RpcClient}, 13 | ext::scale_encode::EncodeAsType, 14 | utils::MultiAddress, 15 | OnlineClient, PolkadotConfig as DefaultConfig, 16 | }; 17 | 18 | const DRY_RUN_GAS_LIMIT: Option = None; 19 | 20 | #[subxt::subxt(runtime_metadata_path = "metadata/contracts-node.scale")] 21 | pub mod api {} 22 | 23 | pub struct ContractsApi { 24 | pub client: OnlineClient, 25 | pub rpc: LegacyRpcMethods, 26 | nonces_cache: RefCell::Public, u64>>, 27 | } 28 | 29 | impl ContractsApi { 30 | pub async fn new(rpc_client: RpcClient) -> color_eyre::Result { 31 | let client = OnlineClient::::from_rpc_client(rpc_client.clone()).await?; 32 | let rpc = LegacyRpcMethods::::new(rpc_client.clone()); 33 | 34 | Ok(Self { 35 | client, 36 | rpc, 37 | nonces_cache: Default::default(), 38 | }) 39 | } 40 | 41 | /// Submit extrinsic to instantiate a contract with the given code. 42 | pub async fn instantiate_with_code_dry_run( 43 | &self, 44 | value: Balance, 45 | storage_deposit_limit: Option, 46 | code: Vec, 47 | data: Vec, 48 | salt: Vec, 49 | signer: &Signer, 50 | ) -> ContractInstantiateResult { 51 | let code = Code::Upload(code); 52 | let call_request = InstantiateRequest { 53 | origin: subxt::tx::Signer::account_id(signer).clone(), 54 | value, 55 | gas_limit: DRY_RUN_GAS_LIMIT, 56 | storage_deposit_limit, 57 | code, 58 | data, 59 | salt, 60 | }; 61 | let bytes = self 62 | .state_call("ContractsApi_instantiate", Encode::encode(&call_request)) 63 | .await 64 | .unwrap_or_else(|err| panic!("error on ws request `contracts_instantiate`: {err:?}")); 65 | 66 | Decode::decode(&mut bytes.as_ref()) 67 | .unwrap_or_else(|err| panic!("decoding ContractInstantiateResult failed: {err}")) 68 | } 69 | 70 | /// Submit extrinsic to instantiate a contract with the given code. 71 | pub async fn instantiate_with_code( 72 | &self, 73 | value: Balance, 74 | gas_limit: Weight, 75 | storage_deposit_limit: Option, 76 | code: Vec, 77 | data: Vec, 78 | salt: Vec, 79 | signer: &Signer, 80 | ) -> color_eyre::Result { 81 | let call = subxt::tx::Payload::new( 82 | "Contracts", 83 | "instantiate_with_code", 84 | InstantiateWithCode { 85 | value, 86 | gas_limit, 87 | storage_deposit_limit, 88 | code, 89 | data, 90 | salt, 91 | }, 92 | ) 93 | .unvalidated(); 94 | let account_nonce = self.get_account_nonce(signer).await?; 95 | 96 | let tx_hash = self 97 | .client 98 | .tx() 99 | .create_signed_with_nonce(&call, signer, account_nonce, Default::default())? 100 | .submit() 101 | .await?; 102 | 103 | Ok(tx_hash) 104 | } 105 | 106 | /// Submit extrinsic to call a contract. 107 | pub async fn call_dry_run( 108 | &self, 109 | contract: AccountId, 110 | value: Balance, 111 | storage_deposit_limit: Option, 112 | input_data: Vec, 113 | signer: &Signer, 114 | ) -> color_eyre::Result> { 115 | let call_request = RpcCallRequest { 116 | origin: signer.account_id().clone(), 117 | dest: contract, 118 | value, 119 | gas_limit: DRY_RUN_GAS_LIMIT, 120 | storage_deposit_limit, 121 | input_data, 122 | }; 123 | let bytes = self 124 | .state_call("ContractsApi_call", Encode::encode(&call_request)) 125 | .await 126 | .unwrap_or_else(|err| panic!("error on ws request `contract_call`: {err:?}")); 127 | let result: ContractExecResult = Decode::decode(&mut bytes.as_ref()) 128 | .unwrap_or_else(|err| panic!("decoding ContractExecResult failed: {err}")); 129 | 130 | Ok(result) 131 | } 132 | 133 | async fn get_account_nonce(&self, signer: &Signer) -> core::result::Result { 134 | let mut map = self.nonces_cache.borrow_mut(); 135 | 136 | match (*map).entry(signer.signer().public()) { 137 | Entry::Occupied(mut o) => { 138 | *o.get_mut() += 1; 139 | Ok(*o.get()) 140 | } 141 | Entry::Vacant(v) => { 142 | let best_block = self 143 | .rpc 144 | .chain_get_block_hash(None) 145 | .await? 146 | .ok_or(subxt::Error::Other("Best block not found".into()))?; 147 | 148 | let account_nonce = self 149 | .client 150 | .blocks() 151 | .at(best_block) 152 | .await? 153 | .account_nonce(signer.account_id()) 154 | .await?; 155 | v.insert(account_nonce); 156 | Ok(account_nonce) 157 | } 158 | } 159 | } 160 | 161 | /// Submit extrinsic to call a contract. 162 | pub async fn call( 163 | &self, 164 | contract: AccountId, 165 | value: Balance, 166 | gas_limit: Weight, 167 | storage_deposit_limit: Option, 168 | data: Vec, 169 | signer: &Signer, 170 | ) -> color_eyre::Result { 171 | let call = subxt::tx::Payload::new( 172 | "Contracts", 173 | "call", 174 | Call { 175 | dest: contract.into(), 176 | value, 177 | gas_limit, 178 | storage_deposit_limit, 179 | data, 180 | }, 181 | ) 182 | .unvalidated(); 183 | 184 | let account_nonce = self.get_account_nonce(signer).await?; 185 | 186 | let tx_hash = self 187 | .client 188 | .tx() 189 | .create_signed_with_nonce(&call, signer, account_nonce, Default::default())? 190 | .submit() 191 | .await?; 192 | 193 | Ok(tx_hash) 194 | } 195 | 196 | async fn state_call(&self, function: &str, payload: Vec) -> color_eyre::Result> { 197 | let val = self.rpc.state_call(function, Some(&payload), None).await?; 198 | Ok(val) 199 | } 200 | } 201 | 202 | /// A raw call to `pallet-contracts`'s `call`. 203 | #[derive(Debug, Decode, Encode, EncodeAsType)] 204 | #[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")] 205 | pub struct Call { 206 | dest: MultiAddress, 207 | #[codec(compact)] 208 | value: Balance, 209 | gas_limit: Weight, 210 | storage_deposit_limit: Option, 211 | data: Vec, 212 | } 213 | 214 | /// A raw call to `pallet-contracts`'s `instantiate_with_code`. 215 | #[derive(Debug, Encode, Decode, EncodeAsType)] 216 | #[encode_as_type(trait_bounds = "", crate_path = "subxt::ext::scale_encode")] 217 | pub struct InstantiateWithCode { 218 | #[codec(compact)] 219 | value: Balance, 220 | gas_limit: Weight, 221 | storage_deposit_limit: Option, 222 | code: Vec, 223 | data: Vec, 224 | salt: Vec, 225 | } 226 | 227 | /// Copied from `sp_weight` to additionally implement `scale_encode::EncodeAsType`. 228 | #[derive( 229 | Copy, 230 | Clone, 231 | Eq, 232 | PartialEq, 233 | Debug, 234 | Default, 235 | Encode, 236 | Decode, 237 | MaxEncodedLen, 238 | EncodeAsType, 239 | Serialize, 240 | Deserialize, 241 | )] 242 | #[encode_as_type(crate_path = "subxt::ext::scale_encode")] 243 | pub struct Weight { 244 | #[codec(compact)] 245 | /// The weight of computational time used based on some reference hardware. 246 | ref_time: u64, 247 | #[codec(compact)] 248 | /// The weight of storage space used by proof of validity. 249 | proof_size: u64, 250 | } 251 | 252 | impl From for Weight { 253 | fn from(weight: sp_weights::Weight) -> Self { 254 | Self { 255 | ref_time: weight.ref_time(), 256 | proof_size: weight.proof_size(), 257 | } 258 | } 259 | } 260 | 261 | impl From for sp_weights::Weight { 262 | fn from(weight: Weight) -> Self { 263 | sp_weights::Weight::from_parts(weight.ref_time, weight.proof_size) 264 | } 265 | } 266 | 267 | /// A struct that encodes RPC parameters required to instantiate a new smart contract. 268 | #[derive(Serialize, Encode)] 269 | #[serde(rename_all = "camelCase")] 270 | struct InstantiateRequest { 271 | origin: AccountId, 272 | value: Balance, 273 | gas_limit: Option, 274 | storage_deposit_limit: Option, 275 | code: Code, 276 | data: Vec, 277 | salt: Vec, 278 | } 279 | 280 | /// Reference to an existing code hash or a new Wasm module. 281 | #[derive(Serialize, Encode)] 282 | #[serde(rename_all = "camelCase")] 283 | enum Code { 284 | /// A Wasm module as raw bytes. 285 | Upload(Vec), 286 | #[allow(unused)] 287 | /// The code hash of an on-chain Wasm blob. 288 | Existing(H256), 289 | } 290 | 291 | /// A struct that encodes RPC parameters required for a call to a smart contract. 292 | /// 293 | /// Copied from [`pallet-contracts-rpc`]. 294 | #[derive(Serialize, Encode)] 295 | #[serde(rename_all = "camelCase")] 296 | struct RpcCallRequest { 297 | origin: AccountId, 298 | dest: AccountId, 299 | value: Balance, 300 | gas_limit: Option, 301 | storage_deposit_limit: Option, 302 | input_data: Vec, 303 | } 304 | -------------------------------------------------------------------------------- /contracts/solidity/wasm/flipper.contract: -------------------------------------------------------------------------------- 1 | { 2 | "contract": { 3 | "authors": [ 4 | "unknown" 5 | ], 6 | "name": "flipper", 7 | "version": "0.0.1" 8 | }, 9 | "source": { 10 | "compiler": "solang 0.3.2", 11 | "hash": "0x4b895936249f3f18fe6adc395955313cd790e7852f7bc4135bdade5eeafd483f", 12 | "language": "Solidity 0.3.2", 13 | "wasm": "0x0061736d01000000011c0560000060047f7f7f7f017f60027f7f0060037f7f7f0060017f017f027406057365616c310b6765745f73746f726167650001057365616c320b7365745f73746f726167650001057365616c300b7365616c5f72657475726e0003057365616c3005696e7075740002057365616c301176616c75655f7472616e73666572726564000203656e76066d656d6f727902011010030504040000000608017f01418080040b071102066465706c6f7900070463616c6c00080ac40804900201057f027f2000220241086a210141808004210003400240200028020c0d00200028020822032001490d002003200141076a41787122036b220541184f0440200020036a41106a22012000280200220436020020040440200420013602040b2001200541106b3602082001410036020c2001200036020420002001360200200020033602080b2000410136020c200041106a0c020b200028020021000c000b000b2201200236020420012002360200200141086a210002402002450d00200241016b2002410771220304400340200041003a0000200041016a2100200241016b2102200341016b22030d000b0b4107490d00034020004200370000200041086a2100200241086b22020d000b0b20010b2e00418080044100360200418480044100360200418c80044100360200418880043f00411074419080046b3602000bc10202047f027e230041206b22002400100641e8004180800236020041f00041e800100341e40041e80028020022013602002000411036020c200041106a2000410c6a100420002903102104200041186a2903002105230041306b22002400024002400240024002400240200141034d0d0041e00041f0002802002202360200200241d8e6dee679470d00200420058450450d01200141046b220220014b22032003720d052002450d022001200141046b2202490d05200241014b0d0341f4002d00002101200041206a4200370300200042003703182000420037031020004200370308200020014101713a002f200041086a41202000412f6a410110011a0c040b4101410041001002000b4101410041001002000b4101413041241002000b4101413041241002000b41004100100541086a41001002000b4101410041241002000bbd0302037f027e230041206b22002400100641e8004180800236020041f00041e800100341e40041e80028020022013602002000411036020c200041106a2000410c6a100420002903102103200041186a2903002104230041206b220024000240200141034d0d0041e00041f0002802002201360200200141ed9899e703470440200141cdc9bfcf7a472003200484420052720d01230041306b2200240041e8004101360200200041206a4200370300200042003703182000420037031020004200370308200041086a412041f00041e800100045044041f0002d000021020b200041206b22012400200141186a420037030020014200370310200142003703082001420037030020002002417f734101713a002f200141202000412f6a410110011a200041306a240041004100100541086a41001002000b20032004844200520d00200041106b2201240041e8004101360200200041186a420037030020004200370310200042003703082000420037030020012000412041f00041e8001000047f41000541f0002d00000b4101713a000020012d000021004101100541086a220120004101713a00004100200141011002000b4101410041001002000b0b14020041000b054e487b71110041300b044e487b71008e010970726f64756365727302086c616e6775616765010143000c70726f6365737365642d62790105636c616e676131352e302e34202868747470733a2f2f6769746875622e636f6d2f736f6c616e612d6c6162732f6c6c766d2d70726f6a6563742e676974203333633336323963616135396235396438663538353733366134613531393461613965393337376429" 14 | }, 15 | "spec": { 16 | "constructors": [ 17 | { 18 | "args": [ 19 | { 20 | "label": "initvalue", 21 | "type": { 22 | "displayName": [ 23 | "bool" 24 | ], 25 | "type": 0 26 | } 27 | } 28 | ], 29 | "default": false, 30 | "docs": [ 31 | "Constructor that initializes the `bool` value to the given `init_value`." 32 | ], 33 | "label": "new", 34 | "payable": false, 35 | "returnType": null, 36 | "selector": "0x58b3d79c" 37 | } 38 | ], 39 | "docs": [ 40 | "" 41 | ], 42 | "environment": { 43 | "accountId": { 44 | "displayName": [ 45 | "AccountId" 46 | ], 47 | "type": 3 48 | }, 49 | "balance": { 50 | "displayName": [ 51 | "Balance" 52 | ], 53 | "type": 4 54 | }, 55 | "blockNumber": { 56 | "displayName": [ 57 | "BlockNumber" 58 | ], 59 | "type": 5 60 | }, 61 | "chainExtension": { 62 | "displayName": [], 63 | "type": 0 64 | }, 65 | "hash": { 66 | "displayName": [ 67 | "Hash" 68 | ], 69 | "type": 6 70 | }, 71 | "maxEventTopics": 4, 72 | "timestamp": { 73 | "displayName": [ 74 | "Timestamp" 75 | ], 76 | "type": 5 77 | } 78 | }, 79 | "events": [], 80 | "lang_error": { 81 | "displayName": [ 82 | "SolidityError" 83 | ], 84 | "type": 12 85 | }, 86 | "messages": [ 87 | { 88 | "args": [], 89 | "default": false, 90 | "docs": [ 91 | "A message that can be called on instantiated contracts.\nThis one flips the value of the stored `bool` from `true`\nto `false` and vice versa." 92 | ], 93 | "label": "flip", 94 | "mutates": true, 95 | "payable": false, 96 | "returnType": null, 97 | "selector": "0xcde4efa9" 98 | }, 99 | { 100 | "args": [], 101 | "default": false, 102 | "docs": [ 103 | "Simply returns the current value of our `bool`." 104 | ], 105 | "label": "get", 106 | "mutates": false, 107 | "payable": false, 108 | "returnType": { 109 | "displayName": [ 110 | "bool" 111 | ], 112 | "type": 0 113 | }, 114 | "selector": "0x6d4ce63c" 115 | } 116 | ] 117 | }, 118 | "storage": { 119 | "struct": { 120 | "fields": [ 121 | { 122 | "layout": { 123 | "root": { 124 | "layout": { 125 | "leaf": { 126 | "key": "0x00000000", 127 | "ty": 0 128 | } 129 | }, 130 | "root_key": "0x00000000" 131 | } 132 | }, 133 | "name": "value" 134 | } 135 | ], 136 | "name": "flipper" 137 | } 138 | }, 139 | "types": [ 140 | { 141 | "id": 0, 142 | "type": { 143 | "def": { 144 | "primitive": "bool" 145 | }, 146 | "path": [ 147 | "bool" 148 | ] 149 | } 150 | }, 151 | { 152 | "id": 1, 153 | "type": { 154 | "def": { 155 | "primitive": "u8" 156 | }, 157 | "path": [ 158 | "uint8" 159 | ] 160 | } 161 | }, 162 | { 163 | "id": 2, 164 | "type": { 165 | "def": { 166 | "array": { 167 | "len": 32, 168 | "type": 1 169 | } 170 | } 171 | } 172 | }, 173 | { 174 | "id": 3, 175 | "type": { 176 | "def": { 177 | "composite": { 178 | "fields": [ 179 | { 180 | "type": 2 181 | } 182 | ] 183 | } 184 | }, 185 | "path": [ 186 | "ink_primitives", 187 | "types", 188 | "AccountId" 189 | ] 190 | } 191 | }, 192 | { 193 | "id": 4, 194 | "type": { 195 | "def": { 196 | "primitive": "u128" 197 | }, 198 | "path": [ 199 | "uint128" 200 | ] 201 | } 202 | }, 203 | { 204 | "id": 5, 205 | "type": { 206 | "def": { 207 | "primitive": "u64" 208 | }, 209 | "path": [ 210 | "uint64" 211 | ] 212 | } 213 | }, 214 | { 215 | "id": 6, 216 | "type": { 217 | "def": { 218 | "composite": { 219 | "fields": [ 220 | { 221 | "type": 2 222 | } 223 | ] 224 | } 225 | }, 226 | "path": [ 227 | "ink_primitives", 228 | "types", 229 | "Hash" 230 | ] 231 | } 232 | }, 233 | { 234 | "id": 7, 235 | "type": { 236 | "def": { 237 | "primitive": "str" 238 | }, 239 | "path": [ 240 | "string" 241 | ] 242 | } 243 | }, 244 | { 245 | "id": 8, 246 | "type": { 247 | "def": { 248 | "composite": { 249 | "fields": [ 250 | { 251 | "type": 7 252 | } 253 | ] 254 | } 255 | }, 256 | "path": [ 257 | "0x08c379a0" 258 | ] 259 | } 260 | }, 261 | { 262 | "id": 9, 263 | "type": { 264 | "def": { 265 | "array": { 266 | "len": 4, 267 | "type": 5 268 | } 269 | } 270 | } 271 | }, 272 | { 273 | "id": 10, 274 | "type": { 275 | "def": { 276 | "composite": { 277 | "fields": [ 278 | { 279 | "type": 9 280 | } 281 | ] 282 | } 283 | }, 284 | "path": [ 285 | "ink_env", 286 | "types", 287 | "U256" 288 | ] 289 | } 290 | }, 291 | { 292 | "id": 11, 293 | "type": { 294 | "def": { 295 | "composite": { 296 | "fields": [ 297 | { 298 | "type": 10 299 | } 300 | ] 301 | } 302 | }, 303 | "path": [ 304 | "0x4e487b71" 305 | ] 306 | } 307 | }, 308 | { 309 | "id": 12, 310 | "type": { 311 | "def": { 312 | "variant": { 313 | "variants": [ 314 | { 315 | "fields": [ 316 | { 317 | "type": 8 318 | } 319 | ], 320 | "index": 0, 321 | "name": "Error" 322 | }, 323 | { 324 | "fields": [ 325 | { 326 | "type": 11 327 | } 328 | ], 329 | "index": 1, 330 | "name": "Panic" 331 | } 332 | ] 333 | } 334 | }, 335 | "path": [ 336 | "SolidityError" 337 | ] 338 | } 339 | } 340 | ], 341 | "version": "4" 342 | } -------------------------------------------------------------------------------- /.github/workflows/benchmark.yml: -------------------------------------------------------------------------------- 1 | name: Benchmark of pallet-contracts and pallet-evm 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | workflow_dispatch: 7 | 8 | env: 9 | MOONBEAM_ARTIFACT: moonbeam 10 | MOONBEAM_DIR: moonbeam_release 11 | MOONBEAM_BIN: moonbeam_release/*/target/release/moonbeam 12 | MOONBEAM_VERSION: version 13 | BENCHMARK_DIR: stats 14 | TEST_PARAMS: --instance-count 1 --call-count 2000 15 | BENCHMARK_URI: https://raw.githubusercontent.com/paritytech/smart-bench/gh-pages 16 | 17 | jobs: 18 | build_dev_moonbeam: 19 | runs-on: ubuntu-20.04 20 | defaults: 21 | run: 22 | shell: bash 23 | steps: 24 | - name: Clean worker 25 | run: | 26 | sudo rm -rf /usr/share/dotnet 27 | sudo rm -rf /opt/ghc 28 | sudo rm -rf "/usr/local/share/boost" 29 | sudo rm -rf "$AGENT_TOOLSDIRECTORY" 30 | 31 | - uses: actions/checkout@v4 32 | with: 33 | sparse-checkout: | 34 | launch/moonbeam.patch 35 | sparse-checkout-cone-mode: false 36 | 37 | - name: Download Moonbeam Release 38 | run: | 39 | API_URL="https://api.github.com/repos/moonbeam-foundation/moonbeam/releases/131808906" 40 | RESPONSE=$(curl -s "${API_URL}") 41 | # Remove control characters causing error while parsing 42 | RESPONSE=$(echo $RESPONSE | tr -cd '[:print:]') 43 | DOWNLOAD_URL=$(echo $RESPONSE | jq -r '.tarball_url') 44 | RELEASE_TAG=$(echo $RESPONSE | jq -r '.tag_name') 45 | mkdir ${{ env.MOONBEAM_DIR }} 46 | echo "Download moonbeam release: ${DOWNLOAD_URL}" 47 | curl -L ${DOWNLOAD_URL} | tar -xzv -C ${{ env.MOONBEAM_DIR }} 48 | echo $RELEASE_TAG > ${{ env.MOONBEAM_DIR }}/${{ env.MOONBEAM_VERSION }} 49 | 50 | - name: Patch 51 | run: | 52 | # Add Dev RPC support 53 | cd moonbeam_release/*/ 54 | patch -p1 < ../../launch/moonbeam.patch 55 | 56 | - name: Build 57 | uses: docker://paritytech/ci-unified:bullseye-1.73.0 58 | with: 59 | args: /bin/bash -c "cd moonbeam_release/*/ && cargo build --release" 60 | 61 | - uses: actions/upload-artifact@v4 62 | with: 63 | name: ${{ env.MOONBEAM_ARTIFACT }} 64 | path: | 65 | ./${{ env.MOONBEAM_BIN }} 66 | ./${{ env.MOONBEAM_DIR }}/${{ env.MOONBEAM_VERSION }} 67 | retention-days: 1 68 | 69 | smart_contract_benchmark: 70 | strategy: 71 | matrix: 72 | type: [ink-wasm, sol-wasm, evm] 73 | contract: [erc20, flipper, triangle-number, storage-read, storage-write] 74 | env: 75 | BENCHMARK_FILE: benchmark_${{ matrix.type }}_${{ matrix.contract }}.csv 76 | needs: build_dev_moonbeam 77 | runs-on: ubuntu-latest 78 | steps: 79 | - name: Checkout 80 | uses: actions/checkout@v4 81 | 82 | - name: Cache 83 | uses: Swatinem/rust-cache@v2 84 | 85 | - uses: actions/download-artifact@v4 86 | with: 87 | name: ${{ env.MOONBEAM_ARTIFACT }} 88 | path: ./${{ env.MOONBEAM_DIR }} 89 | 90 | - name: Set Moonbeam Release 91 | id: moonbeam_release 92 | run: | 93 | mkdir bin 94 | cp ${{ env.MOONBEAM_BIN }} bin/moonbeam 95 | chmod +x bin/moonbeam 96 | RELEASE_TAG=$(cat ${{ env.MOONBEAM_DIR }}/${{ env.MOONBEAM_VERSION }}) 97 | echo "tag=$(echo ${RELEASE_TAG})" >> $GITHUB_OUTPUT 98 | 99 | - name: Download Polkadot-Parachain Release 100 | id: polkadot_parachain_release 101 | run: | 102 | API_URL="https://api.github.com/repos/paritytech/cumulus/releases/latest" 103 | RESPONSE=$(curl -s "${API_URL}") 104 | 105 | # Get the download URL of the release binary from Repo 106 | DOWNLOAD_URL=$(echo $RESPONSE | jq -r '.assets | map(select(.name == "polkadot-parachain")) | .[0].browser_download_url') 107 | RELEASE_TAG=$(echo $RESPONSE | jq -r '.tag_name') 108 | 109 | curl -L -o bin/polkadot-parachain ${DOWNLOAD_URL} && chmod +x bin/polkadot-parachain 110 | echo "tag=$(echo ${RELEASE_TAG})" >> $GITHUB_OUTPUT 111 | 112 | - name: Get Ink contract language verision 113 | id: ink_version 114 | uses: ./.github/actions/get-contract-language 115 | with: 116 | contracts-directory: "./contracts/ink" 117 | 118 | - name: Get Solang contract language verision 119 | id: solang_version 120 | uses: ./.github/actions/get-contract-language 121 | with: 122 | contracts-directory: "./contracts/solidity/wasm" 123 | 124 | - name: Get Solc contract language verision 125 | id: solc_version 126 | run: | 127 | language="" 128 | # Iterate over each contract in the directory 129 | for contract in ./contracts/solidity/evm/contracts/*/*.dbg.json; do 130 | build_info=$(jq -r '.buildInfo' "$contract") 131 | build_info_path="./contracts/solidity/evm/contracts/${build_info#../}" 132 | solc_version=$(jq -r '.solcVersion' "$build_info_path") 133 | current_language="solc $solc_version" 134 | 135 | # Check if the current language is different from the previous one 136 | if [ -n "$language" ] && [ "$current_language" != "$language" ]; then 137 | echo "Error: Different language detected in contract $contract: $language != $current_language" 138 | exit 1 139 | fi 140 | 141 | language="$current_language" 142 | 143 | echo "Contract: $contract, Language: $current_language" 144 | done 145 | echo "language=$language" >> $GITHUB_OUTPUT 146 | 147 | - name: Display variables 148 | run: | 149 | echo moonbeam_tag: ${{ steps.moonbeam_release.outputs.tag }} 150 | echo polkadot_parachain_tag: ${{ steps.polkadot_parachain_release.outputs.tag }} 151 | 152 | - name: Execute tests 153 | id: run_smart_bench 154 | run: | 155 | cd launch 156 | ./download-bins.sh 157 | # overwrite parachains 158 | cp -f ../bin/* ./bin 159 | ./build.sh 160 | 161 | STATS=$(./run.sh -- ${{ matrix.type }} ${{ matrix.contract }} ${TEST_PARAMS}) 162 | BLOCKS=$(echo ${STATS} | grep -o 'Total Blocks: [0-9]*' | awk '{print $3}') 163 | EXTRINSICS=$(echo ${STATS} | grep -o 'Total Extrinsics: [0-9]*' | awk '{print $3}') 164 | TPS=$(echo ${STATS} | grep -o 'sTPS: [0-9]\+\.[0-9]\{2\}' | awk '{print $2}') 165 | echo "Blocks: ${BLOCKS}" 166 | echo "Extrinsics: ${EXTRINSICS}" 167 | echo "TPS: ${TPS}" 168 | 169 | echo "tps=$(echo ${TPS})" >> $GITHUB_OUTPUT 170 | 171 | - name: Extract Ink benchmark stats 172 | if: matrix.type == 'ink-wasm' 173 | run: | 174 | CURRENT_DATE=$(date +"%s") 175 | # date, parachain tag, contract, contract language, TPS 176 | echo "${CURRENT_DATE}, ${{ matrix.type }}, ${{ steps.polkadot_parachain_release.outputs.tag }}, \ 177 | ${{ matrix.contract }}, ${{ steps.run_smart_bench.outputs.tps }}, ${{ steps.ink_version.outputs.language }}, \ 178 | ${{ env.TEST_PARAMS }}" > ${BENCHMARK_DIR}/${BENCHMARK_FILE} 179 | 180 | - name: Extract Solang benchmark stats 181 | if: matrix.type == 'sol-wasm' 182 | run: | 183 | CURRENT_DATE=$(date +"%s") 184 | # date, parachain tag, contract, contract language, TPS 185 | echo "${CURRENT_DATE}, ${{ matrix.type }}, ${{ steps.polkadot_parachain_release.outputs.tag }}, \ 186 | ${{ matrix.contract }}, ${{ steps.run_smart_bench.outputs.tps }}, ${{ steps.solang_version.outputs.language }}, \ 187 | ${{ env.TEST_PARAMS }}" > ${BENCHMARK_DIR}/${BENCHMARK_FILE} 188 | 189 | - name: Extract Solidity benchmark stats 190 | if: matrix.type == 'evm' 191 | run: | 192 | CURRENT_DATE=$(date +"%s") 193 | # date, parachain tag, contract, contract language, TPS 194 | echo "${CURRENT_DATE}, ${{ matrix.type }}, ${{ steps.moonbeam_release.outputs.tag }}, \ 195 | ${{ matrix.contract }}, ${{ steps.run_smart_bench.outputs.tps }}, ${{ steps.solc_version.outputs.language }}, \ 196 | ${{ env.TEST_PARAMS }}" > ${BENCHMARK_DIR}/${BENCHMARK_FILE} 197 | 198 | - uses: actions/upload-artifact@v4 199 | with: 200 | name: ${{ env.BENCHMARK_FILE }} 201 | path: ${{ env.BENCHMARK_DIR }}/${{ env.BENCHMARK_FILE }} 202 | retention-days: 1 203 | 204 | collect: 205 | runs-on: ubuntu-latest 206 | needs: [smart_contract_benchmark] 207 | steps: 208 | - name: Checkout 209 | uses: actions/checkout@v4 210 | 211 | - name: Download artifact 212 | uses: actions/download-artifact@v4 213 | with: 214 | path: ${{ env.BENCHMARK_DIR }} 215 | 216 | - name: Merge CSV 217 | run: | 218 | for file in ${{ env.BENCHMARK_DIR }}/*/*.csv; do 219 | # Extract contract name 220 | contract_name=$(basename "$file" | sed 's/^.*_\(.*\)\.csv$/\1/') 221 | benchmark_file=bench_${contract_name}.csv 222 | if [ ! -f ${{ env.BENCHMARK_DIR }}/${benchmark_file} ]; then 223 | curl -L -o ${{ env.BENCHMARK_DIR }}/${benchmark_file} ${{ env.BENCHMARK_URI }}/${benchmark_file} || exit 1 224 | fi 225 | cat $file >> ${{ env.BENCHMARK_DIR }}/${benchmark_file} 226 | done 227 | 228 | - name: Generate graph 229 | run: | 230 | for file in ${{ env.BENCHMARK_DIR }}/bench_*.csv; do 231 | contract_name=$(basename "$file" | sed 's/^.*_\(.*\)\.csv$/\1/') 232 | pushd stats 233 | ./get_graph.sh --panel-id=2 --csv-data=../${file} --output=../${{ env.BENCHMARK_DIR }}/stps_${contract_name}.png 234 | popd 235 | done 236 | 237 | - name: Commit benchmark stats 238 | run: | 239 | CURRENT_DATE=$(date +"%Y%m%d") 240 | # Set git config 241 | git config user.email "paritytech-ci@parity.io" 242 | git config user.name "paritytech-ci" 243 | git fetch origin gh-pages 244 | # saving stats 245 | mkdir /tmp/stats 246 | mv ${{ env.BENCHMARK_DIR }}/bench_*.csv /tmp/stats 247 | mv ${{ env.BENCHMARK_DIR }}/stps_*.png /tmp/stats 248 | git checkout gh-pages 249 | mv /tmp/stats/* . 250 | # Upload files 251 | git add *.csv *.png --force 252 | git status 253 | git commit -m "Updated stats in ${CURRENT_DATE} and pushed to gh-pages" 254 | git push origin gh-pages --force 255 | rm -rf .git/ ./* 256 | -------------------------------------------------------------------------------- /contracts/solidity/wasm/Computation.contract: -------------------------------------------------------------------------------- 1 | { 2 | "contract": { 3 | "authors": [ 4 | "unknown" 5 | ], 6 | "name": "Computation", 7 | "version": "0.0.1" 8 | }, 9 | "source": { 10 | "compiler": "solang 0.3.2", 11 | "hash": "0xd4e4e72470cc4d61a7ce137a674a4fdc68e7d26931c268536974c6325a52a37b", 12 | "language": "Solidity 0.3.2", 13 | "wasm": "0x0061736d0100000001140460000060037f7f7f0060027f7f0060017f017f024c04057365616c300b7365616c5f72657475726e0001057365616c3005696e7075740002057365616c301176616c75655f7472616e73666572726564000203656e76066d656d6f72790201101003060503000100000608017f01418080040b071102066465706c6f7900060463616c6c00070abc0a05900201057f027f2000220241086a210141808004210003400240200028020c0d00200028020822032001490d002003200141076a41787122036b220541184f0440200020036a41106a22012000280200220436020020040440200420013602040b2001200541106b3602082001410036020c2001200036020420002001360200200020033602080b2000410136020c200041106a0c020b200028020021000c000b000b2201200236020420012002360200200141086a210002402002450d00200241016b2002410771220304400340200041003a0000200041016a2100200241016b2102200341016b22030d000b0b4107490d00034020004200370000200041086a2100200241086b22020d000b0b20010b2e00418080044100360200418480044100360200418c80044100360200418880043f00411074419080046b3602000bad0202097f027e200041046a210441022103027f03404100200341004c0d011a200341016b21032004280200200441046b2104450d000b200341016a0b210a200141046a21044102210302400340200341004c0d01200341016b21032004280200200441046b2104450d000b200341016a21060b200141046b210b410021014100210503402007200120064e6a21070240200520052006486a220520082001200a4e6a22084d04404200210c0c010b200520086b2109200020074102746a2103200b20054102746a21044200210c0340200c4280808080107c200c200d200d200435020020033502007e7c220d561b210c200341046a2103200441046b2104200941016b22090d000b0b200220014102746a200d3e0200200d422088200c84210d200141016a22014102470d000b0ba90102027f027e230041206b22002400100441e8004180800236020041f00041e800100141e40041e80028020022013602002000411036020c200041106a2000410c6a100220002903102102200041186a2903002103024002400240200141034d0d0041e00041f0002802002200360200200041cdff82eb78470d00200220038450450d010c020b4101410041001000000b4101410041001000000b41004100100341086a41001000000b9e0402057f027e230041206b22012400100441e8004180800236020041f00041e800100141e40041e80028020022003602002001411036020c200141106a2001410c6a100220012903102105200141186a2903002106230041306b2202240002400240200041034d0d0041e00041f000280200220136020002400240024002402001419aaec3f97e470440200141fbacc2877e472005200684420052720d05200041046b220120004b22032003720d04200141034d0d032000200041046b2201490d04200141044d0d010c030b20052006844200520d04200041046b220120004b22032003720d03200141034d0d022000200041046b2201490d03200141044d0d010c020b41f4002802002104200241106b220324004201210541012100420121060340200020044a0d0520022005370308200242023703002002200241086a200241106a1005200220063703182002200229031042017d370320200241186a200241206a200241286a10052000200041016a22014c044020022903282106200542017c2105200121000c010b0b0c020b41f4002802002104200241106b220324004200210642012105410121000340200020044a0d042000200041016a22014c0440200520067c2106200542017c2105200121000c010b0b0c010b4101413041241000000b4101410041241000000b4101410041001000000b20032006370300200329030021054108100341086a220020053703004100200041081000000b0b14020041000b054e487b71110041300b044e487b71008e010970726f64756365727302086c616e6775616765010143000c70726f6365737365642d62790105636c616e676131352e302e34202868747470733a2f2f6769746875622e636f6d2f736f6c616e612d6c6162732f6c6c766d2d70726f6a6563742e676974203333633336323963616135396235396438663538353733366134613531393461613965393337376429" 14 | }, 15 | "spec": { 16 | "constructors": [ 17 | { 18 | "args": [], 19 | "default": false, 20 | "docs": [ 21 | "" 22 | ], 23 | "label": "new", 24 | "payable": false, 25 | "returnType": null, 26 | "selector": "0xcdbf608d" 27 | } 28 | ], 29 | "docs": [ 30 | "" 31 | ], 32 | "environment": { 33 | "accountId": { 34 | "displayName": [ 35 | "AccountId" 36 | ], 37 | "type": 4 38 | }, 39 | "balance": { 40 | "displayName": [ 41 | "Balance" 42 | ], 43 | "type": 5 44 | }, 45 | "blockNumber": { 46 | "displayName": [ 47 | "BlockNumber" 48 | ], 49 | "type": 6 50 | }, 51 | "chainExtension": { 52 | "displayName": [], 53 | "type": 0 54 | }, 55 | "hash": { 56 | "displayName": [ 57 | "Hash" 58 | ], 59 | "type": 7 60 | }, 61 | "maxEventTopics": 4, 62 | "timestamp": { 63 | "displayName": [ 64 | "Timestamp" 65 | ], 66 | "type": 6 67 | } 68 | }, 69 | "events": [], 70 | "lang_error": { 71 | "displayName": [ 72 | "SolidityError" 73 | ], 74 | "type": 13 75 | }, 76 | "messages": [ 77 | { 78 | "args": [ 79 | { 80 | "label": "x", 81 | "type": { 82 | "displayName": [ 83 | "int32" 84 | ], 85 | "type": 1 86 | } 87 | } 88 | ], 89 | "default": false, 90 | "docs": [ 91 | "" 92 | ], 93 | "label": "oddProduct", 94 | "mutates": false, 95 | "payable": false, 96 | "returnType": { 97 | "displayName": [ 98 | "int64" 99 | ], 100 | "type": 0 101 | }, 102 | "selector": "0x7b96f0e0" 103 | }, 104 | { 105 | "args": [ 106 | { 107 | "label": "x", 108 | "type": { 109 | "displayName": [ 110 | "int32" 111 | ], 112 | "type": 1 113 | } 114 | } 115 | ], 116 | "default": false, 117 | "docs": [ 118 | "" 119 | ], 120 | "label": "triangleNumber", 121 | "mutates": false, 122 | "payable": false, 123 | "returnType": { 124 | "displayName": [ 125 | "int64" 126 | ], 127 | "type": 0 128 | }, 129 | "selector": "0x1ad730ef" 130 | } 131 | ] 132 | }, 133 | "storage": { 134 | "struct": { 135 | "fields": [], 136 | "name": "Computation" 137 | } 138 | }, 139 | "types": [ 140 | { 141 | "id": 0, 142 | "type": { 143 | "def": { 144 | "primitive": "i64" 145 | }, 146 | "path": [ 147 | "int64" 148 | ] 149 | } 150 | }, 151 | { 152 | "id": 1, 153 | "type": { 154 | "def": { 155 | "primitive": "i32" 156 | }, 157 | "path": [ 158 | "int32" 159 | ] 160 | } 161 | }, 162 | { 163 | "id": 2, 164 | "type": { 165 | "def": { 166 | "primitive": "u8" 167 | }, 168 | "path": [ 169 | "uint8" 170 | ] 171 | } 172 | }, 173 | { 174 | "id": 3, 175 | "type": { 176 | "def": { 177 | "array": { 178 | "len": 32, 179 | "type": 2 180 | } 181 | } 182 | } 183 | }, 184 | { 185 | "id": 4, 186 | "type": { 187 | "def": { 188 | "composite": { 189 | "fields": [ 190 | { 191 | "type": 3 192 | } 193 | ] 194 | } 195 | }, 196 | "path": [ 197 | "ink_primitives", 198 | "types", 199 | "AccountId" 200 | ] 201 | } 202 | }, 203 | { 204 | "id": 5, 205 | "type": { 206 | "def": { 207 | "primitive": "u128" 208 | }, 209 | "path": [ 210 | "uint128" 211 | ] 212 | } 213 | }, 214 | { 215 | "id": 6, 216 | "type": { 217 | "def": { 218 | "primitive": "u64" 219 | }, 220 | "path": [ 221 | "uint64" 222 | ] 223 | } 224 | }, 225 | { 226 | "id": 7, 227 | "type": { 228 | "def": { 229 | "composite": { 230 | "fields": [ 231 | { 232 | "type": 3 233 | } 234 | ] 235 | } 236 | }, 237 | "path": [ 238 | "ink_primitives", 239 | "types", 240 | "Hash" 241 | ] 242 | } 243 | }, 244 | { 245 | "id": 8, 246 | "type": { 247 | "def": { 248 | "primitive": "str" 249 | }, 250 | "path": [ 251 | "string" 252 | ] 253 | } 254 | }, 255 | { 256 | "id": 9, 257 | "type": { 258 | "def": { 259 | "composite": { 260 | "fields": [ 261 | { 262 | "type": 8 263 | } 264 | ] 265 | } 266 | }, 267 | "path": [ 268 | "0x08c379a0" 269 | ] 270 | } 271 | }, 272 | { 273 | "id": 10, 274 | "type": { 275 | "def": { 276 | "array": { 277 | "len": 4, 278 | "type": 6 279 | } 280 | } 281 | } 282 | }, 283 | { 284 | "id": 11, 285 | "type": { 286 | "def": { 287 | "composite": { 288 | "fields": [ 289 | { 290 | "type": 10 291 | } 292 | ] 293 | } 294 | }, 295 | "path": [ 296 | "ink_env", 297 | "types", 298 | "U256" 299 | ] 300 | } 301 | }, 302 | { 303 | "id": 12, 304 | "type": { 305 | "def": { 306 | "composite": { 307 | "fields": [ 308 | { 309 | "type": 11 310 | } 311 | ] 312 | } 313 | }, 314 | "path": [ 315 | "0x4e487b71" 316 | ] 317 | } 318 | }, 319 | { 320 | "id": 13, 321 | "type": { 322 | "def": { 323 | "variant": { 324 | "variants": [ 325 | { 326 | "fields": [ 327 | { 328 | "type": 9 329 | } 330 | ], 331 | "index": 0, 332 | "name": "Error" 333 | }, 334 | { 335 | "fields": [ 336 | { 337 | "type": 12 338 | } 339 | ], 340 | "index": 1, 341 | "name": "Panic" 342 | } 343 | ] 344 | } 345 | }, 346 | "path": [ 347 | "SolidityError" 348 | ] 349 | } 350 | } 351 | ], 352 | "version": "4" 353 | } -------------------------------------------------------------------------------- /contracts/solidity/wasm/incrementer.contract: -------------------------------------------------------------------------------- 1 | { 2 | "contract": { 3 | "authors": [ 4 | "unknown" 5 | ], 6 | "name": "incrementer", 7 | "version": "0.0.1" 8 | }, 9 | "source": { 10 | "compiler": "solang 0.3.2", 11 | "hash": "0x62f8d8433315642d84963f34902f8622409873dce50d3c947ed26dfd70bae172", 12 | "language": "Solidity 0.3.2", 13 | "wasm": "0x0061736d01000000011c0560000060047f7f7f7f017f60027f7f0060037f7f7f0060017f017f027406057365616c310b6765745f73746f726167650001057365616c320b7365745f73746f726167650001057365616c300b7365616c5f72657475726e0003057365616c3005696e7075740002057365616c301176616c75655f7472616e73666572726564000203656e76066d656d6f727902011010030504040000000608017f01418080040b071102066465706c6f7900070463616c6c00080abf0904900201057f027f2000220241086a210141808004210003400240200028020c0d00200028020822032001490d002003200141076a41787122036b220541184f0440200020036a41106a22012000280200220436020020040440200420013602040b2001200541106b3602082001410036020c2001200036020420002001360200200020033602080b2000410136020c200041106a0c020b200028020021000c000b000b2201200236020420012002360200200141086a210002402002450d00200241016b2002410771220304400340200041003a0000200041016a2100200241016b2102200341016b22030d000b0b4107490d00034020004200370000200041086a2100200241086b22020d000b0b20010b2e00418080044100360200418480044100360200418c80044100360200418880043f00411074419080046b3602000bc00202047f027e230041206b22002400100641e8004180800236020041f00041e800100341e40041e80028020022013602002000411036020c200041106a2000410c6a100420002903102104200041186a2903002105230041306b22002400024002400240024002400240200141034d0d0041e00041f0002802002202360200200241c99dff9a78470d00200420058450450d01200141046b220220014b22032003720d05200241034d0d022001200141046b2202490d05200241044b0d0341f4002802002101200041206a42003703002000200136022c200042003703182000420037031020004200370308200041086a41202000412c6a410410011a0c040b4101410041001002000b4101410041001002000b4101413041241002000b4101413041241002000b41004100100541086a41001002000b4101410041241002000bb90402047f027e230041206b22022400100641e8004180800236020041f00041e800100341e40041e80028020022003602002002411036020c200241106a2002410c6a100420022903102104200241186a2903002105230041206b2202240002400240200041034d0d0041e00041f0002802002201360200024002400240200141ed9899e703470440200141ddbbc98a01472004200584420052720d04200041046b220120004b22032003720d03200141034d0d012000200041046b2201490d03200141044d0d024101413041241002000b20042005844200520d03200241106b2200240041e8004104360200200241186a420037030020024200370310200242003703082002420037030020002002412041f00041e8001000047f41000541f0002802000b360200200028020021004104100541086a220220003602004100200241041002000b4101413041241002000b027f41f400280200210341002101230041306b2200240041e8004104360200200041206a4200370300200042003703182000420037031020004200370308200041086a412041f00041e800100045044041f00028020021010b2001200120036a22034d0440200041206b22012400200141186a42003703002001420037031020014200370308200142003703002000200336022c200141202000412c6a410410011a200041306a240041000c010b4101410041241002000b0440200241206a24000c030b41004100100541086a41001002000b4101410041241002000b4101410041001002000b000b0b14020041000b054e487b71110041300b044e487b71008e010970726f64756365727302086c616e6775616765010143000c70726f6365737365642d62790105636c616e676131352e302e34202868747470733a2f2f6769746875622e636f6d2f736f6c616e612d6c6162732f6c6c766d2d70726f6a6563742e676974203333633336323963616135396235396438663538353733366134613531393461613965393337376429" 14 | }, 15 | "spec": { 16 | "constructors": [ 17 | { 18 | "args": [ 19 | { 20 | "label": "initvalue", 21 | "type": { 22 | "displayName": [ 23 | "uint32" 24 | ], 25 | "type": 0 26 | } 27 | } 28 | ], 29 | "default": false, 30 | "docs": [ 31 | "Constructor that initializes the `int32` value to the given `init_value`." 32 | ], 33 | "label": "new", 34 | "payable": false, 35 | "returnType": null, 36 | "selector": "0xc9ce5f83" 37 | } 38 | ], 39 | "docs": [ 40 | "" 41 | ], 42 | "environment": { 43 | "accountId": { 44 | "displayName": [ 45 | "AccountId" 46 | ], 47 | "type": 3 48 | }, 49 | "balance": { 50 | "displayName": [ 51 | "Balance" 52 | ], 53 | "type": 4 54 | }, 55 | "blockNumber": { 56 | "displayName": [ 57 | "BlockNumber" 58 | ], 59 | "type": 5 60 | }, 61 | "chainExtension": { 62 | "displayName": [], 63 | "type": 0 64 | }, 65 | "hash": { 66 | "displayName": [ 67 | "Hash" 68 | ], 69 | "type": 6 70 | }, 71 | "maxEventTopics": 4, 72 | "timestamp": { 73 | "displayName": [ 74 | "Timestamp" 75 | ], 76 | "type": 5 77 | } 78 | }, 79 | "events": [], 80 | "lang_error": { 81 | "displayName": [ 82 | "SolidityError" 83 | ], 84 | "type": 12 85 | }, 86 | "messages": [ 87 | { 88 | "args": [ 89 | { 90 | "label": "by", 91 | "type": { 92 | "displayName": [ 93 | "uint32" 94 | ], 95 | "type": 0 96 | } 97 | } 98 | ], 99 | "default": false, 100 | "docs": [ 101 | "This increments the value by `by`." 102 | ], 103 | "label": "inc", 104 | "mutates": true, 105 | "payable": false, 106 | "returnType": null, 107 | "selector": "0xdd5d5211" 108 | }, 109 | { 110 | "args": [], 111 | "default": false, 112 | "docs": [ 113 | "Simply returns the current value of our `uint32`." 114 | ], 115 | "label": "get", 116 | "mutates": false, 117 | "payable": false, 118 | "returnType": { 119 | "displayName": [ 120 | "uint32" 121 | ], 122 | "type": 0 123 | }, 124 | "selector": "0x6d4ce63c" 125 | } 126 | ] 127 | }, 128 | "storage": { 129 | "struct": { 130 | "fields": [ 131 | { 132 | "layout": { 133 | "root": { 134 | "layout": { 135 | "leaf": { 136 | "key": "0x00000000", 137 | "ty": 0 138 | } 139 | }, 140 | "root_key": "0x00000000" 141 | } 142 | }, 143 | "name": "value" 144 | } 145 | ], 146 | "name": "incrementer" 147 | } 148 | }, 149 | "types": [ 150 | { 151 | "id": 0, 152 | "type": { 153 | "def": { 154 | "primitive": "u32" 155 | }, 156 | "path": [ 157 | "uint32" 158 | ] 159 | } 160 | }, 161 | { 162 | "id": 1, 163 | "type": { 164 | "def": { 165 | "primitive": "u8" 166 | }, 167 | "path": [ 168 | "uint8" 169 | ] 170 | } 171 | }, 172 | { 173 | "id": 2, 174 | "type": { 175 | "def": { 176 | "array": { 177 | "len": 32, 178 | "type": 1 179 | } 180 | } 181 | } 182 | }, 183 | { 184 | "id": 3, 185 | "type": { 186 | "def": { 187 | "composite": { 188 | "fields": [ 189 | { 190 | "type": 2 191 | } 192 | ] 193 | } 194 | }, 195 | "path": [ 196 | "ink_primitives", 197 | "types", 198 | "AccountId" 199 | ] 200 | } 201 | }, 202 | { 203 | "id": 4, 204 | "type": { 205 | "def": { 206 | "primitive": "u128" 207 | }, 208 | "path": [ 209 | "uint128" 210 | ] 211 | } 212 | }, 213 | { 214 | "id": 5, 215 | "type": { 216 | "def": { 217 | "primitive": "u64" 218 | }, 219 | "path": [ 220 | "uint64" 221 | ] 222 | } 223 | }, 224 | { 225 | "id": 6, 226 | "type": { 227 | "def": { 228 | "composite": { 229 | "fields": [ 230 | { 231 | "type": 2 232 | } 233 | ] 234 | } 235 | }, 236 | "path": [ 237 | "ink_primitives", 238 | "types", 239 | "Hash" 240 | ] 241 | } 242 | }, 243 | { 244 | "id": 7, 245 | "type": { 246 | "def": { 247 | "primitive": "str" 248 | }, 249 | "path": [ 250 | "string" 251 | ] 252 | } 253 | }, 254 | { 255 | "id": 8, 256 | "type": { 257 | "def": { 258 | "composite": { 259 | "fields": [ 260 | { 261 | "type": 7 262 | } 263 | ] 264 | } 265 | }, 266 | "path": [ 267 | "0x08c379a0" 268 | ] 269 | } 270 | }, 271 | { 272 | "id": 9, 273 | "type": { 274 | "def": { 275 | "array": { 276 | "len": 4, 277 | "type": 5 278 | } 279 | } 280 | } 281 | }, 282 | { 283 | "id": 10, 284 | "type": { 285 | "def": { 286 | "composite": { 287 | "fields": [ 288 | { 289 | "type": 9 290 | } 291 | ] 292 | } 293 | }, 294 | "path": [ 295 | "ink_env", 296 | "types", 297 | "U256" 298 | ] 299 | } 300 | }, 301 | { 302 | "id": 11, 303 | "type": { 304 | "def": { 305 | "composite": { 306 | "fields": [ 307 | { 308 | "type": 10 309 | } 310 | ] 311 | } 312 | }, 313 | "path": [ 314 | "0x4e487b71" 315 | ] 316 | } 317 | }, 318 | { 319 | "id": 12, 320 | "type": { 321 | "def": { 322 | "variant": { 323 | "variants": [ 324 | { 325 | "fields": [ 326 | { 327 | "type": 8 328 | } 329 | ], 330 | "index": 0, 331 | "name": "Error" 332 | }, 333 | { 334 | "fields": [ 335 | { 336 | "type": 11 337 | } 338 | ], 339 | "index": 1, 340 | "name": "Panic" 341 | } 342 | ] 343 | } 344 | }, 345 | "path": [ 346 | "SolidityError" 347 | ] 348 | } 349 | } 350 | ], 351 | "version": "4" 352 | } -------------------------------------------------------------------------------- /macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | 3 | use contract_metadata::ContractMetadata; 4 | use heck::ToUpperCamelCase as _; 5 | use ink_metadata::{InkProject, MetadataVersion, Selector}; 6 | use proc_macro::TokenStream; 7 | use proc_macro_error::{abort_call_site, proc_macro_error}; 8 | use subxt_codegen::{CratePath, DerivesRegistry, TypeGenerator, TypeSubstitutes}; 9 | 10 | #[proc_macro] 11 | #[proc_macro_error] 12 | pub fn contract(input: TokenStream) -> TokenStream { 13 | let contract_path = syn::parse_macro_input!(input as syn::LitStr); 14 | 15 | let root = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into()); 16 | let metadata_path: std::path::PathBuf = [&root, &contract_path.value()].iter().collect(); 17 | 18 | let reader = std::fs::File::open(metadata_path) 19 | .unwrap_or_else(|e| abort_call_site!("Failed to read metadata file: {}", e)); 20 | let metadata: ContractMetadata = serde_json::from_reader(reader) 21 | .unwrap_or_else(|e| abort_call_site!("Failed to deserialize contract metadata: {}", e)); 22 | let contract_name = metadata.contract.name; 23 | let ink_metadata: InkProject = serde_json::from_value(serde_json::Value::Object(metadata.abi)) 24 | .unwrap_or_else(|e| abort_call_site!("Failed to deserialize ink metadata: {}", e)); 25 | if &MetadataVersion::V4 == ink_metadata.version() { 26 | let contract_mod = generate_contract_mod(contract_name, ink_metadata); 27 | contract_mod.into() 28 | } else { 29 | abort_call_site!("Invalid contract metadata version") 30 | } 31 | } 32 | 33 | fn generate_contract_mod(contract_name: String, metadata: InkProject) -> proc_macro2::TokenStream { 34 | let crate_path = CratePath::default(); 35 | let mut type_substitutes = TypeSubstitutes::with_default_substitutes(&crate_path); 36 | 37 | let path_account: syn::Path = syn::parse_quote!(#crate_path::utils::AccountId32); 38 | let path_u256: syn::Path = syn::parse_quote!(::primitive_types::U256); 39 | 40 | type_substitutes 41 | .insert( 42 | syn::parse_quote!(ink_primitives::types::AccountId), 43 | path_account.try_into().unwrap(), 44 | ) 45 | .expect("Error in type substitutions"); 46 | 47 | type_substitutes 48 | .insert( 49 | syn::parse_quote!(ink_env::types::U256), 50 | path_u256.try_into().unwrap(), 51 | ) 52 | .expect("Error in type substitutions"); 53 | 54 | let type_generator = TypeGenerator::new( 55 | metadata.registry(), 56 | "contract_types", 57 | type_substitutes, 58 | DerivesRegistry::with_default_derives(&crate_path), 59 | crate_path, 60 | false, 61 | ); 62 | let types_mod = type_generator 63 | .generate_types_mod() 64 | .expect("Error in type generation"); 65 | let types_mod_ident = types_mod.ident(); 66 | 67 | let contract_name = quote::format_ident!("{}", contract_name); 68 | let constructors = generate_constructors(&metadata, &type_generator); 69 | let messages = generate_messages(&metadata, &type_generator); 70 | 71 | quote::quote!( 72 | pub mod #contract_name { 73 | #types_mod 74 | 75 | pub mod constructors { 76 | use super::#types_mod_ident; 77 | #( #constructors )* 78 | } 79 | 80 | pub mod messages { 81 | use super::#types_mod_ident; 82 | #( #messages )* 83 | } 84 | } 85 | ) 86 | } 87 | 88 | fn generate_constructors( 89 | metadata: &ink_metadata::InkProject, 90 | type_gen: &TypeGenerator, 91 | ) -> Vec { 92 | let trait_path = syn::parse_quote!(crate::InkConstructor); 93 | metadata 94 | .spec() 95 | .constructors() 96 | .iter() 97 | .map(|constructor| { 98 | let name = constructor.label(); 99 | let args = constructor 100 | .args() 101 | .iter() 102 | .map(|arg| (arg.label().as_str(), arg.ty().ty().id)) 103 | .collect::>(); 104 | generate_message_impl(type_gen, name, args, constructor.selector(), &trait_path) 105 | }) 106 | .collect() 107 | } 108 | 109 | fn generate_messages( 110 | metadata: &ink_metadata::InkProject, 111 | type_gen: &TypeGenerator, 112 | ) -> Vec { 113 | let trait_path = syn::parse_quote!(crate::InkMessage); 114 | metadata 115 | .spec() 116 | .messages() 117 | .iter() 118 | .map(|message| { 119 | // strip trait prefix from trait message labels 120 | let name = 121 | message.label().split("::").last().unwrap_or_else(|| { 122 | abort_call_site!("Invalid message label: {}", message.label()) 123 | }); 124 | let args = message 125 | .args() 126 | .iter() 127 | .map(|arg| (arg.label().as_str(), arg.ty().ty().id)) 128 | .collect::>(); 129 | 130 | generate_message_impl(type_gen, name, args, message.selector(), &trait_path) 131 | }) 132 | .collect() 133 | } 134 | 135 | fn generate_message_impl( 136 | type_gen: &TypeGenerator, 137 | name: &str, 138 | args: Vec<(&str, u32)>, 139 | selector: &Selector, 140 | impl_trait: &syn::Path, 141 | ) -> proc_macro2::TokenStream { 142 | let struct_ident = quote::format_ident!("{}", name.to_upper_camel_case()); 143 | let fn_ident = quote::format_ident!("{}", name); 144 | let (args, arg_names): (Vec<_>, Vec<_>) = args 145 | .iter() 146 | .enumerate() 147 | .map(|(i, (name, type_id))| { 148 | // In Solidity, function arguments may not have names. 149 | // If an argument without a name is included in the metadata, a name is generated for it 150 | let name = if name.is_empty() { 151 | format!("arg{i}") 152 | } else { 153 | name.to_string() 154 | }; 155 | let name = quote::format_ident!("{}", name.as_str()); 156 | let ty = type_gen.resolve_type_path(*type_id); 157 | (quote::quote!( #name: #ty ), name) 158 | }) 159 | .unzip(); 160 | let selector_bytes = hex_lits(selector); 161 | quote::quote! ( 162 | #[derive(::codec::Encode)] 163 | pub struct #struct_ident { 164 | #( #args ), * 165 | } 166 | 167 | impl #impl_trait for #struct_ident { 168 | const SELECTOR: [u8; 4] = [ #( #selector_bytes ),* ]; 169 | } 170 | 171 | pub fn #fn_ident(#( #args ), *) -> #struct_ident { 172 | #struct_ident { 173 | #( #arg_names ), * 174 | } 175 | } 176 | ) 177 | } 178 | 179 | /// Returns the 4 bytes that make up the selector as hex encoded bytes. 180 | fn hex_lits(selector: &ink_metadata::Selector) -> [syn::LitInt; 4] { 181 | let hex_lits = selector 182 | .to_bytes() 183 | .iter() 184 | .map(|byte| { 185 | syn::LitInt::new( 186 | &format!("0x{:02X}_u8", byte), 187 | proc_macro2::Span::call_site(), 188 | ) 189 | }) 190 | .collect::>(); 191 | hex_lits.try_into().expect("Invalid selector bytes length") 192 | } 193 | 194 | #[cfg(test)] 195 | mod tests { 196 | use super::*; 197 | use ink_metadata::{ 198 | layout::{Layout, StructLayout}, 199 | ConstructorSpec, ContractSpec, MessageParamSpec, MessageSpec, ReturnTypeSpec, TypeSpec, 200 | }; 201 | use ink_primitives::AccountId; 202 | use scale_info::{IntoPortable, Registry}; 203 | 204 | // Helper for creating a InkProject with custom MessageSpec 205 | fn ink_project_with_custom_message(message: MessageSpec) -> InkProject { 206 | let mut registry = Registry::default(); 207 | let spec = ContractSpec::new() 208 | .constructors([ConstructorSpec::from_label("New") 209 | .selector(Default::default()) 210 | .payable(true) 211 | .args(Vec::new()) 212 | .docs(Vec::new()) 213 | .returns(ReturnTypeSpec::new(None)) 214 | .done()]) 215 | .messages([message]) 216 | .docs(Vec::new()) 217 | .done() 218 | .into_portable(&mut registry); 219 | let layout = 220 | Layout::Struct(StructLayout::new("Struct", Vec::new())).into_portable(&mut registry); 221 | InkProject::new_portable(layout, spec, registry.into()) 222 | } 223 | 224 | #[test] 225 | fn test_contract_mod_with_ink_types_success() { 226 | let message = MessageSpec::from_label("set") 227 | .selector(Default::default()) 228 | .mutates(false) 229 | .payable(true) 230 | .args(vec![MessageParamSpec::new("to") 231 | .of_type(TypeSpec::with_name_segs::( 232 | ::core::iter::Iterator::map( 233 | ::core::iter::IntoIterator::into_iter(["AccountId"]), 234 | ::core::convert::AsRef::as_ref, 235 | ), 236 | )) 237 | .done()]) 238 | .returns(ReturnTypeSpec::new(None)) 239 | .docs(Vec::new()) 240 | .done(); 241 | let metadata = ink_project_with_custom_message(message); 242 | let expected_output = quote::quote!( 243 | pub mod Test { 244 | pub mod contract_types { 245 | use super::contract_types; 246 | } 247 | pub mod constructors { 248 | use super::contract_types; 249 | #[derive(:: codec :: Encode)] 250 | pub struct New {} 251 | impl crate::InkConstructor for New { 252 | const SELECTOR: [u8; 4] = [0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8]; 253 | } 254 | pub fn New() -> New { 255 | New {} 256 | } 257 | } 258 | pub mod messages { 259 | use super::contract_types; 260 | #[derive(:: codec :: Encode)] 261 | pub struct Set { 262 | to: ::subxt::utils::AccountId32, 263 | } 264 | impl crate::InkMessage for Set { 265 | const SELECTOR: [u8; 4] = [0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8]; 266 | } 267 | pub fn set(to: ::subxt::utils::AccountId32) -> Set { 268 | Set { to } 269 | } 270 | } 271 | } 272 | ); 273 | 274 | let generated_output = generate_contract_mod("Test".to_string(), metadata).to_string(); 275 | assert_eq!(generated_output, expected_output.to_string()) 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /src/wasm/runner.rs: -------------------------------------------------------------------------------- 1 | use self::xts::api; 2 | 3 | use super::*; 4 | use crate::BlockInfo; 5 | use codec::Encode; 6 | use color_eyre::eyre; 7 | use futures::TryStream; 8 | use sp_runtime::traits::{BlakeTwo256, Hash as _}; 9 | use std::time::{SystemTime, UNIX_EPOCH}; 10 | use subxt::{backend::rpc::RpcClient, OnlineClient, PolkadotConfig as DefaultConfig}; 11 | 12 | use xts::api::{ 13 | contracts::calls::types::Call, contracts::events::Instantiated, system::events::ExtrinsicFailed, 14 | }; 15 | 16 | pub const DEFAULT_STORAGE_DEPOSIT_LIMIT: Option = None; 17 | 18 | pub struct BenchRunner { 19 | url: String, 20 | api: ContractsApi, 21 | signer: Signer, 22 | calls: Vec<(String, Vec)>, 23 | } 24 | 25 | impl BenchRunner { 26 | pub async fn new(signer: Signer, url: &str) -> color_eyre::Result { 27 | let client = RpcClient::from_url(url).await?; 28 | 29 | let api = ContractsApi::new(client).await?; 30 | 31 | let runner = Self { 32 | url: url.to_string(), 33 | api, 34 | signer, 35 | calls: Vec::new(), 36 | }; 37 | Ok(runner) 38 | } 39 | 40 | /// Upload and instantiate instances of contract, and build calls for benchmarking 41 | pub async fn prepare_contract( 42 | &mut self, 43 | path: &str, 44 | name: &str, 45 | constructor: C, 46 | instance_count: u32, 47 | mut create_message: F, 48 | ) -> color_eyre::Result<()> 49 | where 50 | C: InkConstructor, 51 | F: FnMut() -> EncodedMessage, 52 | { 53 | print!("Preparing {name}..."); 54 | 55 | let root = std::env::var("CARGO_MANIFEST_DIR")?; 56 | let contract_path = format!("{path}/{name}.contract"); 57 | let metadata_path: std::path::PathBuf = [&root, &contract_path].iter().collect(); 58 | let reader = std::fs::File::open(metadata_path)?; 59 | let contract: contract_metadata::ContractMetadata = serde_json::from_reader(reader)?; 60 | let code = contract 61 | .source 62 | .wasm 63 | .ok_or_else(|| eyre::eyre!("contract bundle missing source Wasm"))?; 64 | 65 | println!("{}KiB", code.0.len() / 1024); 66 | 67 | let contract_accounts = self 68 | .exec_instantiate(0, code.0, &constructor, instance_count) 69 | .await?; 70 | 71 | println!("Instantiated {} {name} contracts", contract_accounts.len()); 72 | 73 | let calls = contract_accounts 74 | .iter() 75 | .map(|contract| { 76 | let message = create_message(); 77 | RunnerCall { 78 | contract_account: contract.clone(), 79 | call_data: message, 80 | } 81 | }) 82 | .collect::>(); 83 | 84 | self.calls.push((name.to_string(), calls)); 85 | 86 | Ok(()) 87 | } 88 | 89 | async fn exec_instantiate( 90 | &mut self, 91 | value: Balance, 92 | code: Vec, 93 | constructor: &C, 94 | count: u32, 95 | ) -> color_eyre::Result> { 96 | let mut data = C::SELECTOR.to_vec(); 97 | ::encode_to(constructor, &mut data); 98 | 99 | // a value to append to a contract's custom section to make the code unique 100 | let unique_code_salt = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis(); 101 | 102 | // dry run the instantiate to calculate the gas limit 103 | let gas_limit = { 104 | let code = append_unique_name_section(&code, unique_code_salt)?; 105 | let dry_run = self 106 | .api 107 | .instantiate_with_code_dry_run( 108 | value, 109 | DEFAULT_STORAGE_DEPOSIT_LIMIT, 110 | code, 111 | data.clone(), 112 | Vec::new(), 113 | &self.signer, 114 | ) 115 | .await; 116 | dry_run.gas_required 117 | }; 118 | 119 | let mut block_sub = self.api.client.blocks().subscribe_best().await?; 120 | 121 | let mut accounts = Vec::new(); 122 | for i in unique_code_salt..unique_code_salt + count as u128 { 123 | let code = append_unique_name_section(&code, i)?; 124 | let salt = Vec::new(); 125 | 126 | self.api 127 | .instantiate_with_code( 128 | value, 129 | gas_limit.into(), 130 | DEFAULT_STORAGE_DEPOSIT_LIMIT, 131 | code, 132 | data.clone(), 133 | salt, 134 | &mut self.signer, 135 | ) 136 | .await?; 137 | } 138 | 139 | while let Some(Ok(block)) = block_sub.next().await { 140 | let events = block.events().await?; 141 | for event in events.iter() { 142 | let event = event?; 143 | if let Some(instantiated) = event.as_event::()? { 144 | accounts.push(instantiated.contract); 145 | if accounts.len() == count as usize { 146 | return Ok(accounts); 147 | } 148 | } else if event.as_event::()?.is_some() { 149 | let metadata = self.api.client.metadata(); 150 | let dispatch_error = 151 | subxt::error::DispatchError::decode_from(event.field_bytes(), metadata); 152 | return Err(eyre::eyre!( 153 | "Instantiate Extrinsic Failed: {:?}", 154 | dispatch_error 155 | )); 156 | } 157 | } 158 | } 159 | Err(eyre::eyre!( 160 | "Expected {} Instantiated events, received {}", 161 | count, 162 | accounts.len() 163 | )) 164 | } 165 | 166 | async fn get_block_details( 167 | client: OnlineClient, 168 | block_hash: sp_core::H256, 169 | ) -> color_eyre::Result<(u64, Vec)> { 170 | let block = client.blocks().at(block_hash).await?; 171 | let mut tx_hashes = Vec::new(); 172 | let extrinsics_details = block 173 | .extrinsics() 174 | .await? 175 | .iter() 176 | .collect::, _>>()?; 177 | 178 | for extrinsic_detail in extrinsics_details { 179 | if let Some(Call { .. }) = extrinsic_detail.as_extrinsic::()? { 180 | tx_hashes.push(BlakeTwo256::hash_of(&extrinsic_detail.bytes())); 181 | } 182 | } 183 | let storage_timestamp_storage_addr = api::storage().timestamp().now(); 184 | let time_stamp = client 185 | .storage() 186 | .at(block_hash) 187 | .fetch(&storage_timestamp_storage_addr) 188 | .await? 189 | .unwrap(); 190 | Ok((time_stamp, tx_hashes)) 191 | } 192 | 193 | /// Call each contract instance `call_count` times. Wait for all txs to be included in a block 194 | /// before returning. 195 | pub async fn run( 196 | &mut self, 197 | call_count: u32, 198 | ) -> color_eyre::Result + '_> { 199 | let block_stats = blockstats::subscribe_stats(&self.url).await?; 200 | 201 | let mut tx_hashes = Vec::new(); 202 | let max_instance_count = self 203 | .calls 204 | .iter() 205 | .map(|(_, calls)| calls.len()) 206 | .max() 207 | .ok_or_else(|| eyre::eyre!("No prepared contracts for benchmarking."))?; 208 | 209 | for _ in 0..call_count { 210 | for i in 0..max_instance_count { 211 | for (_name, contract_calls) in &self.calls { 212 | if let Some(contract_call) = contract_calls.get(i as usize) { 213 | // dry run the call to calculate the gas limit 214 | let mut gas_limit = { 215 | let dry_run = self 216 | .api 217 | .call_dry_run( 218 | contract_call.contract_account.clone(), 219 | 0, 220 | DEFAULT_STORAGE_DEPOSIT_LIMIT, 221 | contract_call.call_data.0.clone(), 222 | &self.signer, 223 | ) 224 | .await?; 225 | dry_run.gas_required 226 | }; 227 | 228 | // extra 5% of gas limit 229 | // due to "not enough gas" rpc errors 230 | gas_limit = gas_limit.checked_mul(105).expect("Gas limit overflow") / 100; 231 | 232 | let tx_hash = self 233 | .api 234 | .call( 235 | contract_call.contract_account.clone(), 236 | 0, 237 | gas_limit.into(), 238 | DEFAULT_STORAGE_DEPOSIT_LIMIT, 239 | contract_call.call_data.0.clone(), 240 | &self.signer, 241 | ) 242 | .await?; 243 | tx_hashes.push(tx_hash) 244 | } 245 | } 246 | } 247 | } 248 | 249 | println!("Submitted {} total contract calls", tx_hashes.len()); 250 | 251 | let remaining_hashes: std::collections::HashSet = tx_hashes.iter().cloned().collect(); 252 | 253 | let wait_for_txs = crate::collect_block_stats(block_stats, remaining_hashes, |hash| { 254 | let client = self.api.client.clone(); 255 | Self::get_block_details(client, hash) 256 | }); 257 | 258 | Ok(wait_for_txs) 259 | } 260 | } 261 | 262 | /// Add a custom section to make the Wasm code unique to upload many copies of the same contract. 263 | fn append_unique_name_section(code: &[u8], instance_id: u128) -> color_eyre::Result> { 264 | let mut module: parity_wasm::elements::Module = parity_wasm::deserialize_buffer(code)?; 265 | module.set_custom_section("smart-bench-unique", instance_id.to_le_bytes().to_vec()); 266 | let code = module.into_bytes()?; 267 | Ok(code) 268 | } 269 | 270 | #[derive(Clone)] 271 | pub struct EncodedMessage(Vec); 272 | 273 | impl EncodedMessage { 274 | fn new(call: &M) -> Self { 275 | let mut call_data = M::SELECTOR.to_vec(); 276 | ::encode_to(call, &mut call_data); 277 | Self(call_data) 278 | } 279 | } 280 | 281 | impl From for EncodedMessage 282 | where 283 | M: InkMessage, 284 | { 285 | fn from(msg: M) -> Self { 286 | EncodedMessage::new(&msg) 287 | } 288 | } 289 | 290 | #[derive(Clone)] 291 | pub struct RunnerCall { 292 | contract_account: AccountId, 293 | call_data: EncodedMessage, 294 | } 295 | --------------------------------------------------------------------------------