Skip to content

Commit

Permalink
add rust integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ahmadkaouk committed Aug 23, 2023
1 parent f110333 commit 332bde7
Show file tree
Hide file tree
Showing 4 changed files with 251 additions and 39 deletions.
27 changes: 27 additions & 0 deletions frame/evm/src/res/StorageGrowthTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.2;

contract StorageGrowthTest {
mapping(uint256 => uint256) public map;
uint256 foo;
uint256 bar;
uint256 baz;

constructor() {
foo = 1;
bar = 2;
baz = 3;
}

function store() public {
map[0] = 1;
map[1] = 2;
map[2] = 3;
}

function update() public {
foo = 2;
bar = 3;
baz = 4;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
608060405234801561001057600080fd5b506001808190555060028081905550600380819055506101c7806100356000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063975057e714610046578063a2e6204514610050578063b8dda9c71461005a575b600080fd5b61004e61008a565b005b6100586100d6565b005b610074600480360381019061006f919061011d565b6100f0565b6040516100819190610155565b60405180910390f35b6001600080808152602001908152602001600020819055506002600080600181526020019081526020016000208190555060036000806002815260200190815260200160002081905550565b600260018190555060036002819055506004600381905550565b60006020528060005260406000206000915090505481565b6000813590506101178161017a565b92915050565b60006020828403121561012f57600080fd5b600061013d84828501610108565b91505092915050565b61014f81610170565b82525050565b600060208201905061016a6000830184610146565b92915050565b6000819050919050565b61018381610170565b811461018e57600080fd5b5056fea2646970667358221220b25685afab962e465f0b43f6c4de10c82a565f50f6995c1cd444b002bcdd43e964736f6c63430008020033
26 changes: 13 additions & 13 deletions frame/evm/src/runner/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,21 +265,21 @@ where
None => 0,
};

let pov_gas = match executor.state().weight_info() {
Some(weight_info) => weight_info
.proof_size_usage
.unwrap_or_default()
.saturating_mul(T::GasLimitPovSizeRatio::get()),
None => 0,
};

// Post execution.
let used_gas = executor.used_gas();
let effective_gas = match executor.state().weight_info() {
Some(weight_info) => U256::from(sp_std::cmp::max(
sp_std::cmp::max(
used_gas,
weight_info
.proof_size_usage
.unwrap_or_default()
.saturating_mul(T::GasLimitPovSizeRatio::get()),
),
storage_gas,
)),
_ => used_gas.into(),
};
let effective_gas = U256::from(sp_std::cmp::max(
sp_std::cmp::max(used_gas, pov_gas),
storage_gas,
));

let actual_fee = effective_gas.saturating_mul(total_fee_per_gas);
let actual_base_fee = effective_gas.saturating_mul(base_fee);

Expand Down
236 changes: 210 additions & 26 deletions frame/evm/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -626,17 +626,24 @@ mod proof_size_test {

mod storage_growth_test {
use super::*;
use std::env;
use crate::tests::proof_size_test::PROOF_SIZE_TEST_CALLEE_CONTRACT_BYTECODE;
use fp_evm::{
ACCOUNT_CODES_KEY_SIZE, ACCOUNT_CODES_METADATA_PROOF_SIZE, ACCOUNT_STORAGE_PROOF_SIZE,
};

pub const STORAGE_GROWTH_TEST_CONTRACT: &str =
include_str!("./res/proof_size_test_contract_bytecode.txt");
const PROOF_SIZE_CALLEE_CONTRACT_BYTECODE_LEN: u64 = 116;
// The contract bytecode stored on chain.
const STORAGE_GROWTH_TEST_CONTRACT: &str =
include_str!("./res/storage_growth_test_contract_bytecode.txt");
const STORAGE_GROWTH_TEST_CONTRACT_BYTECODE_LEN: u64 = 455;

fn create_erc20_test_contract(
fn create_test_contract(
contract: &str,
gas_limit: u64,
) -> Result<CreateInfo, crate::RunnerError<crate::Error<Test>>> {
<Test as Config>::Runner::create(
H160::default(),
hex::decode(STORAGE_GROWTH_TEST_CONTRACT.trim_end()).unwrap(),
hex::decode(contract.trim_end()).expect("Failed to decode contract"),
U256::zero(),
gas_limit,
Some(FixedGasPrice::min_gas_price().0),
Expand All @@ -649,32 +656,209 @@ mod storage_growth_test {
gas_limit, true,
)),
Some(0),
&<Test as Config>::config().clone(),
&<Test as Config>::config(),
)
}

// This test is to ensure that the storage growth is accounted for correctly when deploying a
// a new contract. In this scenario, the contract is deployed with a gas limit that is not enough
// to cover the storage growth.
// Calls the given contract
fn call_test_contract(
contract_addr: H160,
call_data: &[u8],
value: U256,
gas_limit: u64,
) -> Result<CallInfo, crate::RunnerError<crate::Error<Test>>> {
<Test as Config>::Runner::call(
H160::default(),
contract_addr,
call_data.to_vec(),
value,
gas_limit,
Some(FixedGasPrice::min_gas_price().0),
None,
None,
Vec::new(),
true, // transactional
true, // must be validated
None,
Some(0),
&<Test as Config>::config(),
)
}

// Computes the expected gas for contract creation (related to storage growth).
// `byte_code_len` represents the length of the contract bytecode stored on-chain.
fn expected_contract_create_storage_growth_gas(bytecode_len: u64) -> u64 {
let ratio = <<Test as Config>::GasLimitStorageGrowthRatio as Get<u64>>::get();
(ACCOUNT_CODES_KEY_SIZE + ACCOUNT_CODES_METADATA_PROOF_SIZE + bytecode_len) * ratio
}

// Verifies that contract deployment fails when the necessary storage growth gas isn't
// provided, even if the gas limit surpasses the standard gas usage measured by the
// gasometer.
#[test]
fn contract_deployment_should_fail_oog() {
new_test_ext().execute_with(|| {
let gas_limit: u64 = 80_000;

let result = create_test_contract(PROOF_SIZE_TEST_CALLEE_CONTRACT_BYTECODE, gas_limit)
.expect("create succeeds");

// Assert that the legacy gas is lower than the gas limit.
assert!(result.used_gas.standard < U256::from(gas_limit));
assert_eq!(
result.exit_reason,
crate::ExitReason::Error(crate::ExitError::OutOfGas)
);
assert_eq!(
result.used_gas.effective.as_u64(),
expected_contract_create_storage_growth_gas(
PROOF_SIZE_CALLEE_CONTRACT_BYTECODE_LEN
)
);
// Assert that the contract entry does not exists in the storage.
assert!(!AccountCodes::<Test>::contains_key(result.value));
});
}

/// Test that contract deployment succeeds when the necessary storage growth gas is provided.
#[test]
fn contract_deployment_should_succeed() {
new_test_ext().execute_with(|| {
let gas_limit: u64 = 85_000;

let result = create_test_contract(PROOF_SIZE_TEST_CALLEE_CONTRACT_BYTECODE, gas_limit)
.expect("create succeeds");

assert_eq!(
result.used_gas.effective.as_u64(),
expected_contract_create_storage_growth_gas(
PROOF_SIZE_CALLEE_CONTRACT_BYTECODE_LEN
)
);
assert_eq!(
result.exit_reason,
crate::ExitReason::Succeed(ExitSucceed::Returned)
);
// Assert that the contract entry exists in the storage.
assert!(AccountCodes::<Test>::contains_key(result.value));
});
}

// Test that contract creation with code initialization that results in new storage entries
// succeeds when the necessary storage growth gas is provided.
#[test]
fn contract_creation_with_code_initialization_should_succeed() {
new_test_ext().execute_with(|| {
let gas_limit: u64 = 863_394;
let ratio = <<Test as Config>::GasLimitStorageGrowthRatio as Get<u64>>::get();
// The constructor of the contract creates 3 new storage entries (uint256). So,
// the expected gas is the gas for contract creation + 3 * ACCOUNT_STORAGE_PROOF_SIZE.
let expected_storage_growth_gas = expected_contract_create_storage_growth_gas(
STORAGE_GROWTH_TEST_CONTRACT_BYTECODE_LEN,
) + (3 * ACCOUNT_STORAGE_PROOF_SIZE * ratio);

// Deploy the contract.
let result = create_test_contract(STORAGE_GROWTH_TEST_CONTRACT, gas_limit)
.expect("create succeeds");

assert_eq!(
result.used_gas.effective.as_u64(),
expected_storage_growth_gas
);
assert_eq!(
result.exit_reason,
crate::ExitReason::Succeed(ExitSucceed::Returned)
);
});
}

// Verify that saving new entries fails when insufficient storage growth gas is supplied.
#[test]
fn store_new_entries_should_fail_oog() {
new_test_ext().execute_with(|| {
let gas_limit: u64 = 863_394;
// Deploy the contract.
let res = create_test_contract(STORAGE_GROWTH_TEST_CONTRACT, gas_limit)
.expect("create succeeds");
let contract_addr = res.value;

let gas_limit = 120_000;
// Call the contract method store to store new entries.
let result = call_test_contract(
contract_addr,
&hex::decode("975057e7").unwrap(),
U256::zero(),
gas_limit,
)
.expect("call should succeed");

assert_eq!(
result.exit_reason,
crate::ExitReason::Error(crate::ExitError::OutOfGas)
);
});
}

// Verify that saving new entries succeeds when sufficient storage growth gas is supplied.
#[test]
fn contract_deployement_should_fail_oog() {
fn store_new_entries_should_succeeds() {
new_test_ext().execute_with(|| {
let gas_limit: u64 = 4_700_000;

let result = create_erc20_test_contract(gas_limit).expect("create succeeds");
// The contract is deployed with a gas limit that is not enough to cover the storage
// growth. The contract creation should fail.
// assert_eq!(
// result.exit_reason,
// crate::ExitReason::Error(crate::ExitError::OutOfGas)
// );

// // The contract is deployed with a gas limit that is enough to cover the storage
// // growth. The contract creation should succeed.
// let gas_limit: u64 = 1_500_000;
// let result = create_erc20_test_contract(gas_limit).expect("create succeeds");

// assert_eq!(result.exit_reason, crate::ExitReason::Succeed(ExitSucceed::Returned));
let gas_limit: u64 = 863_394;
// Deploy the contract.
let res = create_test_contract(STORAGE_GROWTH_TEST_CONTRACT, gas_limit)
.expect("create succeeds");
let contract_addr = res.value;

let gas_limit = 128_000;
// Call the contract method store to store new entries.
let result = call_test_contract(
contract_addr,
&hex::decode("975057e7").unwrap(),
U256::zero(),
gas_limit,
)
.expect("call should succeed");

let expected_storage_growth_gas = 3
* ACCOUNT_STORAGE_PROOF_SIZE
* <<Test as Config>::GasLimitStorageGrowthRatio as Get<u64>>::get();
assert_eq!(
result.exit_reason,
crate::ExitReason::Succeed(ExitSucceed::Stopped)
);
assert_eq!(
result.used_gas.effective.as_u64(),
expected_storage_growth_gas
);
});
}

// Verify that updating existing storage entries does not incur any storage growth charges.
#[test]
fn update_exisiting_entries_succeeds() {
new_test_ext().execute_with(|| {
let gas_limit: u64 = 863_394;
// Deploy the contract.
let res = create_test_contract(STORAGE_GROWTH_TEST_CONTRACT, gas_limit)
.expect("create succeeds");
let contract_addr = res.value;

// Providing gas limit of 37_000 is enough to update existing entries, but not enough
// to store new entries.
let gas_limit = 37_000;
// Call the contract method update to update existing entries.
let result = call_test_contract(
contract_addr,
&hex::decode("a2e62045").unwrap(),
U256::zero(),
gas_limit,
)
.expect("call should succeed");

assert_eq!(
result.exit_reason,
crate::ExitReason::Succeed(ExitSucceed::Stopped)
);
});
}
}
Expand Down

0 comments on commit 332bde7

Please sign in to comment.