Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
387 changes: 387 additions & 0 deletions contracts/src/SnapshotRevertTester.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,387 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

/**
* @title SnapshotRevertTester
* @dev Contract to test snapshot/revert logic with transient storage in realistic scenarios
*
* This contract simulates:
* 1. Nested function calls with transient storage
* 2. Delegate calls with transient storage
* 3. Complex state transitions with snapshots
* 4. Error handling and revert scenarios
* 5. Gas optimization with transient storage
*/
contract SnapshotRevertTester {
// State variables for tracking
uint256 public callDepth;
uint256 public snapshotCounter;
mapping(uint256 => bytes32) public snapshotStates;

// Events for tracking
event CallStarted(uint256 depth, address caller);
event CallEnded(uint256 depth, address caller);
event SnapshotCreated(uint256 snapshotId, uint256 depth);
event SnapshotReverted(uint256 snapshotId, uint256 depth);
event TransientStorageSet(bytes32 key, uint256 value, uint256 depth);
event TransientStorageGet(bytes32 key, uint256 value, uint256 depth);
event ErrorOccurred(string message, uint256 depth);

// Test results
mapping(string => bool) public testResults;
mapping(string => string) public errorMessages;

constructor() {
callDepth = 0;
snapshotCounter = 0;
}

/**
* @dev Test nested calls with transient storage
*/
function runNestedCalls() public returns (bool) {
emit CallStarted(1, msg.sender);

// Set transient storage in outer call
bytes32 outerKey = keccak256(abi.encodePacked("outer_call"));
uint256 outerValue = 100;
assembly {
tstore(outerKey, outerValue)
}
emit TransientStorageSet(outerKey, outerValue, 1);

// Simulate nested call
bytes32 innerKey = keccak256(abi.encodePacked("inner_call"));
uint256 innerValue = 200;
assembly {
tstore(innerKey, innerValue)
}
emit TransientStorageSet(innerKey, innerValue, 2);

// Verify both values are accessible
uint256 retrievedOuter;
uint256 retrievedInner;
assembly {
retrievedOuter := tload(outerKey)
retrievedInner := tload(innerKey)
}
require(retrievedOuter == outerValue, "Outer call transient storage not accessible");
require(retrievedInner == innerValue, "Inner call transient storage not accessible");

emit CallEnded(1, msg.sender);
testResults["nested"] = true;
return true;
}

/**
* @dev Test snapshot/revert with transient storage
*/
function runSnapshotRevert() public returns (bool) {
bytes32 key = keccak256(abi.encodePacked("snapshot_revert_test"));
uint256 initialValue = 100;
uint256 modifiedValue = 200;

// Set initial transient storage
assembly {
tstore(key, initialValue)
}
emit TransientStorageSet(key, initialValue, 1);

// Create snapshot
uint256 snapshotId = uint256(blockhash(block.number - 1));
emit SnapshotCreated(snapshotId, 1);

// Modify transient storage after snapshot
assembly {
tstore(key, modifiedValue)
}
emit TransientStorageSet(key, modifiedValue, 2);

// Verify the new value is set
uint256 currentValue;
assembly {
currentValue := tload(key)
}
require(currentValue == modifiedValue, "Transient storage not updated after snapshot");

// Simulate revert by setting back to original value
assembly {
tstore(key, initialValue)
}
emit SnapshotReverted(snapshotId, 1);

// Verify revert worked
uint256 revertedValue;
assembly {
revertedValue := tload(key)
}
require(revertedValue == initialValue, "Transient storage not reverted correctly");

testResults["snapshotRevert"] = true;
return true;
}

/**
* @dev Complex snapshot scenario with multiple operations
*/
function runComplexSnapshotScenario() public returns (bool) {
bytes32[] memory keys = new bytes32[](3);
uint256[] memory values = new uint256[](3);

// Initialize keys and values
keys[0] = keccak256(abi.encodePacked("complex_key_1"));
keys[1] = keccak256(abi.encodePacked("complex_key_2"));
keys[2] = keccak256(abi.encodePacked("complex_key_3"));
values[0] = 100;
values[1] = 200;
values[2] = 300;

// Set initial transient storage
for (uint256 i = 0; i < keys.length; i++) {
bytes32 key = keys[i];
uint256 value = values[i];
assembly {
tstore(key, value)
}
emit TransientStorageSet(key, value, 1);
}

// Create snapshot
uint256 snapshotId = uint256(blockhash(block.number - 1));
emit SnapshotCreated(snapshotId, 1);

// Modify transient storage after snapshot
for (uint256 i = 0; i < keys.length; i++) {
bytes32 key = keys[i];
uint256 newValue = values[i] * 2;
assembly {
tstore(key, newValue)
}
emit TransientStorageSet(key, newValue, 2);
}

// Simulate revert by setting back to original values
for (uint256 i = 0; i < keys.length; i++) {
bytes32 key = keys[i];
uint256 originalValue = values[i];
assembly {
tstore(key, originalValue)
}
}
emit SnapshotReverted(snapshotId, 1);

// Verify revert worked
for (uint256 i = 0; i < keys.length; i++) {
bytes32 key = keys[i];
uint256 retrievedValue;
assembly {
retrievedValue := tload(key)
}
require(retrievedValue == values[i], "Transient storage not reverted correctly");
}

testResults["complexSnapshot"] = true;
return true;
}

/**
* @dev Test error handling with transient storage
*/
function runErrorHandling() public returns (bool) {
bytes32 key = keccak256(abi.encodePacked("error_test"));
uint256 value = 123;

// Set transient storage
assembly {
tstore(key, value)
}
emit TransientStorageSet(key, value, 1);

// Simulate an error condition
bool shouldRevert = true;
if (shouldRevert) {
emit ErrorOccurred("Simulated error", 1);
// In a real scenario, this would revert the transaction
// For testing purposes, we just emit an event
}

// Verify transient storage is still accessible after error
uint256 retrievedValue;
assembly {
retrievedValue := tload(key)
}
require(retrievedValue == value, "Transient storage not accessible after error");

testResults["errorHandling"] = true;
return true;
}

/**
* @dev Test gas optimization with transient storage
*/
function runGasOptimization() public returns (bool) {
bytes32 key = keccak256(abi.encodePacked("gas_test"));
uint256 value = 456;

assembly {
tstore(key, value)
}
emit TransientStorageSet(key, value, 1);

uint256 retrievedValue;
assembly {
retrievedValue := tload(key)
}


// Verify the operation worked
require(retrievedValue == value, "Gas optimization test failed");

// Log gas usage (in a real scenario, you might want to optimize this)
// emit GasUsed("transient_storage", gasUsed); // This event is not defined in the original file

testResults["gasOptimization"] = true;
return true;
}

/**
* @dev Test delegate call with transient storage
*/
function runDelegateCall() public returns (bool) {
bytes32 key = keccak256(abi.encodePacked("delegate_call_test"));
uint256 value = 789;

// Set transient storage in the current context
assembly {
tstore(key, value)
}
emit TransientStorageSet(key, value, 1);

// In a real delegate call scenario, the transient storage would be
// accessible in the delegate-called contract context
// For testing purposes, we simulate this by verifying the value is set

uint256 retrievedValue;
assembly {
retrievedValue := tload(key)
}
require(retrievedValue == value, "Delegate call transient storage test failed");

testResults["delegateCall"] = true;
return true;
}

/**
* @dev Test multiple snapshots with transient storage
*/
function runMultipleSnapshots() public returns (bool) {
bytes32 key = keccak256(abi.encodePacked("multiple_snapshots"));
uint256 value1 = 100;
uint256 value2 = 200;
uint256 value3 = 300;

// Set initial value
assembly {
tstore(key, value1)
}
emit TransientStorageSet(key, value1, 1);

// Create first snapshot
uint256 snapshot1 = uint256(blockhash(block.number - 1));
emit SnapshotCreated(snapshot1, 1);

// Modify after first snapshot
assembly {
tstore(key, value2)
}
emit TransientStorageSet(key, value2, 2);

// Create second snapshot
uint256 snapshot2 = uint256(blockhash(block.number - 1));
emit SnapshotCreated(snapshot2, 1);

// Modify after second snapshot
assembly {
tstore(key, value3)
}
emit TransientStorageSet(key, value3, 3);

// Revert to first snapshot
assembly {
tstore(key, value2)
}
emit SnapshotReverted(snapshot1, 1);

// Verify we're back to the first snapshot state
uint256 retrievedValue;
assembly {
retrievedValue := tload(key)
}
require(retrievedValue == value2, "Multiple snapshots revert failed");

testResults["multipleSnapshots"] = true;
return true;
}

/**
* @dev Comprehensive test that runs all scenarios
*/
function runAllTests() public returns (bool) {
// Reset test results
resetTestResults();

// Run all tests
runNestedCalls();
runSnapshotRevert();
runComplexSnapshotScenario();
runErrorHandling();
runGasOptimization();
runDelegateCall();
runMultipleSnapshots();

return true;
}

/**
* @dev Get all test results
*/
function getAllTestResults() public view returns (
bool nested,
bool snapshotRevert,
bool complexSnapshot,
bool errorHandling,
bool gasOptimization,
bool delegateCall,
bool multipleSnapshots
) {
return (
testResults["nested"],
testResults["snapshotRevert"],
testResults["complexSnapshot"],
testResults["errorHandling"],
testResults["gasOptimization"],
testResults["delegateCall"],
testResults["multipleSnapshots"]
);
}

/**
* @dev Reset all test results
*/
function resetTestResults() public {
delete testResults["nested"];
delete testResults["snapshotRevert"];
delete testResults["complexSnapshot"];
delete testResults["errorHandling"];
delete testResults["gasOptimization"];
delete testResults["delegateCall"];
delete testResults["multipleSnapshots"];
}

/**
* @dev Get error messages
*/
function getErrorMessages() public view returns (string memory) {
return errorMessages["last_error"];
}
}
Loading
Loading