forked from polkadot-evm/frontier
-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support transient storage (EIP-1153)
- Loading branch information
Showing
6 changed files
with
130 additions
and
6 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters