diff --git a/etherlink/bin_benchmark_producer/gas_sinks/create2_loop.sol b/etherlink/bin_benchmark_producer/gas_sinks/create2_loop.sol new file mode 100644 index 0000000000000000000000000000000000000000..e062feec82100f0f6f9e76f14e58275f938dfff7 --- /dev/null +++ b/etherlink/bin_benchmark_producer/gas_sinks/create2_loop.sol @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2025 Nomadic Labs +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +contract DeployerLoop { + function deployMany(bytes memory bytecode, bytes32 salt) public { + for (uint i = 0; i < 1_000_000; i++) { + if (gasleft() < 100_000) break; + address addr; + bytes32 salted = keccak256(abi.encodePacked(salt, i)); + assembly { + addr := create2(0, add(bytecode, 0x20), mload(bytecode), salted) + } + require(addr != address(0), "CREATE2 failed"); + } + } +} \ No newline at end of file diff --git a/etherlink/bin_benchmark_producer/gas_sinks/mstore_loop.sol b/etherlink/bin_benchmark_producer/gas_sinks/mstore_loop.sol new file mode 100644 index 0000000000000000000000000000000000000000..c90d7bd04587e27ce21735e6a3e6a1d216096030 --- /dev/null +++ b/etherlink/bin_benchmark_producer/gas_sinks/mstore_loop.sol @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2025 Nomadic Labs +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +contract MemoryBlower { + function expandMemory() public { + for (uint i = 0; i < 1_000_000; i++) { + if (gasleft() < 100_000) break; + assembly { + mstore(mul(i, 0x20), i) + } + } + } +} \ No newline at end of file diff --git a/etherlink/bin_benchmark_producer/gas_sinks/sha3_loop.sol b/etherlink/bin_benchmark_producer/gas_sinks/sha3_loop.sol new file mode 100644 index 0000000000000000000000000000000000000000..0658c92e18ea672c4aaa694ed313363005e43197 --- /dev/null +++ b/etherlink/bin_benchmark_producer/gas_sinks/sha3_loop.sol @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2025 Nomadic Labs +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +contract UseSHA3Loop { + function hashManyTimes(bytes memory data) public returns (bytes32) { + bytes32 hash = keccak256(data); + for (uint i = 0; i < 1_000_000; i++) { + if (gasleft() < 100_000) break; + hash = keccak256(abi.encodePacked(hash)); + } + return hash; + } +} \ No newline at end of file diff --git a/etherlink/bin_benchmark_producer/general_contracts/erc1155.sol b/etherlink/bin_benchmark_producer/general_contracts/erc1155.sol new file mode 100644 index 0000000000000000000000000000000000000000..b1ed30a897503c2f55741dbad08ebd79a40f20ef --- /dev/null +++ b/etherlink/bin_benchmark_producer/general_contracts/erc1155.sol @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +interface IERC1155 { + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 value, + bytes calldata data + ) external; + + function safeBatchTransferFrom( + address from, + address to, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external; + + function balanceOf(address owner, uint256 id) external view returns (uint256); + + function balanceOfBatch( + address[] calldata owners, + uint256[] calldata ids + ) external view returns (uint256[] memory); + + function setApprovalForAll(address operator, bool approved) external; + + function isApprovedForAll( + address owner, + address operator + ) external view returns (bool); +} + +interface IERC1155TokenReceiver { + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external returns (bytes4); + + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4); +} + +contract ERC1155 is IERC1155 { + event TransferSingle( + address indexed operator, + address indexed from, + address indexed to, + uint256 id, + uint256 value + ); + event TransferBatch( + address indexed operator, + address indexed from, + address indexed to, + uint256[] ids, + uint256[] values + ); + event ApprovalForAll( + address indexed owner, + address indexed operator, + bool approved + ); + event URI(string value, uint256 indexed id); + + // owner => id => balance + mapping(address => mapping(uint256 => uint256)) public balanceOf; + // owner => operator => approved + mapping(address => mapping(address => bool)) public isApprovedForAll; + + function balanceOfBatch( + address[] calldata owners, + uint256[] calldata ids + ) external view returns (uint256[] memory balances) { + require(owners.length == ids.length, "owners length != ids length"); + + balances = new uint[](owners.length); + + unchecked { + for (uint256 i = 0; i < owners.length; i++) { + balances[i] = balanceOf[owners[i]][ids[i]]; + } + } + } + + function setApprovalForAll(address operator, bool approved) external { + isApprovedForAll[msg.sender][operator] = approved; + emit ApprovalForAll(msg.sender, operator, approved); + } + + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 value, + bytes calldata data + ) external { + require( + msg.sender == from || isApprovedForAll[from][msg.sender], + "not approved" + ); + require(to != address(0), "to = 0 address"); + + balanceOf[from][id] -= value; + balanceOf[to][id] += value; + + emit TransferSingle(msg.sender, from, to, id, value); + + if (to.code.length > 0) { + require( + IERC1155TokenReceiver(to).onERC1155Received( + msg.sender, + from, + id, + value, + data + ) == IERC1155TokenReceiver.onERC1155Received.selector, + "unsafe transfer" + ); + } + } + + function safeBatchTransferFrom( + address from, + address to, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external { + require( + msg.sender == from || isApprovedForAll[from][msg.sender], + "not approved" + ); + require(to != address(0), "to = 0 address"); + require(ids.length == values.length, "ids length != values length"); + + for (uint256 i = 0; i < ids.length; i++) { + balanceOf[from][ids[i]] -= values[i]; + balanceOf[to][ids[i]] += values[i]; + } + + emit TransferBatch(msg.sender, from, to, ids, values); + + if (to.code.length > 0) { + require( + IERC1155TokenReceiver(to).onERC1155BatchReceived( + msg.sender, + from, + ids, + values, + data + ) == IERC1155TokenReceiver.onERC1155BatchReceived.selector, + "unsafe transfer" + ); + } + } + + // ERC165 + function supportsInterface(bytes4 interfaceId) external view returns (bool) { + return + interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165 + interfaceId == 0xd9b67a26 || // ERC165 Interface ID for ERC1155 + interfaceId == 0x0e89341c; // ERC165 Interface ID for ERC1155MetadataURI + } + + // ERC1155 Metadata URI + function uri(uint256 id) public view virtual returns (string memory) {} + + // Internal functions + function _mint(address to, uint256 id, uint256 value, bytes memory data) internal { + require(to != address(0), "to = 0 address"); + + balanceOf[to][id] += value; + + emit TransferSingle(msg.sender, address(0), to, id, value); + + if (to.code.length > 0) { + require( + IERC1155TokenReceiver(to).onERC1155Received( + msg.sender, + address(0), + id, + value, + data + ) == IERC1155TokenReceiver.onERC1155Received.selector, + "unsafe transfer" + ); + } + } + + function _batchMint( + address to, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) internal { + require(to != address(0), "to = 0 address"); + require(ids.length == values.length, "ids length != values length"); + + for (uint256 i = 0; i < ids.length; i++) { + balanceOf[to][ids[i]] += values[i]; + } + + emit TransferBatch(msg.sender, address(0), to, ids, values); + + if (to.code.length > 0) { + require( + IERC1155TokenReceiver(to).onERC1155BatchReceived( + msg.sender, + address(0), + ids, + values, + data + ) == IERC1155TokenReceiver.onERC1155BatchReceived.selector, + "unsafe transfer" + ); + } + } + + function _burn(address from, uint256 id, uint256 value) internal { + require(from != address(0), "from = 0 address"); + balanceOf[from][id] -= value; + emit TransferSingle(msg.sender, from, address(0), id, value); + } + + function _batchBurn( + address from, + uint256[] calldata ids, + uint256[] calldata values + ) internal { + require(from != address(0), "from = 0 address"); + require(ids.length == values.length, "ids length != values length"); + + for (uint256 i = 0; i < ids.length; i++) { + balanceOf[from][ids[i]] -= values[i]; + } + + emit TransferBatch(msg.sender, from, address(0), ids, values); + } +} + +contract MyMultiToken is ERC1155 { + function mint(uint256 id, uint256 value, bytes memory data) external { + _mint(msg.sender, id, value, data); + } + + function batchMint( + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external { + _batchMint(msg.sender, ids, values, data); + } + + function burn(uint256 id, uint256 value) external { + _burn(msg.sender, id, value); + } + + function batchBurn(uint256[] calldata ids, uint256[] calldata values) external { + _batchBurn(msg.sender, ids, values); + } +} diff --git a/etherlink/bin_benchmark_producer/general_contracts/external_recursive.sol b/etherlink/bin_benchmark_producer/general_contracts/external_recursive.sol new file mode 100644 index 0000000000000000000000000000000000000000..4856420a6d3585436c4f3ed31cac5327dd511860 --- /dev/null +++ b/etherlink/bin_benchmark_producer/general_contracts/external_recursive.sol @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2025 Nomadic Labs +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +contract RecursiveCaller { + uint256 public depth; + + function recurse() public { + if (gasleft() < 100_000) return; + depth += 1; + this.recurse(); // external recursive call + } +} \ No newline at end of file diff --git a/etherlink/bin_benchmark_producer/general_contracts/gas_guzzler.sol b/etherlink/bin_benchmark_producer/general_contracts/gas_guzzler.sol new file mode 100644 index 0000000000000000000000000000000000000000..01e5055169cc60bc1d490e239b85b537e1467c08 --- /dev/null +++ b/etherlink/bin_benchmark_producer/general_contracts/gas_guzzler.sol @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2024 TriliTech +// +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +contract GasGuzzler { + uint public sum = 0; + + // Only updates a local variable + function incrementLocalSum(uint iterations) public view returns (uint) { + uint localSum = 0; + for (uint i = 0; i < iterations; i++) { + // Perform some operation that involves a state variable + localSum += i; + // Adding some entropy to confuse constant propagation and + // loop unrolling optimizations + if ((localSum % 10) == 0) { + localSum += block.timestamp % 10; + } + } + return sum; + } + + // Updates a global variable in every iteration of the loop + function incrementGlobalSum(uint iterations) public returns (uint) { + for (uint i = 0; i < iterations; i++) { + // Perform some operation that involves a state variable + sum += i; + // Adding some entropy to confuse constant propagation and + // loop unrolling optimizations + if ((sum % 10) == 0) { + sum += block.timestamp % 10; + } + } + // Update the state variable at the end of the loop + return sum; + } +} diff --git a/etherlink/bin_benchmark_producer/general_contracts/keccak.sol b/etherlink/bin_benchmark_producer/general_contracts/keccak.sol new file mode 100644 index 0000000000000000000000000000000000000000..83406bb37401baa189886cb7f5b017b3a11b688c --- /dev/null +++ b/etherlink/bin_benchmark_producer/general_contracts/keccak.sol @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2018 Tasuku Nakamura +// +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +contract GuessTheMagicWord { + bytes32 public answer = + 0x60298f78cc0b47170ba79c10aa3851d7648bd96f2f8e46a19dbc777c36fb0c00; + + // Magic word is "Solidity" + function guess(string memory _word) public view returns (bool) { + return keccak256(abi.encodePacked(_word)) == answer; + } +} \ No newline at end of file diff --git a/etherlink/bin_benchmark_producer/general_contracts/loop.sol b/etherlink/bin_benchmark_producer/general_contracts/loop.sol new file mode 100644 index 0000000000000000000000000000000000000000..d6e09b7b6c3ee088de687f35867ca047b685866d --- /dev/null +++ b/etherlink/bin_benchmark_producer/general_contracts/loop.sol @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2023 Marigold +// +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.2 <0.9.0; +contract Loop { + uint256 count; + + // ask the contract to run the loop a given number of times + function loop(uint256 iter) public { + for (uint256 i = 0; i < iter; i++) { + count += 1; + } + + } +} \ No newline at end of file diff --git a/etherlink/bin_benchmark_producer/general_contracts/read_info.sol b/etherlink/bin_benchmark_producer/general_contracts/read_info.sol new file mode 100644 index 0000000000000000000000000000000000000000..bfdab34662db7472fe739aaad8c8c78d2e189440 --- /dev/null +++ b/etherlink/bin_benchmark_producer/general_contracts/read_info.sol @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2025 Nomadic Labs +// +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +contract ReadInfo { + + function timestamp() public view returns (uint) { + return block.timestamp; + } + + function gas_price() public view returns (uint) { + return tx.gasprice; + } + + function coin_base() public view returns (address) { + return block.coinbase; + } + + function origin() public view returns (address) { + return tx.origin; + } + + function gas_limit() public view returns (uint) { + return block.gaslimit; + } + + function chain_id() public view returns (uint) { + return block.chainid; + } + + function block_number() public view returns (uint) { + return block.number; + } + + function balance() public view returns (uint) { + return address(msg.sender).balance; + } + + function base_fee() public view returns (uint) { + return block.basefee; + } + + function extcodehash() public view returns (bytes32) { + return keccak256(address(this).code); + } + + function get_code() public view returns (bytes memory) { + return address(this).code; + } +} diff --git a/etherlink/bin_benchmark_producer/general_contracts/rec_call.sol b/etherlink/bin_benchmark_producer/general_contracts/rec_call.sol new file mode 100644 index 0000000000000000000000000000000000000000..96d1b65a49e6370143877f75db8dbbf3bac876de --- /dev/null +++ b/etherlink/bin_benchmark_producer/general_contracts/rec_call.sol @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2023 Marigold +// +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +contract A { + + // trigger an unbounded recursion + function call() public { + A callee = A(address(this)); + callee.call(); + } +} \ No newline at end of file diff --git a/etherlink/bin_benchmark_producer/general_contracts/revert_computation.sol b/etherlink/bin_benchmark_producer/general_contracts/revert_computation.sol new file mode 100644 index 0000000000000000000000000000000000000000..1a9a66904bcf94d04dfc5bd72759c6770301d394 --- /dev/null +++ b/etherlink/bin_benchmark_producer/general_contracts/revert_computation.sol @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2024 Functori +// +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.2 <0.9.0; + +contract DummyComputation { + uint256 public result; + + function addToFive(uint256 a) public { + result = a + 5; + + if (result > 10) { + revert("Result exceeds the maximum allowed value of 10"); + } + } +} diff --git a/etherlink/bin_benchmark_producer/general_contracts/selfdestruct.sol b/etherlink/bin_benchmark_producer/general_contracts/selfdestruct.sol new file mode 100644 index 0000000000000000000000000000000000000000..3c0a3450107fbfa3ed3715ff4aa5358f25efbb64 --- /dev/null +++ b/etherlink/bin_benchmark_producer/general_contracts/selfdestruct.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.8.17; + +contract SelfDestructExample { + + receive() external payable {} + + function close() public { + address payable addr = payable(address(msg.sender)); + selfdestruct(addr); + } +} diff --git a/etherlink/bin_benchmark_producer/general_contracts/storage.sol b/etherlink/bin_benchmark_producer/general_contracts/storage.sol new file mode 100644 index 0000000000000000000000000000000000000000..f12c2bb672eaa221d0f5b7fb6412650fa4303155 --- /dev/null +++ b/etherlink/bin_benchmark_producer/general_contracts/storage.sol @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2018 Tasuku Nakamura +// +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +contract SimpleStorage { + // State variable to store a number + uint public num; + + // You need to send a transaction to write to a state variable. + function set(uint _num) public { + num = _num; + } + + // You can read from a state variable without sending a transaction. + function get() public view returns (uint) { + return num; + } +} \ No newline at end of file diff --git a/etherlink/bin_benchmark_producer/general_contracts/verify_signature.sol b/etherlink/bin_benchmark_producer/general_contracts/verify_signature.sol new file mode 100644 index 0000000000000000000000000000000000000000..f103f605ad1c62dc962614d669e314cf42ef3522 --- /dev/null +++ b/etherlink/bin_benchmark_producer/general_contracts/verify_signature.sol @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: 2018 Tasuku Nakamura +// +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +/* Signature Verification + +How to Sign and Verify +# Signing +1. Create message to sign +2. Hash the message +3. Sign the hash (off chain, keep your private key secret) + +# Verify +1. Recreate hash from the original message +2. Recover signer from signature and hash +3. Compare recovered signer to claimed signer +*/ + +contract VerifySignature { + /* 1. Unlock MetaMask account + ethereum.enable() + */ + + /* 2. Get message hash to sign + getMessageHash( + 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C, + 123, + "coffee and donuts", + 1 + ) + + hash = "0xcf36ac4f97dc10d91fc2cbb20d718e94a8cbfe0f82eaedc6a4aa38946fb797cd" + */ + function getMessageHash( + address _to, + uint _amount, + string memory _message, + uint _nonce + ) public pure returns (bytes32) { + return keccak256(abi.encodePacked(_to, _amount, _message, _nonce)); + } + + /* 3. Sign message hash + # using browser + account = "copy paste account of signer here" + ethereum.request({ method: "personal_sign", params: [account, hash]}).then(console.log) + + # using web3 + web3.personal.sign(hash, web3.eth.defaultAccount, console.log) + + Signature will be different for different accounts + 0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b + */ + function getEthSignedMessageHash( + bytes32 _messageHash + ) public pure returns (bytes32) { + /* + Signature is produced by signing a keccak256 hash with the following format: + "\x19Ethereum Signed Message\n" + len(msg) + msg + */ + return + keccak256( + abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash) + ); + } + + /* 4. Verify signature + signer = 0xB273216C05A8c0D4F0a4Dd0d7Bae1D2EfFE636dd + to = 0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C + amount = 123 + message = "coffee and donuts" + nonce = 1 + signature = + 0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b + */ + function verify( + address _signer, + address _to, + uint _amount, + string memory _message, + uint _nonce, + bytes memory signature + ) public pure returns (bool) { + bytes32 messageHash = getMessageHash(_to, _amount, _message, _nonce); + bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash); + + return recoverSigner(ethSignedMessageHash, signature) == _signer; + } + + function recoverSigner( + bytes32 _ethSignedMessageHash, + bytes memory _signature + ) public pure returns (address) { + (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature); + + return ecrecover(_ethSignedMessageHash, v, r, s); + } + + function splitSignature( + bytes memory sig + ) public pure returns (bytes32 r, bytes32 s, uint8 v) { + require(sig.length == 65, "invalid signature length"); + + assembly { + /* + First 32 bytes stores the length of the signature + + add(sig, 32) = pointer of sig + 32 + effectively, skips first 32 bytes of signature + + mload(p) loads next 32 bytes starting at the memory address p into memory + */ + + // first 32 bytes, after the length prefix + r := mload(add(sig, 32)) + // second 32 bytes + s := mload(add(sig, 64)) + // final byte (first byte of the next 32 bytes) + v := byte(0, mload(add(sig, 96))) + } + + // implicitly return (r, s, v) + } +} \ No newline at end of file diff --git a/etherlink/bin_benchmark_producer/main.ml b/etherlink/bin_benchmark_producer/main.ml index 1731ad21614130e8005769cd0bf18865d3198065..ee7586085766f77b87a2ed6f1236ee10dca708a2 100644 --- a/etherlink/bin_benchmark_producer/main.ml +++ b/etherlink/bin_benchmark_producer/main.ml @@ -9,6 +9,17 @@ let () = Producer.register ~title:"general_contracts" ~registered:Primitives.registered_general_contracts - ~tx_per_call:1 - ~max_gas_per_tx:5_000_000 ; + ~tx_per_call:10 ; + Producer.register + ~title:"problematic_opcodes" + ~registered:Primitives.registered_problematic_opcodes + ~tx_per_call:10 ; + Producer.register + ~title:"gas_sinks" + ~registered:Primitives.registered_gas_sinks + ~tx_per_call:1 ; + Producer.register + ~title:"precompiled" + ~registered:Primitives.registered_precompiled + ~tx_per_call:1 ; Test.run () diff --git a/etherlink/bin_benchmark_producer/primitives.ml b/etherlink/bin_benchmark_producer/primitives.ml index d4a64956c64c3416adc3464e513baef58e03b920..ed7dad6b59c5c5682b823688d2e1d1e684b8a121 100644 --- a/etherlink/bin_benchmark_producer/primitives.ml +++ b/etherlink/bin_benchmark_producer/primitives.ml @@ -7,7 +7,13 @@ let evm_version = Evm_version.Cancun -let general_contracts = "etherlink/bin_benchmark_producer/general_contracts/" +let current = "etherlink/bin_benchmark_producer" + +let general_contracts = Filename.concat current "general_contracts" + +let problematic_opcodes = Filename.concat current "problematic_opcodes" + +let gas_sinks = Filename.concat current "gas_sinks" let addr1 = Eth_account.bootstrap_accounts.(1).address @@ -16,7 +22,7 @@ let addr2 = Eth_account.bootstrap_accounts.(2).address let registered_general_contracts = [ ( Solidity_contracts.compile_contract - ~source:(general_contracts ^ "erc20tok.sol") + ~source:(Filename.concat general_contracts "erc20tok.sol") ~label:"erc20tok" ~contract:"ERC20" evm_version, @@ -27,4 +33,209 @@ let registered_general_contracts = ("mint(uint256)", ["10000"]); ("burn(uint256)", ["1000"]); ] ); + ( Solidity_contracts.compile_contract + ~source:(Filename.concat general_contracts "erc1155.sol") + ~label:"erc1155_mint" + ~contract:"MyMultiToken" + evm_version, + [ + ("mint(uint256,uint256,bytes)", ["1"; "100"; "0x"]); + ("batchMint(uint256[],uint256[],bytes)", ["[1,2]"; "[100,200]"; "0x"]); + ("burn(uint256,uint256)", ["1"; "50"]); + ("batchBurn(uint256[],uint256[])", ["[1,2]"; "[50,100]"]); + ] ); + ( Solidity_contracts.compile_contract + ~source:(Filename.concat general_contracts "gas_guzzler.sol") + ~label:"gas_guzzler" + ~contract:"GasGuzzler" + evm_version, + [ + ("incrementLocalSum(uint256)", ["100"]); + ("incrementGlobalSum(uint256)", ["100"]); + ] ); + ( Solidity_contracts.compile_contract + ~source:(Filename.concat general_contracts "keccak.sol") + ~label:"keccak_guess" + ~contract:"GuessTheMagicWord" + evm_version, + [("guess(string)", ["Solidity"])] ); + ( Solidity_contracts.compile_contract + ~source:(Filename.concat general_contracts "loop.sol") + ~label:"loop" + ~contract:"Loop" + evm_version, + [("loop(uint256)", ["1000"])] ); + ( Solidity_contracts.compile_contract + ~source:(Filename.concat general_contracts "read_info.sol") + ~label:"read_info" + ~contract:"ReadInfo" + evm_version, + [ + ("timestamp()", []); + ("gas_price()", []); + ("coin_base()", []); + ("origin()", []); + ("gas_limit()", []); + ("chain_id()", []); + ("block_number()", []); + ("balance()", []); + ("base_fee()", []); + ("extcodehash()", []); + ("msize()", []); + ("get_code()", []); + ] ); + ( Solidity_contracts.compile_contract + ~source:(Filename.concat general_contracts "revert_computation.sol") + ~label:"dummy_computation" + ~contract:"DummyComputation" + evm_version, + [ + ("addToFive(uint256)", ["3"]); + ("addToFive(uint256)", ["6"]); + ("addToFive(uint256)", ["10"]); + ] ); + ( Solidity_contracts.compile_contract + ~source:(Filename.concat general_contracts "selfdestruct.sol") + ~label:"self_destruct_example" + ~contract:"SelfDestructExample" + evm_version, + [("close()", [])] ); + ( Solidity_contracts.compile_contract + ~source:(Filename.concat general_contracts "storage.sol") + ~label:"simple_storage" + ~contract:"SimpleStorage" + evm_version, + [("set(uint256)", ["42"]); ("get()", [])] ); + ( Solidity_contracts.compile_contract + ~source:(Filename.concat general_contracts "verify_signature.sol") + ~label:"verify_signature" + ~contract:"VerifySignature" + evm_version, + [ + ( "getMessageHash(address,uint256,string,uint256)", + [addr1; "123"; "\"coffee and donuts\""; "1"] ); + ( "getEthSignedMessageHash(bytes32)", + ["0xcf36ac4f97dc10d91fc2cbb20d718e94a8cbfe0f82eaedc6a4aa38946fb797cd"] + ); + ( "verify(address,address,uint256,string,uint256,bytes)", + [ + addr2; + addr1; + "123"; + "\"coffee and donuts\""; + "1"; + "0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b"; + ] ); + ( "recoverSigner(bytes32,bytes)", + [ + "0x65ba8777d4d56ae3e119b1b3f390c44fa24cdac2a2ef41c75be6352d3dd2dbfb"; + "0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b"; + ] ); + ( "splitSignature(bytes)", + [ + "0x993dab3dd91f5c6dc28e17439be475478f5635c92a56e17e82349d3fb2f166196f466c0b4e0c146f285204f0dcb13e5ae67bc33f4b888ec32dfe0a063e8f3f781b"; + ] ); + ] ); + ( Solidity_contracts.compile_contract + ~source:(Filename.concat general_contracts "rec_call.sol") + ~label:"rec_call" + ~contract:"A" + evm_version, + [("call()", [])] ); + ( Solidity_contracts.compile_contract + ~source:(Filename.concat general_contracts "external_recursive.sol") + ~label:"external_recursive" + ~contract:"RecursiveCaller" + evm_version, + [("recurse()", [])] ); + ] + +let registered_problematic_opcodes = + [ + ( Solidity_contracts.compile_contract + ~source:(Filename.concat problematic_opcodes "0x20.sol") + ~label:"use_sha3" + ~contract:"UseSHA3" + evm_version, + [("hashSomething(bytes)", ["0x123456"])] ); + ( Solidity_contracts.compile_contract + ~source:(Filename.concat problematic_opcodes "0x37.sol") + ~label:"use_calldata_copy" + ~contract:"UseCalldataCopy" + evm_version, + [("echo()", [])] ); + ( Solidity_contracts.compile_contract + ~source:(Filename.concat problematic_opcodes "0x55.sol") + ~label:"use_sstore" + ~contract:"UseSStore" + evm_version, + [("store(uint256)", ["42"])] ); + ( Solidity_contracts.compile_contract + ~source:(Filename.concat problematic_opcodes "0xf5.sol") + ~label:"use_create2" + ~contract:"Deployer" + evm_version, + [ + ( "deploy(bytes,bytes32)", + [ + "0x600060005560016000f3"; + "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; + ] ); + ] ); + ( Solidity_contracts.compile_contract + ~source:(Filename.concat problematic_opcodes "mcopy.sol") + ~label:"use_mcopy" + ~contract:"UseMCopy" + evm_version, + [("mcopy()", [])] ); + ( Solidity_contracts.compile_contract + ~source:(Filename.concat problematic_opcodes "tstore_tload.sol") + ~label:"use_transient_storage" + ~contract:"UseTransientStorage" + evm_version, + [("store(uint256,uint256)", ["1"; "42"]); ("load(uint256)", ["1"])] ); + ] + +let registered_gas_sinks = + [ + ( Solidity_contracts.compile_contract + ~source:(Filename.concat gas_sinks "create2_loop.sol") + ~label:"create2_loop" + ~contract:"DeployerLoop" + evm_version, + [ + ( "deployMany(bytes,bytes32)", + [ + "0x60006000556001600055"; + "0x0000000000000000000000000000000000000000000000000000000000000042"; + ] ); + ] ); + ( Solidity_contracts.compile_contract + ~source:(Filename.concat gas_sinks "mstore_loop.sol") + ~label:"mstore_loop" + ~contract:"MemoryBlower" + evm_version, + [("expandMemory()", [""])] ); + ( Solidity_contracts.compile_contract + ~source:(Filename.concat gas_sinks "sha3_loop.sol") + ~label:"sha3_loop" + ~contract:"UseSHA3Loop" + evm_version, + [("hashManyTimes(bytes)", ["0x1234"])] ); + ] + +let registered_precompiled = + [ + ( Solidity_contracts.precompiles evm_version, + [ + ("callEcRecover()", [""]); + ("callSha256()", [""]); + ("callRipemd160()", [""]); + ("callIdentity()", [""]); + ("callModexp()", [""]); + ("callEcAdd()", [""]); + ("callEcMul()", [""]); + ("callEcPairing()", [""]); + ("callBlake2f()", [""]); + ] ); ] diff --git a/etherlink/bin_benchmark_producer/problematic_opcodes/0x20.sol b/etherlink/bin_benchmark_producer/problematic_opcodes/0x20.sol new file mode 100644 index 0000000000000000000000000000000000000000..a01c8b024b92529420f4fa9d9a97593bb31612f3 --- /dev/null +++ b/etherlink/bin_benchmark_producer/problematic_opcodes/0x20.sol @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2025 Nomadic Labs +// +// SPDX-License-Identifier: MIT + +// Triggers SHA3 via keccak256() +contract UseSHA3 { + function hashSomething(bytes memory data) public pure returns (bytes32) { + return keccak256(data); // EVM emits 0x20 (SHA3) + } +} \ No newline at end of file diff --git a/etherlink/bin_benchmark_producer/problematic_opcodes/0x37.sol b/etherlink/bin_benchmark_producer/problematic_opcodes/0x37.sol new file mode 100644 index 0000000000000000000000000000000000000000..86dc4614ab15bb9ee12477f51fae6552cf7114f5 --- /dev/null +++ b/etherlink/bin_benchmark_producer/problematic_opcodes/0x37.sol @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2025 Nomadic Labs +// +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// Triggers CALLDATACOPY during calldata read +contract UseCalldataCopy { + function echo() public pure returns (bytes memory result) { + assembly { + // Allocate 36 bytes: 32 for length, 4 for data + result := mload(0x40) + mstore(result, 4) // set length = 4 + let dataStart := add(result, 32) + calldatacopy(dataStart, 0, 4) // copy first 4 bytes of calldata to result + mstore(0x40, add(dataStart, 32)) // update free memory pointer + } + } +} \ No newline at end of file diff --git a/etherlink/bin_benchmark_producer/problematic_opcodes/0x55.sol b/etherlink/bin_benchmark_producer/problematic_opcodes/0x55.sol new file mode 100644 index 0000000000000000000000000000000000000000..ec7b69b6cb5a78a9b0b27671b91a5e68a2b8a17d --- /dev/null +++ b/etherlink/bin_benchmark_producer/problematic_opcodes/0x55.sol @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2025 Nomadic Labs +// +// SPDX-License-Identifier: MIT + +// Triggers SSTORE by writing to storage +contract UseSStore { + uint256 public x; + + function store(uint256 value) public { + x = value; // This compiles down to an SSTORE (0x55) + } +} \ No newline at end of file diff --git a/etherlink/bin_benchmark_producer/problematic_opcodes/0xf5.sol b/etherlink/bin_benchmark_producer/problematic_opcodes/0xf5.sol new file mode 100644 index 0000000000000000000000000000000000000000..174b7585d02ca4494968e26b347a459b1f18e500 --- /dev/null +++ b/etherlink/bin_benchmark_producer/problematic_opcodes/0xf5.sol @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2025 Nomadic Labs +// +// SPDX-License-Identifier: MIT + +contract Deployer { + function deploy(bytes memory bytecode, bytes32 salt) public returns (address) { + address addr; + assembly { + addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt) // 0xf5 + } + require(addr != address(0), "CREATE2 failed"); + return addr; + } +} \ No newline at end of file diff --git a/etherlink/bin_benchmark_producer/problematic_opcodes/mcopy.sol b/etherlink/bin_benchmark_producer/problematic_opcodes/mcopy.sol new file mode 100644 index 0000000000000000000000000000000000000000..9d114929e2e51df55780577782e3cbe51e5ee1f7 --- /dev/null +++ b/etherlink/bin_benchmark_producer/problematic_opcodes/mcopy.sol @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2025 Nomadic Labs +// +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +contract UseMCopy { + function mcopy() external pure returns (bytes memory result) { + assembly { + let src := mload(0x40) + mstore(src, 0x12345678) + + let resultMem := add(src, 0x20) + mcopy(resultMem, src, 0x20) + + result := mload(0x40) + mstore(result, 0x20) // length = 32 bytes + mstore(add(result, 0x20), mload(resultMem)) // copy content + mstore(0x40, add(result, 0x40)) // move free memory pointer + } +} +} \ No newline at end of file diff --git a/etherlink/bin_benchmark_producer/problematic_opcodes/tstore_tload.sol b/etherlink/bin_benchmark_producer/problematic_opcodes/tstore_tload.sol new file mode 100644 index 0000000000000000000000000000000000000000..dc42c9fcb50c7459407eb3301c915d79d5b904c4 --- /dev/null +++ b/etherlink/bin_benchmark_producer/problematic_opcodes/tstore_tload.sol @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2025 Nomadic Labs +// +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +contract UseTransientStorage { + function store(uint256 key, uint256 value) external { + assembly { + tstore(key, value) + } + } + + function load(uint256 key) external view returns (uint256 val) { + assembly { + val := tload(key) + } + } +} \ No newline at end of file diff --git a/etherlink/bin_benchmark_producer/producer.ml b/etherlink/bin_benchmark_producer/producer.ml index 24408f99f84a04bd81c1e9ef7b27b5204638f8fd..b7130b92f253e284e9dadcdfdfecb31c12524cc2 100644 --- a/etherlink/bin_benchmark_producer/producer.ml +++ b/etherlink/bin_benchmark_producer/producer.ml @@ -8,7 +8,7 @@ open Test_helpers open Rpc.Syntax -let register ~title ~registered ~tx_per_call ~max_gas_per_tx = +let register ~title ~registered ~tx_per_call = Test.register ~__FILE__ ~title @@ -23,8 +23,12 @@ let register ~title ~registered ~tx_per_call ~max_gas_per_tx = Constant.smart_rollup_installer; ] @@ fun () -> - let gas_price = 10_000_000_000 in + (* max_gas_per_tx must be equivalent to kernel_latest/kernel/src/block.rs max_gas_per_reboot *) + let max_gas_per_tx = 30_000_000 * 4 / 3 in + (* da_fee_per_byte must be equivalent to kernel_latest/kernel/src/fees.rs DA_FEE_PER_BYTE *) let da_fee_per_byte = Wei.to_wei_z Z.(of_int 4 * pow (of_int 10) 12) in + (* gas_price must be big yumyum value kekw *) + let gas_price = 10_000_000_000 in let account = Eth_account.bootstrap_accounts.(0) in let patch_config = diff --git a/etherlink/kernel_latest/solidity_examples/precompiles.sol b/etherlink/kernel_latest/solidity_examples/precompiles.sol index 880321ab5c9ecb9cc44490379d3ff1d4a97960a2..825af0cbb6bcacd7db4e8c81cc2bd52785b2c625 100644 --- a/etherlink/kernel_latest/solidity_examples/precompiles.sol +++ b/etherlink/kernel_latest/solidity_examples/precompiles.sol @@ -31,7 +31,7 @@ contract PrecompileCaller { blake2fResult = callBlake2f(); } - function callEcRecover() internal pure returns (address) { + function callEcRecover() public pure returns (address) { bytes32 hash = 0x5e806a52647a95f9e0f46d31cbe51e1a99f531dbe3a5f4de7d7a2be1d781c4af; uint8 v = 27; bytes32 r = 0x0b5e42292e5fd622f034cb6cbd7749e8deefee81a9d569cb092e7e51d5365a6a; @@ -40,17 +40,17 @@ contract PrecompileCaller { return ecrecover(hash, v, r, s); } - function callSha256() internal pure returns (bytes32) { + function callSha256() public pure returns (bytes32) { bytes memory data = "Fast"; return sha256(data); } - function callRipemd160() internal pure returns (bytes20) { + function callRipemd160() public pure returns (bytes20) { bytes memory data = "Fair"; return ripemd160(data); } - function callIdentity() internal view returns (bytes memory) { + function callIdentity() public view returns (bytes memory) { bytes memory data = "Free"; bytes memory result = new bytes(data.length); assembly { @@ -69,7 +69,7 @@ contract PrecompileCaller { return result; } - function callModexp() internal view returns (bytes memory) { + function callModexp() public view returns (bytes memory) { bytes memory input = abi.encodePacked( uint256(0x02), // base uint256(0x03), // exponent @@ -92,7 +92,7 @@ contract PrecompileCaller { return result; } - function callEcAdd() internal view returns (uint256[2] memory) { + function callEcAdd() public view returns (uint256[2] memory) { uint8[2] memory input1 = [ 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000 @@ -118,7 +118,7 @@ contract PrecompileCaller { return result; } - function callEcMul() internal view returns (uint256[2] memory) { + function callEcMul() public view returns (uint256[2] memory) { // Point (1,2) on the elliptic curve and scalar 2 uint256[3] memory input = [uint256(1), uint256(2), uint256(2)]; uint256[2] memory result; @@ -138,7 +138,7 @@ contract PrecompileCaller { return result; } - function callEcPairing() internal view returns (bool) { + function callEcPairing() public view returns (bool) { uint8[1] memory inputPairing = [ 0x0000000000000000000000000000000000000000000000000000000000000000 ]; @@ -159,7 +159,7 @@ contract PrecompileCaller { return result; } - function callBlake2f() internal view returns (bytes memory) { + function callBlake2f() public view returns (bytes memory) { bytes memory blake2fInput = abi.encodePacked( hex"0000000048c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001" );