Skip to content

Commit

Permalink
Cancun support (#206)
Browse files Browse the repository at this point in the history
* use Cancun config
* feat: support transient storage (EIP-1153)
* Update evm dependency
* Adds a new opcode (MCOPY) for copying memory
* feat: support cancun selfdestruct changes (EIP-6780)
---------

Co-authored-by: Agusrodri <agusrodriguez2456@gmail.com>
Co-authored-by: Ahmad Kaouk <ahmadkaouk.93@gmail.com>
  • Loading branch information
3 people committed May 22, 2024
1 parent 88dd6be commit 57a4a2a
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 35 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 5 additions & 24 deletions frame/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ use scale_info::TypeInfo;
// Substrate
use frame_support::{
dispatch::{DispatchResultWithPostInfo, Pays, PostDispatchInfo},
storage::{child::KillStorageResult, KeyPrefixIterator},
storage::KeyPrefixIterator,
traits::{
fungible::{Balanced, Credit, Debt},
tokens::{
Expand Down Expand Up @@ -185,7 +185,7 @@ pub mod pallet {

/// EVM config used in the module.
fn config() -> &'static EvmConfig {
&SHANGHAI_CONFIG
&CANCUN_CONFIG
}
}

Expand Down Expand Up @@ -792,7 +792,7 @@ impl<T: Config> GasWeightMapping for FixedGasWeightMapping<T> {
}
}

static SHANGHAI_CONFIG: EvmConfig = EvmConfig::shanghai();
static CANCUN_CONFIG: EvmConfig = EvmConfig::cancun();

impl<T: Config> Pallet<T> {
/// Check whether an account is empty.
Expand Down Expand Up @@ -821,32 +821,13 @@ impl<T: Config> Pallet<T> {
/// Remove an account.
pub fn remove_account(address: &H160) {
if <AccountCodes<T>>::contains_key(address) {
// Remember to call `dec_sufficients` when clearing Suicided.
<Suicided<T>>::insert(address, ());

// In theory, we can always have pre-EIP161 contracts, so we
// make sure the account nonce is at least one.
let account_id = T::AddressMapping::into_account_id(*address);
frame_system::Pallet::<T>::inc_account_nonce(&account_id);
let _ = frame_system::Pallet::<T>::dec_sufficients(&account_id);
}

<AccountCodes<T>>::remove(address);
<AccountCodesMetadata<T>>::remove(address);

if T::SuicideQuickClearLimit::get() > 0 {
#[allow(deprecated)]
let res = <AccountStorages<T>>::remove_prefix(address, Some(T::SuicideQuickClearLimit::get()));

match res {
KillStorageResult::AllRemoved(_) => {
<Suicided<T>>::remove(address);

let account_id = T::AddressMapping::into_account_id(*address);
let _ = frame_system::Pallet::<T>::dec_sufficients(&account_id);
}
KillStorageResult::SomeRemaining(_) => (),
}
}
let _ = <AccountStorages<T>>::clear_prefix(address, u32::max_value(), None);
}

/// Create an account.
Expand Down
38 changes: 37 additions & 1 deletion frame/evm/src/runner/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,7 @@ where
struct SubstrateStackSubstate<'config> {
metadata: StackSubstateMetadata<'config>,
deletes: BTreeSet<H160>,
creates: BTreeSet<H160>,
logs: Vec<Log>,
parent: Option<Box<SubstrateStackSubstate<'config>>>,
}
Expand All @@ -638,6 +639,7 @@ impl<'config> SubstrateStackSubstate<'config> {
metadata: self.metadata.spit_child(gas_limit, is_static),
parent: None,
deletes: BTreeSet::new(),
creates: BTreeSet::new(),
logs: Vec::new(),
};
mem::swap(&mut entering, self);
Expand All @@ -654,7 +656,7 @@ impl<'config> SubstrateStackSubstate<'config> {
self.metadata.swallow_commit(exited.metadata)?;
self.logs.append(&mut exited.logs);
self.deletes.append(&mut exited.deletes);

self.creates.append(&mut exited.creates);
sp_io::storage::commit_transaction();
Ok(())
}
Expand Down Expand Up @@ -689,10 +691,24 @@ impl<'config> SubstrateStackSubstate<'config> {
false
}

pub fn created(&self, address: H160) -> bool {
if self.creates.contains(&address) {
return true;
}

if let Some(parent) = self.parent.as_ref() {
return parent.created(address);
}

false
}

pub fn set_deleted(&mut self, address: H160) {
self.deletes.insert(address);
}

pub fn set_created(&mut self, address: H160) { self.creates.insert(address); }

pub fn log(&mut self, address: H160, topics: Vec<H256>, data: Vec<u8>) {
self.logs.push(Log {
address,
Expand Down Expand Up @@ -725,6 +741,7 @@ pub struct SubstrateStackState<'vicinity, 'config, T> {
vicinity: &'vicinity Vicinity,
substate: SubstrateStackSubstate<'config>,
original_storage: BTreeMap<(H160, H256), H256>,
transient_storage: BTreeMap<(H160, H256), H256>,
recorded: Recorded,
weight_info: Option<WeightInfo>,
storage_meter: Option<StorageMeter>,
Expand All @@ -745,11 +762,13 @@ impl<'vicinity, 'config, T: Config> SubstrateStackState<'vicinity, 'config, T> {
substate: SubstrateStackSubstate {
metadata,
deletes: BTreeSet::new(),
creates: BTreeSet::new(),
logs: Vec::new(),
parent: None,
},
_marker: PhantomData,
original_storage: BTreeMap::new(),
transient_storage: BTreeMap::new(),
recorded: Default::default(),
weight_info,
storage_meter,
Expand Down Expand Up @@ -844,6 +863,13 @@ where
<AccountStorages<T>>::get(address, index)
}

fn transient_storage(&self, address: H160, index: H256) -> H256 {
self.transient_storage
.get(&(address, index))
.copied()
.unwrap_or_default()
}

fn original_storage(&self, address: H160, index: H256) -> Option<H256> {
Some(
self.original_storage
Expand Down Expand Up @@ -891,6 +917,10 @@ where
self.substate.deleted(address)
}

fn created(&self, address: H160) -> bool {
self.substate.created(address)
}

fn inc_nonce(&mut self, address: H160) -> Result<(), ExitError> {
let account_id = T::AddressMapping::into_account_id(address);
frame_system::Pallet::<T>::inc_account_nonce(&account_id);
Expand Down Expand Up @@ -930,6 +960,10 @@ where
}
}

fn set_transient_storage(&mut self, address: H160, key: H256, value: H256) {
self.transient_storage.insert((address, key), value);
}

fn reset_storage(&mut self, address: H160) {
#[allow(deprecated)]
let _ = <AccountStorages<T>>::remove_prefix(address, None);
Expand All @@ -943,6 +977,8 @@ where
self.substate.set_deleted(address)
}

fn set_created(&mut self, address: H160) { self.substate.set_created(address); }

fn set_code(&mut self, address: H160, code: Vec<u8>) {
log::debug!(
target: "evm",
Expand Down
2 changes: 1 addition & 1 deletion frame/evm/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1269,7 +1269,7 @@ fn handle_sufficient_reference() {
assert_eq!(account_2.sufficients, 1);
EVM::remove_account(&addr_2);
let account_2 = frame_system::Account::<Test>::get(substrate_addr_2);
assert_eq!(account_2.sufficients, 1);
assert_eq!(account_2.sufficients, 0);
});
}

Expand Down
2 changes: 1 addition & 1 deletion precompiles/src/testing/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ impl PrecompileHandle for MockHandle {
if self
.record_cost(crate::evm::costs::call_cost(
context.apparent_value,
&evm::Config::london(),
&evm::Config::cancun(),
))
.is_err()
{
Expand Down
4 changes: 2 additions & 2 deletions primitives/evm/src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ mod tests {
UnknownError,
}

static SHANGHAI_CONFIG: evm::Config = evm::Config::shanghai();
static CANCUN_CONFIG: evm::Config = evm::Config::cancun();

impl From<TransactionValidationError> for TestError {
fn from(e: TransactionValidationError) -> Self {
Expand Down Expand Up @@ -345,7 +345,7 @@ mod tests {
} = input;
CheckEvmTransaction::<TestError>::new(
CheckEvmTransactionConfig {
evm_config: &SHANGHAI_CONFIG,
evm_config: &CANCUN_CONFIG,
block_gas_limit: blockchain_gas_limit,
base_fee: blockchain_base_fee,
chain_id: blockchain_chain_id,
Expand Down
57 changes: 57 additions & 0 deletions ts-tests/contracts/eip1153.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract ReentrancyProtected {
// A constant key for the reentrancy guard stored in Transient Storage.
// This acts as a unique identifier for the reentrancy lock.
bytes32 constant REENTRANCY_GUARD = keccak256("REENTRANCY_GUARD");

// Modifier to prevent reentrant calls.
// It checks if the reentrancy guard is set (indicating an ongoing execution)
// and sets the guard before proceeding with the function execution.
// After the function executes, it resets the guard to allow future calls.
modifier nonReentrant() {
// Ensure the guard is not set (i.e., no ongoing execution).
require(tload(REENTRANCY_GUARD) == 0, "Reentrant call detected.");

// Set the guard to block reentrant calls.
tstore(REENTRANCY_GUARD, 1);

_; // Execute the function body.

// Reset the guard after execution to allow future calls.
tstore(REENTRANCY_GUARD, 0);
}

// Uses inline assembly to access the Transient Storage's tstore operation.
function tstore(bytes32 location, uint value) private {
assembly {
tstore(location, value)
}
}

// Uses inline assembly to access the Transient Storage's tload operation.
// Returns the value stored at the given location.
function tload(bytes32 location) private returns (uint value) {
assembly {
value := tload(location)
}
}

function nonReentrantMethod() public nonReentrant() {
(bool success, bytes memory result) = msg.sender.call("");
if (!success) {
assembly {
revert(add(32, result), mload(result))
}
}
}

function test() external {
this.nonReentrantMethod();
}

receive() external payable {
this.nonReentrantMethod();
}
}
2 changes: 1 addition & 1 deletion ts-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"fmt-check": "prettier ./tests --check",
"fmt": "prettier ./tests --write",
"build": "truffle compile",
"test": "mocha -r ts-node/register 'tests/**/*.ts'",
"test": "mocha -r ts-node/register 'tests/test-eip1153.ts'",
"test-sql": "FRONTIER_BACKEND_TYPE='sql' mocha -r ts-node/register 'tests/**/*.ts'"
},
"author": "",
Expand Down
54 changes: 54 additions & 0 deletions ts-tests/tests/test-eip1153.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { expect, use as chaiUse } from "chai";
import chaiAsPromised from "chai-as-promised";
import { AbiItem } from "web3-utils";

import ReentrancyProtected from "../build/contracts/ReentrancyProtected.json";
import { GENESIS_ACCOUNT, GENESIS_ACCOUNT_PRIVATE_KEY } from "./config";
import { createAndFinalizeBlock, customRequest, describeWithFrontier } from "./util";

chaiUse(chaiAsPromised);

describeWithFrontier("Frontier RPC (EIP-1153)", (context) => {
const TEST_CONTRACT_BYTECODE = ReentrancyProtected.bytecode;
const TEST_CONTRACT_ABI = ReentrancyProtected.abi as AbiItem[];
let contract_address: string = null;

// Those test are ordered. In general this should be avoided, but due to the time it takes
// to spin up a frontier node, it saves a lot of time.

before("create the contract", async function () {
this.timeout(15000);
const tx = await context.web3.eth.accounts.signTransaction(
{
from: GENESIS_ACCOUNT,
data: TEST_CONTRACT_BYTECODE,
value: "0x00",
gasPrice: "0x3B9ACA00",
gas: "0x100000",
},
GENESIS_ACCOUNT_PRIVATE_KEY
);
await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]);
await createAndFinalizeBlock(context.web3);

const receipt = await context.web3.eth.getTransactionReceipt(tx.transactionHash);
contract_address = receipt.contractAddress;
});

it("should detect reentrant call and revert", async function () {
const contract = new context.web3.eth.Contract(TEST_CONTRACT_ABI, contract_address, {
from: GENESIS_ACCOUNT,
gasPrice: "0x3B9ACA00"
});

try {
await contract.methods.test().call();
} catch (error) {
return expect(error.message).to.be.eq(
"Returned error: VM Exception while processing transaction: revert Reentrant call detected."
);
}

expect.fail("Expected the contract call to fail");
});
});
2 changes: 1 addition & 1 deletion ts-tests/truffle-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ module.exports = {
// Configure your compilers
compilers: {
solc: {
version: "0.8.2", // Fetch exact version from solc-bin (default: truffle's version)
version: "0.8.25", // Fetch exact version from solc-bin (default: truffle's version)
docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
// settings: { // See the solidity docs for advice about optimization and evmVersion
// optimizer: {
Expand Down

0 comments on commit 57a4a2a

Please sign in to comment.