diff --git a/Cargo.lock b/Cargo.lock index 2ae9afc0b9..7fc459a1a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -702,7 +702,7 @@ dependencies = [ [[package]] name = "async-backing-primitives" version = "0.9.0" -source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#4742f8fb1a5efc5d165f7cb4137ee2cbdcb2969b" +source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#0e8f58525bd96345a0c5420082833afe2fe39d92" dependencies = [ "sp-api", "sp-consensus-slots", @@ -8894,7 +8894,7 @@ dependencies = [ [[package]] name = "nimbus-consensus" version = "0.9.0" -source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#4742f8fb1a5efc5d165f7cb4137ee2cbdcb2969b" +source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#0e8f58525bd96345a0c5420082833afe2fe39d92" dependencies = [ "async-backing-primitives", "async-trait", @@ -8934,7 +8934,7 @@ dependencies = [ [[package]] name = "nimbus-primitives" version = "0.9.0" -source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#4742f8fb1a5efc5d165f7cb4137ee2cbdcb2969b" +source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#0e8f58525bd96345a0c5420082833afe2fe39d92" dependencies = [ "async-trait", "frame-benchmarking", @@ -9491,7 +9491,7 @@ dependencies = [ [[package]] name = "pallet-async-backing" version = "0.9.0" -source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#4742f8fb1a5efc5d165f7cb4137ee2cbdcb2969b" +source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#0e8f58525bd96345a0c5420082833afe2fe39d92" dependencies = [ "cumulus-pallet-parachain-system", "cumulus-primitives-core", @@ -9511,7 +9511,7 @@ dependencies = [ [[package]] name = "pallet-author-inherent" version = "0.9.0" -source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#4742f8fb1a5efc5d165f7cb4137ee2cbdcb2969b" +source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#0e8f58525bd96345a0c5420082833afe2fe39d92" dependencies = [ "frame-benchmarking", "frame-support", @@ -10220,7 +10220,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-balances-erc20" version = "0.1.0" -source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#4742f8fb1a5efc5d165f7cb4137ee2cbdcb2969b" +source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#0e8f58525bd96345a0c5420082833afe2fe39d92" dependencies = [ "fp-evm", "frame-support", @@ -10243,7 +10243,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-batch" version = "0.1.0" -source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#4742f8fb1a5efc5d165f7cb4137ee2cbdcb2969b" +source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#0e8f58525bd96345a0c5420082833afe2fe39d92" dependencies = [ "evm", "fp-evm", @@ -10264,7 +10264,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-call-permit" version = "0.1.0" -source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#4742f8fb1a5efc5d165f7cb4137ee2cbdcb2969b" +source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#0e8f58525bd96345a0c5420082833afe2fe39d92" dependencies = [ "evm", "fp-evm", @@ -10296,7 +10296,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-proxy" version = "0.1.0" -source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#4742f8fb1a5efc5d165f7cb4137ee2cbdcb2969b" +source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#0e8f58525bd96345a0c5420082833afe2fe39d92" dependencies = [ "evm", "fp-evm", @@ -10336,7 +10336,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-xcm" version = "0.1.0" -source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#4742f8fb1a5efc5d165f7cb4137ee2cbdcb2969b" +source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#0e8f58525bd96345a0c5420082833afe2fe39d92" dependencies = [ "cumulus-primitives-core", "evm", @@ -10362,7 +10362,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-xcm-utils" version = "0.1.0" -source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#4742f8fb1a5efc5d165f7cb4137ee2cbdcb2969b" +source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#0e8f58525bd96345a0c5420082833afe2fe39d92" dependencies = [ "fp-evm", "frame-support", @@ -10384,7 +10384,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompileset-assets-erc20" version = "0.1.0" -source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#4742f8fb1a5efc5d165f7cb4137ee2cbdcb2969b" +source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#0e8f58525bd96345a0c5420082833afe2fe39d92" dependencies = [ "fp-evm", "frame-support", @@ -10508,7 +10508,7 @@ dependencies = [ [[package]] name = "pallet-foreign-asset-creator" version = "0.1.0" -source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#4742f8fb1a5efc5d165f7cb4137ee2cbdcb2969b" +source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#0e8f58525bd96345a0c5420082833afe2fe39d92" dependencies = [ "frame-benchmarking", "frame-support", @@ -10678,7 +10678,7 @@ dependencies = [ [[package]] name = "pallet-maintenance-mode" version = "0.1.0" -source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#4742f8fb1a5efc5d165f7cb4137ee2cbdcb2969b" +source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#0e8f58525bd96345a0c5420082833afe2fe39d92" dependencies = [ "cumulus-primitives-core", "frame-support", @@ -10729,7 +10729,7 @@ dependencies = [ [[package]] name = "pallet-migrations" version = "0.1.0" -source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#4742f8fb1a5efc5d165f7cb4137ee2cbdcb2969b" +source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#0e8f58525bd96345a0c5420082833afe2fe39d92" dependencies = [ "frame-benchmarking", "frame-support", @@ -11078,7 +11078,7 @@ dependencies = [ [[package]] name = "pallet-relay-storage-roots" version = "0.1.0" -source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#4742f8fb1a5efc5d165f7cb4137ee2cbdcb2969b" +source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#0e8f58525bd96345a0c5420082833afe2fe39d92" dependencies = [ "cumulus-pallet-parachain-system", "cumulus-primitives-core", @@ -22077,7 +22077,7 @@ dependencies = [ [[package]] name = "xcm-primitives" version = "0.1.0" -source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#4742f8fb1a5efc5d165f7cb4137ee2cbdcb2969b" +source = "git+https://github.com/moondance-labs/moonkit?branch=tanssi-polkadot-stable2412#0e8f58525bd96345a0c5420082833afe2fe39d92" dependencies = [ "frame-support", "impl-trait-for-tuples", diff --git a/chains/container-chains/runtime-templates/frontier/src/lib.rs b/chains/container-chains/runtime-templates/frontier/src/lib.rs index e10dd52826..d071bdef46 100644 --- a/chains/container-chains/runtime-templates/frontier/src/lib.rs +++ b/chains/container-chains/runtime-templates/frontier/src/lib.rs @@ -867,7 +867,8 @@ parameter_types! { pub WeightPerGas: Weight = Weight::from_parts(WEIGHT_PER_GAS, 0); pub SuicideQuickClearLimit: u32 = 0; pub GasLimitPovSizeRatio: u32 = 16; - pub GasLimitStorageGrowthRatio: u64 = 366; + /// Hardcoding the value, since it is computed on block execution. Check calculations in the tests + pub GasLimitStorageGrowthRatio: u64 = 1464; } impl_on_charge_evm_transaction!(); @@ -897,7 +898,7 @@ impl pallet_evm::Config for Runtime { type OnCreate = (); type FindAuthor = FindAuthorAdapter; type GasLimitPovSizeRatio = GasLimitPovSizeRatio; - type GasLimitStorageGrowthRatio = (); + type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio; type Timestamp = Timestamp; type WeightInfo = (); } @@ -1548,14 +1549,22 @@ impl_runtime_apis! { max_fee_per_gas: Option, max_priority_fee_per_gas: Option, nonce: Option, - _estimate: bool, + estimate: bool, access_list: Option)>>, ) -> Result { + let config = if estimate { + let mut config = ::config().clone(); + config.estimate = true; + Some(config) + } else { + None + }; let is_transactional = false; let validate = true; // Estimated encoded transaction size must be based on the heaviest transaction // type (EIP1559Transaction) to be compatible with all transaction types. + // TODO: remove, since we will get rid of base_cost let mut estimated_transaction_len = data.len() + // pallet ethereum index: 1 // transact call index: 1 @@ -1571,13 +1580,15 @@ impl_runtime_apis! { // 65 bytes signature 258; - if access_list.is_some() { - estimated_transaction_len += access_list.encoded_size(); + if let Some(ref list) = access_list { + estimated_transaction_len += list.encoded_size(); } + let gas_limit = gas_limit.min(u64::MAX.into()).low_u64(); let without_base_extrinsic_weight = true; - let (weight_limit, proof_size_base_cost) = - match ::GasWeightMapping::gas_to_weight( + + let (weight_limit, proof_size_base_cost) = match + ::GasWeightMapping::gas_to_weight( gas_limit, without_base_extrinsic_weight ) { @@ -1601,7 +1612,7 @@ impl_runtime_apis! { validate, weight_limit, proof_size_base_cost, - ::config(), + config.as_ref().unwrap_or(::config()), ).map_err(|err| err.error.into()) } @@ -1613,25 +1624,67 @@ impl_runtime_apis! { max_fee_per_gas: Option, max_priority_fee_per_gas: Option, nonce: Option, - _estimate: bool, + estimate: bool, access_list: Option)>>, ) -> Result { + let config = if estimate { + let mut config = ::config().clone(); + config.estimate = true; + Some(config) + } else { + None + }; let is_transactional = false; let validate = true; + + let mut estimated_transaction_len = data.len() + + // from: 20 + // value: 32 + // gas_limit: 32 + // nonce: 32 + // 1 byte transaction action variant + // chain id 8 bytes + // 65 bytes signature + 190; + + if max_fee_per_gas.is_some() { + estimated_transaction_len += 32; + } + if max_priority_fee_per_gas.is_some() { + estimated_transaction_len += 32; + } + if let Some(ref list) = access_list { + estimated_transaction_len += list.encoded_size(); + } + + let gas_limit = gas_limit.min(u64::MAX.into()).low_u64(); + let without_base_extrinsic_weight = true; + + let (weight_limit, proof_size_base_cost) = match + ::GasWeightMapping::gas_to_weight( + gas_limit, + without_base_extrinsic_weight + ) { + weight_limit if weight_limit.proof_size() > 0 => { + (Some(weight_limit), Some(estimated_transaction_len as u64)) + } + _ => (None, None), + }; + ::Runner::create( from, data, value, - gas_limit.min(u64::MAX.into()).low_u64(), + gas_limit, max_fee_per_gas, max_priority_fee_per_gas, nonce, access_list.unwrap_or_default(), is_transactional, validate, - None, - None, - ::config(), + weight_limit, + proof_size_base_cost, + config.as_ref().unwrap_or(::config()), ).map_err(|err| err.error.into()) } @@ -1844,3 +1897,19 @@ cumulus_pallet_parachain_system::register_validate_block! { CheckInherents = CheckInherents, BlockExecutor = pallet_author_inherent::BlockExecutor::, } + +#[cfg(test)] +mod tests { + use super::*; + + /// Block storage limit in bytes. Set to 40 KB. + const BLOCK_STORAGE_LIMIT: u64 = 40 * 1024; + + #[test] + fn check_ratio_constant() { + assert_eq!( + BlockGasLimit::get().min(u64::MAX.into()).low_u64() / BLOCK_STORAGE_LIMIT, + GasLimitStorageGrowthRatio::get() + ); + } +} diff --git a/chains/container-chains/runtime-templates/frontier/src/precompiles.rs b/chains/container-chains/runtime-templates/frontier/src/precompiles.rs index 7c18549dd4..da222f95de 100644 --- a/chains/container-chains/runtime-templates/frontier/src/precompiles.rs +++ b/chains/container-chains/runtime-templates/frontier/src/precompiles.rs @@ -19,7 +19,7 @@ use { xcm_config::{AssetId, ForeignAssetsInstance, XcmConfig}, AccountId, Balances, ForeignAssetsCreator, Runtime, }, - frame_support::parameter_types, + frame_support::{parameter_types, traits::ConstU64}, pallet_evm_precompile_balances_erc20::{Erc20BalancesPrecompile, Erc20Metadata}, pallet_evm_precompile_batch::BatchPrecompile, pallet_evm_precompile_call_permit::CallPermitPrecompile, @@ -71,6 +71,10 @@ pub const FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX: &[u8] = &[255u8; 18]; /// Const to identify ERC20_BALANCES_PRECOMPILE address pub const ERC20_BALANCES_PRECOMPILE: u64 = 2048; +/// System account size in bytes = Pallet_Name_Hash (16) + Storage_name_hash (16) + +/// Blake2_128Concat (16) + AccountId (20) + AccountInfo (4 + 12 + AccountData (4* 16)) = 148 +pub const SYSTEM_ACCOUNT_SIZE: u64 = 148; + parameter_types! { pub ForeignAssetPrefix: &'static [u8] = FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX; } @@ -99,7 +103,7 @@ type TemplatePrecompilesAt = ( // Template specific precompiles: PrecompileAt< AddressU64, - Erc20BalancesPrecompile, + Erc20BalancesPrecompile>, (CallableByContract, CallableByPrecompile), >, PrecompileAt, BatchPrecompile, SubcallWithMaxNesting<2>>, @@ -110,8 +114,8 @@ type TemplatePrecompilesAt = ( >, PrecompileAt< AddressU64<2051>, - XcmUtilsPrecompile, - CallableByContract>, + XcmUtilsPrecompile>, + CallableByContract>>, >, PrecompileAt< AddressU64<2052>, diff --git a/test/contracts/solidity/Fibonacci.sol b/test/contracts/solidity/Fibonacci.sol new file mode 100644 index 0000000000..cf24c7e11e --- /dev/null +++ b/test/contracts/solidity/Fibonacci.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity >=0.8.3; + +contract Fibonacci { + function fib2(uint256 n) public pure returns (uint256 b) { + if (n == 0) { + return 0; + } + uint256 a = 1; + b = 1; + for (uint256 i = 2; i < n; i++) { + uint256 c = a + b; + a = b; + b = c; + } + return b; + } +} diff --git a/test/contracts/solidity/StorageLoop.sol b/test/contracts/solidity/StorageLoop.sol new file mode 100644 index 0000000000..fcab685996 --- /dev/null +++ b/test/contracts/solidity/StorageLoop.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.2 <0.9.0; + +contract StorageLoop { + mapping(uint256 => uint256) public map; + mapping(uint256 => uint256) public map2; + + function store(uint16 n) public { + for (uint16 i = 0; i < n; i++) { + map[i] = i + 1; + } + } + + function store2(uint256 i) public { + map2[i] = i; + } +} diff --git a/test/suites/dev-frontier-template/test-precompiles/test-precompile-proxy.ts b/test/suites/dev-frontier-template/test-precompiles/test-precompile-proxy.ts index 1102eed2b3..de2cc03470 100644 --- a/test/suites/dev-frontier-template/test-precompiles/test-precompile-proxy.ts +++ b/test/suites/dev-frontier-template/test-precompiles/test-precompile-proxy.ts @@ -565,14 +565,13 @@ describeSuite({ .getTransactionReceipt({ hash: result2?.hash as `0x${string}` }); // The tx can create an account, so record 148 bytes of storage growth - // Storage growth ratio is 366 (Not defined in frontier template) - // storage_gas = 148 * 366 = 54168 + // Storage growth ratio is 1464 + // storage_gas = 148 * 1464 = 216672 // pov_gas = 5693 * 16 = 91088 - const expectedGas = 91_088n; + const expectedGas = 216672n; // Allow 10% variance - expect(gasUsed).toBeLessThanOrEqual((expectedGas * 110n) / 100n); - expect(gasUsed).toBeGreaterThanOrEqual((expectedGas * 90n) / 100n); + expect(gasUsed).toEqual(expectedGas); expect(await context.viem().getBalance({ address: randomAccount })).toBe(parseEther("5")); const balAfter = await context.viem().getBalance({ address: DOROTHY_ADDRESS }); diff --git a/test/suites/dev-frontier-template/test-storage-growth/test-block-storage-growth.ts b/test/suites/dev-frontier-template/test-storage-growth/test-block-storage-growth.ts new file mode 100644 index 0000000000..fb97bffad1 --- /dev/null +++ b/test/suites/dev-frontier-template/test-storage-growth/test-block-storage-growth.ts @@ -0,0 +1,36 @@ +import "@tanssi/api-augment"; +import { describeSuite, expect, fetchCompiledContract } from "@moonwall/cli"; +import { createEthersTransaction, sendRawTransaction } from "@moonwall/util"; +import { encodeDeployData } from "viem"; + +describeSuite({ + id: "DE1501", + title: "Storage Block (40kB) - Storage Growth Limit", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + it({ + id: "T01", + title: "should fill a block with 61 tx at most", + test: async () => { + const { abi, bytecode } = fetchCompiledContract("Fibonacci"); + const deployData = encodeDeployData({ + abi, + bytecode, + }); + + for (let i = 0; i < 300; i++) { + const rawTxn = await createEthersTransaction(context, { + data: deployData, + nonce: i, + gasLimit: 969168, // (112[account] + 550[contract]) * 1464[ratio] = 969168 + }); + + await sendRawTransaction(context, rawTxn); + } + + await context.createBlock(); + expect((await context.viem().getBlock()).transactions.length).toBe(61); + }, + }); + }, +}); diff --git a/test/suites/dev-frontier-template/test-storage-growth/test-evm-create-storage-growth.ts b/test/suites/dev-frontier-template/test-storage-growth/test-evm-create-storage-growth.ts new file mode 100644 index 0000000000..6697ab3aa1 --- /dev/null +++ b/test/suites/dev-frontier-template/test-storage-growth/test-evm-create-storage-growth.ts @@ -0,0 +1,64 @@ +import "@tanssi/api-augment"; +import { TransactionTypes, describeSuite, expect, fetchCompiledContract } from "@moonwall/cli"; +import { ALITH_ADDRESS, createEthersTransaction } from "@moonwall/util"; +import { expectEVMResult } from "helpers/eth-transactions"; + +describeSuite({ + id: "DE1502", + title: "Storage growth limit - Contract Creation", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + // This is the gas cost of the transaction that deploys the Fibonacci contract: + // (Account Code Size (112) + Length of the bytecode (550)) * Storage Growth Gas Ratio (1464) + // The length of the bytecode is in the generate Fibonacci.json + // file at 'contract.evm.deployedBytecode.object' + const EXPECTED_STORAGE_GROWTH_GAS = 969168; + for (const txnType of TransactionTypes) { + it({ + id: `T0${TransactionTypes.indexOf(txnType) + 1}`, + title: "should out of gas when gas provided is not enough to cover storage growth", + test: async () => { + const { bytecode } = fetchCompiledContract("Fibonacci"); + // Deploy contract with insufficient gas limit + const rawSigned = await createEthersTransaction(context, { + account: ALITH_ADDRESS, + data: bytecode, + gasLimit: EXPECTED_STORAGE_GROWTH_GAS - 1, + }); + + const { result } = await context.createBlock(rawSigned); + // Check that the transaction failed with an out of gas error + expectEVMResult(result.events, "Error", "OutOfGas"); + }, + }); + + it({ + id: `T0${TransactionTypes.indexOf(txnType) + 4}`, + title: "should estimate enough gas to cover storage growth", + test: async () => { + const estimatedGas = await context.viem().estimateGas({ + account: ALITH_ADDRESS, + data: fetchCompiledContract("Fibonacci").bytecode, + }); + + expect(estimatedGas).toBeGreaterThanOrEqual(EXPECTED_STORAGE_GROWTH_GAS); + expect(estimatedGas).toBe(1013235n); + }, + }); + + it({ + id: `T0${TransactionTypes.indexOf(txnType) + 7}`, + title: "should deploy contract with enough gas to cover storage growth", + test: async () => { + const contractData = fetchCompiledContract("Fibonacci"); + const callCode = (await context.viem().call({ data: contractData.bytecode, gas: 1013235n })).data; + const { contractAddress, hash } = await context.deployContract("Fibonacci"); + const { gasUsed } = await context.viem().getTransactionReceipt({ hash }); + expect(gasUsed).toEqual(BigInt(EXPECTED_STORAGE_GROWTH_GAS)); + const deployedCode = await context.viem("public").getBytecode({ address: contractAddress }); + expect(callCode).to.be.eq(deployedCode); + }, + }); + } + }, +}); diff --git a/test/suites/dev-frontier-template/test-storage-growth/test-evm-store-storage-growth.ts b/test/suites/dev-frontier-template/test-storage-growth/test-evm-store-storage-growth.ts new file mode 100644 index 0000000000..9f56f9a3f8 --- /dev/null +++ b/test/suites/dev-frontier-template/test-storage-growth/test-evm-store-storage-growth.ts @@ -0,0 +1,74 @@ +import "@tanssi/api-augment"; +import { TransactionTypes, beforeAll, deployCreateCompiledContract, describeSuite } from "@moonwall/cli"; +import { createEthersTransaction } from "@moonwall/util"; +import { expectEVMResult } from "helpers/eth-transactions"; +import { type Abi, encodeFunctionData } from "viem"; + +describeSuite({ + id: "DE1503", + title: "Storage growth limit - New Entries", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + let storageLoopAddress: `0x${string}`; + let storageLoopAbi: Abi; + + beforeAll(async () => { + const { contractAddress, abi } = await deployCreateCompiledContract(context, "StorageLoop"); + storageLoopAddress = contractAddress; + storageLoopAbi = abi; + + await context.createBlock(); + }); + + for (const txnType of TransactionTypes) { + it({ + id: `T0${TransactionTypes.indexOf(txnType) + 1}`, + title: "should out of gas when gas provided is not enough to cover storage growth", + test: async () => { + // Number of bytes added to storage for a new entry. + // const ACCOUNT_STORAGE_SIZE = 116; + // Ratio of gas to storage growth. (BlockGasLimit (15_000_000) / BlockStorageLimit (40kb)) + // const GAS_LIMIT_STORAGE_GROWTH_RATIO = 1464; + // Tx is creating 5 new storage entries. So, required gas is: + // (5 * ACCOUNT_STORAGE_SIZE) * GAS_LIMIT_STORAGE_GROWTH_RATIO = 849_120 + // Execute tx with insufficient gas limit + const rawSigned = await createEthersTransaction(context, { + to: storageLoopAddress, + data: encodeFunctionData({ + abi: storageLoopAbi, + functionName: "store", + // for each transaction type, we add 5 new storage entries + args: [5 + 5 * TransactionTypes.indexOf(txnType)], + }), + gasLimit: 849_120 - 10, + }); + + const { result } = await context.createBlock(rawSigned); + // Check that the transaction failed with an out of gas error + expectEVMResult(result.events, "Error", "OutOfGas"); + }, + }); + + it({ + id: `T0${TransactionTypes.indexOf(txnType) + 4}`, + title: "should successfully execute when adding 5 new storage entries", + test: async () => { + // Update 5 existing storage entries. So, required gas should be less than 849_120 + const rawSigned = await createEthersTransaction(context, { + to: storageLoopAddress, + data: encodeFunctionData({ + abi: storageLoopAbi, + functionName: "store", + args: [5], + }), + gasLimit: 849_120, + }); + + const { result } = await context.createBlock(rawSigned); + + expectEVMResult(result.events, "Succeed", "Stopped"); + }, + }); + } + }, +}); diff --git a/test/suites/dev-frontier-template/test-storage-growth/test-precompile-storage-growth.ts b/test/suites/dev-frontier-template/test-storage-growth/test-precompile-storage-growth.ts new file mode 100644 index 0000000000..66f5d05733 --- /dev/null +++ b/test/suites/dev-frontier-template/test-storage-growth/test-precompile-storage-growth.ts @@ -0,0 +1,127 @@ +import { describeSuite, expect, fetchCompiledContract } from "@moonwall/cli"; +import { + BALTATHAR_ADDRESS, + BALTATHAR_PRIVATE_KEY, + CONTRACT_PROXY_TYPE_ANY, + DOROTHY_ADDRESS, + DOROTHY_PRIVATE_KEY, + FAITH_ADDRESS, +} from "@moonwall/util"; +import { parseEther } from "ethers"; +import { expectEVMResult } from "helpers/eth-transactions"; +import { encodeFunctionData } from "viem"; + +const PRECOMPILE_NATIVE_ERC20_ADDRESS = "0x0000000000000000000000000000000000000800"; +const PRECOMPILE_PROXY_ADDRESS = "0x0000000000000000000000000000000000000805"; + +describeSuite({ + id: "DE1504", + title: "Storage growth limit - Precompiles", + foundationMethods: "dev", + testCases: ({ context, it, log }) => { + const newAccount = "0x1ced798a66b803d0dbb665680283980a939a6432"; + // The tx can create an account, so record 148 bytes of storage growth + // Storage growth ratio is 1464 + // expected_gas = 148 * 1464 = 216672 + const expectedGas = 216672n; + + it({ + id: "T01", + title: "should fail transfer due to insufficient gas required to cover the storage growth", + test: async () => { + const { abi: ierc20Abi } = fetchCompiledContract("IERC20"); + + const rawTxn = await context.writeContract?.({ + contractAddress: PRECOMPILE_PROXY_ADDRESS, + contractName: "Proxy", + functionName: "addProxy", + args: [BALTATHAR_ADDRESS, CONTRACT_PROXY_TYPE_ANY, 0], + privateKey: DOROTHY_PRIVATE_KEY, + rawTxOnly: true, + }); + const { result } = await context.createBlock(rawTxn); + expectEVMResult(result?.events, "Succeed"); + + const balBefore = await context.viem().getBalance({ address: FAITH_ADDRESS }); + const rawTxn2 = await context.writeContract?.({ + contractName: "Proxy", + functionName: "proxy", + contractAddress: PRECOMPILE_PROXY_ADDRESS, + args: [ + DOROTHY_ADDRESS, + PRECOMPILE_NATIVE_ERC20_ADDRESS, + encodeFunctionData({ + abi: ierc20Abi, + functionName: "transfer", + args: [newAccount, parseEther("5")], + }), + ], + privateKey: BALTATHAR_PRIVATE_KEY, + rawTxOnly: true, + gas: 216672n - 10n, // Not enough gas to cover the storage growth + }); + + const { result: result2 } = await context.createBlock(rawTxn2); + // Check that the transaction failed with an out of gas error + expectEVMResult(result2.events, "Error", "OutOfGas"); + + const balAfter = await context.viem().getBalance({ address: FAITH_ADDRESS }); + expect(balBefore - balAfter).to.equal(0n); + }, + }); + + it({ + id: "T02", + title: "should transfer correctly with the required gas to cover the storage growth", + test: async () => { + const balanceBefore = await context.viem().getBalance({ address: DOROTHY_ADDRESS }); + const { abi: ierc20Abi } = fetchCompiledContract("IERC20"); + const { abi: proxyAbi } = fetchCompiledContract("Proxy"); + + const proxyProxyEstimatedGas = await context.viem().estimateGas({ + account: BALTATHAR_ADDRESS, + to: PRECOMPILE_PROXY_ADDRESS, + data: encodeFunctionData({ + abi: proxyAbi, + functionName: "proxy", + args: [ + DOROTHY_ADDRESS, + PRECOMPILE_NATIVE_ERC20_ADDRESS, + encodeFunctionData({ + abi: ierc20Abi, + functionName: "transfer", + args: [newAccount, parseEther("5")], + }), + ], + }), + }); + + const rawTxn2 = await context.writeContract?.({ + contractName: "Proxy", + functionName: "proxy", + contractAddress: PRECOMPILE_PROXY_ADDRESS, + args: [ + DOROTHY_ADDRESS, + PRECOMPILE_NATIVE_ERC20_ADDRESS, + encodeFunctionData({ + abi: ierc20Abi, + functionName: "transfer", + args: [newAccount, parseEther("5")], + }), + ], + privateKey: BALTATHAR_PRIVATE_KEY, + rawTxOnly: true, + }); + + const { result } = await context.createBlock(rawTxn2); + expectEVMResult(result?.events, "Succeed"); + + const { gasUsed } = await context.viem().getTransactionReceipt({ hash: result?.hash as `0x${string}` }); + expect(gasUsed).to.equal(expectedGas); + + const balanceAfter = await context.viem().getBalance({ address: DOROTHY_ADDRESS }); + expect(balanceBefore - balanceAfter).to.equal(parseEther("5")); + }, + }); + }, +});