Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cancun support #206

Merged
merged 7 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ derive_more = "0.99"
environmental = { version = "1.1.4", default-features = false }
ethereum = { version = "0.15.0", default-features = false }
ethereum-types = { version = "0.14.1", default-features = false }
evm = { git = "https://github.com/moonbeam-foundation/evm", branch = "moonbeam-polkadot-v1.7.2", default-features = false }
evm = { git = "https://github.com/moonbeam-foundation/evm", branch = "v0.4.1-cancun", default-features = false }
futures = "0.3.30"
hash-db = { version = "0.16.0", default-features = false }
hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
Expand Down
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, ());
RomarQ marked this conversation as resolved.
Show resolved Hide resolved

// 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