diff --git a/prdoc/pr_9414.prdoc b/prdoc/pr_9414.prdoc new file mode 100644 index 0000000000000..956155524907b --- /dev/null +++ b/prdoc/pr_9414.prdoc @@ -0,0 +1,13 @@ +title: '[pallet-revive] EVM backend: Implement tx, block system and call stack instructions' +doc: +- audience: Runtime Dev + description: |- + This PR is part of the road to EVM. + - Implement call and create frames, allowing to call and instantiate other contracts. + - Implement support for tx info, block info, system and contract opcodes. + - The `InstructionResult` <-> `ExecError` conversion functions. +crates: +- name: pallet-revive + bump: major +- name: pallet-revive-fixtures + bump: minor diff --git a/substrate/frame/revive/fixtures/contracts/BlockInfo.sol b/substrate/frame/revive/fixtures/contracts/BlockInfo.sol new file mode 100644 index 0000000000000..62bb799e6b9f7 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/BlockInfo.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract BlockInfo { + function blockNumber() public view returns (uint) { + return block.number; + } + + function coinbase() public view returns (address) { + return block.coinbase; + } + + function timestamp() public view returns (uint) { + return block.timestamp; + } + + function difficulty() public view returns (uint) { + return block.difficulty; + } + + function gaslimit() public view returns (uint) { + return block.gaslimit; + } + + function chainid() public view returns (uint) { + return block.chainid; + } + + function basefee() public view returns (uint) { + return block.basefee; + } +} diff --git a/substrate/frame/revive/fixtures/contracts/Callee.sol b/substrate/frame/revive/fixtures/contracts/Callee.sol new file mode 100644 index 0000000000000..668349bd9d09a --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/Callee.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0; + +contract Callee { + uint public stored; + + function echo(uint _data) external pure returns (uint data) { + data = _data; + } + + function whoSender() external view returns (address) { + return msg.sender; + } + + function store(uint _data) external { + stored = _data; + } + + function revert() public pure returns (uint256) { + require(false, "This is a revert"); + return 42; // never reached + } + + function invalid() public pure returns (uint256 result) { + assembly { + invalid() // 0xFE opcode + } + } + + function stop() public pure returns (uint256 result) { + assembly { + stop() + } + } +} diff --git a/substrate/frame/revive/fixtures/contracts/Caller.sol b/substrate/frame/revive/fixtures/contracts/Caller.sol new file mode 100644 index 0000000000000..39204f2fe963b --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/Caller.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0; + +contract Caller { + function normal( + address _callee, + uint _value, + bytes memory _data, + uint _gas + ) external returns (bool success, bytes memory output) { + (success, output) = _callee.call{value: _value, gas: _gas}(_data); + } + + function delegate( + address _callee, + bytes memory _data, + uint _gas + ) external returns (bool success, bytes memory output) { + (success, output) = _callee.delegatecall{gas: _gas}(_data); + } + + function staticCall( + // Don't rename to `static` (it's a Rust keyword). + address _callee, + bytes memory _data, + uint _gas + ) external view returns (bool success, bytes memory output) { + (success, output) = _callee.staticcall{gas: _gas}(_data); + } + + function create( + bytes memory initcode + ) external payable returns (address addr) { + assembly { + // CREATE with no value + addr := create(0, add(initcode, 0x20), mload(initcode)) + if iszero(addr) { + // bubble failure + let returnDataSize := returndatasize() + returndatacopy(0, 0, returnDataSize) + revert(0, returnDataSize) + } + } + } + + function create2( + bytes memory initcode, + bytes32 salt + ) external payable returns (address addr) { + assembly { + // CREATE2 with no value + addr := create2(0, add(initcode, 0x20), mload(initcode), salt) + if iszero(addr) { + // bubble failure + let returnDataSize := returndatasize() + returndatacopy(0, 0, returnDataSize) + revert(0, returnDataSize) + } + } + } +} diff --git a/substrate/frame/revive/fixtures/contracts/Dummy.sol b/substrate/frame/revive/fixtures/contracts/Dummy.sol new file mode 100644 index 0000000000000..ab359bd38cc2a --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/Dummy.sol @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8; + +contract Dummy {} diff --git a/substrate/frame/revive/fixtures/contracts/Host.sol b/substrate/frame/revive/fixtures/contracts/Host.sol new file mode 100644 index 0000000000000..f1e64d5d0e0dc --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/Host.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract Host { + function balance(address account) public view returns (uint256) { + return account.balance; + } + + function extcodesize(address account) public view returns (uint256) { + uint256 size; + assembly { + size := extcodesize(account) + } + return size; + } + + function extcodecopy( + address /* account */, + uint256 /* destOffset */, + uint256 /* offset */, + uint256 size + ) public pure returns (bytes memory) { + bytes memory code = new bytes(size); + return code; + } + + function extcodehash(address account) public view returns (bytes32) { + bytes32 hash; + assembly { + hash := extcodehash(account) + } + return hash; + } + + function blockhash(uint256 blockNumber) public view returns (bytes32) { + return blockhash(blockNumber); + } + + function sload(uint256 slot) public view returns (uint256) { + uint256 value; + assembly { + value := sload(slot) + } + return value; + } + + function sstore(uint256 slot, uint256 value) public returns (uint256) { + assembly { + sstore(slot, value) + } + return value; + } + + function tload(uint256 slot) public view returns (uint256) { + uint256 value; + assembly { + value := tload(slot) + } + return value; + } + + function tstore(uint256 slot, uint256 value) public returns (uint256) { + assembly { + tstore(slot, value) + } + return value; + } + + function log0(bytes32 data) public { + assembly { + log0(data, 0x20) + } + } + + function log1(bytes32 data, bytes32 topic1) public { + assembly { + log1(data, 0x20, topic1) + } + } + + function log2(bytes32 data, bytes32 topic1, bytes32 topic2) public { + assembly { + log2(data, 0x20, topic1, topic2) + } + } + + function log3( + bytes32 data, + bytes32 topic1, + bytes32 topic2, + bytes32 topic3 + ) public { + assembly { + log3(data, 0x20, topic1, topic2, topic3) + } + } + + function log4( + bytes32 data, + bytes32 topic1, + bytes32 topic2, + bytes32 topic3, + bytes32 topic4 + ) public { + assembly { + log4(data, 0x20, topic1, topic2, topic3, topic4) + } + } + + function selfdestruct(address payable recipient) public { + selfdestruct(recipient); + } + + function selfbalance() public view returns (uint256) { + return address(this).balance; + } +} diff --git a/substrate/frame/revive/fixtures/contracts/System.sol b/substrate/frame/revive/fixtures/contracts/System.sol new file mode 100644 index 0000000000000..6338488c75740 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/System.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +contract System { + function keccak256Func(bytes memory data) public pure returns (bytes32) { + return keccak256(data); + } + + function addressFunc() public view returns (address) { + return address(this); + } + + function caller() public view returns (address) { + return msg.sender; + } + + function callvalue() public payable returns (uint256) { + return msg.value; + } + + function calldataload(uint256 offset) public pure returns (bytes32) { + bytes32 data; + assembly { + data := calldataload(offset) + } + return data; + } + + function calldatasize() public pure returns (uint256) { + return msg.data.length; + } + + function calldatacopy( + uint256 destOffset, + uint256 offset, + uint256 size + ) public pure returns (bytes memory) { + bytes memory data = new bytes(size); + assembly { + calldatacopy(add(data, 0x20), offset, size) + } + return data; + } + + function codesize() public pure returns (uint256) { + uint256 size; + assembly { + size := codesize() + } + return size; + } + + function codecopy( + uint256 /* destOffset */, + uint256 /* offset */, + uint256 size + ) public pure returns (bytes memory) { + bytes memory code = new bytes(size); + return code; + } + + function returndatasize( + address _callee, + bytes memory _data, + uint _gas + ) public returns (uint256) { + uint256 size; + _callee.staticcall{gas: _gas}(_data); + assembly { + size := returndatasize() + } + return size; + } + + function returndatacopy( + address _callee, + bytes memory _data, + uint _gas, + uint256 destOffset, + uint256 offset, + uint256 size + ) public returns (bytes memory) { + bytes memory data = new bytes(size); + _callee.staticcall{gas: _gas}(_data); + assembly { + returndatacopy(add(data, 0x20), offset, size) + } + return data; + } + + function gas() public view returns (uint256) { + return gasleft(); + } +} diff --git a/substrate/frame/revive/fixtures/contracts/TransactionInfo.sol b/substrate/frame/revive/fixtures/contracts/TransactionInfo.sol new file mode 100644 index 0000000000000..e07d890c90719 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/TransactionInfo.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract TransactionInfo { + function origin() public view returns (address) { + return tx.origin; + } + + function gasprice() public view returns (uint256) { + return tx.gasprice; + } + + function blobhash(uint256 index) public view returns (bytes32) { + return blobhash(index); + } +} diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 41fe73aee2094..d6ba7940b3cb8 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -25,9 +25,9 @@ use crate::{ storage::{self, meter::Diff, AccountIdOrAddress, WriteOutcome}, tracing::if_tracing, transient_storage::TransientStorage, - AccountInfo, AccountInfoOf, BalanceOf, BalanceWithDust, CodeInfo, CodeInfoOf, CodeRemoved, - Config, ContractInfo, Error, Event, ImmutableData, ImmutableDataOf, Pallet as Contracts, - RuntimeCosts, LOG_TARGET, + AccountInfo, AccountInfoOf, BalanceOf, BalanceWithDust, Code, CodeInfo, CodeInfoOf, + CodeRemoved, Config, ContractInfo, Error, Event, ImmutableData, ImmutableDataOf, + Pallet as Contracts, RuntimeCosts, LOG_TARGET, }; use alloc::vec::Vec; use core::{fmt::Debug, marker::PhantomData, mem}; @@ -182,7 +182,6 @@ impl Origin { } } } - /// Environment functions only available to host functions. pub trait Ext: PrecompileWithInfoExt { /// Execute code in the current frame. @@ -266,7 +265,7 @@ pub trait PrecompileWithInfoExt: PrecompileExt { &mut self, gas_limit: Weight, deposit_limit: U256, - code: H256, + code: Code, value: U256, input_data: Vec, salt: Option<&[u8; 32]>, @@ -383,6 +382,12 @@ pub trait PrecompileExt: sealing::Sealed { /// Returns the author of the current block. fn block_author(&self) -> Option; + /// Returns the block gas limit. + fn gas_limit(&self) -> u64; + + /// Returns the chain id. + fn chain_id(&self) -> u64; + /// Returns the maximum allowed size of a storage item. fn max_value_size(&self) -> u32; @@ -454,6 +459,9 @@ pub trait Executable: Sized { /// Charges size base load weight from the gas meter. fn from_storage(code_hash: H256, gas_meter: &mut GasMeter) -> Result; + /// Load the executable from EVM bytecode + fn from_evm_init_code(code: Vec, owner: AccountIdOf) -> Result; + /// Execute the specified exported function and return the result. /// /// When the specified function is `Constructor` the executable is stored and its @@ -1794,7 +1802,7 @@ where &mut self, gas_limit: Weight, deposit_limit: U256, - code_hash: H256, + code: Code, value: U256, input_data: Vec, salt: Option<&[u8; 32]>, @@ -1802,9 +1810,16 @@ where // We reset the return data now, so it is cleared out even if no new frame was executed. // This is for example the case when creating the frame fails. *self.last_frame_output_mut() = Default::default(); - - let executable = E::from_storage(code_hash, self.gas_meter_mut())?; - let sender = &self.top_frame().account_id; + let sender = self.top_frame().account_id.clone(); + let executable = match &code { + Code::Upload(bytecode) => { + if !T::AllowEVMBytecode::get() { + return Err(>::CodeRejected.into()); + } + E::from_evm_init_code(bytecode.clone(), sender.clone())? + }, + Code::Existing(hash) => E::from_storage(*hash, self.gas_meter_mut())?, + }; let executable = self.push_frame( FrameArgs::Instantiate { sender: sender.clone(), @@ -1818,7 +1833,7 @@ where self.is_read_only(), )?; let address = T::AddressMapper::to_address(&self.top_frame().account_id); - if_tracing(|t| t.instantiate_code(&crate::Code::Existing(code_hash), salt)); + if_tracing(|t| t.instantiate_code(&code, salt)); self.run(executable.expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE), input_data, BumpNonce::Yes) .map(|_| address) } @@ -2050,7 +2065,15 @@ where } fn block_author(&self) -> Option { - crate::Pallet::::block_author() + Contracts::::block_author() + } + + fn gas_limit(&self) -> u64 { + ::BlockWeights::get().max_block.ref_time() + } + + fn chain_id(&self) -> u64 { + ::ChainId::get() } fn max_value_size(&self) -> u32 { diff --git a/substrate/frame/revive/src/exec/mock_ext.rs b/substrate/frame/revive/src/exec/mock_ext.rs index 28505b7815e69..923eb669784ab 100644 --- a/substrate/frame/revive/src/exec/mock_ext.rs +++ b/substrate/frame/revive/src/exec/mock_ext.rs @@ -23,7 +23,7 @@ use crate::{ precompiles::Diff, storage::{ContractInfo, WriteOutcome}, transient_storage::TransientStorage, - CodeRemoved, Config, ExecReturnValue, ImmutableData, + Code, CodeRemoved, Config, ExecReturnValue, ImmutableData, }; use alloc::vec::Vec; use core::marker::PhantomData; @@ -140,6 +140,14 @@ impl PrecompileExt for MockExt { panic!("MockExt::block_author") } + fn gas_limit(&self) -> u64 { + panic!("MockExt::gas_limit") + } + + fn chain_id(&self) -> u64 { + panic!("MockExt::chain_id") + } + fn max_value_size(&self) -> u32 { panic!("MockExt::max_value_size") } @@ -219,7 +227,7 @@ impl PrecompileWithInfoExt for MockExt { &mut self, _gas_limit: Weight, _deposit_limit: U256, - _code: H256, + _code: Code, _value: U256, _input_data: Vec, _salt: Option<&[u8; 32]>, diff --git a/substrate/frame/revive/src/exec/tests.rs b/substrate/frame/revive/src/exec/tests.rs index 9dfb213cbf52e..163aa1cbf3258 100644 --- a/substrate/frame/revive/src/exec/tests.rs +++ b/substrate/frame/revive/src/exec/tests.rs @@ -148,6 +148,13 @@ impl Executable for MockExecutable { }) } + fn from_evm_init_code( + _code: Vec, + _owner: AccountIdOf, + ) -> Result { + unimplemented!() + } + fn execute>( self, ext: &mut E, @@ -1161,7 +1168,7 @@ fn instantiation_from_contract() { .instantiate( Weight::MAX, U256::MAX, - dummy_ch, + Code::Existing(dummy_ch), Pallet::::convert_native_to_evm(min_balance), vec![], Some(&[48; 32]), @@ -1228,7 +1235,7 @@ fn instantiation_traps() { ctx.ext.instantiate( Weight::zero(), U256::zero(), - dummy_ch, + Code::Existing(dummy_ch), value, vec![], Some(&[0; 32]), @@ -1580,7 +1587,7 @@ fn nonce() { .instantiate( Weight::MAX, U256::MAX, - fail_code, + Code::Existing(fail_code), ctx.ext.minimum_balance() * 100, vec![], Some(&[0; 32]), @@ -1597,7 +1604,7 @@ fn nonce() { .instantiate( Weight::MAX, U256::MAX, - success_code, + Code::Existing(success_code), ctx.ext.minimum_balance() * 100, vec![], Some(&[0; 32]), @@ -2325,8 +2332,10 @@ fn last_frame_output_works_on_instantiate() { let value = Pallet::::convert_native_to_evm(min_balance); // Successful instantiation should set the output - let address = - ctx.ext.instantiate(Weight::MAX, U256::MAX, ok_ch, value, vec![], None).unwrap(); + let address = ctx + .ext + .instantiate(Weight::MAX, U256::MAX, Code::Existing(ok_ch), value, vec![], None) + .unwrap(); assert_eq!( ctx.ext.last_frame_output(), &ExecReturnValue { flags: ReturnFlags::empty(), data: vec![127] } @@ -2348,7 +2357,14 @@ fn last_frame_output_works_on_instantiate() { // Reverted instantiation should set the output ctx.ext - .instantiate(Weight::zero(), U256::zero(), revert_ch, value, vec![], None) + .instantiate( + Weight::zero(), + U256::zero(), + Code::Existing(revert_ch), + value, + vec![], + None, + ) .unwrap(); assert_eq!( ctx.ext.last_frame_output(), @@ -2357,7 +2373,14 @@ fn last_frame_output_works_on_instantiate() { // Trapped instantiation should clear the output ctx.ext - .instantiate(Weight::zero(), U256::zero(), trap_ch, value, vec![], None) + .instantiate( + Weight::zero(), + U256::zero(), + Code::Existing(trap_ch), + value, + vec![], + None, + ) .unwrap_err(); assert_eq!( ctx.ext.last_frame_output(), @@ -2498,7 +2521,7 @@ fn last_frame_output_is_always_reset() { ctx.ext.instantiate( Weight::zero(), U256::zero(), - invalid_code_hash, + Code::Existing(invalid_code_hash), U256::zero(), vec![], None, @@ -2547,7 +2570,7 @@ fn immutable_data_access_checks_work() { // Constructors can not access the immutable data ctx.ext - .instantiate(Weight::MAX, U256::MAX, dummy_ch, value, vec![], None) + .instantiate(Weight::MAX, U256::MAX, Code::Existing(dummy_ch), value, vec![], None) .unwrap(); exec_success() @@ -2714,7 +2737,7 @@ fn immutable_data_set_errors_with_empty_data() { let value = Pallet::::convert_native_to_evm(min_balance); ctx.ext - .instantiate(Weight::MAX, U256::MAX, dummy_ch, value, vec![], None) + .instantiate(Weight::MAX, U256::MAX, Code::Existing(dummy_ch), value, vec![], None) .unwrap(); exec_success() diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs index 8fb426446abaa..518460c75714d 100644 --- a/substrate/frame/revive/src/limits.rs +++ b/substrate/frame/revive/src/limits.rs @@ -220,7 +220,7 @@ pub mod code { Error::::CodeRejected })?; - log::debug!( + log::trace!( target: LOG_TARGET, "Contract memory usage: purgable={}/{} KB baseline={}/{}", program_info.purgeable_ram_consumption, PURGABLE_MEMORY_LIMIT, program_info.baseline_ram_consumption, BASELINE_MEMORY_LIMIT, diff --git a/substrate/frame/revive/src/tests/sol.rs b/substrate/frame/revive/src/tests/sol.rs index 5fe1f578fdf34..2c246546e81a6 100644 --- a/substrate/frame/revive/src/tests/sol.rs +++ b/substrate/frame/revive/src/tests/sol.rs @@ -30,6 +30,11 @@ use frame_support::traits::fungible::Mutate; use pallet_revive_fixtures::{compile_module_with_type, Fibonacci, FixtureType}; use pretty_assertions::assert_eq; +mod block_info; +mod contract; +mod system; +mod tx_info; + #[test] fn basic_evm_flow_works() { let (code, init_hash) = compile_module_with_type("Fibonacci", FixtureType::Solc).unwrap(); diff --git a/substrate/frame/revive/src/tests/sol/block_info.rs b/substrate/frame/revive/src/tests/sol/block_info.rs new file mode 100644 index 0000000000000..158100805cfe1 --- /dev/null +++ b/substrate/frame/revive/src/tests/sol/block_info.rs @@ -0,0 +1,195 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The pallet-revive shared VM integration test suite. + +use crate::{ + test_utils::{builder::Contract, ALICE}, + tests::{builder, Contracts, ExtBuilder, System, Test, Timestamp}, + vm::evm::{U256Converter, BASE_FEE, DIFFICULTY}, + Code, Config, +}; + +use alloy_core::{primitives::U256, sol_types::SolInterface}; +use frame_support::traits::fungible::Mutate; +use pallet_revive_fixtures::{compile_module_with_type, BlockInfo, FixtureType}; +use pretty_assertions::assert_eq; +use sp_core::H160; + +/// Tests that the blocknumber opcode works as expected. +#[test] +fn block_number_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("BlockInfo", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + System::set_block_number(42); + + let result = builder::bare_call(addr) + .data( + BlockInfo::BlockInfoCalls::blockNumber(BlockInfo::blockNumberCall {}) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + U256::from(42u32), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()) + ); + }); + } +} + +/// Tests that the blockauthor opcode works as expected. +#[test] +fn block_author_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("BlockInfo", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let result = builder::bare_call(addr) + .data(BlockInfo::BlockInfoCalls::coinbase(BlockInfo::coinbaseCall {}).abi_encode()) + .build_and_unwrap_result(); + assert_eq!( + Contracts::block_author().unwrap(), + // Padding is used into the 32 bytes + H160::from_slice(&result.data[12..]) + ); + }); + } +} + +/// Tests that the chainid opcode works as expected. +#[test] +fn chainid_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("BlockInfo", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let result = builder::bare_call(addr) + .data(BlockInfo::BlockInfoCalls::chainid(BlockInfo::chainidCall {}).abi_encode()) + .build_and_unwrap_result(); + assert_eq!( + U256::from(::ChainId::get()), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()) + ); + }); + } +} + +/// Tests that the timestamp opcode works as expected. +#[test] +fn timestamp_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("BlockInfo", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + Timestamp::set_timestamp(2000); + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let result = builder::bare_call(addr) + .data( + BlockInfo::BlockInfoCalls::timestamp(BlockInfo::timestampCall {}).abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + // Solidity expects timestamps in seconds, whereas pallet_timestamp uses + // milliseconds. + U256::from(Timestamp::get() / 1000), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()) + ); + }); + } +} + +/// Tests that the gaslimit opcode works as expected. +#[test] +fn gaslimit_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("BlockInfo", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let result = builder::bare_call(addr) + .data(BlockInfo::BlockInfoCalls::gaslimit(BlockInfo::gaslimitCall {}).abi_encode()) + .build_and_unwrap_result(); + assert_eq!( + U256::from( + ::BlockWeights::get().max_block.ref_time() + ), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()) + ); + }); + } +} + +/// Tests that the basefee opcode works as expected. +#[test] +fn basefee_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("BlockInfo", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let result = builder::bare_call(addr) + .data(BlockInfo::BlockInfoCalls::basefee(BlockInfo::basefeeCall {}).abi_encode()) + .build_and_unwrap_result(); + assert_eq!( + BASE_FEE.into_revm_u256(), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()) + ); + }); + } +} + +/// Tests that the difficulty opcode works as expected. +#[test] +fn difficulty_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("BlockInfo", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let result = builder::bare_call(addr) + .data( + BlockInfo::BlockInfoCalls::difficulty(BlockInfo::difficultyCall {}) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + // Alligned with the value set for PVM + U256::from(DIFFICULTY), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()) + ); + }); + } +} diff --git a/substrate/frame/revive/src/tests/sol/contract.rs b/substrate/frame/revive/src/tests/sol/contract.rs new file mode 100644 index 0000000000000..583e47a8bd000 --- /dev/null +++ b/substrate/frame/revive/src/tests/sol/contract.rs @@ -0,0 +1,456 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The pallet-revive shared VM integration test suite. + +use crate::{ + test_utils::{builder::Contract, ALICE, ALICE_ADDR}, + tests::{builder, ExtBuilder, Test}, + Code, Config, Error, +}; +use alloy_core::{ + primitives::{Bytes, FixedBytes, U256}, + sol_types::SolCall, +}; +use frame_support::{assert_err, traits::fungible::Mutate}; +use pallet_revive_fixtures::{compile_module_with_type, Callee, Caller, FixtureType}; +use pretty_assertions::assert_eq; +use sp_core::H160; + +/// Tests that the `CALL` opcode works as expected by having one contract call another. +#[test] +fn staticcall_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (caller_code, _) = compile_module_with_type("Caller", fixture_type).unwrap(); + let (callee_code, _) = compile_module_with_type("Callee", fixture_type).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + + // Instantiate the callee contract, which can echo a value. + let Contract { addr: callee_addr, .. } = + builder::bare_instantiate(Code::Upload(callee_code)).build_and_unwrap_contract(); + + log::info!("Callee addr: {:?}", callee_addr); + + // Instantiate the caller contract. + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(caller_code)).build_and_unwrap_contract(); + + log::info!("Caller addr: {:?}", caller_addr); + + let magic_number = U256::from(42); + log::info!("Calling callee from caller"); + let result = builder::bare_call(caller_addr) + .data( + Caller::staticCallCall { + _callee: callee_addr.0.into(), + _data: Callee::echoCall { _data: magic_number }.abi_encode().into(), + _gas: U256::MAX, + } + .abi_encode(), + ) + .build_and_unwrap_result(); + + let result = Caller::staticCallCall::abi_decode_returns(&result.data).unwrap(); + assert!(result.success, "the call must succeed"); + assert_eq!( + magic_number, + U256::from_be_bytes::<32>(result.output.as_ref().try_into().unwrap()), + "the call must reproduce the magic number" + ); + + // Enable it once sstore host fn is implemented + // log::info!("Calling callee from caller"); + // let result = builder::bare_call(caller_addr) + // .data( + // Caller::staticCallCall { + // _callee: callee_addr.0.into(), + // _data: Callee::storeCall { _data: magic_number }.abi_encode().into(), + // _gas: U256::MAX, + // } + // .abi_encode(), + // ) + // .build_and_unwrap_result(); + + // let result = Caller::staticCallCall::abi_decode_returns(&result.data).unwrap(); + // assert!(!result.success, "Can not store in static call"); + }); + } +} + +#[test] +fn call_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (caller_code, _) = compile_module_with_type("Caller", fixture_type).unwrap(); + let (callee_code, _) = compile_module_with_type("Callee", fixture_type).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + + // Instantiate the callee contract, which can echo a value. + let Contract { addr: callee_addr, .. } = + builder::bare_instantiate(Code::Upload(callee_code)).build_and_unwrap_contract(); + + log::info!("Callee addr: {:?}", callee_addr); + + // Instantiate the caller contract. + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(caller_code)).build_and_unwrap_contract(); + + log::info!("Caller addr: {:?}", caller_addr); + + let magic_number = U256::from(42); + log::info!("Calling callee from caller"); + let result = builder::bare_call(caller_addr) + .data( + Caller::normalCall { + _callee: callee_addr.0.into(), + _value: U256::ZERO, + _data: Callee::echoCall { _data: magic_number }.abi_encode().into(), + _gas: U256::MAX, + } + .abi_encode(), + ) + .build_and_unwrap_result(); + + let result = Caller::normalCall::abi_decode_returns(&result.data).unwrap(); + assert!(result.success, "the call must succeed"); + assert_eq!( + magic_number, + U256::from_be_bytes::<32>(result.output.as_ref().try_into().unwrap()), + "the call must reproduce the magic number" + ); + + // Enable it once sstore host fn is implemented + // log::info!("Calling callee from caller"); + // let result = builder::bare_call(caller_addr) + // .data( + // Caller::normalCall { + // _callee: callee_addr.0.into(), + // _value: U256::ZERO, + // _data: Callee::storeCall { _data: magic_number }.abi_encode().into(), + // _gas: U256::MAX, + // } + // .abi_encode(), + // ) + // .build_and_unwrap_result(); + + // let result = Caller::normalCall::abi_decode_returns(&result.data).unwrap(); + // assert!(result.success, "the store call must succeed"); + }); + } +} + +#[test] +fn call_revert() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (caller_code, _) = compile_module_with_type("Caller", fixture_type).unwrap(); + let (callee_code, _) = compile_module_with_type("Callee", fixture_type).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + + // Instantiate the callee contract, which can echo a value. + let Contract { addr: callee_addr, .. } = + builder::bare_instantiate(Code::Upload(callee_code)).build_and_unwrap_contract(); + + log::info!("Callee addr: {:?}", callee_addr); + + // Instantiate the caller contract. + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(caller_code)).build_and_unwrap_contract(); + + log::info!("Caller addr: {:?}", caller_addr); + + // Call revert and assert failure + let result = builder::bare_call(caller_addr) + .data( + Caller::normalCall { + _callee: callee_addr.0.into(), + _value: U256::ZERO, + _data: Callee::revertCall {}.abi_encode().into(), + _gas: U256::MAX, + } + .abi_encode(), + ) + .build_and_unwrap_result(); + let result = Caller::normalCall::abi_decode_returns(&result.data).unwrap(); + assert!(!result.success, "Call should propagate revert"); + log::info!("Returned data: {:?}", result.output); + assert!(result.output.len() > 0, "Returned data should contain revert message"); + + let data = result.output.as_ref(); + + // string length + let str_len = U256::from_be_slice(&data[36..68]).to::(); + + // string bytes + let str_start = 68; + let str_end = str_start + str_len; + let reason_bytes = &data[str_start..str_end]; + let reason = std::str::from_utf8(reason_bytes).unwrap(); + assert_eq!(reason, "This is a revert"); + }); + } +} + +#[test] +fn call_invalid_opcode() { + for fixture_type in [FixtureType::Resolc, FixtureType::Solc] { + let (caller_code, _) = compile_module_with_type("Caller", fixture_type).unwrap(); + let (callee_code, _) = compile_module_with_type("Callee", fixture_type).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + + // Instantiate the callee contract, which can echo a value. + let Contract { addr: callee_addr, .. } = + builder::bare_instantiate(Code::Upload(callee_code)).build_and_unwrap_contract(); + + log::info!("Callee addr: {:?}", callee_addr); + + // Instantiate the caller contract. + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(caller_code)).build_and_unwrap_contract(); + + log::info!("Caller addr: {:?}", caller_addr); + + let result = builder::bare_call(caller_addr) + .data( + Caller::normalCall { + _callee: callee_addr.0.into(), + _value: U256::ZERO, + _data: Callee::invalidCall {}.abi_encode().into(), + _gas: U256::MAX, + } + .abi_encode(), + ) + .build_and_unwrap_result(); + let result = Caller::normalCall::abi_decode_returns(&result.data).unwrap(); + + assert!(!result.success, "Invalid opcode should propagate as error"); + + let data = result.output.as_ref(); + assert!(data.iter().all(|&x| x == 0), "Returned data should be empty") + }); + } +} + +#[test] +fn invalid_opcode_evm() { + let (callee_code, _) = compile_module_with_type("Callee", FixtureType::Solc).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + + // Instantiate the callee contract, which can echo a value. + let Contract { addr: callee_addr, .. } = + builder::bare_instantiate(Code::Upload(callee_code)).build_and_unwrap_contract(); + + let result = builder::bare_call(callee_addr) + .data(Callee::invalidCall {}.abi_encode().into()) + .build(); + assert_err!(result.result, >::InvalidInstruction); + }); +} + +#[test] +fn call_stop_opcode() { + for fixture_type in [FixtureType::Resolc, FixtureType::Solc] { + let (caller_code, _) = compile_module_with_type("Caller", fixture_type).unwrap(); + let (callee_code, _) = compile_module_with_type("Callee", fixture_type).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + + // Instantiate the callee contract, which can echo a value. + let Contract { addr: callee_addr, .. } = + builder::bare_instantiate(Code::Upload(callee_code)).build_and_unwrap_contract(); + + log::info!("Callee addr: {:?}", callee_addr); + + // Instantiate the caller contract. + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(caller_code)).build_and_unwrap_contract(); + + log::info!("Caller addr: {:?}", caller_addr); + + let result = builder::bare_call(caller_addr) + .data( + Caller::normalCall { + _callee: callee_addr.0.into(), + _value: U256::ZERO, + _data: Callee::stopCall {}.abi_encode().into(), + _gas: U256::MAX, + } + .abi_encode(), + ) + .build_and_unwrap_result(); + let result = Caller::normalCall::abi_decode_returns(&result.data).unwrap(); + + assert!(result.success); + + let data = result.output.as_ref(); + assert!(data.iter().all(|&x| x == 0), "Returned data should be empty") + }); + } +} + +#[test] +fn delegatecall_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (caller_code, _) = compile_module_with_type("Caller", fixture_type).unwrap(); + let (callee_code, _) = compile_module_with_type("Callee", fixture_type).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + + // Instantiate the callee contract, which can echo a value. + let Contract { addr: callee_addr, .. } = + builder::bare_instantiate(Code::Upload(callee_code)).build_and_unwrap_contract(); + + log::info!("Callee addr: {:?}", callee_addr); + + // Instantiate the caller contract. + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(caller_code)).build_and_unwrap_contract(); + + log::info!("Caller addr: {:?}", caller_addr); + + let magic_number = U256::from(42); + log::info!("Calling callee.echo() from caller"); + let result = builder::bare_call(caller_addr) + .data( + Caller::delegateCall { + _callee: callee_addr.0.into(), + _data: Callee::echoCall { _data: magic_number }.abi_encode().into(), + _gas: U256::MAX, + } + .abi_encode(), + ) + .build_and_unwrap_result(); + + let result = Caller::delegateCall::abi_decode_returns(&result.data).unwrap(); + assert!(result.success, "the call must succeed"); + assert_eq!( + magic_number, + U256::from_be_bytes::<32>(result.output.as_ref().try_into().unwrap()), + "the call must reproduce the magic number" + ); + + log::info!("Calling callee.whoSender() from caller"); + let result = builder::bare_call(caller_addr) + .data( + Caller::delegateCall { + _callee: callee_addr.0.into(), + _data: Callee::whoSenderCall {}.abi_encode().into(), + _gas: U256::MAX, + } + .abi_encode(), + ) + .build_and_unwrap_result(); + + let result = Caller::delegateCall::abi_decode_returns(&result.data).unwrap(); + assert!(result.success, "the whoSender call must succeed"); + assert_eq!(ALICE_ADDR, H160::from_slice(&result.output.as_ref()[12..])); + }); + } +} + +#[test] +fn create_works() { + let (caller_code, _) = compile_module_with_type("Caller", FixtureType::Solc).unwrap(); + let (callee_code, _) = compile_module_with_type("Callee", FixtureType::Solc).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000_000); + + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(caller_code)).build_and_unwrap_contract(); + + let create_call_data = + Caller::createCall { initcode: Bytes::from(callee_code.clone()) }.abi_encode(); + + let result = builder::bare_call(caller_addr) + .data(create_call_data) + .native_value(1_000) + .build_and_unwrap_result(); + + let callee_addr = Caller::createCall::abi_decode_returns(&result.data).unwrap(); + + log::info!("Created addr: {:?}", callee_addr); + + let magic_number = U256::from(42); + + // Check if the created contract is working + let echo_result = builder::bare_call(callee_addr.0 .0.into()) + .data(Callee::echoCall { _data: magic_number }.abi_encode()) + .build_and_unwrap_result(); + + let echo_output = Callee::echoCall::abi_decode_returns(&echo_result.data).unwrap(); + + assert_eq!(magic_number, echo_output, "Callee.echo must return 42"); + }); +} + +#[test] +fn create2_works() { + let (caller_code, _) = compile_module_with_type("Caller", FixtureType::Solc).unwrap(); + let (callee_code, _) = compile_module_with_type("Callee", FixtureType::Solc).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000_000); + + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(caller_code)).build_and_unwrap_contract(); + + let salt = [42u8; 32]; + + let initcode = Bytes::from(callee_code); + // Prepare the CREATE2 call + let create_call_data = + Caller::create2Call { initcode: initcode.clone(), salt: FixedBytes(salt) }.abi_encode(); + + let result = builder::bare_call(caller_addr) + .data(create_call_data) + .native_value(1000) + .build_and_unwrap_result(); + + let callee_addr = Caller::create2Call::abi_decode_returns(&result.data).unwrap(); + + log::info!("Created addr: {:?}", callee_addr); + + // Compute expected CREATE2 address + let expected_addr = crate::address::create2(&caller_addr, &initcode, &[], &salt); + + let callee_addr: H160 = callee_addr.0 .0.into(); + + assert_eq!(callee_addr, expected_addr, "CREATE2 address should be deterministic"); + + let magic_number = U256::from(42); + + // Check if the created contract is working + let echo_result = builder::bare_call(callee_addr) + .data(Callee::echoCall { _data: magic_number }.abi_encode()) + .build_and_unwrap_result(); + + let echo_output = Callee::echoCall::abi_decode_returns(&echo_result.data).unwrap(); + + assert_eq!(magic_number, echo_output, "Callee.echo must return 42"); + }); +} diff --git a/substrate/frame/revive/src/tests/sol/system.rs b/substrate/frame/revive/src/tests/sol/system.rs new file mode 100644 index 0000000000000..1bb28b265b6fb --- /dev/null +++ b/substrate/frame/revive/src/tests/sol/system.rs @@ -0,0 +1,286 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The pallet-revive shared VM integration test suite. + +use crate::{ + test_utils::{builder::Contract, ALICE, ALICE_ADDR}, + tests::{builder, Contracts, ExtBuilder, Test}, + Code, Config, +}; +use alloy_core::{primitives::U256, sol_types::SolCall}; +use frame_support::traits::fungible::Mutate; +use pallet_revive_fixtures::{ + compile_module_with_type, Callee, FixtureType, System as SystemFixture, +}; +use pretty_assertions::assert_eq; +use revm::primitives::Bytes; +use sp_core::H160; +use sp_io::hashing::keccak_256; + +#[test] +fn keccak_256_works() { + for fixture_type in [FixtureType::Resolc, FixtureType::Solc] { + let (code, _) = compile_module_with_type("System", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let pre = b"revive"; + let expected = keccak_256(pre); + + let result = builder::bare_call(addr) + .data(SystemFixture::keccak256FuncCall { data: Bytes::from(pre) }.abi_encode()) + .build_and_unwrap_result(); + + assert_eq!(&expected, result.data.as_slice()); + }); + } +} + +#[test] +fn address_works() { + for fixture_type in [FixtureType::Resolc, FixtureType::Solc] { + let (code, _) = compile_module_with_type("System", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let result = builder::bare_call(addr) + .data(SystemFixture::addressFuncCall {}.abi_encode()) + .build_and_unwrap_result(); + + let returned_addr: H160 = H160::from_slice(&result.data[12..]); + assert_eq!(addr, returned_addr); + }); + } +} + +#[test] +fn caller_works() { + for fixture_type in [FixtureType::Resolc, FixtureType::Solc] { + let (code, _) = compile_module_with_type("System", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let result = builder::bare_call(addr) + .data(SystemFixture::callerCall {}.abi_encode()) + .build_and_unwrap_result(); + + let returned_caller = H160::from_slice(&result.data[12..]); + assert_eq!(ALICE_ADDR, returned_caller); + }); + } +} + +#[test] +fn callvalue_works() { + for fixture_type in [FixtureType::Resolc, FixtureType::Solc] { + let (code, _) = compile_module_with_type("System", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let value = 1337u64; + + let result = builder::bare_call(addr) + .evm_value(value.into()) + .data(SystemFixture::callvalueCall {}.abi_encode()) + .build_and_unwrap_result(); + + let returned_val = U256::from_be_bytes::<32>(result.data.try_into().unwrap()); + assert_eq!(U256::from(value), returned_val); + }); + } +} + +#[test] +fn calldataload_works() { + for fixture_type in [FixtureType::Resolc, FixtureType::Solc] { + let (code, _) = compile_module_with_type("System", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let result = builder::bare_call(addr) + .data( + SystemFixture::calldataloadCall { offset: U256::from(4u32) } /* skip selector */ + .abi_encode(), + ) + .build_and_unwrap_result(); + + // Call calldataload(offset=4) → returns the argument "4" + let returned = U256::from_be_bytes::<32>(result.data.as_slice().try_into().unwrap()); + assert_eq!(U256::from(4u32), returned); + }); + } +} + +#[test] +fn calldatasize_works() { + for fixture_type in [FixtureType::Resolc, FixtureType::Solc] { + let (code, _) = compile_module_with_type("System", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // calldata = selector + encoded argument + let result = builder::bare_call(addr) + .data(SystemFixture::calldatasizeCall {}.abi_encode()) + .build_and_unwrap_result(); + + // ABI encodes: 4 (selector) + 0 (no args) = 4 + let returned = U256::from_be_bytes::<32>(result.data.as_slice().try_into().unwrap()); + assert_eq!(returned, U256::from(4u32)); + }); + } +} + +#[test] +fn calldatacopy_works() { + for fixture_type in [FixtureType::Resolc, FixtureType::Solc] { + let (code, _) = compile_module_with_type("System", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let call_data = SystemFixture::calldatacopyCall { + destOffset: U256::from(0u32), // unused + offset: U256::from(4u32), // skip selector + size: U256::from(64u32), // copy destOffset + offset + } + .abi_encode(); + + let result = builder::bare_call(addr).data(call_data.clone()).build_and_unwrap_result(); + + let returned_data = + SystemFixture::calldatacopyCall::abi_decode_returns(&result.data).unwrap(); + + let returned_data = returned_data.as_ref(); + assert_eq!(returned_data.len(), 64); + + // The expected data is the slice of the original calldata that was copied. + let expected_data = &call_data.as_slice()[4..(4 + 64) as usize]; + assert_eq!(expected_data, returned_data); + }); + } +} + +#[test] +fn codesize_works() { + for fixture_type in [FixtureType::Resolc, FixtureType::Solc] { + let (code, _) = compile_module_with_type("System", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code.clone())).build_and_unwrap_contract(); + + let result = builder::bare_call(addr) + .data(SystemFixture::codesizeCall {}.abi_encode()) + .build_and_unwrap_result(); + + // Now fetch the actual *runtime* code size from storage + let code = Contracts::code(&addr); + + let returned_size = + U256::from_be_bytes::<32>(result.data.as_slice().try_into().unwrap()); + let expected_size = U256::from(code.len()); + + assert_eq!(expected_size, returned_size); + }); + } +} + +#[test] +fn returndatasize_works() { + for fixture_type in [FixtureType::Resolc, FixtureType::Solc] { + let (code, _) = compile_module_with_type("System", fixture_type).unwrap(); + let (callee_code, _) = compile_module_with_type("Callee", fixture_type).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + + // Instantiate the callee contract, which can echo a value. + let Contract { addr: callee_addr, .. } = + builder::bare_instantiate(Code::Upload(callee_code)).build_and_unwrap_contract(); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let magic_number = U256::from(42); + let result = builder::bare_call(addr) + .data( + SystemFixture::returndatasizeCall { + _callee: callee_addr.0.into(), + _data: Callee::echoCall { _data: magic_number }.abi_encode().into(), + _gas: U256::MAX, + } + .abi_encode(), + ) + .build_and_unwrap_result(); + + let size = U256::from_be_bytes::<32>(result.data.try_into().unwrap()); + // Always 32 bytes for a single uint256 + assert_eq!(U256::from(32), size); + }); + } +} + +#[test] +fn returndatacopy_works() { + for fixture_type in [FixtureType::Resolc, FixtureType::Solc] { + let (code, _) = compile_module_with_type("System", fixture_type).unwrap(); + let (callee_code, _) = compile_module_with_type("Callee", fixture_type).unwrap(); + + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + // Instantiate the callee contract, which can echo a value. + let Contract { addr: callee_addr, .. } = + builder::bare_instantiate(Code::Upload(callee_code)).build_and_unwrap_contract(); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let magic_number = U256::from(42); + let result = builder::bare_call(addr) + .data( + SystemFixture::returndatacopyCall { + _callee: callee_addr.0.into(), + _data: Callee::echoCall { _data: magic_number }.abi_encode().into(), + _gas: U256::MAX, + destOffset: U256::ZERO, + offset: U256::ZERO, + size: U256::from(32u32), + } + .abi_encode(), + ) + .build_and_unwrap_result(); + + let result = + SystemFixture::returndatacopyCall::abi_decode_returns(&result.data).unwrap(); + assert_eq!(magic_number, U256::from_be_bytes::<32>(result.as_ref().try_into().unwrap())) + }); + } +} diff --git a/substrate/frame/revive/src/tests/sol/tx_info.rs b/substrate/frame/revive/src/tests/sol/tx_info.rs new file mode 100644 index 0000000000000..d1e38c26ac837 --- /dev/null +++ b/substrate/frame/revive/src/tests/sol/tx_info.rs @@ -0,0 +1,82 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The pallet-revive shared VM integration test suite. + +use crate::{ + evm::runtime::GAS_PRICE, + test_utils::{builder::Contract, ALICE, ALICE_ADDR}, + tests::{builder, ExtBuilder, Test}, + Code, Config, +}; + +use alloy_core::{primitives::U256, sol_types::SolInterface}; +use frame_support::traits::fungible::Mutate; +use pallet_revive_fixtures::{compile_module_with_type, FixtureType, TransactionInfo}; +use pretty_assertions::assert_eq; +use sp_core::H160; + +/// Tests that the gasprice opcode works as expected. +#[test] +fn gasprice_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("TransactionInfo", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let result = builder::bare_call(addr) + .data( + TransactionInfo::TransactionInfoCalls::gasprice( + TransactionInfo::gaspriceCall {}, + ) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + U256::from(GAS_PRICE), + U256::from_be_bytes::<32>(result.data.try_into().unwrap()) + ); + }); + } +} + +/// Tests that the origin opcode works as expected. +#[test] +fn origin_works() { + for fixture_type in [FixtureType::Solc, FixtureType::Resolc] { + let (code, _) = compile_module_with_type("TransactionInfo", fixture_type).unwrap(); + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000_000); + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let result = builder::bare_call(addr) + .data( + TransactionInfo::TransactionInfoCalls::origin(TransactionInfo::originCall {}) + .abi_encode(), + ) + .build_and_unwrap_result(); + assert_eq!( + ALICE_ADDR, + // Padding is used into the 32 bytes + H160::from_slice(&result.data[12..]) + ); + }); + } +} diff --git a/substrate/frame/revive/src/vm/evm.rs b/substrate/frame/revive/src/vm/evm.rs index 52ec7b296b2c3..652d65785a244 100644 --- a/substrate/frame/revive/src/vm/evm.rs +++ b/substrate/frame/revive/src/vm/evm.rs @@ -15,27 +15,47 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod instructions; - use crate::{ + exec::ExecError, + vec, vm::{BytecodeType, ExecResult, Ext}, - AccountIdOf, CodeInfo, Config, ContractBlob, DispatchError, Error, ExecReturnValue, H256, - LOG_TARGET, + AccountIdOf, Code, CodeInfo, Config, ContractBlob, DispatchError, Error, ExecReturnValue, H256, + LOG_TARGET, U256, }; -use alloc::vec::Vec; +use alloc::{boxed::Box, vec::Vec}; +use core::cmp::min; use instructions::instruction_table; use pallet_revive_uapi::ReturnFlags; use revm::{ bytecode::Bytecode, + context::CreateScheme, interpreter::{ host::DummyHost, interpreter::{ExtBytecode, ReturnDataImpl, RuntimeFlags}, interpreter_action::InterpreterAction, - interpreter_types::InputsTr, - CallInput, Gas, Interpreter, InterpreterResult, InterpreterTypes, SharedMemory, Stack, + interpreter_types::{InputsTr, MemoryTr, ReturnData}, + CallInput, CallInputs, CallScheme, CreateInputs, FrameInput, Gas, InstructionResult, + Interpreter, InterpreterResult, InterpreterTypes, SharedMemory, Stack, }, primitives::{self, hardfork::SpecId, Address, Bytes}, }; +use sp_core::H160; +use sp_runtime::Weight; + +mod instructions; + +/// Hard-coded value returned by the EVM `DIFFICULTY` opcode. +/// +/// After Ethereum's Merge (Sept 2022), the `DIFFICULTY` opcode was redefined to return +/// `prevrandao`, a randomness value from the beacon chain. In Substrate pallet-revive +/// a fixed constant is returned instead for compatibility with contracts that still read this +/// opcode. The value is aligned with the difficulty hardcoded for PVM contracts. +pub(crate) const DIFFICULTY: u64 = 2500000000000000_u64; + +/// The base fee per gas used in the network as defined by EIP-1559. +/// +/// For `pallet-revive`, this is hardcoded to 0 +pub(crate) const BASE_FEE: U256 = U256::zero(); impl ContractBlob { /// Create a new contract from EVM init code. @@ -44,6 +64,11 @@ impl ContractBlob { return Err(>::BlobTooLarge.into()); } + // EIP-3541: Reject new contract code starting with the 0xEF byte + if code.first() == Some(&0xEF) { + return Err(>::CodeRejected.into()); + } + let code_len = code.len() as u32; let code_info = CodeInfo { owner, @@ -111,32 +136,153 @@ pub fn call<'a, E: Ext>(bytecode: Bytecode, ext: &'a mut E, inputs: EVMInputs) - let table = instruction_table::<'a, E>(); let result = run(&mut interpreter, &table); - if result.is_error() { - Err(Error::::ContractTrapped.into()) - } else { - Ok(ExecReturnValue { - flags: if result.is_revert() { ReturnFlags::REVERT } else { ReturnFlags::empty() }, - data: result.output.to_vec(), + instruction_result_into_exec_error::(result.result) + .map(Err) + .unwrap_or_else(|| { + Ok(ExecReturnValue { + flags: if result.is_revert() { ReturnFlags::REVERT } else { ReturnFlags::empty() }, + data: result.output.to_vec(), + }) }) - } } /// Runs the EVM interpreter -fn run( - interpreter: &mut Interpreter, - table: &revm::interpreter::InstructionTable, +fn run<'a, E: Ext>( + interpreter: &mut Interpreter>, + table: &revm::interpreter::InstructionTable, DummyHost>, ) -> InterpreterResult { let host = &mut DummyHost {}; - let action = interpreter.run_plain(table, host); - match action { - InterpreterAction::Return(result) => return result, - InterpreterAction::NewFrame(_) => { - // TODO handle new frame - InterpreterResult::new( - revm::interpreter::InstructionResult::FatalExternalError, - Default::default(), - interpreter.gas, - ) + loop { + let action = interpreter.run_plain(table, host); + match action { + InterpreterAction::Return(result) => { + log::trace!(target: LOG_TARGET, "Evm return {:?}", result); + debug_assert!( + result.gas.limit() == 0 && + result.gas.remaining() == 0 && + result.gas.refunded() == 0, + "Interpreter gas state should remain unchanged; found: {:?}", + result.gas, + ); + return result; + }, + InterpreterAction::NewFrame(frame_input) => match frame_input { + FrameInput::Call(call_input) => run_call(interpreter, call_input), + FrameInput::Create(create_input) => run_create(interpreter, create_input), + FrameInput::Empty => unreachable!(), + }, + } + } +} + +fn run_call<'a, E: Ext>( + interpreter: &mut Interpreter>, + call_input: Box, +) { + let callee: H160 = if call_input.scheme.is_delegate_call() { + call_input.bytecode_address.0 .0.into() + } else { + call_input.target_address.0 .0.into() + }; + + let input = match &call_input.input { + CallInput::Bytes(bytes) => bytes.to_vec(), + // Consider the usage fo SharedMemory as REVM is doing + CallInput::SharedBuffer(range) => interpreter.memory.global_slice(range.clone()).to_vec(), + }; + let call_result = match call_input.scheme { + CallScheme::Call | CallScheme::StaticCall => interpreter.extend.call( + Weight::from_parts(call_input.gas_limit, u64::MAX), + U256::MAX, + &callee, + U256::from_revm_u256(&call_input.call_value()), + input, + true, + call_input.is_static, + ), + CallScheme::CallCode => { + unreachable!() + }, + CallScheme::DelegateCall => interpreter.extend.delegate_call( + Weight::from_parts(call_input.gas_limit, u64::MAX), + U256::MAX, + callee, + input, + ), + }; + + let return_value = interpreter.extend.last_frame_output(); + let return_data: Bytes = return_value.data.clone().into(); + + let mem_length = call_input.return_memory_offset.len(); + let mem_start = call_input.return_memory_offset.start; + let returned_len = return_data.len(); + let target_len = min(mem_length, returned_len); + // Set the interpreter with the nested frame result + interpreter.return_data.set_buffer(return_data); + + match call_result { + Ok(()) => { + // success or revert + // TODO: Charge CopyToContract + interpreter + .memory + .set(mem_start, &interpreter.return_data.buffer()[..target_len]); + let _ = + interpreter.stack.push(primitives::U256::from(!return_value.did_revert() as u8)); + }, + Err(err) => { + let _ = interpreter.stack.push(primitives::U256::ZERO); + if let Some(reason) = exec_error_into_halt_reason::(err) { + interpreter.halt(reason); + } + }, + } +} + +fn run_create<'a, E: Ext>( + interpreter: &mut Interpreter>, + create_input: Box, +) { + let value = U256::from_revm_u256(&create_input.value); + + let salt = match create_input.scheme { + CreateScheme::Create => None, + CreateScheme::Create2 { salt } => Some(salt.to_le_bytes()), + CreateScheme::Custom { .. } => unreachable!("custom create schemes are not supported"), + }; + + let call_result = interpreter.extend.instantiate( + Weight::from_parts(create_input.gas_limit, u64::MAX), + U256::MAX, + Code::Upload(create_input.init_code.to_vec()), + value, + vec![], + salt.as_ref(), + ); + + let return_value = interpreter.extend.last_frame_output(); + let return_data: Bytes = return_value.data.clone().into(); + + match call_result { + Ok(address) => { + if return_value.did_revert() { + // Contract creation reverted — return data must be propagated + // TODO: Charge CopyToContract + interpreter.return_data.set_buffer(return_data); + let _ = interpreter.stack.push(primitives::U256::ZERO); + } else { + // Otherwise clear it. Note that RETURN opcode should abort. + interpreter.return_data.clear(); + let stack_item: Address = address.0.into(); + let _ = interpreter.stack.push(stack_item.into_word().into()); + } + }, + Err(err) => { + let _ = interpreter.stack.push(primitives::U256::ZERO); + if let Some(reason) = exec_error_into_halt_reason::(err) { + interpreter.halt(reason); + } }, } } @@ -197,13 +343,94 @@ impl InputsTr for EVMInputs { } fn call_value(&self) -> primitives::U256 { - // TODO replae by panic once instruction that use call_value are updated - primitives::U256::ZERO + panic!() + } +} + +/// Conversion of a `ExecError` to `ReturnErrorCode`. +/// +/// Used when converting the error returned from a subcall in order to map it to the +/// equivalent EVM interpreter [InstructionResult]. +/// +/// - Returns `None` when the caller can recover the error. +/// - Otherwise, some [InstructionResult] error code (the halt reason) is returned. Most [ExecError] +/// variants don't map to a [InstructionResult]. The conversion is lossy and defaults to +/// [InstructionResult::Revert] for most cases. +/// +/// Uses the overarching [super::exec_error_into_return_code] method to determine if +/// the error is recoverable or not. This guarantees consistent behavior accross both +/// VM backends. +fn exec_error_into_halt_reason(from: ExecError) -> Option { + log::trace!("call frame execution error in EVM caller: {:?}", &from); + + if super::exec_error_into_return_code::(from).is_ok() { + return None; + } + + let static_memory_too_large = Error::::StaticMemoryTooLarge.into(); + let code_rejected = Error::::CodeRejected.into(); + let transfer_failed = Error::::TransferFailed.into(); + let duplicate_contract = Error::::DuplicateContract.into(); + let balance_conversion_failed = Error::::BalanceConversionFailed.into(); + let value_too_large = Error::::ValueTooLarge.into(); + let out_of_gas = Error::::OutOfGas.into(); + let out_of_deposit = Error::::StorageDepositLimitExhausted.into(); + + Some(match from.error { + err if err == static_memory_too_large => InstructionResult::MemoryLimitOOG, + err if err == code_rejected => InstructionResult::OpcodeNotFound, + err if err == transfer_failed => InstructionResult::OutOfFunds, + err if err == duplicate_contract => InstructionResult::CreateCollision, + err if err == balance_conversion_failed => InstructionResult::OverflowPayment, + err if err == value_too_large => InstructionResult::OverflowPayment, + err if err == out_of_deposit => InstructionResult::OutOfFunds, + err if err == out_of_gas => InstructionResult::OutOfGas, + _ => InstructionResult::Revert, + }) +} + +/// Map [InstructionResult] into an [ExecError] for passing it up the stack. +/// +/// Returns `None` if the instruction result is not an error case. +fn instruction_result_into_exec_error(from: InstructionResult) -> Option { + match from { + InstructionResult::OutOfGas | + InstructionResult::InvalidOperandOOG | + InstructionResult::ReentrancySentryOOG | + InstructionResult::PrecompileOOG | + InstructionResult::MemoryOOG => Some(Error::::OutOfGas), + InstructionResult::MemoryLimitOOG => Some(Error::::StaticMemoryTooLarge), + InstructionResult::OpcodeNotFound | + InstructionResult::InvalidJump | + InstructionResult::NotActivated | + InstructionResult::InvalidFEOpcode | + InstructionResult::CreateContractStartingWithEF => Some(Error::::InvalidInstruction), + InstructionResult::CallNotAllowedInsideStatic | + InstructionResult::StateChangeDuringStaticCall => Some(Error::::StateChangeDenied), + InstructionResult::StackUnderflow | + InstructionResult::StackOverflow | + InstructionResult::NonceOverflow | + InstructionResult::PrecompileError | + InstructionResult::FatalExternalError => Some(Error::::ContractTrapped), + InstructionResult::OutOfOffset => Some(Error::::OutOfBounds), + InstructionResult::CreateCollision => Some(Error::::DuplicateContract), + InstructionResult::OverflowPayment => Some(Error::::BalanceConversionFailed), + InstructionResult::CreateContractSizeLimit | InstructionResult::CreateInitCodeSizeLimit => + Some(Error::::StaticMemoryTooLarge), + InstructionResult::CallTooDeep => Some(Error::::MaxCallDepthReached), + InstructionResult::OutOfFunds => Some(Error::::TransferFailed), + InstructionResult::CreateInitCodeStartingEF00 | + InstructionResult::InvalidEOFInitCode | + InstructionResult::InvalidExtDelegateCallTarget => Some(Error::::ContractTrapped), + InstructionResult::Stop | + InstructionResult::Return | + InstructionResult::Revert | + InstructionResult::SelfDestruct => None, } + .map(Into::into) } /// Blanket conversion trait between `sp_core::U256` and `revm::primitives::U256` -#[allow(dead_code)] pub trait U256Converter { /// Convert `self` into `revm::primitives::U256` fn into_revm_u256(&self) -> revm::primitives::U256; diff --git a/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs b/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs index 6aa47e2bae86b..ffd2d968981cf 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/arithmetic.rs @@ -109,7 +109,7 @@ pub fn mulmod<'ext, E: Ext>(context: Context<'_, 'ext, E>) { pub fn exp<'ext, E: Ext>(context: Context<'_, 'ext, E>) { let spec_id = context.interpreter.runtime_flag.spec_id(); popn_top!([op1], op2, context.interpreter); - gas_or_fail!(context.interpreter, revm_gas::exp_cost(spec_id, *op2)); + gas_or_fail_legacy!(context.interpreter, revm_gas::exp_cost(spec_id, *op2)); *op2 = op1.pow(*op2); } diff --git a/substrate/frame/revive/src/vm/evm/instructions/block_info.rs b/substrate/frame/revive/src/vm/evm/instructions/block_info.rs index 375de2f6f9c25..0f91828f7e65c 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/block_info.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/block_info.rs @@ -16,33 +16,42 @@ // limitations under the License. use super::Context; -use crate::{vm::Ext, RuntimeCosts}; +use crate::{ + vm::{ + evm::{U256Converter, BASE_FEE, DIFFICULTY}, + Ext, + }, + RuntimeCosts, +}; use revm::{ - interpreter::{gas as revm_gas, host::Host, interpreter_types::RuntimeFlag}, - primitives::{hardfork::SpecId::*, U256}, + interpreter::gas as revm_gas, + primitives::{Address, U256}, }; +use sp_core::H160; /// EIP-1344: ChainID opcode pub fn chainid<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - check!(context.interpreter, ISTANBUL); gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, context.host.chain_id()); + push!(context.interpreter, U256::from(context.interpreter.extend.chain_id())); } /// Implements the COINBASE instruction. /// /// Pushes the current block's beneficiary address onto the stack. pub fn coinbase<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, context.host.beneficiary().into_word().into()); + gas!(context.interpreter, RuntimeCosts::BlockAuthor); + let coinbase: Address = + context.interpreter.extend.block_author().unwrap_or(H160::zero()).0.into(); + push!(context.interpreter, coinbase.into_word().into()); } /// Implements the TIMESTAMP instruction. /// /// Pushes the current block's timestamp onto the stack. pub fn timestamp<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, context.host.timestamp()); + gas!(context.interpreter, RuntimeCosts::Now); + let timestamp = context.interpreter.extend.now(); + push!(context.interpreter, timestamp.into_revm_u256()); } /// Implements the NUMBER instruction. @@ -51,7 +60,7 @@ pub fn timestamp<'ext, E: Ext>(context: Context<'_, 'ext, E>) { pub fn block_number<'ext, E: Ext>(context: Context<'_, 'ext, E>) { gas!(context.interpreter, RuntimeCosts::BlockNumber); let block_number = context.interpreter.extend.block_number(); - push!(context.interpreter, U256::from_limbs(block_number.0)); + push!(context.interpreter, block_number.into_revm_u256()); } /// Implements the DIFFICULTY/PREVRANDAO instruction. @@ -59,32 +68,25 @@ pub fn block_number<'ext, E: Ext>(context: Context<'_, 'ext, E>) { /// Pushes the block difficulty (pre-merge) or prevrandao (post-merge) onto the stack. pub fn difficulty<'ext, E: Ext>(context: Context<'_, 'ext, E>) { gas_legacy!(context.interpreter, revm_gas::BASE); - if context.interpreter.runtime_flag.spec_id().is_enabled_in(MERGE) { - // Unwrap is safe as this fields is checked in validation handler. - push!(context.interpreter, context.host.prevrandao().unwrap()); - } else { - push!(context.interpreter, context.host.difficulty()); - } + push!(context.interpreter, U256::from(DIFFICULTY)); } /// Implements the GASLIMIT instruction. /// /// Pushes the current block's gas limit onto the stack. pub fn gaslimit<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, context.host.gas_limit()); + gas!(context.interpreter, RuntimeCosts::GasLimit); + let gas_limit = context.interpreter.extend.gas_limit(); + push!(context.interpreter, U256::from(gas_limit)); } /// EIP-3198: BASEFEE opcode pub fn basefee<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - check!(context.interpreter, LONDON); - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, context.host.basefee()); + gas!(context.interpreter, RuntimeCosts::BaseFee); + push!(context.interpreter, BASE_FEE.into_revm_u256()); } -/// EIP-7516: BLOBBASEFEE opcode +/// EIP-7516: BLOBBASEFEE opcode is not supported pub fn blob_basefee<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - check!(context.interpreter, CANCUN); - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, context.host.blob_gasprice()); + context.interpreter.halt(revm::interpreter::InstructionResult::NotActivated); } diff --git a/substrate/frame/revive/src/vm/evm/instructions/contract.rs b/substrate/frame/revive/src/vm/evm/instructions/contract.rs index 1cf67725db88a..b6f269f308ab4 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/contract.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/contract.rs @@ -17,23 +17,24 @@ mod call_helpers; -pub use call_helpers::{calc_call_gas, get_memory_input_and_out_ranges}; - use super::{utility::IntoAddress, Context}; -use crate::vm::Ext; +use crate::{ + vm::{evm::U256Converter, Ext, RuntimeCosts}, + Pallet, +}; use alloc::boxed::Box; +pub use call_helpers::{calc_call_gas, get_memory_input_and_out_ranges}; use revm::{ context_interface::CreateScheme, interpreter::{ gas as revm_gas, - host::Host, interpreter_action::{ CallInputs, CallScheme, CallValue, CreateInputs, FrameInput, InterpreterAction, }, - interpreter_types::{InputsTr, LoopControl, RuntimeFlag, StackTr}, + interpreter_types::{LoopControl, RuntimeFlag, StackTr}, CallInput, InstructionResult, }, - primitives::{hardfork::SpecId, Address, Bytes, B256, U256}, + primitives::{Address, Bytes, B256, U256}, }; /// Implements the CREATE/CREATE2 instruction. @@ -42,24 +43,28 @@ use revm::{ pub fn create<'ext, const IS_CREATE2: bool, E: Ext>(context: Context<'_, 'ext, E>) { require_non_staticcall!(context.interpreter); - // EIP-1014: Skinny CREATE2 - if IS_CREATE2 { - check!(context.interpreter, PETERSBURG); - } - popn!([value, code_offset, len], context.interpreter); let len = as_usize_or_fail!(context.interpreter, len); + // TODO: We do not charge for the new code in storage. When implementing the new gas: + // Introduce EthInstantiateWithCode, which shall charge gas based on the code length. + // See #9577 for more context. + let val = crate::U256::from_revm_u256(&value); + gas!( + context.interpreter, + RuntimeCosts::Instantiate { + input_data_len: len as u32, // We charge for initcode execution + balance_transfer: Pallet::::has_balance(val), + dust_transfer: Pallet::::has_dust(val), + } + ); + let mut code = Bytes::new(); if len != 0 { - // EIP-3860: Limit and meter initcode - if context.interpreter.runtime_flag.spec_id().is_enabled_in(SpecId::SHANGHAI) { - // Limit is set as double of max contract bytecode size - if len > context.host.max_initcode_size() { - context.interpreter.halt(InstructionResult::CreateInitCodeSizeLimit); - return; - } - gas_legacy!(context.interpreter, revm_gas::initcode_cost(len)); + // EIP-3860: Limit initcode + if len > revm::primitives::eip3860::MAX_INITCODE_SIZE { + context.interpreter.halt(InstructionResult::CreateInitCodeSizeLimit); + return; } let code_offset = as_usize_or_fail!(context.interpreter, code_offset); @@ -71,33 +76,22 @@ pub fn create<'ext, const IS_CREATE2: bool, E: Ext>(context: Context<'_, 'ext, E // EIP-1014: Skinny CREATE2 let scheme = if IS_CREATE2 { popn!([salt], context.interpreter); - // SAFETY: `len` is reasonable in size as gas for it is already deducted. - gas_or_fail!(context.interpreter, revm_gas::create2_cost(len)); CreateScheme::Create2 { salt } } else { gas_legacy!(context.interpreter, revm_gas::CREATE); CreateScheme::Create }; - let mut gas_limit = context.interpreter.gas.remaining(); - - // EIP-150: Gas cost changes for IO-heavy operations - if context.interpreter.runtime_flag.spec_id().is_enabled_in(SpecId::TANGERINE) { - // Take remaining gas and deduce l64 part of it. - gas_limit -= gas_limit / 64 - } - gas_legacy!(context.interpreter, gas_limit); - // Call host to interact with target contract context .interpreter .bytecode .set_action(InterpreterAction::NewFrame(FrameInput::Create(Box::new(CreateInputs { - caller: context.interpreter.input.target_address(), + caller: context.interpreter.extend.address().0.into(), scheme, value, init_code: code, - gas_limit, + gas_limit: u64::MAX, // TODO: set the right limit })))); } @@ -107,8 +101,9 @@ pub fn create<'ext, const IS_CREATE2: bool, E: Ext>(context: Context<'_, 'ext, E pub fn call<'ext, E: Ext>(context: Context<'_, 'ext, E>) { popn!([local_gas_limit, to, value], context.interpreter); let to = to.into_address(); - // Max gas limit is not possible in real ethereum situation. - let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); + // TODO: Max gas limit is not possible in a real Ethereum situation. This issue will be + // addressed in #9577. + let _local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); let has_transfer = !value.is_zero(); if context.interpreter.runtime_flag.is_static() && has_transfer { @@ -121,36 +116,25 @@ pub fn call<'ext, E: Ext>(context: Context<'_, 'ext, E>) { return; }; - let Some(account_load) = context.host.load_account_delegated(to) else { - context.interpreter.halt(InstructionResult::FatalExternalError); - return; - }; + let scheme = CallScheme::Call; + let input = CallInput::SharedBuffer(input); - let Some(mut gas_limit) = - calc_call_gas(context.interpreter, account_load, has_transfer, local_gas_limit) - else { + let Some(gas_limit) = calc_call_gas(context.interpreter, to, scheme, input.len(), value) else { return; }; - gas_legacy!(context.interpreter, gas_limit); - - // Add call stipend if there is value to be transferred. - if has_transfer { - gas_limit = gas_limit.saturating_add(revm_gas::CALL_STIPEND); - } - // Call host to interact with target contract context .interpreter .bytecode .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(CallInputs { - input: CallInput::SharedBuffer(input), + input, gas_limit, target_address: to, - caller: context.interpreter.input.target_address(), + caller: Address::default(), bytecode_address: to, value: CallValue::Transfer(value), - scheme: CallScheme::Call, + scheme, is_static: context.interpreter.runtime_flag.is_static(), return_memory_offset, })))); @@ -159,95 +143,48 @@ pub fn call<'ext, E: Ext>(context: Context<'_, 'ext, E>) { /// Implements the CALLCODE instruction. /// /// Message call with alternative account's code. +/// +/// Isn't supported yet: [`solc` no longer emits it since Solidity v0.3.0 in 2016] +/// (https://soliditylang.org/blog/2016/03/11/solidity-0.3.0-release-announcement/). pub fn call_code<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - popn!([local_gas_limit, to, value], context.interpreter); - let to = Address::from_word(B256::from(to)); - // Max gas limit is not possible in real ethereum situation. - let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); - - //pop!(context.interpreter, value); - let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter) - else { - return; - }; - - let Some(mut load) = context.host.load_account_delegated(to) else { - context.interpreter.halt(InstructionResult::FatalExternalError); - return; - }; - - // Set `is_empty` to false as we are not creating this account. - load.is_empty = false; - let Some(mut gas_limit) = - calc_call_gas(context.interpreter, load, !value.is_zero(), local_gas_limit) - else { - return; - }; - - gas_legacy!(context.interpreter, gas_limit); - - // Add call stipend if there is value to be transferred. - if !value.is_zero() { - gas_limit = gas_limit.saturating_add(revm_gas::CALL_STIPEND); - } - - // Call host to interact with target contract - context - .interpreter - .bytecode - .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(CallInputs { - input: CallInput::SharedBuffer(input), - gas_limit, - target_address: context.interpreter.input.target_address(), - caller: context.interpreter.input.target_address(), - bytecode_address: to, - value: CallValue::Transfer(value), - scheme: CallScheme::CallCode, - is_static: context.interpreter.runtime_flag.is_static(), - return_memory_offset, - })))); + context.interpreter.halt(revm::interpreter::InstructionResult::NotActivated); } /// Implements the DELEGATECALL instruction. /// /// Message call with alternative account's code but same sender and value. pub fn delegate_call<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - check!(context.interpreter, HOMESTEAD); popn!([local_gas_limit, to], context.interpreter); let to = Address::from_word(B256::from(to)); - // Max gas limit is not possible in real ethereum situation. - let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); + // TODO: Max gas limit is not possible in a real Ethereum situation. This issue will be + // addressed in #9577. + let _local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter) else { return; }; - let Some(mut load) = context.host.load_account_delegated(to) else { - context.interpreter.halt(InstructionResult::FatalExternalError); - return; - }; + let scheme = CallScheme::DelegateCall; + let input = CallInput::SharedBuffer(input); - // Set is_empty to false as we are not creating this account. - load.is_empty = false; - let Some(gas_limit) = calc_call_gas(context.interpreter, load, false, local_gas_limit) else { + let Some(gas_limit) = calc_call_gas(context.interpreter, to, scheme, input.len(), U256::ZERO) + else { return; }; - gas_legacy!(context.interpreter, gas_limit); - // Call host to interact with target contract context .interpreter .bytecode .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(CallInputs { - input: CallInput::SharedBuffer(input), + input, gas_limit, - target_address: context.interpreter.input.target_address(), - caller: context.interpreter.input.caller_address(), + target_address: Default::default(), + caller: Default::default(), bytecode_address: to, - value: CallValue::Apparent(context.interpreter.input.call_value()), - scheme: CallScheme::DelegateCall, + value: CallValue::Apparent(Default::default()), + scheme, is_static: context.interpreter.runtime_flag.is_static(), return_memory_offset, })))); @@ -257,40 +194,37 @@ pub fn delegate_call<'ext, E: Ext>(context: Context<'_, 'ext, E>) { /// /// Static message call (cannot modify state). pub fn static_call<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - check!(context.interpreter, BYZANTIUM); popn!([local_gas_limit, to], context.interpreter); let to = Address::from_word(B256::from(to)); - // Max gas limit is not possible in real ethereum situation. - let local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); + // TODO: Max gas limit is not possible in a real Ethereum situation. This issue will be + // addressed in #9577. + let _local_gas_limit = u64::try_from(local_gas_limit).unwrap_or(u64::MAX); let Some((input, return_memory_offset)) = get_memory_input_and_out_ranges(context.interpreter) else { return; }; - let Some(mut load) = context.host.load_account_delegated(to) else { - context.interpreter.halt(InstructionResult::FatalExternalError); - return; - }; - // Set `is_empty` to false as we are not creating this account. - load.is_empty = false; - let Some(gas_limit) = calc_call_gas(context.interpreter, load, false, local_gas_limit) else { + let scheme = CallScheme::StaticCall; + let input = CallInput::SharedBuffer(input); + + let Some(gas_limit) = calc_call_gas(context.interpreter, to, scheme, input.len(), U256::ZERO) + else { return; }; - gas_legacy!(context.interpreter, gas_limit); // Call host to interact with target contract context .interpreter .bytecode .set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(CallInputs { - input: CallInput::SharedBuffer(input), + input, gas_limit, target_address: to, - caller: context.interpreter.input.target_address(), + caller: Default::default(), bytecode_address: to, value: CallValue::Transfer(U256::ZERO), - scheme: CallScheme::StaticCall, + scheme, is_static: true, return_memory_offset, })))); diff --git a/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs b/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs index e60886ff53656..3a3d7284d2786 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/contract/call_helpers.rs @@ -15,17 +15,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::vm::Ext; -use core::{cmp::min, ops::Range}; +use crate::{ + precompiles::{All as AllPrecompiles, Precompiles}, + vm::{evm::U256Converter, Ext}, + Pallet, RuntimeCosts, +}; +use core::ops::Range; use revm::{ - context_interface::{context::StateLoad, journaled_state::AccountLoad}, interpreter::{ - gas as revm_gas, - interpreter_types::{MemoryTr, RuntimeFlag, StackTr}, + interpreter_action::CallScheme, + interpreter_types::{MemoryTr, StackTr}, Interpreter, }, - primitives::{hardfork::SpecId::*, U256}, + primitives::{Address, U256}, }; +use sp_core::H160; /// Gets memory input and output ranges for call instructions. #[inline] @@ -68,21 +72,47 @@ pub fn resize_memory<'a, E: Ext>( #[inline] pub fn calc_call_gas<'a, E: Ext>( interpreter: &mut Interpreter>, - account_load: StateLoad, - has_transfer: bool, - local_gas_limit: u64, + callee: Address, + scheme: CallScheme, + input_len: usize, + value: U256, ) -> Option { - let call_cost = - revm_gas::call_cost(interpreter.runtime_flag.spec_id(), has_transfer, account_load); - gas_legacy!(interpreter, call_cost, None); + let callee: H160 = callee.0 .0.into(); + let precompile = >::get::(&callee.as_fixed_bytes()); - // EIP-150: Gas cost changes for IO-heavy operations - let gas_limit = if interpreter.runtime_flag.spec_id().is_enabled_in(TANGERINE) { - // Take l64 part of gas_limit - min(interpreter.gas.remaining_63_of_64_parts(), local_gas_limit) - } else { - local_gas_limit - }; + match precompile { + Some(precompile) => { + // Base cost depending on contract info + let base_cost = if precompile.has_contract_info() { + RuntimeCosts::PrecompileWithInfoBase + } else { + RuntimeCosts::PrecompileBase + }; + gas!(interpreter, base_cost, None); - Some(gas_limit) + // Cost for decoding input + gas!(interpreter, RuntimeCosts::PrecompileDecode(input_len as u32), None); + }, + None => { + // Regular CALL / DELEGATECALL base cost / CALLCODE not supported + let base_cost = if scheme.is_delegate_call() { + RuntimeCosts::DelegateCallBase + } else { + RuntimeCosts::CallBase + }; + gas!(interpreter, base_cost, None); + + gas!(interpreter, RuntimeCosts::CopyFromContract(input_len as u32), None); + }, + }; + if !value.is_zero() { + gas!( + interpreter, + RuntimeCosts::CallTransferSurcharge { + dust_transfer: Pallet::::has_dust(crate::U256::from_revm_u256(&value)), + }, + None + ); + } + Some(u64::MAX) // TODO: Set the right gas limit } diff --git a/substrate/frame/revive/src/vm/evm/instructions/host.rs b/substrate/frame/revive/src/vm/evm/instructions/host.rs index 0ff40c29f2cb9..9767e25c57037 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/host.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/host.rs @@ -124,7 +124,7 @@ pub fn extcodecopy<'ext, E: Ext>(context: Context<'_, 'ext, E>) { }; let len = as_usize_or_fail!(context.interpreter, len_u256); - gas_or_fail!( + gas_or_fail_legacy!( context.interpreter, gas::extcodecopy_cost(context.interpreter.runtime_flag.spec_id(), len, code.is_cold) ); @@ -259,7 +259,7 @@ pub fn log<'ext, const N: usize, E: Ext>(context: Context<'_, 'ext, E>) { popn!([offset, len], context.interpreter); let len = as_usize_or_fail!(context.interpreter, len); - gas_or_fail!(context.interpreter, gas::log_cost(N as u8, len as u64)); + gas_or_fail_legacy!(context.interpreter, gas::log_cost(N as u8, len as u64)); let data = if len == 0 { Bytes::new() } else { diff --git a/substrate/frame/revive/src/vm/evm/instructions/macros.rs b/substrate/frame/revive/src/vm/evm/instructions/macros.rs index cb706d52add5a..943e344d30553 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/macros.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/macros.rs @@ -107,9 +107,9 @@ macro_rules! gas { /// Same as [`gas_legacy!`], but with `gas` as an option. #[macro_export] -macro_rules! gas_or_fail { +macro_rules! gas_or_fail_legacy { ($interpreter:expr, $gas:expr) => { - gas_or_fail!($interpreter, $gas, ()) + gas_or_fail_legacy!($interpreter, $gas, ()) }; ($interpreter:expr, $gas:expr, $ret:expr) => { match $gas { diff --git a/substrate/frame/revive/src/vm/evm/instructions/memory.rs b/substrate/frame/revive/src/vm/evm/instructions/memory.rs index d767e3fe9dff3..c17b5e22820d4 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/memory.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/memory.rs @@ -32,7 +32,7 @@ use revm::{ pub fn mload<'ext, E: Ext>(context: Context<'_, 'ext, E>) { gas_legacy!(context.interpreter, revm_gas::VERYLOW); popn_top!([], top, context.interpreter); - let offset = as_usize_or_fail!(context.interpreter, top); + let offset: usize = as_usize_or_fail!(context.interpreter, top); resize_memory!(context.interpreter, offset, 32); *top = U256::try_from_be_slice(context.interpreter.memory.slice_len(offset, 32).as_ref()).unwrap() @@ -78,7 +78,7 @@ pub fn mcopy<'ext, E: Ext>(context: Context<'_, 'ext, E>) { // Into usize or fail let len = as_usize_or_fail!(context.interpreter, len); // Deduce gas - gas_or_fail!(context.interpreter, revm_gas::copy_cost_verylow(len)); + gas_or_fail_legacy!(context.interpreter, revm_gas::copy_cost_verylow(len)); if len == 0 { return; } diff --git a/substrate/frame/revive/src/vm/evm/instructions/system.rs b/substrate/frame/revive/src/vm/evm/instructions/system.rs index fa83212b37723..72c31c7955237 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/system.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/system.rs @@ -16,16 +16,23 @@ // limitations under the License. use super::Context; -use crate::vm::Ext; +use crate::{ + address::AddressMapper, + vm::{evm::U256Converter, Ext, RuntimeCosts}, + Config, +}; use core::ptr; use revm::{ interpreter::{ gas as revm_gas, - interpreter_types::{InputsTr, LegacyBytecode, MemoryTr, ReturnData, RuntimeFlag, StackTr}, + interpreter_types::{InputsTr, LegacyBytecode, MemoryTr, ReturnData, StackTr}, CallInput, InstructionResult, Interpreter, }, - primitives::{B256, KECCAK_EMPTY, U256}, + primitives::{Address, B256, KECCAK_EMPTY, U256}, }; +use sp_io::hashing::keccak_256; + +// TODO: Fix the gas handling for the memory operations /// Implements the KECCAK256 instruction. /// @@ -33,15 +40,13 @@ use revm::{ pub fn keccak256<'ext, E: Ext>(context: Context<'_, 'ext, E>) { popn_top!([offset], top, context.interpreter); let len = as_usize_or_fail!(context.interpreter, top); - gas_or_fail!(context.interpreter, revm_gas::keccak256_cost(len)); + gas!(context.interpreter, RuntimeCosts::HashKeccak256(len as u32)); let hash = if len == 0 { KECCAK_EMPTY } else { let from = as_usize_or_fail!(context.interpreter, offset); resize_memory!(context.interpreter, from, len); - let data = context.interpreter.memory.slice_len(from, len); - let data: &[u8] = data.as_ref(); - revm::primitives::keccak256(data) + keccak_256(context.interpreter.memory.slice_len(from, len).as_ref()).into() }; *top = hash.into(); } @@ -50,16 +55,27 @@ pub fn keccak256<'ext, E: Ext>(context: Context<'_, 'ext, E>) { /// /// Pushes the current contract's address onto the stack. pub fn address<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, context.interpreter.input.target_address().into_word().into()); + gas!(context.interpreter, RuntimeCosts::Address); + let address: Address = context.interpreter.extend.address().0.into(); + push!(context.interpreter, address.into_word().into()); } /// Implements the CALLER instruction. /// /// Pushes the caller's address onto the stack. pub fn caller<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, context.interpreter.input.caller_address().into_word().into()); + gas!(context.interpreter, RuntimeCosts::Caller); + match context.interpreter.extend.caller().account_id() { + Ok(account_id) => { + let address: Address = ::AddressMapper::to_address(account_id).0.into(); + push!(context.interpreter, address.into_word().into()); + }, + Err(_) => { + context + .interpreter + .halt(revm::interpreter::InstructionResult::FatalExternalError); + }, + } } /// Implements the CODESIZE instruction. @@ -141,8 +157,9 @@ pub fn calldatasize<'ext, E: Ext>(context: Context<'_, 'ext, E>) { /// /// Pushes the value sent with the current call onto the stack. pub fn callvalue<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, context.interpreter.input.call_value()); + gas!(context.interpreter, RuntimeCosts::ValueTransferred); + let call_value = context.interpreter.extend.value_transferred(); + push!(context.interpreter, call_value.into_revm_u256()); } /// Implements the CALLDATACOPY instruction. @@ -176,14 +193,12 @@ pub fn calldatacopy<'ext, E: Ext>(context: Context<'_, 'ext, E>) { /// EIP-211: New opcodes: RETURNDATASIZE and RETURNDATACOPY pub fn returndatasize<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - check!(context.interpreter, BYZANTIUM); gas_legacy!(context.interpreter, revm_gas::BASE); push!(context.interpreter, U256::from(context.interpreter.return_data.buffer().len())); } /// EIP-211: New opcodes: RETURNDATASIZE and RETURNDATACOPY pub fn returndatacopy<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - check!(context.interpreter, BYZANTIUM); popn!([memory_offset, offset, len], context.interpreter); let len = as_usize_or_fail!(context.interpreter, len); @@ -213,8 +228,11 @@ pub fn returndatacopy<'ext, E: Ext>(context: Context<'_, 'ext, E>) { /// /// Pushes the amount of remaining gas onto the stack. pub fn gas<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, U256::from(context.interpreter.gas.remaining())); + gas!(context.interpreter, RuntimeCosts::RefTimeLeft); + // TODO: This accounts only for 'ref_time' now. It should be fixed to also account for other + // costs. See #9577 for more context. + let gas = context.interpreter.extend.gas_meter().gas_left().ref_time(); + push!(context.interpreter, U256::from(gas)); } /// Common logic for copying data from a source buffer to the EVM's memory. @@ -225,8 +243,7 @@ pub fn memory_resize<'a, E: Ext>( memory_offset: U256, len: usize, ) -> Option { - // Safe to cast usize to u64 - gas_or_fail!(interpreter, revm_gas::copy_cost_verylow(len), None); + gas!(interpreter, RuntimeCosts::CopyToContract(len as u32), None); if len == 0 { return None; } diff --git a/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs b/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs index 1b7d1196be609..3060a4e4f83a5 100644 --- a/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs +++ b/substrate/frame/revive/src/vm/evm/instructions/tx_info.rs @@ -15,41 +15,41 @@ // See the License for the specific language governing permissions and // limitations under the License. -use revm::{ - interpreter::{ - gas as revm_gas, - host::Host, - interpreter_types::{RuntimeFlag, StackTr}, - }, - primitives::U256, -}; +use crate::{address::AddressMapper, evm::runtime::GAS_PRICE, vm::RuntimeCosts}; +use revm::primitives::{Address, U256}; use super::Context; -use crate::vm::Ext; +use crate::{vm::Ext, Config}; /// Implements the GASPRICE instruction. /// /// Gets the gas price of the originating transaction. pub fn gasprice<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, U256::from(context.host.effective_gas_price())); + gas!(context.interpreter, RuntimeCosts::GasPrice); + push!(context.interpreter, U256::from(GAS_PRICE)); } /// Implements the ORIGIN instruction. /// /// Gets the execution origination address. pub fn origin<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - gas_legacy!(context.interpreter, revm_gas::BASE); - push!(context.interpreter, context.host.caller().into_word().into()); + gas!(context.interpreter, RuntimeCosts::Origin); + match context.interpreter.extend.origin().account_id() { + Ok(account_id) => { + let address: Address = ::AddressMapper::to_address(account_id).0.into(); + push!(context.interpreter, address.into_word().into()); + }, + Err(_) => { + context + .interpreter + .halt(revm::interpreter::InstructionResult::FatalExternalError); + }, + } } /// Implements the BLOBHASH instruction. /// /// EIP-4844: Shard Blob Transactions - gets the hash of a transaction blob. pub fn blob_hash<'ext, E: Ext>(context: Context<'_, 'ext, E>) { - check!(context.interpreter, CANCUN); - gas_legacy!(context.interpreter, revm_gas::VERYLOW); - popn_top!([], index, context.interpreter); - let i = as_usize_saturated!(index); - *index = context.host.blob_hash(i).unwrap_or_default(); + context.interpreter.halt(revm::interpreter::InstructionResult::NotActivated); } diff --git a/substrate/frame/revive/src/vm/mod.rs b/substrate/frame/revive/src/vm/mod.rs index b13aaeac47c18..dc01b7879ec3e 100644 --- a/substrate/frame/revive/src/vm/mod.rs +++ b/substrate/frame/revive/src/vm/mod.rs @@ -30,8 +30,8 @@ use crate::{ gas::{GasMeter, Token}, storage::meter::Diff, weights::WeightInfo, - AccountIdOf, BalanceOf, CodeInfoOf, CodeRemoved, Config, Error, HoldReason, PristineCode, - Weight, LOG_TARGET, + AccountIdOf, BalanceOf, CodeInfoOf, CodeRemoved, Config, Error, ExecError, HoldReason, + PristineCode, Weight, LOG_TARGET, }; use alloc::vec::Vec; use codec::{Decode, Encode, MaxEncodedLen}; @@ -42,6 +42,7 @@ use frame_support::{ tokens::{Fortitude, Precision, Preservation}, }, }; +use pallet_revive_uapi::ReturnErrorCode; use sp_core::{Get, H256, U256}; use sp_runtime::DispatchError; @@ -331,6 +332,10 @@ where Ok(Self { code, code_info, code_hash }) } + fn from_evm_init_code(code: Vec, owner: AccountIdOf) -> Result { + ContractBlob::from_evm_init_code(code, owner) + } + fn execute>( self, ext: &mut E, @@ -364,3 +369,30 @@ where &self.code_info } } + +/// Fallible conversion of a `ExecError` to `ReturnErrorCode`. +/// +/// This is used when converting the error returned from a subcall in order to decide +/// whether to trap the caller or allow handling of the error. +pub(crate) fn exec_error_into_return_code( + from: ExecError, +) -> Result { + use crate::exec::ErrorOrigin::Callee; + use ReturnErrorCode::*; + + let transfer_failed = Error::::TransferFailed.into(); + let out_of_gas = Error::::OutOfGas.into(); + let out_of_deposit = Error::::StorageDepositLimitExhausted.into(); + let duplicate_contract = Error::::DuplicateContract.into(); + let unsupported_precompile = Error::::UnsupportedPrecompileAddress.into(); + + // errors in the callee do not trap the caller + match (from.error, from.origin) { + (err, _) if err == transfer_failed => Ok(TransferFailed), + (err, _) if err == duplicate_contract => Ok(DuplicateContractAddress), + (err, _) if err == unsupported_precompile => Err(err), + (err, Callee) if err == out_of_gas || err == out_of_deposit => Ok(OutOfResources), + (_, Callee) => Ok(CalleeTrapped), + (err, _) => Err(err), + } +} diff --git a/substrate/frame/revive/src/vm/pvm.rs b/substrate/frame/revive/src/vm/pvm.rs index d9229eb00c0c1..79902ef69b20e 100644 --- a/substrate/frame/revive/src/vm/pvm.rs +++ b/substrate/frame/revive/src/vm/pvm.rs @@ -29,7 +29,7 @@ use crate::{ limits, precompiles::{All as AllPrecompiles, Precompiles}, primitives::ExecReturnValue, - BalanceOf, Config, Error, Pallet, RuntimeCosts, LOG_TARGET, SENTINEL, + BalanceOf, Code, Config, Error, Pallet, RuntimeCosts, LOG_TARGET, SENTINEL, }; use alloc::{vec, vec::Vec}; use codec::Encode; @@ -458,31 +458,6 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { Ok(()) } - /// Fallible conversion of a `ExecError` to `ReturnErrorCode`. - /// - /// This is used when converting the error returned from a subcall in order to decide - /// whether to trap the caller or allow handling of the error. - fn exec_error_into_return_code(from: ExecError) -> Result { - use crate::exec::ErrorOrigin::Callee; - use ReturnErrorCode::*; - - let transfer_failed = Error::::TransferFailed.into(); - let out_of_gas = Error::::OutOfGas.into(); - let out_of_deposit = Error::::StorageDepositLimitExhausted.into(); - let duplicate_contract = Error::::DuplicateContract.into(); - let unsupported_precompile = Error::::UnsupportedPrecompileAddress.into(); - - // errors in the callee do not trap the caller - match (from.error, from.origin) { - (err, _) if err == transfer_failed => Ok(TransferFailed), - (err, _) if err == duplicate_contract => Ok(DuplicateContractAddress), - (err, _) if err == unsupported_precompile => Err(err), - (err, Callee) if err == out_of_gas || err == out_of_deposit => Ok(OutOfResources), - (_, Callee) => Ok(CalleeTrapped), - (err, _) => Err(err), - } - } - fn decode_key(&self, memory: &M, key_ptr: u32, key_len: u32) -> Result { let res = match key_len { SENTINEL => { @@ -823,7 +798,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { Ok(self.ext.last_frame_output().into()) }, Err(err) => { - let error_code = Self::exec_error_into_return_code(err)?; + let error_code = super::exec_error_into_return_code::(err)?; memory.write(output_len_ptr, &0u32.to_le_bytes())?; Ok(error_code) }, @@ -880,7 +855,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { match self.ext.instantiate( weight, deposit_limit, - code_hash, + Code::Existing(code_hash), value, input_data, salt.as_ref(), @@ -908,7 +883,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { write_result?; Ok(self.ext.last_frame_output().into()) }, - Err(err) => Ok(Self::exec_error_into_return_code(err)?), + Err(err) => Ok(super::exec_error_into_return_code::(err)?), } } }