Commit 12a72abf authored by Diego's avatar Diego Committed by GitHub

Create Context-Aware Transient Storage Library (#10259)

* contracts-bedrock: create TransientContext.sol

* contracts-bedrock: create test for Transient.sol

* contracts-bedrock: add documentation for Transient library

* contracts-bedrock: fix transient test

* contracts-bedrock: minor improvements to Transient lib tests

* contracts-bedrock: minor improvements to documentation for tests of Transient

* contracts-bedrock: minor improvements to documentation for tests of Transient

* contracts-bedrock: refactor Transient lib

* contracts-bedrock: add missing documentation in Transient lib

* contracts-bedrock: set correct CALL_DEPTH_SLOT in Transient lib

* contracts-bedrock: refactor Transient lib into TransientContext lib

* contracts-bedrock: improve labeling of vars in TransientContext

* contracts-bedrock: create tests for TransientContext

* contracts-bedrock: change var type of CALL_DEPTH_SLOT

* contracts-bedrock: refactor tests for TransientContext

* contracts-bedrock: refactor tests for TransientContext

* contracts-bedrock: add testFuzz_increment_fromMax_reverts

* contracts-bedrock: create test_increment_overflow_succeeds

* contracts-bedrock: drop underflow check in TransientContext

* contracts-bedrock: add additional tests for TransientContext

* contracts-bedrock: add documentation for TransientContext

* contracts-bedrock: add documentation for TransientContext

* contracts-bedrock: use suffix for return vars in TransientContext

* contracts-bedrock: use inline hash for callDepthSlot in TransienttContext tests

* contracts-bedrock: reintroduce transient-storage ignored error code to foundry.toml

* contracts-bedrock: remove unnecessary tests for TransientContext

* contracts-bedrock: create snapshots
parent 1f551590
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/// @title Transient
/// @notice Library for transient storage.
library TransientContext {
/// @notice Slot for call depth.
/// Equal to bytes32(uint256(keccak256("transient.calldepth")) - 1).
bytes32 internal constant CALL_DEPTH_SLOT = 0x7a74fd168763fd280eaec3bcd2fd62d0e795027adc8183a693c497a7c2b10b5c;
/// @notice Gets the call depth.
/// @return callDepth_ Current call depth.
function callDepth() internal view returns (uint256 callDepth_) {
assembly {
callDepth_ := tload(CALL_DEPTH_SLOT)
}
}
/// @notice Gets value in transient storage for a slot at the current call depth.
/// @param _slot Slot to get.
/// @return value_ Transient value.
function get(bytes32 _slot) internal view returns (uint256 value_) {
assembly {
mstore(0, tload(CALL_DEPTH_SLOT))
mstore(32, _slot)
value_ := tload(keccak256(0, 64))
}
}
/// @notice Sets a value in transient storage for a slot at the current call depth.
/// @param _slot Slot to set.
/// @param _value Value to set.
function set(bytes32 _slot, uint256 _value) internal {
assembly {
mstore(0, tload(CALL_DEPTH_SLOT))
mstore(32, _slot)
tstore(keccak256(0, 64), _value)
}
}
/// @notice Increments call depth.
/// This function can overflow. However, this is ok because there's still
/// only one value stored per slot.
function increment() internal {
assembly {
tstore(CALL_DEPTH_SLOT, add(tload(CALL_DEPTH_SLOT), 1))
}
}
/// @notice Decrements call depth.
/// This function can underflow. However, this is ok because there's still
/// only one value stored per slot.
function decrement() internal {
assembly {
tstore(CALL_DEPTH_SLOT, sub(tload(CALL_DEPTH_SLOT), 1))
}
}
}
/// @title TransientReentrancyAware
/// @notice Reentrancy-aware modifier for transient storage, which increments and
/// decrements the call depth when entering and exiting a function.
contract TransientReentrancyAware {
/// @notice Modifier to make a function reentrancy-aware.
modifier reentrantAware() {
TransientContext.increment();
_;
TransientContext.decrement();
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
// Testing utilities
import { Test } from "forge-std/Test.sol";
// Target contractS
import { TransientContext } from "src/libraries/TransientContext.sol";
import { TransientReentrancyAware } from "src/libraries/TransientContext.sol";
/// @title TransientContextTest
/// @notice Tests for the TransientContext library.
contract TransientContextTest is Test {
/// @notice Slot for call depth.
bytes32 internal callDepthSlot = bytes32(uint256(keccak256("transient.calldepth")) - 1);
/// @notice Tests that `callDepth()` outputs the corrects call depth.
/// @param _callDepth Call depth to test.
function testFuzz_callDepth_succeeds(uint256 _callDepth) public {
assembly {
tstore(sload(callDepthSlot.slot), _callDepth)
}
assertEq(TransientContext.callDepth(), _callDepth);
}
/// @notice Tests that `increment()` increments the call depth.
/// @param _startingCallDepth Starting call depth.
function testFuzz_increment_succeeds(uint256 _startingCallDepth) public {
vm.assume(_startingCallDepth < type(uint256).max);
assembly {
tstore(sload(callDepthSlot.slot), _startingCallDepth)
}
assertEq(TransientContext.callDepth(), _startingCallDepth);
TransientContext.increment();
assertEq(TransientContext.callDepth(), _startingCallDepth + 1);
}
/// @notice Tests that `decrement()` decrements the call depth.
/// @param _startingCallDepth Starting call depth.
function testFuzz_decrement_succeeds(uint256 _startingCallDepth) public {
vm.assume(_startingCallDepth > 0);
assembly {
tstore(sload(callDepthSlot.slot), _startingCallDepth)
}
assertEq(TransientContext.callDepth(), _startingCallDepth);
TransientContext.decrement();
assertEq(TransientContext.callDepth(), _startingCallDepth - 1);
}
/// @notice Tests that `get()` returns the correct value.
/// @param _slot Slot to test.
/// @param _value Value to test.
function testFuzz_get_succeeds(bytes32 _slot, uint256 _value) public {
assertEq(TransientContext.get(_slot), 0);
bytes32 tSlot = keccak256(abi.encodePacked(TransientContext.callDepth(), _slot));
assembly {
tstore(tSlot, _value)
}
assertEq(TransientContext.get(_slot), _value);
}
/// @notice Tests that `set()` sets the correct value.
/// @param _slot Slot to test.
/// @param _value Value to test.
function testFuzz_set_succeeds(bytes32 _slot, uint256 _value) public {
TransientContext.set(_slot, _value);
bytes32 tSlot = keccak256(abi.encodePacked(TransientContext.callDepth(), _slot));
uint256 tValue;
assembly {
tValue := tload(tSlot)
}
assertEq(tValue, _value);
}
/// @notice Tests that `set()` and `get()` work together.
/// @param _slot Slot to test.
/// @param _value Value to test.
function testFuzz_setGet_succeeds(bytes32 _slot, uint256 _value) public {
testFuzz_set_succeeds(_slot, _value);
assertEq(TransientContext.get(_slot), _value);
}
/// @notice Tests that `set()` and `get()` work together at the same depth.
/// @param _slot Slot to test.
/// @param _value1 Value to write to slot at call depth 0.
/// @param _value2 Value to write to slot at call depth 1.
function testFuzz_setGet_twice_sameDepth_succeeds(bytes32 _slot, uint256 _value1, uint256 _value2) public {
assertEq(TransientContext.callDepth(), 0);
testFuzz_set_succeeds(_slot, _value1);
assertEq(TransientContext.get(_slot), _value1);
assertEq(TransientContext.callDepth(), 0);
testFuzz_set_succeeds(_slot, _value2);
assertEq(TransientContext.get(_slot), _value2);
}
/// @notice Tests that `set()` and `get()` work together at different depths.
/// @param _slot Slot to test.
/// @param _value1 Value to write to slot at call depth 0.
/// @param _value2 Value to write to slot at call depth 1.
function testFuzz_setGet_twice_differentDepth_succeeds(bytes32 _slot, uint256 _value1, uint256 _value2) public {
assertEq(TransientContext.callDepth(), 0);
testFuzz_set_succeeds(_slot, _value1);
assertEq(TransientContext.get(_slot), _value1);
TransientContext.increment();
assertEq(TransientContext.callDepth(), 1);
testFuzz_set_succeeds(_slot, _value2);
assertEq(TransientContext.get(_slot), _value2);
TransientContext.decrement();
assertEq(TransientContext.callDepth(), 0);
assertEq(TransientContext.get(_slot), _value1);
}
}
/// @title TransientReentrancyAwareTest
/// @notice Tests for TransientReentrancyAware.
contract TransientReentrancyAwareTest is TransientContextTest, TransientReentrancyAware {
/// @notice Reentrant-aware mock function to set a value in transient storage.
/// @param _slot Slot to set.
/// @param _value Value to set.
function mock(bytes32 _slot, uint256 _value) internal reentrantAware {
TransientContext.set(_slot, _value);
}
/// @notice Reentrant-aware mock function to set a value in transient storage at multiple depths.
/// @param _slot Slot to set.
/// @param _value1 Value to set at call depth 1.
/// @param _value2 Value to set at call depth 2.
function mockMultiDepth(bytes32 _slot, uint256 _value1, uint256 _value2) internal reentrantAware {
TransientContext.set(_slot, _value1);
mock(_slot, _value2);
}
/// @notice Tests the mock function is reentrant-aware.
/// @param _callDepth Call depth to test.
/// @param _slot Slot to test.
/// @param _value Value to test.
function testFuzz_reentrantAware_succeeds(uint256 _callDepth, bytes32 _slot, uint256 _value) public {
vm.assume(_callDepth < type(uint256).max);
assembly {
tstore(sload(callDepthSlot.slot), _callDepth)
}
assertEq(TransientContext.callDepth(), _callDepth);
mock(_slot, _value);
assertEq(TransientContext.get(_slot), 0);
TransientContext.increment();
assertEq(TransientContext.callDepth(), _callDepth + 1);
assertEq(TransientContext.get(_slot), _value);
}
/// @notice Tests the mock function is reentrant-aware at multiple depths.
/// @param _callDepth Call depth to test.
/// @param _slot Slot to test.
/// @param _value1 Value to test at call depth 1.
/// @param _value2 Value to test at call depth 2.
function testFuzz_reentrantAware_multiDepth_succeeds(
uint256 _callDepth,
bytes32 _slot,
uint256 _value1,
uint256 _value2
)
public
{
vm.assume(_callDepth < type(uint256).max - 1);
assembly {
tstore(sload(callDepthSlot.slot), _callDepth)
}
assertEq(TransientContext.callDepth(), _callDepth);
mockMultiDepth(_slot, _value1, _value2);
assertEq(TransientContext.get(_slot), 0);
TransientContext.increment();
assertEq(TransientContext.callDepth(), _callDepth + 1);
assertEq(TransientContext.get(_slot), _value1);
TransientContext.increment();
assertEq(TransientContext.callDepth(), _callDepth + 2);
assertEq(TransientContext.get(_slot), _value2);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment