|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | + |
| 3 | +pragma solidity ^0.8.20; |
| 4 | + |
| 5 | +/** |
| 6 | + * @dev Library for simulating external calls and inspecting the result of the call while reverting any state changes |
| 7 | + * of events the call may have produced. |
| 8 | + * |
| 9 | + * This pattern is useful when you need to simulate the result of a call without actually executing it on-chain. Since |
| 10 | + * the address of the sender is preserved, this supports simulating calls that perform token swap that use the caller's |
| 11 | + * balance, or any operation that is restricted to the caller. |
| 12 | + */ |
| 13 | +library SimulateCall { |
| 14 | + /// @dev Simulates a call to the target contract through a dynamically deployed simulator. |
| 15 | + function simulateCall(address target, bytes memory data) internal returns (bool success, bytes memory retData) { |
| 16 | + return simulateCall(target, 0, data); |
| 17 | + } |
| 18 | + |
| 19 | + /// @dev Same as {simulateCall-address-bytes} but with a value. |
| 20 | + function simulateCall( |
| 21 | + address target, |
| 22 | + uint256 value, |
| 23 | + bytes memory data |
| 24 | + ) internal returns (bool success, bytes memory retData) { |
| 25 | + (success, retData) = getSimulator().delegatecall(abi.encodePacked(target, value, data)); |
| 26 | + success = !success; // getSimulator() returns the success value inverted |
| 27 | + } |
| 28 | + |
| 29 | + /** |
| 30 | + * @dev Returns the simulator address. |
| 31 | + * |
| 32 | + * The simulator REVERTs on success and RETURNs on failure, preserving the return data in both cases. |
| 33 | + * |
| 34 | + * * A failed target call returns the return data and succeeds in our context (no state changes). |
| 35 | + * * A successful target call causes a revert in our context (undoing all state changes) while still |
| 36 | + * capturing the return data. |
| 37 | + */ |
| 38 | + function getSimulator() internal returns (address instance) { |
| 39 | + // [Simulator details] |
| 40 | + // deployment prefix: 60315f8160095f39f3 |
| 41 | + // deployed bytecode: 60333611600a575f5ffd5b6034360360345f375f5f603436035f6014355f3560601c5af13d5f5f3e5f3d91602f57f35bfd |
| 42 | + // |
| 43 | + // offset | bytecode | opcode | stack |
| 44 | + // -------|-------------|----------------|-------- |
| 45 | + // 0x0000 | 6033 | push1 0x33 | 0x33 |
| 46 | + // 0x0002 | 36 | calldatasize | cds 0x33 |
| 47 | + // 0x0003 | 11 | gt | (cds>0x33) |
| 48 | + // 0x0004 | 600a | push1 0x0a | 0x0a (cds>0x33) |
| 49 | + // 0x0006 | 57 | jumpi | |
| 50 | + // 0x0007 | 5f | push0 | 0 |
| 51 | + // 0x0008 | 5f | push0 | 0 0 |
| 52 | + // 0x0009 | fd | revert | |
| 53 | + // 0x000a | 5b | jumpdest | |
| 54 | + // 0x000b | 6034 | push1 0x34 | 0x34 |
| 55 | + // 0x000d | 36 | calldatasize | cds 0x34 |
| 56 | + // 0x000e | 03 | sub | (cds-0x34) |
| 57 | + // 0x000f | 6034 | push1 0x34 | 0x34 (cds-0x34) |
| 58 | + // 0x0011 | 5f | push0 | 0 0x34 (cds-0x34) |
| 59 | + // 0x0012 | 37 | calldatacopy | |
| 60 | + // 0x0013 | 5f | push0 | 0 |
| 61 | + // 0x0014 | 5f | push0 | 0 0 |
| 62 | + // 0x0015 | 6034 | push1 0x34 | 0x34 0 0 |
| 63 | + // 0x0017 | 36 | calldatasize | cds 0x34 0 0 |
| 64 | + // 0x0018 | 03 | sub | (cds-0x34) 0 0 |
| 65 | + // 0x0019 | 5f | push0 | 0 (cds-0x34) 0 0 |
| 66 | + // 0x001a | 6014 | push1 0x14 | 0x14 0 (cds-0x34) 0 0 |
| 67 | + // 0x001c | 35 | calldataload | cd[0x14] 0 (cds-0x34) 0 0 |
| 68 | + // 0x001d | 5f | push0 | 0 cd[0x14] 0 (cds-0x34) 0 0 |
| 69 | + // 0x001e | 35 | calldataload | cd[0] cd[0x14] 0 (cds-0x34) 0 0 |
| 70 | + // 0x001f | 6060 | push1 0x60 | 0x60 cd[0] cd[0x14] 0 (cds-0x34) 0 0 |
| 71 | + // 0x0021 | 1c | shr | target cd[0x14] 0 (cds-0x34) 0 0 |
| 72 | + // 0x0022 | 5a | gas | gas target cd[0x14] 0 (cds-0x34) 0 0 |
| 73 | + // 0x0023 | f1 | call | suc |
| 74 | + // 0x0024 | 3d | returndatasize | rds suc |
| 75 | + // 0x0025 | 5f | push0 | 0 rds suc |
| 76 | + // 0x0026 | 5f | push0 | 0 0 rds suc |
| 77 | + // 0x0027 | 3e | returndatacopy | suc |
| 78 | + // 0x0028 | 5f | push0 | 0 suc |
| 79 | + // 0x0029 | 3d | returndatasize | rds 0 suc |
| 80 | + // 0x002a | 91 | swap2 | suc 0 rds |
| 81 | + // 0x002b | 602f | push1 0x2f | 0x2f suc 0 rds |
| 82 | + // 0x002d | 57 | jumpi | 0 rds |
| 83 | + // 0x002e | f3 | return | |
| 84 | + // 0x002f | 5b | jumpdest | 0 rds |
| 85 | + // 0x0030 | fd | revert | |
| 86 | + assembly ("memory-safe") { |
| 87 | + let fmp := mload(0x40) |
| 88 | + |
| 89 | + // build initcode at FMP |
| 90 | + mstore(add(fmp, 0x20), 0x5f375f5f603436035f6014355f3560601c5af13d5f5f3e5f3d91602f57f35bfd) |
| 91 | + mstore(fmp, 0x60315f8160095f39f360333611600a575f5ffd5b603436036034) |
| 92 | + let initcodehash := keccak256(add(fmp, 0x06), 0x3a) |
| 93 | + |
| 94 | + // compute create2 address |
| 95 | + mstore(0x40, initcodehash) |
| 96 | + mstore(0x20, 0) |
| 97 | + mstore(0x00, address()) |
| 98 | + mstore8(0x0b, 0xff) |
| 99 | + instance := and(keccak256(0x0b, 0x55), shr(96, not(0))) |
| 100 | + |
| 101 | + // if simulator not yet deployed, deploy it |
| 102 | + if iszero(extcodesize(instance)) { |
| 103 | + if iszero(create2(0, add(fmp, 0x06), 0x3a, 0)) { |
| 104 | + returndatacopy(fmp, 0x00, returndatasize()) |
| 105 | + revert(fmp, returndatasize()) |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + // cleanup fmp space used as scratch |
| 110 | + mstore(0x40, fmp) |
| 111 | + } |
| 112 | + } |
| 113 | +} |
0 commit comments