Skip to content

Commit

Permalink
feat: support transient storage (EIP-1153)
Browse files Browse the repository at this point in the history
  • Loading branch information
RomarQ committed May 3, 2024
1 parent 2b75c75 commit c350186
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 2 deletions.
13 changes: 13 additions & 0 deletions frame/evm/src/runner/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,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 @@ -750,6 +751,7 @@ impl<'vicinity, 'config, T: Config> SubstrateStackState<'vicinity, 'config, T> {
},
_marker: PhantomData,
original_storage: BTreeMap::new(),
transient_storage: BTreeMap::new(),
recorded: Default::default(),
weight_info,
storage_meter,
Expand Down Expand Up @@ -844,6 +846,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 @@ -930,6 +939,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 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 c350186

Please sign in to comment.