Skip to content

Commit 314e271

Browse files
RomarQAgusrodriahmadkaouk
authored andcommitted
Cancun support (#206)
* 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>
1 parent af392d9 commit 314e271

File tree

10 files changed

+163
-39
lines changed

10 files changed

+163
-39
lines changed

Cargo.lock

Lines changed: 4 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frame/evm/src/lib.rs

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ use scale_info::TypeInfo;
7979
// Substrate
8080
use frame_support::{
8181
dispatch::{DispatchResultWithPostInfo, Pays, PostDispatchInfo},
82-
storage::{child::KillStorageResult, KeyPrefixIterator},
82+
storage::KeyPrefixIterator,
8383
traits::{
8484
fungible::{Balanced, Credit, Debt},
8585
tokens::{
@@ -185,7 +185,7 @@ pub mod pallet {
185185

186186
/// EVM config used in the module.
187187
fn config() -> &'static EvmConfig {
188-
&SHANGHAI_CONFIG
188+
&CANCUN_CONFIG
189189
}
190190
}
191191

@@ -793,7 +793,7 @@ impl<T: Config> GasWeightMapping for FixedGasWeightMapping<T> {
793793
}
794794
}
795795

796-
static SHANGHAI_CONFIG: EvmConfig = EvmConfig::shanghai();
796+
static CANCUN_CONFIG: EvmConfig = EvmConfig::cancun();
797797

798798
impl<T: Config> Pallet<T> {
799799
/// Check whether an account is empty.
@@ -822,32 +822,13 @@ impl<T: Config> Pallet<T> {
822822
/// Remove an account.
823823
pub fn remove_account(address: &H160) {
824824
if <AccountCodes<T>>::contains_key(address) {
825-
// Remember to call `dec_sufficients` when clearing Suicided.
826-
<Suicided<T>>::insert(address, ());
827-
828-
// In theory, we can always have pre-EIP161 contracts, so we
829-
// make sure the account nonce is at least one.
830825
let account_id = T::AddressMapping::into_account_id(*address);
831-
frame_system::Pallet::<T>::inc_account_nonce(&account_id);
826+
let _ = frame_system::Pallet::<T>::dec_sufficients(&account_id);
832827
}
833828

834829
<AccountCodes<T>>::remove(address);
835830
<AccountCodesMetadata<T>>::remove(address);
836-
837-
if T::SuicideQuickClearLimit::get() > 0 {
838-
#[allow(deprecated)]
839-
let res = <AccountStorages<T>>::remove_prefix(address, Some(T::SuicideQuickClearLimit::get()));
840-
841-
match res {
842-
KillStorageResult::AllRemoved(_) => {
843-
<Suicided<T>>::remove(address);
844-
845-
let account_id = T::AddressMapping::into_account_id(*address);
846-
let _ = frame_system::Pallet::<T>::dec_sufficients(&account_id);
847-
}
848-
KillStorageResult::SomeRemaining(_) => (),
849-
}
850-
}
831+
let _ = <AccountStorages<T>>::clear_prefix(address, u32::max_value(), None);
851832
}
852833

853834
/// Create an account.

frame/evm/src/runner/stack.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,7 @@ where
571571
struct SubstrateStackSubstate<'config> {
572572
metadata: StackSubstateMetadata<'config>,
573573
deletes: BTreeSet<H160>,
574+
creates: BTreeSet<H160>,
574575
logs: Vec<Log>,
575576
parent: Option<Box<SubstrateStackSubstate<'config>>>,
576577
}
@@ -589,6 +590,7 @@ impl<'config> SubstrateStackSubstate<'config> {
589590
metadata: self.metadata.spit_child(gas_limit, is_static),
590591
parent: None,
591592
deletes: BTreeSet::new(),
593+
creates: BTreeSet::new(),
592594
logs: Vec::new(),
593595
};
594596
mem::swap(&mut entering, self);
@@ -605,7 +607,7 @@ impl<'config> SubstrateStackSubstate<'config> {
605607
self.metadata.swallow_commit(exited.metadata)?;
606608
self.logs.append(&mut exited.logs);
607609
self.deletes.append(&mut exited.deletes);
608-
610+
self.creates.append(&mut exited.creates);
609611
sp_io::storage::commit_transaction();
610612
Ok(())
611613
}
@@ -640,10 +642,24 @@ impl<'config> SubstrateStackSubstate<'config> {
640642
false
641643
}
642644

645+
pub fn created(&self, address: H160) -> bool {
646+
if self.creates.contains(&address) {
647+
return true;
648+
}
649+
650+
if let Some(parent) = self.parent.as_ref() {
651+
return parent.created(address);
652+
}
653+
654+
false
655+
}
656+
643657
pub fn set_deleted(&mut self, address: H160) {
644658
self.deletes.insert(address);
645659
}
646660

661+
pub fn set_created(&mut self, address: H160) { self.creates.insert(address); }
662+
647663
pub fn log(&mut self, address: H160, topics: Vec<H256>, data: Vec<u8>) {
648664
self.logs.push(Log {
649665
address,
@@ -676,6 +692,7 @@ pub struct SubstrateStackState<'vicinity, 'config, T> {
676692
vicinity: &'vicinity Vicinity,
677693
substate: SubstrateStackSubstate<'config>,
678694
original_storage: BTreeMap<(H160, H256), H256>,
695+
transient_storage: BTreeMap<(H160, H256), H256>,
679696
recorded: Recorded,
680697
weight_info: Option<WeightInfo>,
681698
_marker: PhantomData<T>,
@@ -693,11 +710,13 @@ impl<'vicinity, 'config, T: Config> SubstrateStackState<'vicinity, 'config, T> {
693710
substate: SubstrateStackSubstate {
694711
metadata,
695712
deletes: BTreeSet::new(),
713+
creates: BTreeSet::new(),
696714
logs: Vec::new(),
697715
parent: None,
698716
},
699717
_marker: PhantomData,
700718
original_storage: BTreeMap::new(),
719+
transient_storage: BTreeMap::new(),
701720
recorded: Default::default(),
702721
weight_info,
703722
}
@@ -791,6 +810,13 @@ where
791810
<AccountStorages<T>>::get(address, index)
792811
}
793812

813+
fn transient_storage(&self, address: H160, index: H256) -> H256 {
814+
self.transient_storage
815+
.get(&(address, index))
816+
.copied()
817+
.unwrap_or_default()
818+
}
819+
794820
fn original_storage(&self, address: H160, index: H256) -> Option<H256> {
795821
Some(
796822
self.original_storage
@@ -838,6 +864,10 @@ where
838864
self.substate.deleted(address)
839865
}
840866

867+
fn created(&self, address: H160) -> bool {
868+
self.substate.created(address)
869+
}
870+
841871
fn inc_nonce(&mut self, address: H160) -> Result<(), ExitError> {
842872
let account_id = T::AddressMapping::into_account_id(address);
843873
frame_system::Pallet::<T>::inc_account_nonce(&account_id);
@@ -877,6 +907,10 @@ where
877907
}
878908
}
879909

910+
fn set_transient_storage(&mut self, address: H160, key: H256, value: H256) {
911+
self.transient_storage.insert((address, key), value);
912+
}
913+
880914
fn reset_storage(&mut self, address: H160) {
881915
#[allow(deprecated)]
882916
let _ = <AccountStorages<T>>::remove_prefix(address, None);
@@ -890,6 +924,8 @@ where
890924
self.substate.set_deleted(address)
891925
}
892926

927+
fn set_created(&mut self, address: H160) { self.substate.set_created(address); }
928+
893929
fn set_code(&mut self, address: H160, code: Vec<u8>) {
894930
log::debug!(
895931
target: "evm",

frame/evm/src/tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1037,7 +1037,7 @@ fn handle_sufficient_reference() {
10371037
assert_eq!(account_2.sufficients, 1);
10381038
EVM::remove_account(&addr_2);
10391039
let account_2 = frame_system::Account::<Test>::get(substrate_addr_2);
1040-
assert_eq!(account_2.sufficients, 1);
1040+
assert_eq!(account_2.sufficients, 0);
10411041
});
10421042
}
10431043

precompiles/src/testing/handle.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ impl PrecompileHandle for MockHandle {
117117
if self
118118
.record_cost(crate::evm::costs::call_cost(
119119
context.apparent_value,
120-
&evm::Config::london(),
120+
&evm::Config::cancun(),
121121
))
122122
.is_err()
123123
{

primitives/evm/src/validation.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ mod tests {
271271
UnknownError,
272272
}
273273

274-
static SHANGHAI_CONFIG: evm::Config = evm::Config::shanghai();
274+
static CANCUN_CONFIG: evm::Config = evm::Config::cancun();
275275

276276
impl From<TransactionValidationError> for TestError {
277277
fn from(e: TransactionValidationError) -> Self {
@@ -345,7 +345,7 @@ mod tests {
345345
} = input;
346346
CheckEvmTransaction::<TestError>::new(
347347
CheckEvmTransactionConfig {
348-
evm_config: &SHANGHAI_CONFIG,
348+
evm_config: &CANCUN_CONFIG,
349349
block_gas_limit: blockchain_gas_limit,
350350
base_fee: blockchain_base_fee,
351351
chain_id: blockchain_chain_id,

ts-tests/contracts/eip1153.sol

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.24;
3+
4+
contract ReentrancyProtected {
5+
// A constant key for the reentrancy guard stored in Transient Storage.
6+
// This acts as a unique identifier for the reentrancy lock.
7+
bytes32 constant REENTRANCY_GUARD = keccak256("REENTRANCY_GUARD");
8+
9+
// Modifier to prevent reentrant calls.
10+
// It checks if the reentrancy guard is set (indicating an ongoing execution)
11+
// and sets the guard before proceeding with the function execution.
12+
// After the function executes, it resets the guard to allow future calls.
13+
modifier nonReentrant() {
14+
// Ensure the guard is not set (i.e., no ongoing execution).
15+
require(tload(REENTRANCY_GUARD) == 0, "Reentrant call detected.");
16+
17+
// Set the guard to block reentrant calls.
18+
tstore(REENTRANCY_GUARD, 1);
19+
20+
_; // Execute the function body.
21+
22+
// Reset the guard after execution to allow future calls.
23+
tstore(REENTRANCY_GUARD, 0);
24+
}
25+
26+
// Uses inline assembly to access the Transient Storage's tstore operation.
27+
function tstore(bytes32 location, uint value) private {
28+
assembly {
29+
tstore(location, value)
30+
}
31+
}
32+
33+
// Uses inline assembly to access the Transient Storage's tload operation.
34+
// Returns the value stored at the given location.
35+
function tload(bytes32 location) private returns (uint value) {
36+
assembly {
37+
value := tload(location)
38+
}
39+
}
40+
41+
function nonReentrantMethod() public nonReentrant() {
42+
(bool success, bytes memory result) = msg.sender.call("");
43+
if (!success) {
44+
assembly {
45+
revert(add(32, result), mload(result))
46+
}
47+
}
48+
}
49+
50+
function test() external {
51+
this.nonReentrantMethod();
52+
}
53+
54+
receive() external payable {
55+
this.nonReentrantMethod();
56+
}
57+
}

ts-tests/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"fmt-check": "prettier ./tests --check",
88
"fmt": "prettier ./tests --write",
99
"build": "truffle compile",
10-
"test": "mocha -r ts-node/register 'tests/**/*.ts'",
10+
"test": "mocha -r ts-node/register 'tests/test-eip1153.ts'",
1111
"test-sql": "FRONTIER_BACKEND_TYPE='sql' mocha -r ts-node/register 'tests/**/*.ts'"
1212
},
1313
"author": "",

ts-tests/tests/test-eip1153.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { expect, use as chaiUse } from "chai";
2+
import chaiAsPromised from "chai-as-promised";
3+
import { AbiItem } from "web3-utils";
4+
5+
import ReentrancyProtected from "../build/contracts/ReentrancyProtected.json";
6+
import { GENESIS_ACCOUNT, GENESIS_ACCOUNT_PRIVATE_KEY } from "./config";
7+
import { createAndFinalizeBlock, customRequest, describeWithFrontier } from "./util";
8+
9+
chaiUse(chaiAsPromised);
10+
11+
describeWithFrontier("Frontier RPC (EIP-1153)", (context) => {
12+
const TEST_CONTRACT_BYTECODE = ReentrancyProtected.bytecode;
13+
const TEST_CONTRACT_ABI = ReentrancyProtected.abi as AbiItem[];
14+
let contract_address: string = null;
15+
16+
// Those test are ordered. In general this should be avoided, but due to the time it takes
17+
// to spin up a frontier node, it saves a lot of time.
18+
19+
before("create the contract", async function () {
20+
this.timeout(15000);
21+
const tx = await context.web3.eth.accounts.signTransaction(
22+
{
23+
from: GENESIS_ACCOUNT,
24+
data: TEST_CONTRACT_BYTECODE,
25+
value: "0x00",
26+
gasPrice: "0x3B9ACA00",
27+
gas: "0x100000",
28+
},
29+
GENESIS_ACCOUNT_PRIVATE_KEY
30+
);
31+
await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]);
32+
await createAndFinalizeBlock(context.web3);
33+
34+
const receipt = await context.web3.eth.getTransactionReceipt(tx.transactionHash);
35+
contract_address = receipt.contractAddress;
36+
});
37+
38+
it("should detect reentrant call and revert", async function () {
39+
const contract = new context.web3.eth.Contract(TEST_CONTRACT_ABI, contract_address, {
40+
from: GENESIS_ACCOUNT,
41+
gasPrice: "0x3B9ACA00"
42+
});
43+
44+
try {
45+
await contract.methods.test().call();
46+
} catch (error) {
47+
return expect(error.message).to.be.eq(
48+
"Returned error: VM Exception while processing transaction: revert Reentrant call detected."
49+
);
50+
}
51+
52+
expect.fail("Expected the contract call to fail");
53+
});
54+
});

ts-tests/truffle-config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ module.exports = {
8585
// Configure your compilers
8686
compilers: {
8787
solc: {
88-
version: "0.8.2", // Fetch exact version from solc-bin (default: truffle's version)
88+
version: "0.8.25", // Fetch exact version from solc-bin (default: truffle's version)
8989
docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
9090
// settings: { // See the solidity docs for advice about optimization and evmVersion
9191
// optimizer: {

0 commit comments

Comments
 (0)