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 8119ca3
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 6 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.

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-eip1153", 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
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();
}
}
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 8119ca3

Please sign in to comment.