├── .env.example ├── .gitignore ├── .gitmodules ├── Cargo.lock ├── Cargo.toml ├── README.md ├── contracts ├── .github │ └── workflows │ │ └── test.yml ├── foundry.toml └── src │ ├── Simulator.sol │ ├── interfaces │ ├── IERC20.sol │ ├── IUniswapV2Pair.sol │ ├── IUniswapV2Router01.sol │ ├── IUniswapV2Router02.sol │ └── IUniswapV3Pool.sol │ └── utils │ ├── Address.sol │ └── SafeERC20.sol └── src ├── .cached-honeypot.csv ├── .cached-pools.csv ├── .cached-tokens.csv ├── arbitrage.rs ├── constants.rs ├── honeypot.rs ├── interfaces ├── mod.rs ├── pool.rs ├── simulator.rs └── token.rs ├── lib.rs ├── main.rs ├── paths.rs ├── pools.rs ├── sandwich.rs ├── simulator.rs ├── strategy.rs ├── streams.rs ├── tokens.rs ├── trace.rs └── utils.rs /.env.example: -------------------------------------------------------------------------------- 1 | HTTPS_URL=http://192.168.200.182:8545 2 | WSS_URL=ws://192.168.200.182:8546 3 | CHAIN_ID=1 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | # Compiler files 4 | cache/ 5 | out/ 6 | lib/ 7 | 8 | # Ignores development broadcast logs 9 | !broadcast 10 | broadcast/*/31337/ 11 | broadcast/**/dry-run/ 12 | 13 | # Docs 14 | docs/ 15 | 16 | # Dotenv file 17 | .env -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "contracts/lib/forge-std"] 2 | path = contracts/lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "evm-simulation" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | bytes = "1.2.1" 8 | hex = "0.4.3" 9 | dotenv = "0.15.0" 10 | tokio = { version = "1.29.0", features = ["full"] } 11 | tokio-stream = { version = "0.1", features = ['sync'] } 12 | futures = "0.3.5" 13 | async-trait = "0.1.64" 14 | anyhow = "1.0.70" 15 | serde = "1.0.145" 16 | serde_json = "1.0" 17 | itertools = "0.11.0" 18 | 19 | cfmms = "*" 20 | ethers-flashbots = { git = "https://github.com/onbjerg/ethers-flashbots"} 21 | ethers = { version = "2.0", features = ["abigen", "ws"]} 22 | ethers-core = "2.0" 23 | ethers-providers = "2.0" 24 | ethers-contract = "2.0" 25 | foundry-evm = { git = "https://github.com/solidquant/foundry.git", branch = "version-fix" } 26 | anvil = { git = "https://github.com/solidquant/foundry.git", branch = "version-fix" } 27 | eth-encode-packed = "0.1.0" 28 | 29 | colored = "2.0.0" 30 | log = "0.4.17" 31 | indicatif = "0.17.5" 32 | indoc = "2" 33 | fern = {version = "0.6.2", features = ["colored"]} 34 | chrono = "0.4.23" 35 | csv = "1.2.2" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EVM Simulation 2 | 3 | REVM + Foundry = ❤️ -------------------------------------------------------------------------------- /contracts/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /contracts/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 7 | -------------------------------------------------------------------------------- /contracts/src/Simulator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "./interfaces/IUniswapV2Pair.sol"; 5 | import "./interfaces/IUniswapV2Router02.sol"; 6 | import "./interfaces/IUniswapV3Pool.sol"; 7 | import "./interfaces/IERC20.sol"; 8 | 9 | import "./utils/SafeERC20.sol"; 10 | 11 | contract Simulator { 12 | using SafeERC20 for IERC20; 13 | 14 | function v2SimulateSwap( 15 | uint256 amountIn, 16 | address targetPair, 17 | address inputToken, 18 | address outputToken 19 | ) external returns (uint256 amountOut, uint256 realAfterBalance) { 20 | // 1. Check if you can transfer the token 21 | // Some honeypot tokens won't allow you to transfer tokens 22 | IERC20(inputToken).safeTransfer(targetPair, amountIn); 23 | 24 | uint256 reserveIn; 25 | uint256 reserveOut; 26 | 27 | { 28 | (uint256 reserve0, uint256 reserve1, ) = IUniswapV2Pair(targetPair) 29 | .getReserves(); 30 | 31 | if (inputToken < outputToken) { 32 | reserveIn = reserve0; 33 | reserveOut = reserve1; 34 | } else { 35 | reserveIn = reserve1; 36 | reserveOut = reserve0; 37 | } 38 | } 39 | 40 | // 2. Calculate the amount out you are supposed to get if the token isn't taxed 41 | uint256 actualAmountIn = IERC20(inputToken).balanceOf(targetPair) - 42 | reserveIn; 43 | amountOut = this.getAmountOut(actualAmountIn, reserveIn, reserveOut); 44 | 45 | // If the token is taxed, you won't receive amountOut back, and the swap will revert 46 | uint256 outBalanceBefore = IERC20(outputToken).balanceOf(address(this)); 47 | 48 | (uint256 amount0Out, uint256 amount1Out) = inputToken < outputToken 49 | ? (uint256(0), amountOut) 50 | : (amountOut, uint256(0)); 51 | IUniswapV2Pair(targetPair).swap( 52 | amount0Out, 53 | amount1Out, 54 | address(this), 55 | new bytes(0) 56 | ); 57 | 58 | // 3. Check the real balance of outputToken after the swap 59 | realAfterBalance = 60 | IERC20(outputToken).balanceOf(address(this)) - 61 | outBalanceBefore; 62 | } 63 | 64 | function getAmountOut( 65 | uint256 amountIn, 66 | uint256 reserveIn, 67 | uint256 reserveOut 68 | ) external pure returns (uint256 amountOut) { 69 | require(amountIn > 0, "UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT"); 70 | require( 71 | reserveIn > 0 && reserveOut > 0, 72 | "UniswapV2Library: INSUFFICIENT_LIQUIDITY" 73 | ); 74 | uint256 amountInWithFee = amountIn * 997; 75 | uint256 numerator = amountInWithFee * reserveOut; 76 | uint256 denominator = reserveIn * 1000 + amountInWithFee; 77 | amountOut = numerator / denominator; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /contracts/src/interfaces/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | interface IERC20 { 7 | event Transfer(address indexed from, address indexed to, uint256 value); 8 | event Approval( 9 | address indexed owner, 10 | address indexed spender, 11 | uint256 value 12 | ); 13 | 14 | function totalSupply() external view returns (uint256); 15 | 16 | function balanceOf(address account) external view returns (uint256); 17 | 18 | function transfer(address to, uint256 value) external returns (bool); 19 | 20 | function allowance( 21 | address owner, 22 | address spender 23 | ) external view returns (uint256); 24 | 25 | function approve(address spender, uint256 value) external returns (bool); 26 | 27 | function transferFrom( 28 | address from, 29 | address to, 30 | uint256 value 31 | ) external returns (bool); 32 | } 33 | -------------------------------------------------------------------------------- /contracts/src/interfaces/IUniswapV2Pair.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | interface IUniswapV2Pair { 7 | event Approval(address indexed owner, address indexed spender, uint value); 8 | event Transfer(address indexed from, address indexed to, uint value); 9 | 10 | function name() external pure returns (string memory); 11 | 12 | function symbol() external pure returns (string memory); 13 | 14 | function decimals() external pure returns (uint8); 15 | 16 | function totalSupply() external view returns (uint); 17 | 18 | function balanceOf(address owner) external view returns (uint); 19 | 20 | function allowance( 21 | address owner, 22 | address spender 23 | ) external view returns (uint); 24 | 25 | function approve(address spender, uint value) external returns (bool); 26 | 27 | function transfer(address to, uint value) external returns (bool); 28 | 29 | function transferFrom( 30 | address from, 31 | address to, 32 | uint value 33 | ) external returns (bool); 34 | 35 | function DOMAIN_SEPARATOR() external view returns (bytes32); 36 | 37 | function PERMIT_TYPEHASH() external pure returns (bytes32); 38 | 39 | function nonces(address owner) external view returns (uint); 40 | 41 | function permit( 42 | address owner, 43 | address spender, 44 | uint value, 45 | uint deadline, 46 | uint8 v, 47 | bytes32 r, 48 | bytes32 s 49 | ) external; 50 | 51 | event Mint(address indexed sender, uint amount0, uint amount1); 52 | event Burn( 53 | address indexed sender, 54 | uint amount0, 55 | uint amount1, 56 | address indexed to 57 | ); 58 | event Swap( 59 | address indexed sender, 60 | uint amount0In, 61 | uint amount1In, 62 | uint amount0Out, 63 | uint amount1Out, 64 | address indexed to 65 | ); 66 | event Sync(uint112 reserve0, uint112 reserve1); 67 | 68 | function MINIMUM_LIQUIDITY() external pure returns (uint); 69 | 70 | function factory() external view returns (address); 71 | 72 | function token0() external view returns (address); 73 | 74 | function token1() external view returns (address); 75 | 76 | function getReserves() 77 | external 78 | view 79 | returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 80 | 81 | function price0CumulativeLast() external view returns (uint); 82 | 83 | function price1CumulativeLast() external view returns (uint); 84 | 85 | function kLast() external view returns (uint); 86 | 87 | function mint(address to) external returns (uint liquidity); 88 | 89 | function burn(address to) external returns (uint amount0, uint amount1); 90 | 91 | function swap( 92 | uint amount0Out, 93 | uint amount1Out, 94 | address to, 95 | bytes calldata data 96 | ) external; 97 | 98 | function skim(address to) external; 99 | 100 | function sync() external; 101 | 102 | function initialize(address, address) external; 103 | } 104 | -------------------------------------------------------------------------------- /contracts/src/interfaces/IUniswapV2Router01.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IUniswapV2Router01 { 5 | function factory() external pure returns (address); 6 | 7 | function WETH() external pure returns (address); 8 | 9 | function addLiquidity( 10 | address tokenA, 11 | address tokenB, 12 | uint amountADesired, 13 | uint amountBDesired, 14 | uint amountAMin, 15 | uint amountBMin, 16 | address to, 17 | uint deadline 18 | ) external returns (uint amountA, uint amountB, uint liquidity); 19 | 20 | function addLiquidityETH( 21 | address token, 22 | uint amountTokenDesired, 23 | uint amountTokenMin, 24 | uint amountETHMin, 25 | address to, 26 | uint deadline 27 | ) 28 | external 29 | payable 30 | returns (uint amountToken, uint amountETH, uint liquidity); 31 | 32 | function removeLiquidity( 33 | address tokenA, 34 | address tokenB, 35 | uint liquidity, 36 | uint amountAMin, 37 | uint amountBMin, 38 | address to, 39 | uint deadline 40 | ) external returns (uint amountA, uint amountB); 41 | 42 | function removeLiquidityETH( 43 | address token, 44 | uint liquidity, 45 | uint amountTokenMin, 46 | uint amountETHMin, 47 | address to, 48 | uint deadline 49 | ) external returns (uint amountToken, uint amountETH); 50 | 51 | function removeLiquidityWithPermit( 52 | address tokenA, 53 | address tokenB, 54 | uint liquidity, 55 | uint amountAMin, 56 | uint amountBMin, 57 | address to, 58 | uint deadline, 59 | bool approveMax, 60 | uint8 v, 61 | bytes32 r, 62 | bytes32 s 63 | ) external returns (uint amountA, uint amountB); 64 | 65 | function removeLiquidityETHWithPermit( 66 | address token, 67 | uint liquidity, 68 | uint amountTokenMin, 69 | uint amountETHMin, 70 | address to, 71 | uint deadline, 72 | bool approveMax, 73 | uint8 v, 74 | bytes32 r, 75 | bytes32 s 76 | ) external returns (uint amountToken, uint amountETH); 77 | 78 | function swapExactTokensForTokens( 79 | uint amountIn, 80 | uint amountOutMin, 81 | address[] calldata path, 82 | address to, 83 | uint deadline 84 | ) external returns (uint[] memory amounts); 85 | 86 | function swapTokensForExactTokens( 87 | uint amountOut, 88 | uint amountInMax, 89 | address[] calldata path, 90 | address to, 91 | uint deadline 92 | ) external returns (uint[] memory amounts); 93 | 94 | function swapExactETHForTokens( 95 | uint amountOutMin, 96 | address[] calldata path, 97 | address to, 98 | uint deadline 99 | ) external payable returns (uint[] memory amounts); 100 | 101 | function swapTokensForExactETH( 102 | uint amountOut, 103 | uint amountInMax, 104 | address[] calldata path, 105 | address to, 106 | uint deadline 107 | ) external returns (uint[] memory amounts); 108 | 109 | function swapExactTokensForETH( 110 | uint amountIn, 111 | uint amountOutMin, 112 | address[] calldata path, 113 | address to, 114 | uint deadline 115 | ) external returns (uint[] memory amounts); 116 | 117 | function swapETHForExactTokens( 118 | uint amountOut, 119 | address[] calldata path, 120 | address to, 121 | uint deadline 122 | ) external payable returns (uint[] memory amounts); 123 | 124 | function quote( 125 | uint amountA, 126 | uint reserveA, 127 | uint reserveB 128 | ) external pure returns (uint amountB); 129 | 130 | function getAmountOut( 131 | uint amountIn, 132 | uint reserveIn, 133 | uint reserveOut 134 | ) external pure returns (uint amountOut); 135 | 136 | function getAmountIn( 137 | uint amountOut, 138 | uint reserveIn, 139 | uint reserveOut 140 | ) external pure returns (uint amountIn); 141 | 142 | function getAmountsOut( 143 | uint amountIn, 144 | address[] calldata path 145 | ) external view returns (uint[] memory amounts); 146 | 147 | function getAmountsIn( 148 | uint amountOut, 149 | address[] calldata path 150 | ) external view returns (uint[] memory amounts); 151 | } 152 | -------------------------------------------------------------------------------- /contracts/src/interfaces/IUniswapV2Router02.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "./IUniswapV2Router01.sol"; 5 | 6 | interface IUniswapV2Router02 is IUniswapV2Router01 { 7 | function removeLiquidityETHSupportingFeeOnTransferTokens( 8 | address token, 9 | uint liquidity, 10 | uint amountTokenMin, 11 | uint amountETHMin, 12 | address to, 13 | uint deadline 14 | ) external returns (uint amountETH); 15 | 16 | function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( 17 | address token, 18 | uint liquidity, 19 | uint amountTokenMin, 20 | uint amountETHMin, 21 | address to, 22 | uint deadline, 23 | bool approveMax, 24 | uint8 v, 25 | bytes32 r, 26 | bytes32 s 27 | ) external returns (uint amountETH); 28 | 29 | function swapExactTokensForTokensSupportingFeeOnTransferTokens( 30 | uint amountIn, 31 | uint amountOutMin, 32 | address[] calldata path, 33 | address to, 34 | uint deadline 35 | ) external; 36 | 37 | function swapExactETHForTokensSupportingFeeOnTransferTokens( 38 | uint amountOutMin, 39 | address[] calldata path, 40 | address to, 41 | uint deadline 42 | ) external payable; 43 | 44 | function swapExactTokensForETHSupportingFeeOnTransferTokens( 45 | uint amountIn, 46 | uint amountOutMin, 47 | address[] calldata path, 48 | address to, 49 | uint deadline 50 | ) external; 51 | } 52 | -------------------------------------------------------------------------------- /contracts/src/interfaces/IUniswapV3Pool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IUniswapV3Pool { 5 | function swap( 6 | address recipient, 7 | bool zeroForOne, 8 | int256 amountSpecified, 9 | uint160 sqrtPriceLimitX96, 10 | bytes calldata data 11 | ) external returns (int256 amount0, int256 amount1); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/src/utils/Address.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | /** 7 | * @dev Collection of functions related to the address type 8 | */ 9 | library Address { 10 | /** 11 | * @dev The ETH balance of the account is not enough to perform the operation. 12 | */ 13 | error AddressInsufficientBalance(address account); 14 | 15 | /** 16 | * @dev There's no code at `target` (it is not a contract). 17 | */ 18 | error AddressEmptyCode(address target); 19 | 20 | /** 21 | * @dev A call to an address target failed. The target may have reverted. 22 | */ 23 | error FailedInnerCall(); 24 | 25 | /** 26 | * @dev Replacement for Solidity's `transfer`: sends `amount` wei to 27 | * `recipient`, forwarding all available gas and reverting on errors. 28 | * 29 | * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost 30 | * of certain opcodes, possibly making contracts go over the 2300 gas limit 31 | * imposed by `transfer`, making them unable to receive funds via 32 | * `transfer`. {sendValue} removes this limitation. 33 | * 34 | * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. 35 | * 36 | * IMPORTANT: because control is transferred to `recipient`, care must be 37 | * taken to not create reentrancy vulnerabilities. Consider using 38 | * {ReentrancyGuard} or the 39 | * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. 40 | */ 41 | function sendValue(address payable recipient, uint256 amount) internal { 42 | if (address(this).balance < amount) { 43 | revert AddressInsufficientBalance(address(this)); 44 | } 45 | 46 | (bool success, ) = recipient.call{value: amount}(""); 47 | if (!success) { 48 | revert FailedInnerCall(); 49 | } 50 | } 51 | 52 | /** 53 | * @dev Performs a Solidity function call using a low level `call`. A 54 | * plain `call` is an unsafe replacement for a function call: use this 55 | * function instead. 56 | * 57 | * If `target` reverts with a revert reason or custom error, it is bubbled 58 | * up by this function (like regular Solidity function calls). However, if 59 | * the call reverted with no returned reason, this function reverts with a 60 | * {FailedInnerCall} error. 61 | * 62 | * Returns the raw returned data. To convert to the expected return value, 63 | * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. 64 | * 65 | * Requirements: 66 | * 67 | * - `target` must be a contract. 68 | * - calling `target` with `data` must not revert. 69 | */ 70 | function functionCall( 71 | address target, 72 | bytes memory data 73 | ) internal returns (bytes memory) { 74 | return functionCallWithValue(target, data, 0); 75 | } 76 | 77 | /** 78 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 79 | * but also transferring `value` wei to `target`. 80 | * 81 | * Requirements: 82 | * 83 | * - the calling contract must have an ETH balance of at least `value`. 84 | * - the called Solidity function must be `payable`. 85 | */ 86 | function functionCallWithValue( 87 | address target, 88 | bytes memory data, 89 | uint256 value 90 | ) internal returns (bytes memory) { 91 | if (address(this).balance < value) { 92 | revert AddressInsufficientBalance(address(this)); 93 | } 94 | (bool success, bytes memory returndata) = target.call{value: value}( 95 | data 96 | ); 97 | return verifyCallResultFromTarget(target, success, returndata); 98 | } 99 | 100 | /** 101 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 102 | * but performing a static call. 103 | */ 104 | function functionStaticCall( 105 | address target, 106 | bytes memory data 107 | ) internal view returns (bytes memory) { 108 | (bool success, bytes memory returndata) = target.staticcall(data); 109 | return verifyCallResultFromTarget(target, success, returndata); 110 | } 111 | 112 | /** 113 | * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], 114 | * but performing a delegate call. 115 | */ 116 | function functionDelegateCall( 117 | address target, 118 | bytes memory data 119 | ) internal returns (bytes memory) { 120 | (bool success, bytes memory returndata) = target.delegatecall(data); 121 | return verifyCallResultFromTarget(target, success, returndata); 122 | } 123 | 124 | /** 125 | * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target 126 | * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an 127 | * unsuccessful call. 128 | */ 129 | function verifyCallResultFromTarget( 130 | address target, 131 | bool success, 132 | bytes memory returndata 133 | ) internal view returns (bytes memory) { 134 | if (!success) { 135 | _revert(returndata); 136 | } else { 137 | // only check if target is a contract if the call was successful and the return data is empty 138 | // otherwise we already know that it was a contract 139 | if (returndata.length == 0 && target.code.length == 0) { 140 | revert AddressEmptyCode(target); 141 | } 142 | return returndata; 143 | } 144 | } 145 | 146 | /** 147 | * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the 148 | * revert reason or with a default {FailedInnerCall} error. 149 | */ 150 | function verifyCallResult( 151 | bool success, 152 | bytes memory returndata 153 | ) internal pure returns (bytes memory) { 154 | if (!success) { 155 | _revert(returndata); 156 | } else { 157 | return returndata; 158 | } 159 | } 160 | 161 | /** 162 | * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}. 163 | */ 164 | function _revert(bytes memory returndata) private pure { 165 | // Look for revert reason and bubble it up if present 166 | if (returndata.length > 0) { 167 | // The easiest way to bubble the revert reason is using memory via assembly 168 | /// @solidity memory-safe-assembly 169 | assembly { 170 | let returndata_size := mload(returndata) 171 | revert(add(32, returndata), returndata_size) 172 | } 173 | } else { 174 | revert FailedInnerCall(); 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /contracts/src/utils/SafeERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol) 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import {IERC20} from "../interfaces/IERC20.sol"; 7 | import {Address} from "./Address.sol"; 8 | 9 | /** 10 | * @title SafeERC20 11 | * @dev Wrappers around ERC20 operations that throw on failure (when the token 12 | * contract returns false). Tokens that return no value (and instead revert or 13 | * throw on failure) are also supported, non-reverting calls are assumed to be 14 | * successful. 15 | * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, 16 | * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. 17 | */ 18 | library SafeERC20 { 19 | using Address for address; 20 | 21 | /** 22 | * @dev An operation with an ERC20 token failed. 23 | */ 24 | error SafeERC20FailedOperation(address token); 25 | 26 | /** 27 | * @dev Indicates a failed `decreaseAllowance` request. 28 | */ 29 | error SafeERC20FailedDecreaseAllowance( 30 | address spender, 31 | uint256 currentAllowance, 32 | uint256 requestedDecrease 33 | ); 34 | 35 | /** 36 | * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, 37 | * non-reverting calls are assumed to be successful. 38 | */ 39 | function safeTransfer(IERC20 token, address to, uint256 value) internal { 40 | _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); 41 | } 42 | 43 | /** 44 | * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the 45 | * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. 46 | */ 47 | function safeTransferFrom( 48 | IERC20 token, 49 | address from, 50 | address to, 51 | uint256 value 52 | ) internal { 53 | _callOptionalReturn( 54 | token, 55 | abi.encodeCall(token.transferFrom, (from, to, value)) 56 | ); 57 | } 58 | 59 | /** 60 | * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, 61 | * non-reverting calls are assumed to be successful. 62 | */ 63 | function safeIncreaseAllowance( 64 | IERC20 token, 65 | address spender, 66 | uint256 value 67 | ) internal { 68 | uint256 oldAllowance = token.allowance(address(this), spender); 69 | forceApprove(token, spender, oldAllowance + value); 70 | } 71 | 72 | /** 73 | * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no value, 74 | * non-reverting calls are assumed to be successful. 75 | */ 76 | function safeDecreaseAllowance( 77 | IERC20 token, 78 | address spender, 79 | uint256 requestedDecrease 80 | ) internal { 81 | unchecked { 82 | uint256 currentAllowance = token.allowance(address(this), spender); 83 | if (currentAllowance < requestedDecrease) { 84 | revert SafeERC20FailedDecreaseAllowance( 85 | spender, 86 | currentAllowance, 87 | requestedDecrease 88 | ); 89 | } 90 | forceApprove(token, spender, currentAllowance - requestedDecrease); 91 | } 92 | } 93 | 94 | /** 95 | * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, 96 | * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval 97 | * to be set to zero before setting it to a non-zero value, such as USDT. 98 | */ 99 | function forceApprove( 100 | IERC20 token, 101 | address spender, 102 | uint256 value 103 | ) internal { 104 | bytes memory approvalCall = abi.encodeCall( 105 | token.approve, 106 | (spender, value) 107 | ); 108 | 109 | if (!_callOptionalReturnBool(token, approvalCall)) { 110 | _callOptionalReturn( 111 | token, 112 | abi.encodeCall(token.approve, (spender, 0)) 113 | ); 114 | _callOptionalReturn(token, approvalCall); 115 | } 116 | } 117 | 118 | /** 119 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement 120 | * on the return value: the return value is optional (but if data is returned, it must not be false). 121 | * @param token The token targeted by the call. 122 | * @param data The call data (encoded using abi.encode or one of its variants). 123 | */ 124 | function _callOptionalReturn(IERC20 token, bytes memory data) private { 125 | // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since 126 | // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that 127 | // the target address contains contract code and also asserts for success in the low-level call. 128 | 129 | bytes memory returndata = address(token).functionCall(data); 130 | if (returndata.length != 0 && !abi.decode(returndata, (bool))) { 131 | revert SafeERC20FailedOperation(address(token)); 132 | } 133 | } 134 | 135 | /** 136 | * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement 137 | * on the return value: the return value is optional (but if data is returned, it must not be false). 138 | * @param token The token targeted by the call. 139 | * @param data The call data (encoded using abi.encode or one of its variants). 140 | * 141 | * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead. 142 | */ 143 | function _callOptionalReturnBool( 144 | IERC20 token, 145 | bytes memory data 146 | ) private returns (bool) { 147 | // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since 148 | // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false 149 | // and not revert is the subcall reverts. 150 | 151 | (bool success, bytes memory returndata) = address(token).call(data); 152 | return 153 | success && 154 | (returndata.length == 0 || abi.decode(returndata, (bool))) && 155 | address(token).code.length > 0; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/.cached-honeypot.csv: -------------------------------------------------------------------------------- 1 | 0x0fff6438a6f75bed346c05743eb299d7ea280020 2 | 0x1a3d2d5359f9a0ec0a28414f028707ba77b2009f 3 | 0xd5c033bc60a7d263ed13999e971c26cd758b5a77 4 | 0x8762db106b2c2a0bccb3a80d1ed41273552616e8 5 | 0x1d3b136da37689c11919ec5441bc89af8e03bde0 6 | 0x381b1f1e56aef2a429e4ecc774ca5db1b5c82c1c 7 | 0xf49ce156cf672e7e344566261db71c3b045f8a83 8 | 0x61152b50b7162f994d0cfbb9d4e108f96646e81f 9 | 0xbd753a8d3be45f4772145d75e28014a333b705fb 10 | 0x4ba012f6e411a1be55b98e9e62c3a4ceb16ec88b 11 | 0x6ff7c968b50ec34fa13e99b80ce97d5df14c7921 12 | 0xa75a25aeef83e643f716536b882bb633c4ed6e93 13 | 0x9c659cb48c4406cd2857aeceff1609b2db280d0e 14 | 0x8f23fe8b4d5884746d4cf67563233d999664e82c 15 | 0x28f886fa1751e623ff4d8422a230d8b49879bf61 16 | 0x348f9b40df1d4ec2082371bacb136fc727ccdd8c 17 | 0xb4742e2013f96850a5cef850a3bb74cf63b9a5d5 18 | 0xf0bf29600d10a69206c1117efd4be737d921ebbb 19 | 0x110492b31c59716ac47337e616804e3e3adc0b4a 20 | 0x4b7ee45f30767f36f06f79b32bf1fca6f726deda 21 | 0x1a7ac70bcb1571cb056c45710446da4d070cbb80 22 | 0x2ad1219367dcbff80d37bb770de008875ab58cd9 23 | 0xdf2ce46119a95795598ba421ee4ea0469f919ba4 24 | 0x3f64f0775de4379c204141755baed5c68e54cd64 25 | 0xc0c712d8293179662644651c80636876e607a470 26 | 0xce5ba265661808836858573e412ed438399d45b7 27 | 0xd2f373c832ebeec803eb6a3555e1fe34ad2ef9f7 28 | 0x56de8bc61346321d4f2211e3ac3c0a7f00db9b76 29 | 0x2541e3b93a59773b2f14d71c89fe44a60f151bcf 30 | 0x3989cadb8a41bf987117be349b6c610f01996caf 31 | 0x08a2e41fb99a7599725190b9c970ad3893fa33cf 32 | 0xdbcd885e554cc0584aca1f754c0f3e3bc677b5c9 33 | 0xa10c97bf5629340a35c41a8aa308af0804750605 34 | 0x925f6331ad0ea73657e591700a9f729ceeb2aee1 35 | 0x2dbc2423931d870933813945a4c5aeb7b45a72ce 36 | 0xb1cec6f8b51ec52b24a930e11d7a00e9c83c2b9a 37 | 0xea8e09bcda60bacabee14dc80420bdfa0e73208a 38 | 0xc88be5684db7d52bcabb27200d2ada534e5492de 39 | 0xea52d0d5659d6c897068c8eb9bdeeb1fe1d44cf6 40 | 0xc2acaa4cfed995c9ed9d3fc502b53c0dbd904474 41 | 0x94e9085f113f73b39046d8b155ee3937b2fc7489 42 | 0x057da43f5ed569e65e8a7a6451795a42ec09933c 43 | 0x9da242c3fed08b643dc0d94d01b7ac200931749a 44 | 0xf260608a577b64000747d533e27bbc2e53f9108d 45 | 0xc616420bdb355b36c2718d2471739a14f5e40e57 46 | 0xc0a07eafb93acf9e897f787c79a9657e8e07d65c 47 | 0xad7263464b520be2fb8d143520ba3dfba3db4175 48 | 0xac8827e20ef8416709efa60e357a58dd98e06390 49 | 0xf43809d3cfe742d0830e14d3489b61cb6e30e0e9 50 | 0xbe2974f1e2c42a7f0909c6708435be243279a74e 51 | 0x78a685e0762096ed0f98107212e98f8c35a9d1d8 52 | 0xeb1feed340fb1cd21b2808b410297359f6775c72 53 | 0xacfa209fb73bf3dd5bbfb1101b9bc999c49062a5 54 | 0x5c4cb662fa069f73511d661eeb1f4024fa9a22e8 55 | 0x9f881fe137a8f2dfb41df496afbb61a05f450b89 56 | 0xf0cf5224c61a50a425bcabfb62aafe322f59f36c 57 | 0xcd8352a5d8033c724c496c76aa3dfeeae2141d0f 58 | 0x3194e560c9e49cc02195714dcb1075a5519a9dbd 59 | 0xad1da531e63d2b1203ed828c47780ea9900a7755 60 | 0xe6397cfc0c47c8f7652068e4e40c37c59e46f339 61 | 0xe5f166c0d8872b68790061317bb6cca04582c912 62 | 0xf10d9664828e80eea2f8bf139c3cc6041ae0cba0 63 | 0x944ae19f972acf343f4eea664af9e021f625959f 64 | 0x6b45428865061487dc8881de1b3aa094544bf1b1 65 | 0xe3da4e02bdffb004c463dabb846d77d4e0d34163 66 | 0xda047c2b6434b99f0977b905d2a584f8796a5013 67 | 0x3c76ef53be46ed2e9be224e8f0b92e8acbc24ea0 68 | 0x7eb9e898c1714b4533cd4bd2206545f83693e26b 69 | 0x0423276a1da214b094d54386a1fb8489a9d32730 70 | 0xe8991e72e2543cc39ee02a6010c2422583547e9e 71 | 0x07e42d9dc1471f9c3c823f1786cca07ed6180197 72 | 0xfe5c207d266e0349dedeee1058e87cb41537bbea 73 | 0x27054b13b1b798b345b591a4d22e6562d47ea75a 74 | 0xbe78daa6e2b5c39cd2d2054bc5ac984da3133794 75 | 0xf83c911be97c84c78d7328c4db89c307906f90dc 76 | 0xe7eaec9bca79d537539c00c58ae93117fb7280b9 77 | 0x16e4ce2cfcb776cb75720e75a9db9f0443778115 78 | 0xb52fc0f17df38ad76f290467aab57cabaeeada14 79 | 0x9a48bd0ec040ea4f1d3147c025cd4076a2e71e3e 80 | 0xd77b2c7807e331d57bb1bacf6ea1d74b3a5aa3ba 81 | 0x282eb00ab8c0e1e4396cd58bd0150200a94195c6 82 | 0xedb6231bd4ce4e64402507537d670d25fc185102 83 | 0x0bafa383ea3726982fcf0507f63c9c3d02f28eec 84 | 0x2129ff6000b95a973236020bcd2b2006b0d8e019 85 | 0x1ec0e884ee78a947e7fd0c3e35cc190d3216d48e 86 | 0x378e8c47eb42cce0dd9cff48276a2ab73e9c254f 87 | 0x45af324f53a8d7da1752dad74adc1748126d7978 88 | 0x8b793a3910534deeb45d257deb1d1b158d1b5bee 89 | 0xded6ab54a78d1f4eaf8d7429e7db7a4706925ad8 90 | 0xc567bca531992352166252ea5121e535432e81ed 91 | 0x53c4871322bb47e7a24136fce291a6dcc832a294 92 | 0x704ebb34c391621b4725b68f71d51aa4d7abda16 93 | 0x00d289ff3d63909f4d42085ee5cc58a434b0636c 94 | 0xff98a08c143311719ca492e4b8c950c940f26872 95 | 0x3dbeba7da4f4f0b061e260cec71727616c75c0e1 96 | 0x504389196a86272812150924cc86ff8c6e9541dd 97 | 0x588ed7e35595e3396f6aa72b7ddcec2fefbca46e 98 | 0xe1f83480f5937364bda3410f034c42bddea6ec85 99 | 0x497559ddecd57028990d6df58eff962b493829d8 100 | 0xb140bc28db29fc4c19079ebb6374044d3002ebb4 101 | 0x092ac353612d41e5ad1c2bb511b51a8619c639af 102 | 0xd0929d411954c47438dc1d871dd6081f5c5e149c 103 | 0xa3e059c0b01f07f211c85bf7b4f1d907afb011df 104 | 0xed35b94f85881522a8bea5bba183374d1925961e 105 | 0xe74c74e989be298f34edd2160a61e2d754b5bec8 106 | 0x189741cff8a06fe1d704d4a72933b4e4451de8a6 107 | 0xaa902e03ec2c4c7094b12a24406ab8394aa99594 108 | 0x106552c11272420aad5d7e94f8acab9095a6c952 109 | 0x99c2d2b7d2951e32f69e8ca14b6bcba78bf216db 110 | 0x1abe67dae031664eee31200e4714c969fd73146a 111 | 0xa866f0198208eb07c83081d5136be7f775c2399e 112 | 0x8214762371d589ec441a7144fc55e773bf0a1955 113 | 0xf0acf8949e705e0ebb6cb42c2164b0b986454223 114 | 0x12e30f4f5765f9290d77df8e251e7fcbcbef63fb 115 | 0x9ed68f8e328b03a1dc82e97ca7c785252659a681 116 | 0x4a9d8b8fce0b6ec033932b13c4e24d24dc4113cd 117 | 0x029b8799de1304c7579d5fcf2a8bbbf7d3e888b9 118 | 0x8d4193fb3871c43c442ea17c6d9bb33d0644e81d 119 | 0x12f649a9e821f90bb143089a6e56846945892ffb 120 | 0xcf9601b2117b6971c75d3c8c7b3e68a876047d9a 121 | 0xda2c8cf8de7b12a223dc38a4a125af398f04ef5f 122 | 0x24ae5f20630cf62bdccd13c9d642c1d25da1380d 123 | 0x082c0bc1b15ee36db1a8190674b4de867db43dda 124 | 0xe0a3796fad195951d13407d63faec771fa4f49d5 125 | 0xa65c0f468c6e679a5034d7dbd86cbed0040e8f7d 126 | 0x979d77120b559d501fa0a00576bd8da208988366 127 | 0xae31b85bfe62747d0836b82608b4830361a3d37a 128 | 0xbc4b615c6c1bd10bc4e93a55eee7688a7c42e5cb 129 | 0xebf56b58be8339a827f9e9bed5feae3a6c63a562 130 | 0x0cd57d43cc275cec1a2660ca145e87c664e4748e 131 | 0x824a50df33ac1b41afc52f4194e2e8356c17c3ac 132 | 0x3a9fff453d50d4ac52a6890647b823379ba36b9e 133 | 0xe0839f9b9688a77924208ad509e29952dc660261 134 | 0x14409b0fc5c7f87b5dad20754fe22d29a3de8217 135 | 0x5f861826fb1f1547509a469865fba56e1420ea1b 136 | 0x394cb37e9e7c5ab5906f3440c53d5dc37d6d7348 137 | 0x9f2e677e38a5bd9a4efd2514854978c6c826f4c5 138 | 0x162945a333cfc1048bbd7180bf50203588f48d7c 139 | 0x44017598f2af1bd733f9d87b5017b4e7c1b28dde 140 | 0xcd10fb882c05e7d16483522ff98a2fe2efbe0cf9 141 | 0x660ad693d77fb33325acdf0250e5417d929f8d7d 142 | 0x59854ccb382b0b8c0629cc273ce0aa186d41f85b 143 | 0x81464dfaff59045822b85bf230ea3174314afcbd 144 | 0xd68b761597f07bcb2dea7b56cc096b097f964ce2 145 | 0xd28ca16fa044e1d7f7ba998bbff87724a7cf366d 146 | 0x21efe20be784ac5da569f72070e64525f95ccab6 147 | 0xbc8a0c3f8569ae11e083e0ce46a395a0aa1ce7ae 148 | 0xb6b1d11edac811cae21772baeb29f54a60db6d13 149 | 0x086c6a3eebbf5d9ccb47d37f75169cbb0f572ae0 150 | 0xffe02ee4c69edf1b340fcad64fbd6b37a7b9e265 151 | 0xdbe35d5c41ccc960e1d67f24f9f8797833d1d964 152 | 0x9582bea37cc8881fb36277d7675c45bcd9334e1a 153 | 0x4599836c212cd988eaccc54c820ee9261cdaac71 154 | 0x04203b7668eb832a5cbfe248b57defeb709e48e3 155 | 0x48666fe01d123d14b53eacf21f36786f2a786c86 156 | 0x3b8953d315729c4ecc23f139e87b51681af67475 157 | 0x3b7f247f21bf3a07088c2d3423f64233d4b069f7 158 | 0x10e35e8b605a10bc7d748312e5578915838fd31b 159 | 0x709f5fe25a965e227978d94b760536dbde4295f9 160 | 0x72aa58a6bc3efc77cc8fe89b73bad27b468910e9 161 | 0xca7e925ba8938a1b587172658b6c24054329aef8 162 | 0x3affcca64c2a6f4e3b6bd9c64cd2c969efd1ecbe 163 | 0xd7e0f80fb28233bdde0006c50568606a8feb964c 164 | 0x49c5cd3b1ad9718db6e275b1b3d81c459cb59a6a 165 | 0x82dad985afd3708680bac660f4e527da4f0dcfa5 166 | 0xdb5163b60b3d36db8f1486d904ac3f711efabf60 167 | 0xcf8335727b776d190f9d15a54e6b9b9348439eee 168 | 0xef8a446e9a14ba1b1a655c9c5d790bb354c3100c 169 | 0x51bb7917efcad03ec8b1d37251a06cd56b0c4a72 170 | 0x88865e4b483131cbe0036f5642cec821a7c0b404 171 | 0x9bc3d9b1f9fdcf25f5c350fcdb2657941649f004 172 | 0x0947b0e6d821378805c9598291385ce7c791a6b2 173 | 0x85c0688cd6a998bf7a650e1407fd3606c6ef360e 174 | 0x82619a31ab374f4b3d0ef8a74d8dc84d6a5c751d 175 | 0xef2889b8d67c59b551254750a726fb4c45bd6494 176 | 0xffd1861450a57b50e6d938f6fdc2875dfea21fae 177 | 0x0adb905e46ffd36a20c843af7a094b3590ff5836 178 | 0x96e272c35e885bfe0d7cf49083a84acfb2181bf9 179 | 0x69564568caef58dfb6462e7e5fe622e70c587eb1 180 | 0x7a4960c8c2ebb1bc9b3abc8660692ecc06f2374b 181 | 0x165b6749812900538402cd540c55be68411f8ea0 182 | 0x2dbf63976c9364dd00a4c67df9ff037b98eb2f78 183 | 0xdf3a0a958f5a599aa8f3071aa6488c22416efb89 184 | 0x728eb1d6858910f9ef729586248707a610680019 185 | 0xd5dc2f0fabea20bdb0ab71ec6d5ae77c8db4d6fe 186 | 0x8aa688ab789d1848d131c65d98ceaa8875d97ef1 187 | 0x42070e9b59b93591dbbe44315f31db025d135940 188 | 0xd434e2ec93be12d72934543f68278c2b8e0cb3fc 189 | 0xb76f2e7ad565e3fbb5dbc7ad1b3786928cc73164 190 | 0xa3f9fbe00b577560fcf8ecf6fb5280e598014bf0 191 | 0x6d6506e6f438ede269877a0a720026559110b7d5 192 | 0xea5eab01e6f70558c3fefd12218a6b3f801da5d7 193 | 0x6139f7fb1937806d21cc6b3b8152492c21239bdd 194 | 0x05860d453c7974cbf46508c06cba14e211c629ce 195 | 0xe05fad8faba5cc1f809a5700e5b60d2475cfb461 196 | 0x742e0ff7a65a4d6c04bdd081831486122d9e4060 197 | 0xcde83a2685428f8556c4b884b7fd2cc26284256f 198 | 0xd62e4aa4102e784cfd3f79028eebb89e12e37e48 199 | 0x5a9329c1985a63bf5d7f461fa83180e70c748fb1 200 | 0x0268f0dd879cea41210b8191149d06bd318a56e7 201 | 0x0d88ed6e74bbfd96b831231638b66c05571e824f 202 | 0x07e14406b6175cc2b375d714278c746216867e2e 203 | 0xc65ce105d35d35ddb6d6d936f9b5c6a395aac47b 204 | 0x80ad36be194afe4e43037bd1618933e226d0c0d9 205 | 0xf5238462e7235c7b62811567e63dd17d12c2eaa0 206 | 0x90f10470a7e90d9cb74ceb57cc7af22a8c4994ff 207 | 0x69fe11b042789bc6e2fdaad064afd32bb298ae05 208 | 0x8af1ed18af94cbc846991731702111afbf904b45 209 | 0x07afb925be0fbfc79ab1be6fe367b908ad602d38 210 | 0x33f1626539e9d2b18ccbf45d73fe963b30900951 211 | 0x0e7f79e89ba8c4a13431129fb2db0d4f444b5b9a 212 | 0x5c4ac68aac56ebe098d621cd8ce9f43270aaa355 213 | 0x9e24c0ff766dc89c290ac9c792e0ebf3d4bb8cab 214 | 0xf117d436b471e19f462b561833e90ec5deea7c8c 215 | 0xb1bf54d4beeb77e5b99fd2ca7811191d80d79f88 216 | 0x48bcb71f7f07502ca6899d875ec4cae84c9fca68 217 | 0x58692be60fce9809add8521446a4d66fa04b92b2 218 | 0xcccb6460bc41a6db9c830b8318f3557baff0dfd1 219 | 0x3614b88b52a8de9de4473289cdc793c42343907d 220 | 0xea319e87cf06203dae107dd8e5672175e3ee976c 221 | 0x625ae63000f46200499120b906716420bd059240 222 | 0x20bd52b804e52489090e7bb23526c0481de85cd4 223 | 0xa51cff785cf93e905e851f3fa55eb7c29a3319fe 224 | 0x75084627d60b3f3be97e3e19516f3bb2bb2bbeec 225 | 0x519c650570ccd63cfbd77a0fe64a82b8650b4ed5 226 | 0x26eac4b2080c343621c9792c0f27edd25e2c3197 227 | 0x3d3e392a734c07c7c97f3355f6024b408598f5a6 228 | 0xce4a410b06fc6f6728994a43789ca1b5e288d21c 229 | 0xeee2b2a93fd890e6373b8758e48cc523d24fd337 230 | 0x4607cde2730cd1389c665d02f5a7b09e680f9bdb 231 | 0x187d1018e8ef879be4194d6ed7590987463ead85 232 | 0xa089ee09941c4459b936980d33796a9fee31212a 233 | 0xecdc63d1753eade74d88ecbc1301e84ff3681e70 234 | 0xfbe80d9fffe6a0275256a3bb8f37413e3e45c697 235 | 0x0ee815f8be0b0259e2657c8b8d1e57bd3d60f26b 236 | 0x1ad0f68203d66ae8281e579e35b2a0ecff4de3ab 237 | 0xaf1425dc4ae9c2eb3c12ef0629801d74cbdb35da 238 | 0x5d7f6540b8d592e81def42904c853a6dd01bd486 239 | 0x587e276dc7f2c97d986e8adf9b82d3f14d6cd8d2 240 | 0xa2a1565184816c25c69f449a62a3a1658a41bbec 241 | 0xe343245de92181bc06ba5cd1152c705a2c5f3e2f 242 | 0xbc766b23f820be4315f8ca8e527dd3b4b1b13233 243 | 0x2123fc8d0d36fbb1bd75b30417689d157fe06017 244 | 0x15874d65e649880c2614e7a480cb7c9a55787ff6 245 | 0x339109c3de40d6dee3e8540ba04aea218275c896 246 | 0xa58fc90ce1e6ee712ed0ae7a7327bd5d8da3fe86 247 | 0x735152dc18fd8ce2b93ef3a8de3a978d6a38df5b 248 | 0x159b67bb724307e5d614f9483da0c9842e74db88 249 | 0xb4ef27a6f63ffe1e7730af8dcbab6fedcf1d12c7 250 | 0x424eee506f88dd596275258b294e12e913675e89 251 | 0x1cb3209d45b2a60b7fbca1ccdbf87f674237a4aa 252 | 0xc58596d7c48bcff23541a31251f09802ba597657 253 | 0x2d7ef5876d4173922d5a7fcc6978c24d815dae3b 254 | 0x5d368655d72780b2b9e1a227407c1525d32cb2e9 255 | 0x4037d91dd4d7c4fbe7833e20c57efdcee2154974 256 | 0x1ccba10441e5ef272a711fa6bbac3d2fbce3284d 257 | 0xf18432ef894ef4b2a5726f933718f5a8cf9ff831 258 | 0x0f8794f66c7170c4f9163a8498371a747114f6c4 259 | 0x60da34ee5c163d5416b72df3561a662155b60752 260 | 0x2cdbd1bba2758d05ba423bcdf49c627323339a85 261 | 0xaa17c9e111c3e8400f4789054cefec245a94576a 262 | 0xd690aa91dc5c68e3967495e9533e09930c723e73 263 | 0xbd948a6d35e8f36d3549ea31f37855b56a78d819 264 | 0x08de69e5e9d08bb1dff0b5a7c8cc6672df7c0412 265 | 0x85b64f8ae9dd6ee20e6359797e2192179ea20e39 266 | 0x449b25b8db95ca26ad7b7ec896145a2b900afe0b 267 | 0xfad45e47083e4607302aa43c65fb3106f1cd7607 268 | 0x5f72be0835e32d7c853e2ffd5dd3d181d5b2ad51 269 | 0x2aa5ce395b00cc486159adbdd97c55b535cf2cf9 270 | 0x329d75f6d8a95b23b021665db5d63e93e0f1079a 271 | 0xb1a2efe104e253cb36a7b26eeaf9c20cc2c56130 272 | 0x24df929f3b8947aca48622410224129bf25ff844 273 | 0x123442ac6297777cbb51fbef56df37f73ce786dc 274 | 0x5b2e4a700dfbc560061e957edec8f6eeeb74a320 275 | 0x407b0059f87ac505ea7e7696f2599454587a4c1c 276 | 0xdb6ebffe39f36b8a6b8e6f79f143e3c4b17ea787 277 | 0xf2f91516e1950529065183690b2649cbb19a64ed 278 | 0x8bedb57393f24fbddf36a76881e21ed5037d00a4 279 | 0x0af155077c8d1b405970615351dc0c94598d1e18 280 | 0x24bdd87f2c286025e0eb84e4f919d48f000d301b 281 | 0x2b1eb94d2aed2dea74d85a6bac5f44df03b8dedb 282 | 0x87cb6ea8efa1bad09c3ef1575e01a3f18112947c 283 | 0xe4589f7785ca9c5001bdb29a96f948773cdf95cc 284 | 0x230ccf2ecd9294ce8176216499cc018cbfc00d01 285 | 0xa7de087329bfcda5639247f96140f9dabe3deed1 286 | 0x2bf157e727e9e46daee65403156a6244e1e26a69 287 | 0x34ac258ffc47ca4251cd7b5c10bffa6e9e3a2b57 288 | 0xaf89ea294556a5f0cbabb28a8df39364a649c130 289 | 0xbe16a705e15d9b45fd71c2f7759f787be5a44d3d 290 | 0x93b2fff814fcaeffb01406e80b4ecd89ca6a021b 291 | 0x814f583c1978367bff85285bc5fb668f3d4f0b03 292 | 0x2279f6df1cc166aae65acf679486e089b6bc0915 293 | 0xb3a824707e146fcfa583738bbe49d061fe0d1294 294 | 0x46101fbe580940c7dd2d2879662bc98954b5edd1 295 | 0xa4ccc61b79a21692d78da43fc25d3afa6855fae5 296 | 0xd8c1232fcd219286e341271385bd70601503b3d7 297 | 0xb22ecdfe16bef29ce48a63cde0add3e8b536d122 298 | 0x28ec7bb05d3be184ace7585630852ca164667ab8 299 | 0xbc9f3c0fd0bf21df9dfde2e5a2333aaf3640ea76 300 | 0x7181041f3ba67247d65df55a576477841329981a 301 | 0xfc84f7682235b7d03b073bc28f33263d09e23eac 302 | 0xc7924bf912ebc9b92e3627aed01f816629c7e400 303 | 0x7ff4169a6b5122b664c51c95727d87750ec07c84 304 | 0xab1c02c7d5ebfddbb8824ccd193e7df59b34a841 305 | 0xf54f5cd89aaf3aa7bbdd7cd6bacdfa3889bdad56 306 | 0xed2c0b5fcb32c2ec723cee4d14b5fa056b79fca8 307 | 0x6874bf4b913e10f08938db435eb2fd140bc10647 308 | 0xc5ed51dbaddef62a70cdde72fec280e4097dda4a 309 | 0x68c751ae64e9c786b5b576bcec63d82755d52143 310 | 0x3c3f450aa47c87c822d3e305b066e713df04c1b3 311 | 0x71b6296174c5f07d37cafd6e9b72ab5bb3f14fac 312 | 0x9cf77be84214beb066f26a4ea1c38ddcc2afbcf7 313 | 0x4946fcea7c692606e8908002e55a582af44ac121 314 | 0xe9b8897f3bd92a5845cdeaaebea1be5703d53fe2 315 | 0xcfad57a67689809cda997f655802a119838c9cec 316 | 0xeeee2a622330e6d2036691e983dee87330588603 317 | 0x4aa5bb4b3ba12633b36d677da5c13766d9024f2f 318 | 0x94193e30371d1568f9bd536df955716d9a5bca06 319 | 0x5679f8223fc2a9adbd7b9555ed2fee84b584ab42 320 | 0x4e56d8c83f495961ff015c0f9b145fce6e7ceccc 321 | 0x354ea96636b7347b3cf64ca2490b2f94cb39dac6 322 | 0xe716637ba037e840a46a1cac83556a2dd83c0285 323 | 0x3f01fa3ebaf10b496630808d4fa87f82ba40b926 324 | 0x7d9a91a6ca8265fd83ea09b7fa3982165b243c7e 325 | 0xb0b1d4732efe32aea466ed6bc3c79181ed4810c4 326 | 0x9ea3b5b4ec044b70375236a281986106457b20ef 327 | 0xdc5e76b8d921137327ef81666a6bec04d9375d7f 328 | 0x7f28a3eaeb728104986bf48fe2b1473cc4779cd5 329 | 0xc3d6d86ed7bfe24c40d3943e2f9ccff79b448041 330 | 0xf48dda868130ce65ae38cc8fc7285b2f67af49a5 331 | 0x209c1808febf6c1ab7c65764bb61ad67d3923fcc 332 | 0x907cb97615b7cd7320bc89bb7cdb46e37432ebe7 333 | 0xf1a9cc9ef6014d1ece8fa5504d03184dea016de4 334 | 0xf41fd65f30b4b4cef3dbea800468100b2eb19aae 335 | 0x9bee1b5b3b33e1e54434f0fb9bedf5d5018c97a0 336 | 0x4eeac9248cb9a2919899b9f834c8b25524e50d75 337 | 0x192422d3a1a199f9dcbd2781fc462757c97409f5 338 | 0xc68b73d9509f5343602480656f5da512a5a20b9e 339 | 0x26946ada5ecb57f3a1f91605050ce45c482c9eb1 340 | 0xc100a3f3d015b278212713d1b1c8863e254f37ad 341 | 0xa87c85f907335869f0f3797dc3f983408c9b18ef 342 | 0x340702983e023f7795d29f86627d7c603ebfb988 343 | 0x4f8cc34bfdd972bdbeaf4cc5118fba1382abb550 344 | 0x44bcd5471f6062739d3d4a7b99992723e94b2246 345 | 0xb6cd2fe50428dc273d3cb91e2af8ea7e1aff0467 346 | 0x5fae66045aadb8e6faeee636aaa3bb1652289c32 347 | 0x1961b3331969ed52770751fc718ef530838b6dee 348 | 0x8909684b6934cd76a4486a558bdc3220a9f5df12 349 | 0x74ac195583d3c5020d840978b05e1e4bec1c87f2 350 | 0x61cdb66e56fad942a7b5ce3f419ffe9375e31075 351 | 0xbaac39ed1f57addb6a02f6ca57f478c4fdaa8899 352 | 0xa333ba20f785d17548076454584890891240a835 353 | 0x1aceb72687b62d7fc8589e56a7db055119d2ae6c 354 | 0xbcda9e0658f4eecf56a0bd099e6dbc0c91f6a8c2 355 | 0x5e6045d5241225d9394a08490799e573e7d270c2 356 | 0xb68b75c2256071badddfadd6f65f5079c44df89b 357 | 0x8b3192f5eebd8579568a2ed41e6feb402f93f73f 358 | 0xfc1e690f61efd961294b3e1ce3313fbd8aa4f85d 359 | 0x83ea3965f503d1cdaccdc991ed1a68c948b83399 360 | 0x4d55ae1c1adaba7a5cd4cdf3068f40d928bdf03b 361 | 0xa1afffe3f4d611d252010e3eaf6f4d77088b0cd7 362 | 0x026195ce819f1cb4fa872ce306b14b91ab16a3ab 363 | 0xf0b0a13d908253d954ba031a425dfd54f94a2e3d 364 | 0x11112298a6977b28f0210e70ef4d18a92b6907dd 365 | 0x14c556a5e779574eb26d4f72f2a43c8064f125e7 366 | 0xd29fa4b8cc936a68bb560b19eed969ebfdbaa565 367 | 0xaade0ae5b3d7d808d4f23b2f9e62a7d41b0dc43c 368 | 0xf839c271722c2ceedb2a65883a810e90b537404d 369 | 0x53472e85d4046abe3626a0d71208de554ce2999c 370 | 0xa4faf53fd5f0e0ce7359e2681b4fff46cdaab3d2 371 | 0x58435b3dc4f38f66db7d9ec0e6c40f9645758aad 372 | 0x06f59a767f33eabe3f1a3d3c14997bae8058430c 373 | 0x94634a8005500f777cbdd2d38ee5bb114fb33548 374 | 0xd70ce7c2f0645b768ab8e7bb67c0d4f5d6cbf8dc 375 | 0x73d16546e92cd4f8ebbf3b2d97be76eedb7d2e4d 376 | 0x51409c733091f095b26a2c4bd75f90deea8d49c5 377 | 0xf3a7fb874e8d7fc38b0551d7889ea1ee1123bca3 378 | 0x6752787d5a119dad443e97cc6cc4449c655ed5d2 379 | 0x35cae9dd395e82403c50693f71d06f1ea5a311fb 380 | 0xb60fde5d798236fbf1e2697b2a0645380921fccf 381 | 0x7737107cf7ef2337ce1b55669e61a4184915c651 382 | 0x1de9eb72e250dd1cdc4470dd0d2d6d737e29c90e 383 | 0x143988448c8ae2028b82accaf9cefbd14f2c2504 384 | 0xf10b95da4533252e95d7f7610fb7b0e05837aa17 385 | 0x81611acd4460fd996aa657835af044644fa8ae29 386 | 0x45804880de22913dafe09f4980848ece6ecbaf78 387 | 0xdef1fac7bf08f173d286bbbdcbeeade695129840 388 | 0xc497186efcd4615322679c0bcf5cf6020e1e5ccf 389 | 0x1daaa369dc59fb021c147c2a191e6d180e981cdf 390 | 0xbdfb11b6c960e1b6411eb938edf7c52846b3181b 391 | 0xde899f9b9982871db4107ec49e8e38c83aa0c7d3 392 | 0x9d454f470535079b6451c0f7cfcbb0b3829557d7 393 | 0x49bdb39a10e89cdeac430edf2d4e9da56f4bb7db 394 | 0x9c7b882a970ba8dc474b9eaa62ba9f4be03abb22 395 | 0x67912118a6b7b66c799ae81936aeeaeaeb2914e1 396 | 0x8a7b7b9b2f7d0c63f66171721339705a6188a7d5 397 | 0xe2da716381d7e0032cecaa5046b34223fc3f218d 398 | 0x53425a7e693a622a21654301da4ded5cd7d00d3f 399 | 0x49e8fa2bf3dacacd35472e4400254faa02e0d1b9 400 | 0xbfd6b7d47c474fbdd33041384ecacb986d48b78c 401 | 0x3f8a2f7bcd70e7f7bdd3fbb079c11d073588dea2 402 | 0x67052f36152098e1001f62ba8b4d40ba53730110 403 | 0xcd900d555efcdeb905eabf319491bd497378a641 404 | 0x347925b22d0217a4797f470faa2afebbdb150b7a 405 | 0xf96459323030137703483b46fd59a71d712bf0aa 406 | 0xa072fbb3a8e324c0931e021b8ece0c95a96d0bcb 407 | 0x25aff2cd328ab97043fadc2c14fae5b0ef5aa9ce 408 | 0xa6dd98031551c23bb4a2fbe2c4d524e8f737c6f7 409 | 0x3d9a801720cdbce293edbc7d8dcd82d6ceaabbd3 410 | 0x59eb2e859726c7d69f942c907363485127c342f9 411 | 0xdf56f8200b0abc1e3674bde29e66bda706a4c08b 412 | 0xaa25900dca926d55da9f6bde3889429f5411d1c8 413 | 0xa23c4aa7050425d2922956bedd9d513da1b4a977 414 | 0x656ab07e581a247a74c9d92ea15688c8fa67c384 415 | 0xd4d8f4adc206c6593ed2af3a1c93a20e48f246e2 416 | 0xc523062f7e84f81a3fd87348178f28555418b377 417 | 0xedabb369e7286cad134d3aa52d1bb5e7c67ec732 418 | 0xaf90579d216816f27e3dcc601a0d10613a874fa8 419 | 0x86fadb80d8d2cff3c3680819e4da99c10232ba0f 420 | 0xb23236c3d35bc3dc9d13ac409a7f128aec6a0745 421 | 0xcca0c9c383076649604ee31b20248bc04fdf61ca 422 | 0x3e370a6c8255b065bd42bc0ac9255b269cfcc172 423 | 0xd41d582c2cf6a29dcf6aecfd96a7d5ca092b574d 424 | 0xd9467327499e51609dc0493f0e5d6cbcc7d3fe01 425 | 0x11bbad1c935b87cb83543b9288d1a2a73a90bf1c 426 | 0x5655a698b9be6e73572d3d3e3e473ad1c399d7dc 427 | 0x9b85babc0cc89899ccd47e9226a0b1fae577b19e 428 | 0x32c4adb9cf57f972bc375129de91c897b4f364f1 429 | 0xf34c55b03e4bd6c541786743e9c67ef1abd9ec67 430 | 0x026e96a9fcfeb4436624d64408786420b42e121f 431 | 0xbaac72d52b0793e3f75f661dde547d09b6d88899 432 | 0x8d69248c3b6890568dabccd44bc5c9c0c5b5c256 433 | 0xb15e9442f343393cfa85f5a2de39972e461d5dbc 434 | 0x3476054535318056212d08fe4c8e59167e4fe6cb 435 | 0x609a8db1645ab041c12262039b4a266a458e02c4 436 | 0x8db6da2120b346faa7f206841f2fb005bbe0dfd8 437 | 0x0e4a10629e54046ba196d0c9dccb42b42b756592 438 | 0x2c7d9a0416d00ff65bd851e8fc3badb1607db582 439 | 0x5d0773b82644c28c96813dcf2d58b2b83d785b6d 440 | 0x7d7470bc321a60cb11c7989356ad66a161c56628 441 | 0xed93a0bf115382bde967b7ded2f06c692b71634e 442 | 0xe218ae707e7aa0c138f7a44bcd89e79616e7430d 443 | 0x9b22ccda58a849adc93c7f8987acee9f50ecb925 444 | 0x1321f1f1aa541a56c31682c57b80ecfccd9bb288 445 | 0xc842c15394dd87498affe811db8c0ae80f990da3 446 | 0x96936443150b54cd88c865cd92dfb156881268c7 447 | 0x239119c43e3cac84c8a2d45bcba0e46f528e5f77 448 | 0xbebe03d890a535fd2427358eb030996cfe456ed7 449 | 0x74fbae8295699e7cd1422131e6eff75041aac955 450 | 0x18964fe4183434d344cb060dc9c753e8f3f78bcd 451 | 0x24456f2786d975973a0905fd53236c8311cc3006 452 | 0xbf8e70954ae322c4e39d91cdd1833bad425b0100 453 | 0xa4eed63db85311e22df4473f87ccfc3dadcfa3e3 454 | 0xacb2f5280e3615c18abd38ba8ba24dfe99a0da93 455 | 0x8472953dc2bc205e45234b41f63c99d873afb52d 456 | 0x757bc268bd50da88b2d0cf1966652b18e56ca803 457 | 0xcd9de7c910cb7a435e4d3640f4398094da876a5a 458 | 0x17540494ad5e39aefd49901774528e9ff17fe40b 459 | 0x4ecb692b0fedecd7b486b4c99044392784877e8c 460 | 0xeeb0ff1db9217a97c87ca9e2770ea0af2684975d 461 | 0x5d84cda74c22f25f4feb1035e7e14f20e9d3ab79 462 | 0xad2aa3c57570ad9811ba8ee90316f9c73f78035a 463 | 0x4b1802f3553c708fe6f4fe7230e5606cde47d907 464 | 0xb220d53f7d0f52897bcf25e47c4c3dc0bac344f8 465 | 0xfe2786d7d1ccab8b015f6ef7392f67d778f8d8d7 466 | 0x7b6c1fbd15ddb55bbee4e973b85f80493a5dbc0a 467 | 0xe4f7a608b0eedc93a97d1249c8bbda0e98077d29 468 | 0x1c5857e110cd8411054660f60b5de6a6958cfae2 469 | 0xf1a2b00a8f3a8ef925c36bbec7715c3e6da32c67 470 | 0xad3d703f8229d44f68a2e3846925c75360e02d3c 471 | 0xde8239e84c4f31875ea7a22dbdb232c28ac77be4 472 | 0x07977b31dd8078a877d202d976d37bc1ca95663e 473 | 0x53ee4a83f3576e951e3a21c4ed260073c0a838fb 474 | 0x2b000332cd291ef558af76298a4d6f6001e4e015 475 | 0xb73f00feeafc232c247516aa180261fec0e909fc 476 | 0xdbf0fac1499a931ed6e5f6122dbbcd3b80f66c7e 477 | 0xfa62a53d12afdf618159123e5ad899fdb1574bb4 478 | 0x245b6b700d5bd1228dabb5da3999980f0496fe4f 479 | 0xcffa421120373191efb04e77b8f7bd112dedd117 480 | 0xf1f5f0353d5cbcdb6d6cbdbde553fcb0c0957eab 481 | 0x31bb711de2e457066c6281f231fb473fc5c2afd3 482 | 0x4eb8b4c65d8430647586cf44af4bf23ded2bb794 483 | 0x51c9c2475f106fd6bed2bd45824a9ab5b0d24113 484 | 0xd23b2b2d3a2508dfb26005c00c1e68e63d65a3bf 485 | 0x403fc67bc995aee4dae6f5461788c94c977282b5 486 | 0xb4272071ecadd69d933adcd19ca99fe80664fc08 487 | 0xc9bca77c9fed40710727948f6a934208918d316a 488 | 0x744d31702d6344b0138f4bbd38868ed97c2565b1 489 | 0xc8f03617fc2ec4e6a0b89f8ccc30104109f5fb3f 490 | 0x976f1a24b7096950192e4192dc5e841b931e02e2 491 | 0xa872e0a44bbd66c1486a756cb5bd3f0beec4e32e 492 | 0x97215b40e454b435d2871c9a74e9939133a58089 493 | 0xc73015dcd0dbc70434749d43337ce1a3cf7dbc7d 494 | 0xf62751dc7afbba6776b14554e87fecba8272422f 495 | 0x53c1d7fb6d0c8044bf6faf8bc53a8818fda8b4bd 496 | 0x4ea242169e02dd951689b13ba98657a6b3a0a4fa 497 | 0xd152c6aabf7b6736bb345a2e3e8e1e956241fdb1 498 | 0x3efe03c53f94b65099dff79b7ca5abf37a0b028c 499 | 0x5f75112bbb4e1af516fbe3e21528c63da2b6a1a5 500 | 0x303bbede872cd9c6e0890f869e86cff21e7ce676 501 | 0xb3124aa036224f49a578efc62a714763f18eebc4 502 | 0x045dbce87d2ff3b99dcdd7a9d6d8e3c9aefb0573 503 | 0x20f1ce2ece9b82fbe501cf4ad62dd7f7dce52534 504 | 0x2afd9ce07d4186e8f3671d52200850760a8e1f70 505 | 0x9120c9e34c605f54c9967144d4033ed7af0fb495 506 | 0x211ba03e3d6af08cb0a021aa695cd9c380f21884 507 | 0xa2b4c0af19cc16a6cfacce81f192b024d625817d 508 | 0x5f0f88bbf19809e60dd561c2088ece55cb46a43c 509 | 0xd00d7964b2eb798c713feeeba17efaf64c80a89f 510 | 0x821144518dfe9e7b44fcf4d0824e15e8390d4637 511 | 0xd275b95b32e0a75ade4d033f7a53fb1eefe0e5a1 512 | 0x77ee79c5ccef975c942732c0d54bab4473566fed 513 | 0x5f62c96fbe504244f69ec99c4638a507fefc3ed7 514 | 0x0955a73d014f0693ac7b53cfe77706dab02b3ef9 515 | 0x1405c709d6bed996d046cd94d174af7ec0c39f43 516 | 0x601332de4379c7c7e9823f6852c81200f4aa83bd 517 | 0xd8f0d752ecf95daab93a5b2a6ab3d94ee28ceebc 518 | 0x20b362b43b0ab24409e5dcc0af247199365cf1ba 519 | 0xc757c058001116d1df5751400acec8d2e919311f 520 | 0x24e634089be96e3754f0dfa628a94ea57089e4bc 521 | 0xf3db7560e820834658b590c96234c333cd3d5e5e 522 | 0x178432c6f123aaf9cdf3c632c6d09791ac3da081 523 | 0x879321dced820d668f5f3378d61af4602156267e 524 | 0x24800cbae0b7345054a1df4e41031a8202b84f5e 525 | 0xe1f238374943bb9a2d254dfb713b3b2ff8efdb87 526 | 0x80640db285cc63496bdd8c1980a7f4526a4d477f 527 | 0xd4c435f5b09f855c3317c8524cb1f586e42795fa 528 | 0x88d9333251b43eabf33b0cf74e11cab8c1653100 529 | 0xbee4eec7b37e4ae9da91ec0b900048fb0079a95e 530 | 0x7841479c5976b8184dbcde9a7a5113901b233efb 531 | 0x4e3955b37eadf78ca289695d4598bbb65acb1d2e 532 | 0xd7362318a19bee70d1dad79fabe1d2b9f5ea722a 533 | 0xc0d070682c063bfcf3c1e1d6f2e4489fec2a5242 534 | 0xfec0a2fbcc41017cfd272ff8caf56290fcf7e9ad 535 | 0x3934fb5c73eb1eda8ef8c80e7971f070040a455f 536 | 0x77f973fcaf871459aa58cd81881ce453759281bc 537 | 0x4cafb49bda083fec5ccb6c28da18ea3adbadceb5 538 | 0x70861e862e1ac0c96f853c8231826e469ead37b1 539 | 0x0db4271a932e8c110f07be6d5f7403a39a7099d1 540 | 0x2b6dd33b57579d93f0fb799abbc47e16d589a024 541 | 0xb895fa349864b0fddb71b7e8c2234bfdefafb51a 542 | 0xf3ba22bfadb74173ba80c5c8529c5d51c55282b0 543 | 0x2b915b505c017abb1547aa5ab355fbe69865cc6d 544 | 0xf9d5bf0c8678c95424f334ac3b14d7315ab28a4a 545 | 0xb2536f4320a0bb9157b74ee93631cdff5b7f13dc 546 | 0x744d70fdbe2ba4cf95131626614a1763df805b9e 547 | 0x90cff03dca5f3248284df0d9020f2172c1a947b7 548 | 0x032ae2bd448904e0d468167dc25b4c35d3d72a36 549 | 0x755709e7dbaf4ec49c41a6395960d705153a4cb9 550 | 0x566653c597dbd6ea6a1659531c6a7af1a2c71fe6 551 | 0x32c483009626db3a5a67ad7850194129dd63df83 552 | 0x6328253b570845b1ad79a436804d6b21b01b0feb 553 | 0x89f3d002225fbcbe4a6ce29218ac4ec727051d87 554 | 0x0698dda3c390ff92722f9eed766d8b1727621df9 555 | 0x7bb50ae05147882107218fe9dab9f58bd2e8ff55 556 | 0xb578fc136a72ca6628b9ff05ef8cf64786084d94 557 | 0xa12bd9d0f059349055a3dc4e38833171b4ad2a35 558 | 0x92b767185fb3b04f881e3ac8e5b0662a027a1d9f 559 | 0xed6f3bc8e502f0ba527dd5303d79f16b3070cbbe 560 | 0x2543822ee83e4e8f646191b7761619988907bf11 561 | 0xe68296991b9e4409c678dc770bf9cd5ecd84f2f5 562 | 0x78bd9d44285642f3ce29e26ccb6ed3dd41c008d7 563 | 0x51ad5b530ad35ac0d8c3bb6ed65b345b48142f0c 564 | 0x618d7d8e86ce4e6b319b11c4604f6fb95f073330 565 | 0x45e007750cc74b1d2b4dd7072230278d9602c499 566 | 0xd74c61bb1b61db2459a40126095dec210346ef90 567 | 0x5d11326bafc2c3734934820c889520caf4adc023 568 | 0xb72c84a9084b21b3fd10ca41661fb7538cea7b56 569 | 0xc7a2273be2c462fe8d76e0818f408b04d6bd89af 570 | 0xf079d3361fb92365ffb52d98a20fbe6f7e50a74f 571 | 0x1efb2286bf89f01488c6b2a22b2556c0f45e972b 572 | 0xb1f4b66104353ec63d8d59d3da42c0b4fb06e7f3 573 | 0x2ddbfb13677e8df85f37aa8e578424e99af7cb62 574 | 0x70efdc485a10210b056ef8e0a32993bc6529995e 575 | 0x9535423410fc63104cde4a1afec06e9eb87839ee 576 | 0x98b69ebb4ce228145463d7c08aa71fbb6791fe86 577 | 0xbcd8756ea481608ea3dd5a555493305cf0a79640 578 | 0x580ee6050e58ec72ba294018afd3e6da0d11f134 579 | 0xc841210471ae257824b1ddaf24d0ef64f2297485 580 | 0x48dcaa53d38792ff0c4f4353bcf7cd64718f7047 581 | 0x348471781f03d99ae7957cc135446745c1b6970d 582 | 0x8eb24319393716668d768dcec29356ae9cffe285 583 | 0x02080b9c5d393a19ff311bb41401dcdac8a0b3dc 584 | 0xc35dbe934b566e0cccee6fe0e7b181df384d2d54 585 | 0x34f06527a3646fc64492fcdeebef668747df0cc7 586 | 0x82a6a22d68ffba4057d5b49f93de5c05e4416bd1 587 | 0x7c1946e7b41473c39419f27f5873768755cfa963 588 | 0x1b63fdfb843afc1ce967e29d6e675eb8ccb5732f 589 | 0x86165b4b86276bbbcce2e9abeacde07a67f8b960 590 | 0x34bf0622a644aeac6a496ca0985d944a39f8aee3 591 | 0xf3504e1ac58e333f013f1595c435c9ebefd47ede 592 | 0xca7b3ba66556c4da2e2a9afef9c64f909a59430a 593 | 0x8a4ee36b7ac021ea1ac780858da54b0711ae0d13 594 | 0x10f5010457249110ce32cd6b16e65ed9741e55fe 595 | 0x515ed8eb95e7fe50ee09d02151b149a0035314a2 596 | 0x40adfc7c23c22cc06f94f199a4750d7196f46fbe 597 | 0xe00f567b3b4472a0f125645bcf343cb27f1a8fa6 598 | 0x082220922b2c95d7b8551f8cf0891b649b280beb 599 | 0x16631e53c20fd2670027c6d53efe2642929b285c 600 | 0x46ba0bfc76cafcef5563c9cbf281a2e4f05fdff9 601 | 0x93c1119a6a7bf9cd207dbc72e8614a5d82ff427b 602 | 0xef19f4e48830093ce5bc8b3ff7f903a0ae3e9fa1 603 | 0x0923067100b2a4839f1803ad73b24097e20109df 604 | 0x42039f55bb984e2315cefaed3a12d0b4c3328a08 605 | 0x00eca744d071cc3d497a522cc8b1aa6cf2562528 606 | 0x52f301dce8fb167ac95673cc6b3b51740a75fbd3 607 | 0x6ff24ef3f0f3155702b04188b1f18f54499f752b 608 | 0x8a52b7ca8db16aa9b673d78d0da88c04d4e54a40 609 | 0x7142e25f69eff34dcede59e93db734e4ad3cb6bd 610 | 0x39ddc0a04f0e1f2830f3f1fec414cd6e23168bee 611 | 0xebad12c59bca1239f020f534f9c9defe69f8730c 612 | 0x8fcfad991abb23589418cc552039a98ca6c544bb 613 | 0xc452f2a4034edeccaa82689b6e41e40623435918 614 | 0xc43e80a72c10f22db7f98eeb22759a136b99d28e 615 | 0x12bcf29d1a5ee44c0872170facbc1d6f1b5a88d7 616 | 0x62cf65a6c6a4568820489facaf659ba6b878d902 617 | 0xd8a837ee1d4bfbd779cfc5ae3c7ac278a20b331f 618 | 0x3a3e29ffb06b516a1a85da7a5efad22fbc7c3cb9 619 | 0xb379b20bf605ba720b63501274a92b0ff717b293 620 | 0x4bee0504a440474f1acff4a2e739aaa83d705695 621 | 0xa1c7bafffa9ce6aafd68c7ab24c5482f3dd488cb 622 | 0x566113069683ce664958a784f18336b4020a1350 623 | 0xe3be66702b73282c84ff74989994e890acd190af 624 | 0x5644b9eed6f578c820e6fa500b3c9f21d22347d8 625 | 0xe14417012f306b89d0138fed7a04a134c978d5a2 626 | 0x343e65c02a857138ba221b9b4057b27a8379e5f6 627 | 0xab440d462dad8679142366714784bc75776225e8 628 | 0x43f5e4ae7a5ed4c92ef99800d75bedac01665cea 629 | 0xa02fea637178bb96394c3bc9af2db3139e8284e4 630 | 0x72b819a6650880647e0f76335461af9dbbe2b777 631 | 0x11980d4bb4465cb4fc77a57b98a16787b3d834e7 632 | 0x01f666dbc313ae35702e41958a11b17c521e8a16 633 | 0x5cc008904d3eb2120635df882e4e549aa254a450 634 | 0x2c07d40e9578195836d968b4f277923e4f7f66a2 635 | 0x8bd13abe5131fb8cf5e1ce237e1178da29a1ab16 636 | 0x6ff313fb38d53d7a458860b1bf7512f54a03e968 637 | 0x43c1c012aa89c60019b4e941ccac98f22d2f3c01 638 | 0xa46730479d8b42ea35354a5eeaceaa3c55ec534f 639 | 0x0d84dfe4b2183f40ffe8cfea85118026f1a79db4 640 | 0xc1e180bda655ecdc3467617dd19c8b00f873b0e1 641 | 0xae7ab96520de3a18e5e111b5eaab095312d7fe84 642 | 0x0808d1fe7a249a8b91a6c4037ed9f7ce5e2f90da 643 | 0x9366b7c00894c3555c7590b0384e5f6a9d55659f 644 | 0x35e9b078e0bc09557a5cec00b28a9f9d5d8faa66 645 | 0x3647e297d7457cd75401239f86bf2ff0dd857920 646 | 0xa5eeeaf8d4b27e653dfbd638d3cfc2a30ae3892c 647 | 0xed3fbb9d036bc133a9bdee5cd188f56cd2bb20f1 648 | 0xb90753057cdf3c379357725385f42a23c0aacff0 649 | 0xc58fe114867f2398463019c3cda87a08e0f7430a 650 | 0xe2e31fa09327289b544c814ae1f0a678b32584e5 651 | 0x28fdb12fcf6a926040617ff16a18875ce6e4f227 652 | 0x8cfe51f5f1cbaa2cede9312d8d3d6381448f9a31 653 | 0xf68d7685172e6702fed746bde7e050c046baa744 654 | 0xb4d80a7105b7c2ce7e1ae865aa716d5e27441425 655 | 0xb6a56c702ac117cc8933661431314bed43400cf5 656 | 0x67748c77cfcc194e0efe00b2bb28c0bee833ddda 657 | 0x619aaf15d3564c3e23eea2969f07e2bddf8d5882 658 | 0x55332170cb7d36545b500f66a80291c83d40bba7 659 | 0x2f6753304c2c20aab4b12c46706341522a2e9542 660 | 0x0e5ad274d1fc232555286246be0bad3affb2f4cc 661 | 0x300bc0d809bc6f4128907e7dcae2d716f4e475d5 662 | 0x72b89605ac528bb6037fd00dd2567b134355b777 663 | 0x794b4a0f0d8957ea043d29de0bce393a0c08ebc2 664 | 0x4b5889a61bacf0ec18472ea4861c2bf0ae61e2e1 665 | 0x52f849e9713219e004f25fbc7143c06495ef46f9 666 | 0x7e8f6c007884cd726b434aeb983645031300cdbd 667 | 0xb1481f1e95662596f873081f8544ee8841cee207 668 | 0x4f3afec4e5a3f2a6a1a411def7d7dfe50ee057bf 669 | 0xd9d9e73488eb6a9a6230116c4dcdd56cd62a1e71 670 | 0x317b5edf549bd69dbfa6fad89c8121533b0e7f7f 671 | 0xa8511af2f945b0a939a9772f403f41ec637bffcd 672 | 0xb11ec0198541bc59620bfa2e5d8607583f328734 673 | 0x99d7159b0a2f0f65ea7551ce560db24c19875a8b 674 | 0x69b6ae8e3e3d5908d61931af1dea3514ad1e134c 675 | 0xeccab39acb2caf9adba72c1cb92fdc106b993e0b 676 | 0x2fd8bc03d9e827f77fac20b5130ee98b7f80149d 677 | 0xe804964c296e9fb666fbe67767838c1ff9ab3209 678 | 0xa45a801187c86ee3d99b4c476207f45579d42148 679 | 0x534de7435da2d92ba684e41e090c5ce46b7dbdf6 680 | 0x5f96d878dd1f6267a9f557334af491128dcf157b 681 | 0xa5721d363dcedfe80bbb6cf4f108adec083f2214 682 | 0xf073c0b2da690e3a5cf5c6c0130e912054cd3d70 683 | 0xe187b8a0c36cebe542d0e65863e5fa65f1d88552 684 | 0x4ba6ddd7b89ed838fed25d208d4f644106e34279 685 | 0xc2bfa5d764c42179ec30e92edc2835c81da34a2e 686 | 0x1a3496c18d558bd9c6c8f609e1b129f67ab08163 687 | 0xd9c05b4bafc26f32fc2774cd7e6ae544a490dc54 688 | 0x22e65b5531ce8bbb98560a2d82f8650bdba1f202 689 | 0x445f51299ef3307dbd75036dd896565f5b4bf7a5 690 | 0x4b970903a1692ad958a2264e587af7df03c33fa9 691 | 0xbd6467a31899590474ce1e84f70594c53d628e46 692 | 0x0aacfbec6a24756c20d41914f2caba817c0d8521 693 | 0x39653bf251dd51c59517701911cdb7071b64ec6a 694 | 0x75f70ef6e0871de7567a2daf9e1013af8844c22c 695 | 0xc4d586ef7be9ebe80bd5ee4fbd228fe2db5f2c4e 696 | 0xf9026647d6247a92a1895d9a3ad243c3c8d07c69 697 | 0x281a481e19ff07fb1afd51f0a9ac55246df45733 698 | 0x010ebea9af6ce644d56c6808ae324ae9476bb11d 699 | 0x865d176351f287fe1b0010805b110d08699c200a 700 | 0xbfd78659212f00de65a6411dadc75878930725ec 701 | 0xa52676ba9876840cce96e177a48555162b6a5cda 702 | 0xbf79a2e768692fa0fa29ec5bb7de163a484c9398 703 | 0x98ada01d31b840ac3580f8646bbdfe4bb81dbb00 704 | 0xf7a6acd437edc08320ab17644bb5963753a4e6d8 705 | 0x739b9c59446a1e4ad23f78d82534b034b4b23525 706 | 0xf6c4c1259ba6defe5120fb782b923ca3170d2357 707 | 0x6de91d2ebbb2d06ee569f82dc7e27e5088897a03 708 | 0x27c165ec92bf98b00d4af65228a342f84da8cf30 709 | 0xb7215b64874352de3984e9944a51ad9d6b6053ef 710 | 0xfe217a5597dc3ee39297eb30dc735689aed3587b 711 | 0x19a370296ad5a90254c96da562159dd0386ce777 712 | 0x417d6feeae8b2fcb73d14d64be7f192e49431978 713 | 0x03cbfd14f07824f1376958dc4ad7c97e140d9ae2 714 | 0xa4d2255949b8d255cb62e3e8c37f43d1217e0546 715 | 0x0dd2ee1278342ff207bccc8e38e7a9cdf0bbe5e1 716 | 0x6b9dd2a8f872ab0759d9e5208c4d80fd65503864 717 | 0x02f6a50f89a7b98ea39813538e590238d8bc5c48 718 | 0x62359ed7505efc61ff1d56fef82158ccaffa23d7 719 | 0x786001c9c5ca6e502deb8a8a72480d2147891f32 720 | 0x256845e721c0c46d54e6afbd4fa3b52cb72353ea 721 | 0x9c061df134d11412151e9c200ce3f9f6f295094a 722 | 0x9e0a940efde36db60e97c8ece43d608c6b0d95e5 723 | 0x53a1e9912323b8016424d6287286e3b6de263f76 724 | 0xf49c17470ecc377a59de71eab8dcb5e78b5cb670 725 | 0xcdd0a6b15b49a9eb3ce011cce22fac2ccf09ece6 726 | 0x335031e1f9e2bbb1e25d34bd567211da1479931a 727 | 0xc060443d763e7ce487595db13700b2aa99b8fc19 728 | 0xc3596c99113084d5f175279087ce75f908da2450 729 | 0xeffcbdab85d79e3fbc53c0e491b0e844d676633d 730 | 0x169d79a5b69579c96008a908797f207fd2021287 731 | 0x3b94a34218b965812068d1f46999e44febb4ae83 732 | 0x33307f0f1029487e0a77ba2a20794a5a047e3e92 733 | 0x39f5793480472f677b921c35639bc4bedee4d61c 734 | 0xceff8279f83882cc28946e7c4516418f01213aa4 735 | 0x731a04e1271d7e0dd287ecd22c6f10bd76befb13 736 | 0x08f5a9235b08173b7569f83645d2c7fb55e8ccd8 737 | 0xc6e64729931f60d2c8bc70a27d66d9e0c28d1bf9 738 | 0xca15518ab51b94aae16ce9454ab981479122fa8f 739 | 0xf1f6be5468638ce9503b4bd892948e10fa5c3cbd 740 | 0x8ce5256f14a432579f7ee608a61761e1c4af7d93 741 | 0xe150fddcbb37805853c7de467f01e7b2af71ec26 742 | 0xd7d6e57892fe8df9109e87d5ed62ae2337e1872a 743 | 0xb8a958df32f68c6743e92f59df95798b382e8132 744 | 0x14ffd42f843b056668585626f48f06429c0867e9 745 | 0xd0f12a5d6d74c92e0600ce4274ac19ec6e7fe6ae 746 | 0x4bf323f931c1e8a34e6099ca21bc1ec4f4916f7c 747 | 0xe509e9d96cb055c64c664976ddb421f82b255447 748 | 0x98a803533f590d8738c217510d248b9aef62a7a5 749 | 0x827eed050df933f6fda3a606b5f716cec660ecba 750 | 0xe5bab43b7b6b8daab4be47a32b69529c099133ec 751 | 0x780df867dae33550dbd1db946eed451832780b8e 752 | 0x1c68a7eb5ffce9d93401eebf521b58ac4fb65855 753 | 0xa68dd8cb83097765263adad881af6eed479c4a33 754 | 0x4434d1ccfb4fdb63cfc24128b075be038a3d3d9a 755 | 0x400837e4a4c34289d73de18f1a493b5ed7d782a5 756 | 0x77571182045efd1f0feff3dc213c5dfa2351f007 757 | 0xa2180fe3a57d750b61f0265e4216adf1948f3a03 758 | 0x612efa32e2aed8730d17b70ff71e7a29d44a7908 759 | 0x983c059d1be984f8f06c2559351c5ab1cb1dcdb7 760 | 0x8eb0ca5f45cf7b3dd1593cdefd3ee46d8739241b 761 | 0x348adbacf9c23d600f76b51ac2e55d4b5d2732dd 762 | 0x2ac5a7441d0ad7aa69504a82a7c0cad6872cf6e7 763 | 0xb3ad0a45cd07ad7596ed6405996d1eb8b189caf1 764 | 0x5c2e024d836e651629f0dc18273068424087ac2e 765 | 0x1e807a4bb462ca081540b98469851a842bc6ebdb 766 | 0x25fadd49c8d95108d2f86af7998431277e35fc01 767 | 0xc0f4014a41c7511bf22351a132a7282f84eaec3a 768 | 0x034b848cae7639bb93038c6a751c21a7bcf0ece3 769 | 0x69fa8e7f6bf1ca1fb0de61e1366f7412b827cc51 770 | 0x06ec2d284764c510816e2433f5106fa6d7a77fb9 771 | 0x5ab58adf38630b551a74e9c6a3e2d92857d57bd6 772 | 0x875bf9be244970b8572dd042053508bf758371ee 773 | 0x4d13d624a87baa278733c068a174412afa9ca6c8 774 | 0x7bbfb91dc56c36ab8d61e36e5b4dc91c0ff5d0ad 775 | 0x995de3d961b40ec6cdee0009059d48768ccbdd48 776 | 0x12d3aa80c05d1c83443164d60d1796bbca6d5702 777 | 0x68a118ef45063051eac49c7e647ce5ace48a68a5 778 | 0xd066421c8700fbfaf988130d88ea4f9c15c9178f 779 | 0xefd21938ae5e45032609038c10c4e45ba2edd95a 780 | 0x18d2abccc1b02093986acbd249107d5b17e081a4 781 | 0xf83cc87d4f15acfd0e4e7efd056f2a63f61e42cc 782 | 0x15cbcfe107d1b1b0513e7db70c05d96f406c72b6 783 | 0x985dd3d42de1e256d09e1c10f112bccb8015ad41 784 | 0xc12d1c73ee7dc3615ba4e37e4abfdbddfa38907e 785 | 0xbc65cd35d78833dccacd68eefe0bd4418bfa2f2b 786 | 0x7461c43bb1e96863233d72a09191008ee9217ee8 787 | 0x126e17df0c017bfc9ba7372cd1e3ea8d3c362108 788 | 0x7d5edcd23daa3fb94317d32ae253ee1af08ba14d 789 | 0x4c85796ca5a3559b551732082996bd99389d5bd3 790 | 0xf1ad0a06af87b69d35ef11a295ebf8c97ef3928c 791 | 0x93ecd2ecdfb91ab2fee28a8779a6adfe2851cda6 792 | 0x52b0b8d859fb6a3ff9206f6957e9957a7eab5505 793 | 0xacdbba1b68a8b7131ebb26285bdb90e3d95795fb 794 | 0x704f160cd2d67ccfb19a7fc7d70b41003c0f2a3d 795 | 0xbc192ada0ce9d181c453a09209c5ea2838e105ca 796 | 0x512e12633ad24ed6df25f1d22e5b4b95cce1b1ef 797 | 0x0e2298e3b3390e3b945a5456fbf59ecc3f55da16 798 | 0x388fbff7fef6cee150e4820bbd4e22c377e80201 799 | 0x89bd734a451db48a590954294f5aac6a06cf14b7 800 | 0xee4831724293c487afd01f896d35beef50eaa3f0 801 | 0x7e444b75fe1dd857539ef9fe070a49fd4b141687 802 | 0x98ad9b32dd10f8d8486927d846d4df8baf39abe2 803 | 0x69b6ab6866bcebeca2a1afb203d210bac57d6267 804 | 0xdef42afc9d4b4767b61add3d926a9aa5cc210275 805 | 0xcabb170c0fabaf1cbc373f00777e46c27ba6a774 806 | 0x030427ec61b32979dd6c4377838140f5f587f80e 807 | 0xe532dd44927a24765419f8f5d83351d30a1996dd 808 | 0x35b29b327e5a493334a3123715e47b589c0ae1d4 809 | 0x9c3b003cb0e448a52c53674a55188a9bf802afbd 810 | 0x520bba6b38cd3a9d8f799685a5f782d21568210a 811 | 0x1a3aacc4fceb968de9219691d7b1a63cc6da65e0 812 | 0xa6e2f89b03d1b7a2d8fa7f28b971bf2a7be0f91b 813 | 0x0abe1fbd97b9655189baab092766bfc107c09f53 814 | 0x7cc23e195d4db133a78b43eb93b524796777c3e7 815 | 0x85eba557c06c348395fd49e35d860f58a4f7c95a 816 | 0x14094949152eddbfcd073717200da82fed8dc960 817 | 0x2c537e5624e4af88a7ae4060c022609376c8d0eb 818 | 0xb4d930279552397bba2ee473229f89ec245bc365 819 | 0xe0b9bcd54bf8a730ea5d3f1ffce0885e911a502c 820 | 0x370941ba0979187fd8ab4487cd76f5b95fe40e00 821 | 0xcfa93536df928d97036b7fed10776a1382199f4d 822 | 0xa30ac7093a38de158410d6904cff27fc74b46539 823 | 0xaa53024f8b75913e6f5fbfbc8806dbf763df1a9d 824 | 0x5adc961d6ac3f7062d2ea45fefb8d8167d44b190 825 | 0x2b1f91afbac59fafb0e5eaa22e0ff0539e97eba8 826 | 0x30bf33b9b5fce1b70bbdf2041160bc62d2ba9df0 827 | -------------------------------------------------------------------------------- /src/arbitrage.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use ethers::types::{H160, U256, U64}; 3 | use ethers_providers::Middleware; 4 | use foundry_evm::{executor::fork::SharedBackend, revm::db::CacheDB}; 5 | use log::info; 6 | use std::sync::Arc; 7 | 8 | use crate::paths::ArbPath; 9 | use crate::simulator::EvmSimulator; 10 | use crate::tokens::Token; 11 | 12 | #[derive(Debug, Clone)] 13 | pub struct TriangularArbitrage { 14 | pub amount_in: U256, 15 | pub path: ArbPath, 16 | pub balance_slot: u32, 17 | pub target_token: Token, 18 | } 19 | 20 | pub fn simulate_triangular_arbitrage( 21 | arb: TriangularArbitrage, 22 | provider: Arc, 23 | owner: H160, 24 | block_number: U64, 25 | fork_db: Option>, 26 | ) -> Result { 27 | info!("\n[🔮 Arbitrage Path Simulation]"); 28 | 29 | let target_token = arb.target_token; 30 | 31 | let mut simulator = EvmSimulator::new(provider, owner, block_number); 32 | let simulator_address = simulator.simulator_address; 33 | match fork_db { 34 | Some(db) => simulator.inject_db(db), 35 | None => { 36 | simulator.set_eth_balance(100000); 37 | simulator.deploy_simulator(); 38 | simulator.set_token_balance( 39 | simulator_address, 40 | target_token.address, 41 | target_token.decimals, 42 | arb.balance_slot, 43 | 100000, 44 | ); 45 | } 46 | } 47 | 48 | let mut amount_out = arb.amount_in; 49 | 50 | for n in 0..arb.path.nhop { 51 | let pool = arb.path.get_pool(n); 52 | let zero_for_one = arb.path.get_zero_for_one(n); 53 | let (input_token, output_token) = if zero_for_one { 54 | (pool.token0, pool.token1) 55 | } else { 56 | (pool.token1, pool.token0) 57 | }; 58 | 59 | let out = simulator.v2_simulate_swap( 60 | amount_out, 61 | pool.address, 62 | input_token, 63 | output_token, 64 | true, 65 | )?; 66 | amount_out = out.1; 67 | info!("✅ Swap #{}: {:?}", n + 1, amount_out); 68 | } 69 | 70 | let profit = (amount_out.as_u64() as i128) - (arb.amount_in.as_u64() as i128); 71 | let divisor = (10.0 as f64).powi(target_token.decimals as i32); 72 | let profit_in_target_token = (profit as f64) / divisor; 73 | info!( 74 | "▶️ Profit: {:?} {}", 75 | profit_in_target_token, target_token.symbol 76 | ); 77 | 78 | Ok(profit) 79 | } 80 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | use ethers::{ 2 | prelude::Lazy, 3 | types::{Address, Bytes, U256, U64}, 4 | }; 5 | use std::str::FromStr; 6 | 7 | pub static WEI: Lazy = Lazy::new(|| U256::from(10).pow(U256::from(18))); 8 | pub static GWEI: Lazy = Lazy::new(|| U256::from(10).pow(U256::from(9))); 9 | 10 | pub static ZERO_ADDRESS: Lazy
= 11 | Lazy::new(|| Address::from_str("0x0000000000000000000000000000000000000000").unwrap()); 12 | 13 | pub fn get_env(key: &str) -> String { 14 | std::env::var(key).unwrap() 15 | } 16 | 17 | #[derive(Debug, Clone)] 18 | pub struct Env { 19 | pub https_url: String, 20 | pub wss_url: String, 21 | pub chain_id: U64, 22 | } 23 | 24 | impl Env { 25 | pub fn new() -> Self { 26 | Env { 27 | https_url: get_env("HTTPS_URL"), 28 | wss_url: get_env("WSS_URL"), 29 | chain_id: U64::from_str(&get_env("CHAIN_ID")).unwrap(), 30 | } 31 | } 32 | } 33 | 34 | pub static SIMULATOR_CODE: Lazy = Lazy::new(|| { 35 | "0x608060405234801561001057600080fd5b50600436106100365760003560e01c8063054d50d41461003b57806364bfce6f14610061575b600080fd5b61004e6100493660046106e4565b610089565b6040519081526020015b60405180910390f35b61007461006f36600461072c565b6101ae565b60408051928352602083019190915201610058565b60008084116100f35760405162461bcd60e51b815260206004820152602b60248201527f556e697377617056324c6962726172793a20494e53554646494349454e545f4960448201526a1394155517d05353d5539560aa1b60648201526084015b60405180910390fd5b6000831180156101035750600082115b6101605760405162461bcd60e51b815260206004820152602860248201527f556e697377617056324c6962726172793a20494e53554646494349454e545f4c604482015267495155494449545960c01b60648201526084016100ea565b600061016e856103e561078f565b9050600061017c848361078f565b905060008261018d876103e861078f565b61019791906107a6565b90506101a381836107b9565b979650505050505050565b6000806101c56001600160a01b03851686886104ef565b600080600080886001600160a01b0316630902f1ac6040518163ffffffff1660e01b8152600401606060405180830381865afa158015610209573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061022d91906107f2565b506001600160701b031691506001600160701b03169150866001600160a01b0316886001600160a01b0316101561026957819350809250610270565b8093508192505b50506040516370a0823160e01b81526001600160a01b03888116600483015260009184918916906370a0823190602401602060405180830381865afa1580156102bd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102e19190610842565b6102eb919061085b565b604051630153543560e21b8152600481018290526024810185905260448101849052909150309063054d50d490606401602060405180830381865afa158015610338573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061035c9190610842565b6040516370a0823160e01b81523060048201529095506000906001600160a01b038816906370a0823190602401602060405180830381865afa1580156103a6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103ca9190610842565b9050600080886001600160a01b03168a6001600160a01b0316106103f0578760006103f4565b6000885b6040805160008152602081019182905263022c0d9f60e01b90915291935091506001600160a01b038c169063022c0d9f906104389085908590309060248101610892565b600060405180830381600087803b15801561045257600080fd5b505af1158015610466573d6000803e3d6000fd5b50506040516370a0823160e01b81523060048201528592506001600160a01b038c1691506370a0823190602401602060405180830381865afa1580156104b0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104d49190610842565b6104de919061085b565b965050505050505094509492505050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b179052610541908490610546565b505050565b600061055b6001600160a01b038416836105a9565b9050805160001415801561058057508080602001905181019061057e91906108e2565b155b1561054157604051635274afe760e01b81526001600160a01b03841660048201526024016100ea565b60606105b7838360006105c0565b90505b92915050565b6060814710156105e55760405163cd78605960e01b81523060048201526024016100ea565b600080856001600160a01b031684866040516106019190610904565b60006040518083038185875af1925050503d806000811461063e576040519150601f19603f3d011682016040523d82523d6000602084013e610643565b606091505b509150915061065386838361065f565b925050505b9392505050565b6060826106745761066f826106bb565b610658565b815115801561068b57506001600160a01b0384163b155b156106b457604051639996b31560e01b81526001600160a01b03851660048201526024016100ea565b5080610658565b8051156106cb5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b6000806000606084860312156106f957600080fd5b505081359360208301359350604090920135919050565b80356001600160a01b038116811461072757600080fd5b919050565b6000806000806080858703121561074257600080fd5b8435935061075260208601610710565b925061076060408601610710565b915061076e60608601610710565b905092959194509250565b634e487b7160e01b600052601160045260246000fd5b80820281158282048414176105ba576105ba610779565b808201808211156105ba576105ba610779565b6000826107d657634e487b7160e01b600052601260045260246000fd5b500490565b80516001600160701b038116811461072757600080fd5b60008060006060848603121561080757600080fd5b610810846107db565b925061081e602085016107db565b9150604084015163ffffffff8116811461083757600080fd5b809150509250925092565b60006020828403121561085457600080fd5b5051919050565b818103818111156105ba576105ba610779565b60005b83811015610889578181015183820152602001610871565b50506000910152565b84815283602082015260018060a01b038316604082015260806060820152600082518060808401526108cb8160a085016020870161086e565b601f01601f19169190910160a00195945050505050565b6000602082840312156108f457600080fd5b8151801515811461065857600080fd5b6000825161091681846020870161086e565b919091019291505056fea26469706673582212201d6da94f2d6ac0535f5153da5aac14a1f6ef19d15801986cfe2b2d6fab019c6564736f6c63430008140033" 36 | .parse() 37 | .unwrap() 38 | }); 39 | -------------------------------------------------------------------------------- /src/honeypot.rs: -------------------------------------------------------------------------------- 1 | use ethers::types::{Block, BlockId, BlockNumber, H160, H256, U256, U64}; 2 | use ethers_providers::Middleware; 3 | use log::info; 4 | use std::{collections::HashMap, path::Path, str::FromStr, sync::Arc}; 5 | 6 | use crate::pools::Pool; 7 | use crate::simulator::EvmSimulator; 8 | use crate::tokens::{get_implementation, get_token_info, Token}; 9 | use crate::trace::EvmTracer; 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct SafeTokens { 13 | pub weth: H160, 14 | pub usdt: H160, 15 | pub usdc: H160, 16 | pub dai: H160, 17 | } 18 | 19 | impl SafeTokens { 20 | pub fn new() -> Self { 21 | Self { 22 | usdt: H160::from_str("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap(), 23 | weth: H160::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap(), 24 | usdc: H160::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").unwrap(), 25 | dai: H160::from_str("0x6B175474E89094C44Da98b954EedeAC495271d0F").unwrap(), 26 | } 27 | } 28 | } 29 | 30 | pub struct HoneypotFilter { 31 | pub simulator: EvmSimulator, 32 | pub safe_tokens: SafeTokens, 33 | pub token_info: HashMap, 34 | pub safe_token_info: HashMap, 35 | pub balance_slots: HashMap, 36 | pub honeypot: HashMap, 37 | } 38 | 39 | impl HoneypotFilter { 40 | pub fn new(provider: Arc, block: Block) -> Self { 41 | let owner = H160::from_str("0x001a06BF8cE4afdb3f5618f6bafe35e9Fc09F187").unwrap(); 42 | let simulator = EvmSimulator::new(provider.clone(), owner, block.number.unwrap()); 43 | let safe_tokens = SafeTokens::new(); 44 | let token_info = HashMap::new(); 45 | let safe_token_info = HashMap::new(); 46 | let balance_slots = HashMap::new(); 47 | let honeypot = HashMap::new(); 48 | Self { 49 | simulator, 50 | safe_tokens, 51 | token_info, 52 | safe_token_info, 53 | balance_slots, 54 | honeypot, 55 | } 56 | } 57 | 58 | pub async fn setup(&mut self) { 59 | // Get safe_token_info using the four following tokens that are widely used as safe tokens 60 | let provider = &self.simulator.provider; 61 | let owner = self.simulator.owner; 62 | let block_number = &self.simulator.block_number; 63 | 64 | let tracer = EvmTracer::new(provider.clone()); 65 | 66 | let chain_id = provider.get_chainid().await.unwrap(); 67 | let nonce = self 68 | .simulator 69 | .provider 70 | .get_transaction_count( 71 | owner, 72 | Some(BlockId::Number(BlockNumber::Number(*block_number))), 73 | ) 74 | .await 75 | .unwrap(); 76 | 77 | for token in vec![ 78 | self.safe_tokens.usdt, 79 | self.safe_tokens.weth, 80 | self.safe_tokens.usdc, 81 | self.safe_tokens.dai, 82 | ] { 83 | if !self.safe_token_info.contains_key(&token) { 84 | match tracer 85 | .find_balance_slot( 86 | token, 87 | owner, 88 | nonce, 89 | U64::from(chain_id.as_u64()), 90 | block_number.as_u64(), 91 | ) 92 | .await 93 | { 94 | Ok(slot) => { 95 | if slot.0 { 96 | self.balance_slots.insert(token, slot.1); 97 | let mut info = get_token_info(provider.clone(), token).await.unwrap(); 98 | info!("{} ({:?}): {:?}", info.name, token, slot.1); 99 | match get_implementation(provider.clone(), token, *block_number).await { 100 | Ok(implementation) => info.add_implementation(implementation), 101 | Err(_) => {} 102 | } 103 | self.safe_token_info.insert(token, info); 104 | } 105 | } 106 | Err(_) => {} 107 | } 108 | } 109 | } 110 | } 111 | 112 | pub async fn filter_tokens(&mut self, pools: &Vec) { 113 | // load cached 114 | let token_file_path = Path::new("src/.cached-tokens.csv"); 115 | let honeypot_file_path = Path::new("src/.cached-honeypot.csv"); 116 | 117 | if token_file_path.exists() { 118 | let mut reader = csv::Reader::from_path(token_file_path).unwrap(); 119 | for row in reader.records() { 120 | let row = row.unwrap(); 121 | let token = Token::from(row); 122 | self.token_info.insert(token.address, token); 123 | } 124 | } 125 | info!("✔️ Loaded {:?} token info from cache", self.token_info.len()); 126 | 127 | if honeypot_file_path.exists() { 128 | let mut reader = csv::Reader::from_path(honeypot_file_path).unwrap(); 129 | for row in reader.records() { 130 | let row = row.unwrap(); 131 | let honeypot_address = H160::from_str(row.get(0).unwrap()).unwrap(); 132 | self.honeypot.insert(honeypot_address, true); 133 | } 134 | } 135 | info!( 136 | "✔️ Loaded {:?} honeypot info from cache", 137 | self.honeypot.len() 138 | ); 139 | 140 | self.simulator.deploy_simulator(); 141 | 142 | for (idx, pool) in pools.iter().enumerate() { 143 | let token0_is_safe = self.safe_token_info.contains_key(&pool.token0); 144 | let token1_is_safe = self.safe_token_info.contains_key(&pool.token1); 145 | 146 | if token0_is_safe && token1_is_safe { 147 | continue; 148 | } 149 | 150 | // only test for token if it's a match with either of the safe tokens 151 | if token0_is_safe || token1_is_safe { 152 | let (safe_token, test_token) = if token0_is_safe { 153 | (pool.token0, pool.token1) 154 | } else { 155 | (pool.token1, pool.token0) 156 | }; 157 | 158 | if self.token_info.contains_key(&test_token) 159 | || self.honeypot.contains_key(&test_token) 160 | { 161 | // skip if test_tokens was already tested 162 | continue; 163 | } 164 | 165 | // We take extra measures to filter out the pools with too little liquidity 166 | // Using the below amount to test swaps, we know that there's enough liquidity in the pool 167 | let mut amount_in_u32 = 1; 168 | 169 | if safe_token == self.safe_tokens.weth { 170 | amount_in_u32 = 20; 171 | } else if safe_token == self.safe_tokens.usdt { 172 | amount_in_u32 = 10000; 173 | } else if safe_token == self.safe_tokens.usdc { 174 | amount_in_u32 = 10000; 175 | } else if safe_token == self.safe_tokens.dai { 176 | amount_in_u32 = 10000 177 | } 178 | 179 | // seed the simulator with some safe token balance 180 | let safe_token_info = self.safe_token_info.get(&safe_token).unwrap(); 181 | let safe_token_slot = self.balance_slots.get(&safe_token).unwrap(); 182 | 183 | self.simulator.set_token_balance( 184 | self.simulator.simulator_address, 185 | safe_token, 186 | safe_token_info.decimals, 187 | *safe_token_slot, 188 | amount_in_u32, 189 | ); 190 | 191 | info!( 192 | "✅ [{}] {} -> {:?}", 193 | idx, safe_token_info.symbol, test_token 194 | ); 195 | 196 | let amount_in = U256::from(amount_in_u32) 197 | .checked_mul(U256::from(10).pow(U256::from(safe_token_info.decimals))) 198 | .unwrap(); 199 | 200 | // Buy Test 201 | let buy_output = self.simulator.v2_simulate_swap( 202 | amount_in, 203 | pool.address, 204 | safe_token, 205 | test_token, 206 | true, 207 | ); 208 | let out = match buy_output { 209 | Ok(out) => out, 210 | Err(e) => { 211 | info!(" {:?}", e); 212 | self.honeypot.insert(test_token, true); 213 | continue; 214 | } 215 | }; 216 | 217 | if out.0 == out.1 { 218 | // Sell Test 219 | let amount_in = out.1; 220 | let sell_output = self.simulator.v2_simulate_swap( 221 | amount_in, 222 | pool.address, 223 | test_token, 224 | safe_token, 225 | true, 226 | ); 227 | let out = match sell_output { 228 | Ok(out) => out, 229 | Err(e) => { 230 | info!(" {:?}", e); 231 | self.honeypot.insert(test_token, true); 232 | continue; 233 | } 234 | }; 235 | 236 | if out.0 == out.1 { 237 | match get_token_info(self.simulator.provider.clone(), test_token).await { 238 | Ok(info) => { 239 | info!( 240 | "Added safe token info ({}). Total: {:?} tokens", 241 | info.symbol, 242 | self.token_info.len() 243 | ); 244 | self.token_info.insert(test_token, info); 245 | } 246 | Err(_) => {} 247 | } 248 | } else { 249 | self.honeypot.insert(test_token, true); 250 | } 251 | } else { 252 | self.honeypot.insert(test_token, true); 253 | } 254 | } 255 | } 256 | 257 | // cache to csv files 258 | let mut token_writer = csv::Writer::from_path(token_file_path).unwrap(); 259 | for (_, info) in &self.token_info { 260 | token_writer.serialize(info.cache_row()).unwrap(); 261 | } 262 | token_writer.flush().unwrap(); 263 | 264 | let mut honeypot_writer = csv::Writer::from_path(honeypot_file_path).unwrap(); 265 | for (token, _) in &self.honeypot { 266 | honeypot_writer.serialize(token).unwrap(); 267 | } 268 | honeypot_writer.flush().unwrap(); 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/interfaces/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod pool; 2 | pub mod simulator; 3 | pub mod token; 4 | -------------------------------------------------------------------------------- /src/interfaces/pool.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use bytes::Bytes as OutputBytes; 3 | use ethers::abi::parse_abi; 4 | use ethers::prelude::BaseContract; 5 | use ethers::types::Bytes; 6 | 7 | #[derive(Clone)] 8 | pub struct V2PoolABI { 9 | pub abi: BaseContract, 10 | } 11 | 12 | impl V2PoolABI { 13 | pub fn new() -> Self { 14 | let abi = BaseContract::from( 15 | parse_abi(&["function getReserves() external view returns (uint112,uint112,uint32)"]) 16 | .unwrap(), 17 | ); 18 | Self { abi } 19 | } 20 | 21 | pub fn get_reserves_input(&self) -> Result { 22 | let calldata = self.abi.encode("getReserves", ())?; 23 | Ok(calldata) 24 | } 25 | 26 | pub fn get_reserves_output(&self, output: OutputBytes) -> Result<(u128, u128, u32)> { 27 | let out = self.abi.decode_output("getReserves", output)?; 28 | Ok(out) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/interfaces/simulator.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use bytes::Bytes as OutputBytes; 3 | use ethers::abi::parse_abi; 4 | use ethers::prelude::BaseContract; 5 | use ethers::types::{Bytes, H160, U256}; 6 | 7 | #[derive(Clone)] 8 | pub struct SimulatorABI { 9 | pub abi: BaseContract, 10 | } 11 | 12 | impl SimulatorABI { 13 | pub fn new() -> Self { 14 | let abi = BaseContract::from( 15 | parse_abi(&[ 16 | "function v2SimulateSwap(uint256,address,address,address) external returns (uint256, uint256)", 17 | "function getAmountOut(uint256,uint256,uint256) external returns (uint256)", 18 | ]).unwrap() 19 | ); 20 | Self { abi } 21 | } 22 | 23 | pub fn v2_simulate_swap_input( 24 | &self, 25 | amount_in: U256, 26 | target_pool: H160, 27 | input_token: H160, 28 | output_token: H160, 29 | ) -> Result { 30 | let calldata = self.abi.encode( 31 | "v2SimulateSwap", 32 | (amount_in, target_pool, input_token, output_token), 33 | )?; 34 | Ok(calldata) 35 | } 36 | 37 | pub fn v2_simulate_swap_output(&self, output: OutputBytes) -> Result<(U256, U256)> { 38 | let out = self.abi.decode_output("v2SimulateSwap", output)?; 39 | Ok(out) 40 | } 41 | 42 | pub fn get_amount_out_input( 43 | &self, 44 | amount_in: U256, 45 | reserve_in: U256, 46 | reserve_out: U256, 47 | ) -> Result { 48 | let calldata = self 49 | .abi 50 | .encode("getAmountOut", (amount_in, reserve_in, reserve_out))?; 51 | Ok(calldata) 52 | } 53 | 54 | pub fn get_amount_out_output(&self, output: OutputBytes) -> Result { 55 | let out = self.abi.decode_output("getAmountOut", output)?; 56 | Ok(out) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/interfaces/token.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use bytes::Bytes as OutputBytes; 3 | use ethers::abi::parse_abi; 4 | use ethers::prelude::BaseContract; 5 | use ethers::types::{Bytes, H160, U256}; 6 | 7 | #[derive(Clone)] 8 | pub struct TokenABI { 9 | pub abi: BaseContract, 10 | } 11 | 12 | impl TokenABI { 13 | pub fn new() -> Self { 14 | let abi = BaseContract::from( 15 | parse_abi(&[ 16 | "function balanceOf(address) external view returns (uint256)", 17 | "function approve(address spender, uint256 value) external view returns (bool)", 18 | ]) 19 | .unwrap(), 20 | ); 21 | Self { abi } 22 | } 23 | 24 | pub fn balance_of_input(&self, account: H160) -> Result { 25 | let calldata = self.abi.encode("balanceOf", account)?; 26 | Ok(calldata) 27 | } 28 | 29 | pub fn balance_of_output(&self, output: OutputBytes) -> Result { 30 | let out = self.abi.decode_output("balanceOf", output)?; 31 | Ok(out) 32 | } 33 | 34 | pub fn approve_input(&self, spender: H160) -> Result { 35 | let calldata = self.abi.encode("approve", (spender, U256::MAX))?; 36 | Ok(calldata) 37 | } 38 | 39 | pub fn approve_output(&self, output: OutputBytes) -> Result { 40 | let out = self.abi.decode_output("approve", output)?; 41 | Ok(out) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod arbitrage; 2 | pub mod constants; 3 | pub mod honeypot; 4 | pub mod interfaces; 5 | pub mod paths; 6 | pub mod pools; 7 | pub mod sandwich; 8 | pub mod simulator; 9 | pub mod strategy; 10 | pub mod streams; 11 | pub mod tokens; 12 | pub mod trace; 13 | pub mod utils; 14 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use cfmms::dex::DexVariant; 3 | use ethers::providers::{Middleware, Provider, Ws}; 4 | use ethers::types::{BlockNumber, H160, U256}; 5 | use log::info; 6 | use std::{str::FromStr, sync::Arc}; 7 | use tokio::sync::broadcast::{self, Sender}; 8 | use tokio::task::JoinSet; 9 | 10 | use evm_simulation::arbitrage::{simulate_triangular_arbitrage, TriangularArbitrage}; 11 | use evm_simulation::constants::Env; 12 | use evm_simulation::honeypot::HoneypotFilter; 13 | use evm_simulation::paths::generate_triangular_paths; 14 | use evm_simulation::pools::{load_all_pools, Pool}; 15 | use evm_simulation::strategy::event_handler; 16 | use evm_simulation::streams::{stream_new_blocks, stream_pending_transactions, Event}; 17 | use evm_simulation::utils::setup_logger; 18 | 19 | #[tokio::main] 20 | async fn main() -> Result<()> { 21 | dotenv::dotenv().ok(); 22 | setup_logger()?; 23 | 24 | info!("[⚡️🦀⚡️ Starting EVM simulation]"); 25 | 26 | let env = Env::new(); 27 | let ws = Ws::connect(&env.wss_url).await.unwrap(); 28 | let provider = Arc::new(Provider::new(ws)); 29 | 30 | let block = provider 31 | .get_block(BlockNumber::Latest) 32 | .await 33 | .unwrap() 34 | .unwrap(); 35 | 36 | let factories = vec![ 37 | ( 38 | // Uniswap v2 39 | "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", 40 | DexVariant::UniswapV2, 41 | 10000835u64, 42 | ), 43 | ( 44 | // Sushiswap V2 45 | "0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac", 46 | DexVariant::UniswapV2, 47 | 10794229u64, 48 | ), 49 | ]; 50 | let pools = load_all_pools(env.wss_url.clone(), factories).await?; 51 | 52 | let mut honeypot_filter = HoneypotFilter::new(provider.clone(), block.clone()); 53 | honeypot_filter.setup().await; 54 | honeypot_filter 55 | .filter_tokens(&pools[0..5000].to_vec()) 56 | .await; 57 | 58 | let verified_pools: Vec = pools 59 | .into_iter() 60 | .filter(|pool| { 61 | let token0_verified = honeypot_filter.safe_token_info.contains_key(&pool.token0) 62 | || honeypot_filter.token_info.contains_key(&pool.token0); 63 | let token1_verified = honeypot_filter.safe_token_info.contains_key(&pool.token1) 64 | || honeypot_filter.token_info.contains_key(&pool.token1); 65 | token0_verified && token1_verified 66 | }) 67 | .collect(); 68 | info!("Verified pools: {:?} pools", verified_pools.len()); 69 | 70 | let usdt = H160::from_str("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap(); 71 | let arb_paths = generate_triangular_paths(&verified_pools, usdt); 72 | 73 | let owner = H160::from_str("0x001a06BF8cE4afdb3f5618f6bafe35e9Fc09F187").unwrap(); 74 | let amount_in = U256::from(10) 75 | .checked_mul(U256::from(10).pow(U256::from(6))) 76 | .unwrap(); 77 | let balance_slot = honeypot_filter.balance_slots.get(&usdt).unwrap(); 78 | let target_token = honeypot_filter.safe_token_info.get(&usdt).unwrap(); 79 | for path in &arb_paths { 80 | let arb = TriangularArbitrage { 81 | amount_in, 82 | path: path.clone(), 83 | balance_slot: *balance_slot, 84 | target_token: target_token.clone(), 85 | }; 86 | match simulate_triangular_arbitrage( 87 | arb, 88 | provider.clone(), 89 | owner, 90 | block.number.unwrap(), 91 | None, 92 | ) { 93 | Ok(profit) => {} 94 | Err(e) => {} 95 | } 96 | } 97 | 98 | // let (event_sender, _): (Sender, _) = broadcast::channel(512); 99 | 100 | // let mut set = JoinSet::new(); 101 | 102 | // set.spawn(stream_new_blocks(provider.clone(), event_sender.clone())); 103 | // set.spawn(stream_pending_transactions( 104 | // provider.clone(), 105 | // event_sender.clone(), 106 | // )); 107 | // set.spawn(event_handler(provider.clone(), event_sender.clone())); 108 | 109 | // while let Some(res) = set.join_next().await { 110 | // info!("{:?}", res); 111 | // } 112 | 113 | Ok(()) 114 | } 115 | -------------------------------------------------------------------------------- /src/paths.rs: -------------------------------------------------------------------------------- 1 | use ethers::types::H160; 2 | use indicatif::{ProgressBar, ProgressStyle}; 3 | use itertools::Itertools; 4 | use std::time::Instant; 5 | 6 | use crate::pools::Pool; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct ArbPath { 10 | pub nhop: u8, 11 | pub pool_1: Pool, 12 | pub pool_2: Pool, 13 | pub pool_3: Pool, 14 | pub zero_for_one_1: bool, 15 | pub zero_for_one_2: bool, 16 | pub zero_for_one_3: bool, 17 | } 18 | 19 | impl ArbPath { 20 | pub fn get_pool(&self, i: u8) -> &Pool { 21 | match i { 22 | 0 => Some(&self.pool_1), 23 | 1 => Some(&self.pool_2), 24 | 2 => Some(&self.pool_3), 25 | _ => None, 26 | } 27 | .unwrap() 28 | } 29 | 30 | pub fn get_zero_for_one(&self, i: u8) -> bool { 31 | match i { 32 | 0 => Some(self.zero_for_one_1), 33 | 1 => Some(self.zero_for_one_2), 34 | 2 => Some(self.zero_for_one_3), 35 | _ => None, 36 | } 37 | .unwrap() 38 | } 39 | } 40 | 41 | pub fn generate_triangular_paths(pools: &Vec, token_in: H160) -> Vec { 42 | let start_time = Instant::now(); 43 | 44 | let token_out = token_in.clone(); 45 | let mut paths = Vec::new(); 46 | 47 | let pb = ProgressBar::new(pools.len() as u64); 48 | pb.set_style( 49 | ProgressStyle::with_template( 50 | "[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}", 51 | ) 52 | .unwrap() 53 | .progress_chars("##-"), 54 | ); 55 | 56 | for i in 0..pools.len() { 57 | let pool_1 = &pools[i]; 58 | let can_trade_1 = (pool_1.token0 == token_in) || (pool_1.token1 == token_in); 59 | 60 | if can_trade_1 { 61 | let zero_for_one_1 = pool_1.token0 == token_in; 62 | let token_out_1 = if zero_for_one_1 { 63 | pool_1.token1 64 | } else { 65 | pool_1.token0 66 | }; 67 | 68 | for j in 0..pools.len() { 69 | let pool_2 = &pools[j]; 70 | let can_trade_2 = (pool_2.token0 == token_out_1) || (pool_2.token1 == token_out_1); 71 | 72 | if can_trade_2 { 73 | let zero_for_one_2 = pool_2.token0 == token_out_1; 74 | let token_out_2 = if zero_for_one_2 { 75 | pool_2.token1 76 | } else { 77 | pool_2.token0 78 | }; 79 | 80 | for k in 0..pools.len() { 81 | let pool_3 = &pools[k]; 82 | let can_trade_3 = 83 | (pool_3.token0 == token_out_2) || (pool_3.token1 == token_out_2); 84 | 85 | if can_trade_3 { 86 | let zero_for_one_3 = 87 | (pool_3.token0 == token_out_2) || (pool_3.token1 == token_out_2); 88 | let token_out_3 = if zero_for_one_3 { 89 | pool_3.token1 90 | } else { 91 | pool_3.token0 92 | }; 93 | 94 | if token_out_3 == token_out { 95 | let unique_pool_cnt = 96 | vec![pool_1.address, pool_2.address, pool_3.address] 97 | .into_iter() 98 | .unique() 99 | .collect::>() 100 | .len(); 101 | 102 | if unique_pool_cnt < 3 { 103 | continue; 104 | } 105 | 106 | let arb_path = ArbPath { 107 | nhop: 3, 108 | pool_1: pool_1.clone(), 109 | pool_2: pool_2.clone(), 110 | pool_3: pool_3.clone(), 111 | zero_for_one_1: zero_for_one_1, 112 | zero_for_one_2: zero_for_one_2, 113 | zero_for_one_3: zero_for_one_3, 114 | }; 115 | 116 | paths.push(arb_path); 117 | } 118 | } 119 | } 120 | } 121 | } 122 | } 123 | 124 | pb.inc(1); 125 | } 126 | 127 | pb.finish_with_message(format!( 128 | "Generated {} 3-hop arbitrage paths in {} seconds", 129 | paths.len(), 130 | start_time.elapsed().as_secs() 131 | )); 132 | paths 133 | } 134 | -------------------------------------------------------------------------------- /src/pools.rs: -------------------------------------------------------------------------------- 1 | /* 2 | This module is adapted from the mev-templates code: 3 | https://github.com/solidquant/mev-templates 4 | */ 5 | use anyhow::{Ok, Result}; 6 | use cfmms::{ 7 | dex::{Dex, DexVariant as CfmmsDexVariant}, 8 | pool::Pool as CfmmsPool, 9 | sync::sync_pairs, 10 | }; 11 | use csv::StringRecord; 12 | use ethers::{ 13 | providers::{Provider, Ws}, 14 | types::H160, 15 | }; 16 | use log::info; 17 | use std::{collections::HashMap, path::Path, str::FromStr, sync::Arc}; 18 | 19 | #[derive(Debug, Clone)] 20 | pub enum DexVariant { 21 | UniswapV2, 22 | UniswapV3, 23 | } 24 | 25 | #[derive(Debug, Clone)] 26 | pub struct Pool { 27 | pub address: H160, 28 | pub version: DexVariant, 29 | pub token0: H160, 30 | pub token1: H160, 31 | pub decimals0: u8, 32 | pub decimals1: u8, 33 | pub fee: u32, 34 | } 35 | 36 | impl From for Pool { 37 | fn from(record: StringRecord) -> Self { 38 | let version = if record.get(1).unwrap() == "2" { 39 | DexVariant::UniswapV2 40 | } else { 41 | DexVariant::UniswapV3 42 | }; 43 | Self { 44 | address: H160::from_str(record.get(0).unwrap()).unwrap(), 45 | version, 46 | token0: H160::from_str(record.get(2).unwrap()).unwrap(), 47 | token1: H160::from_str(record.get(3).unwrap()).unwrap(), 48 | decimals0: record.get(4).unwrap().parse().unwrap(), 49 | decimals1: record.get(5).unwrap().parse().unwrap(), 50 | fee: record.get(6).unwrap().parse().unwrap(), 51 | } 52 | } 53 | } 54 | 55 | impl Pool { 56 | pub fn cache_row(&self) -> (String, i32, String, String, u8, u8, u32) { 57 | ( 58 | format!("{:?}", self.address), 59 | match self.version { 60 | DexVariant::UniswapV2 => 2, 61 | DexVariant::UniswapV3 => 3, 62 | }, 63 | format!("{:?}", self.token0), 64 | format!("{:?}", self.token1), 65 | self.decimals0, 66 | self.decimals1, 67 | self.fee, 68 | ) 69 | } 70 | 71 | pub fn has_token(&self, token: H160) -> bool { 72 | self.token0 == token || self.token1 == token 73 | } 74 | } 75 | 76 | pub async fn load_all_pools( 77 | wss_url: String, 78 | factories: Vec<(&str, CfmmsDexVariant, u64)>, 79 | ) -> Result> { 80 | // Load from cached file if the file exists 81 | let file_path = Path::new("src/.cached-pools.csv"); 82 | if file_path.exists() { 83 | let mut reader = csv::Reader::from_path(file_path)?; 84 | 85 | let mut pools_vec: Vec = Vec::new(); 86 | for row in reader.records() { 87 | let row = row.unwrap(); 88 | let pool = Pool::from(row); 89 | pools_vec.push(pool); 90 | } 91 | return Ok(pools_vec); 92 | } 93 | 94 | let ws = Ws::connect(wss_url).await?; 95 | let provider = Arc::new(Provider::new(ws)); 96 | 97 | let dexes: Vec<_> = factories 98 | .into_iter() 99 | .map(|(address, variant, number)| { 100 | Dex::new( 101 | H160::from_str(&address).unwrap(), 102 | variant, 103 | number, 104 | Some(3000), 105 | ) 106 | }) 107 | .collect(); 108 | 109 | let pools_vec: Vec = sync_pairs(dexes.clone(), provider.clone(), None).await?; 110 | let pools_vec: Vec = pools_vec 111 | .into_iter() 112 | .map(|pool| match pool { 113 | CfmmsPool::UniswapV2(pool) => Pool { 114 | address: pool.address, 115 | version: DexVariant::UniswapV2, 116 | token0: pool.token_a, 117 | token1: pool.token_b, 118 | decimals0: pool.token_a_decimals, 119 | decimals1: pool.token_b_decimals, 120 | fee: pool.fee, 121 | }, 122 | CfmmsPool::UniswapV3(pool) => Pool { 123 | address: pool.address, 124 | version: DexVariant::UniswapV3, 125 | token0: pool.token_a, 126 | token1: pool.token_b, 127 | decimals0: pool.token_a_decimals, 128 | decimals1: pool.token_b_decimals, 129 | fee: pool.fee, 130 | }, 131 | }) 132 | .collect(); 133 | info!("Synced to {} pools", pools_vec.len()); 134 | 135 | let mut writer = csv::Writer::from_path(file_path)?; 136 | writer.write_record(&[ 137 | "address", 138 | "version", 139 | "token0", 140 | "token1", 141 | "decimals0", 142 | "decimals1", 143 | "fee", 144 | ])?; 145 | 146 | for pool in &pools_vec { 147 | writer.serialize(pool.cache_row())?; 148 | } 149 | writer.flush()?; 150 | 151 | Ok(pools_vec) 152 | } 153 | 154 | pub fn get_tokens(pools: &Vec) -> HashMap { 155 | let mut tokens = HashMap::new(); 156 | for pool in pools { 157 | tokens.insert(pool.token0, pool.decimals0); 158 | tokens.insert(pool.token1, pool.decimals1); 159 | } 160 | tokens 161 | } 162 | -------------------------------------------------------------------------------- /src/sandwich.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use ethers::types::{Transaction, H160, U256, U64}; 3 | use ethers_providers::Middleware; 4 | use foundry_evm::{executor::fork::SharedBackend, revm::db::CacheDB}; 5 | use log::info; 6 | use std::{collections::HashMap, sync::Arc}; 7 | 8 | use crate::honeypot::HoneypotFilter; 9 | use crate::pools::Pool; 10 | use crate::simulator::EvmSimulator; 11 | use crate::tokens::Token; 12 | 13 | #[derive(Debug, Clone)] 14 | pub struct Sandwich { 15 | pub amount_in: U256, 16 | pub balance_slot: u32, 17 | pub target_token: Token, 18 | pub target_pool: Pool, 19 | pub meat_tx: Transaction, 20 | } 21 | 22 | pub struct SandwichSimulator { 23 | pub simulator: EvmSimulator, 24 | } 25 | 26 | impl SandwichSimulator { 27 | pub fn new(provider: Arc, owner: H160, block_number: U64) -> Self { 28 | let simulator = EvmSimulator::new(provider, owner, block_number); 29 | Self { simulator } 30 | } 31 | 32 | pub fn db_snapshot(&mut self) -> CacheDB { 33 | self.simulator.evm.db.as_mut().unwrap().clone() 34 | } 35 | 36 | pub async fn simulate( 37 | &mut self, 38 | tx: &Transaction, 39 | sandwichable_pools: &HashMap>, 40 | verified_pools_map: &HashMap, 41 | honeypot_filter: &HoneypotFilter, 42 | ) -> Result<()> { 43 | // Setup DB and retrieve storage values required to run simulation 44 | self.simulator.set_eth_balance(10000); 45 | self.simulator.deploy_simulator(); 46 | 47 | let mut sandwiches = Vec::new(); 48 | 49 | for (touched_pool, used_token) in sandwichable_pools { 50 | // if used_token is not None, we can sandwich this tx 51 | match used_token { 52 | Some(safe_token) => { 53 | // seed simulator contract with some used_token balance 54 | let simulator_address = self.simulator.simulator_address; 55 | let token_info = honeypot_filter.safe_token_info.get(safe_token).unwrap(); 56 | let balance_slot = honeypot_filter.balance_slots.get(safe_token).unwrap(); 57 | self.simulator.set_token_balance( 58 | simulator_address, 59 | *safe_token, 60 | token_info.decimals, 61 | *balance_slot, 62 | 10000, 63 | ); 64 | 65 | // load storage values before cloning db 66 | // storage values required to simulate swap: token0/token1 balance & pool reserves 67 | let pool = verified_pools_map.get(touched_pool).unwrap(); 68 | _ = self 69 | .simulator 70 | .token_balance_of(pool.token0, simulator_address); 71 | _ = self 72 | .simulator 73 | .token_balance_of(pool.token1, simulator_address); 74 | _ = self.simulator.v2_pool_get_reserves(*touched_pool); 75 | 76 | let sandwich = Sandwich { 77 | amount_in: U256::zero(), 78 | balance_slot: *balance_slot, 79 | target_token: token_info.clone(), 80 | target_pool: pool.clone(), 81 | meat_tx: tx.clone(), 82 | }; 83 | sandwiches.push(sandwich); 84 | } 85 | None => {} 86 | } 87 | } 88 | 89 | // Clone the DB and inject it into simulator to run multiple bundles in parallel 90 | let fork_db = self.db_snapshot(); 91 | 92 | // Try running simulations one by one at first 93 | for mut sandwich in sandwiches { 94 | let amount_in = 95 | U256::from(1) * U256::from(10).pow(U256::from(sandwich.target_token.decimals)); 96 | sandwich.amount_in = amount_in; 97 | match simulate_sandwich_bundle( 98 | sandwich.clone(), 99 | self.simulator.provider.clone(), 100 | self.simulator.owner, 101 | self.simulator.block_number, 102 | Some(fork_db.clone()), 103 | ) { 104 | Ok(_) => {} 105 | Err(e) => info!("[SIMULATION ERROR] {:?} {:?}", sandwich, e), 106 | }; 107 | } 108 | 109 | Ok(()) 110 | } 111 | } 112 | 113 | pub fn simulate_sandwich_bundle( 114 | sandwich: Sandwich, 115 | provider: Arc, 116 | owner: H160, 117 | block_number: U64, 118 | fork_db: Option>, 119 | ) -> Result { 120 | // Create a simulator instance and inject the forked db 121 | let amount_in = sandwich.amount_in; 122 | let target_token = sandwich.target_token; 123 | let target_pool = sandwich.target_pool; 124 | 125 | info!("\n[🔮 Sandwich Bundle Simulation]"); 126 | info!( 127 | "- Pool: {:?} / Token: {:?}", 128 | target_pool.address, target_token.symbol 129 | ); 130 | info!("- Amount in: {:?} {:?}", amount_in, target_token.symbol); 131 | 132 | let (input_token, output_token) = if target_pool.token0 == target_token.address { 133 | (target_pool.token0, target_pool.token1) 134 | } else { 135 | (target_pool.token1, target_pool.token0) 136 | }; 137 | 138 | let mut simulator = EvmSimulator::new(provider, owner, block_number); 139 | let simulator_address = simulator.simulator_address; 140 | match fork_db { 141 | Some(db) => simulator.inject_db(db), 142 | None => { 143 | simulator.set_eth_balance(10000); 144 | simulator.deploy_simulator(); 145 | simulator.set_token_balance( 146 | simulator_address, 147 | target_token.address, 148 | target_token.decimals, 149 | sandwich.balance_slot, 150 | 10000, 151 | ); 152 | } 153 | } 154 | 155 | // Frontrun tx 156 | let frontrun_out = simulator.v2_simulate_swap( 157 | amount_in, 158 | target_pool.address, 159 | input_token, 160 | output_token, 161 | true, 162 | )?; 163 | info!("✅ Frontrun out: {:?}", frontrun_out.1); 164 | 165 | // Meat tx 166 | match simulator.run_pending_tx(&sandwich.meat_tx) { 167 | Ok(_) => info!("✅ Meat TX Successful"), 168 | Err(e) => info!("✖️ Meat TX Failed: {:?}", e), 169 | } 170 | 171 | // Backrun tx 172 | let backrun_out = simulator.v2_simulate_swap( 173 | frontrun_out.1, 174 | target_pool.address, 175 | output_token, 176 | input_token, 177 | true, 178 | )?; 179 | info!("✅ Backrun out: {:?}", backrun_out.1); 180 | 181 | let amount_out = backrun_out.1; 182 | let profit = (amount_out.as_u64() as i128) - (amount_in.as_u64() as i128); 183 | info!("▶️ Profit: {:?} {:?}", profit, target_token.symbol); 184 | 185 | Ok(profit) 186 | } 187 | -------------------------------------------------------------------------------- /src/simulator.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use bytes::Bytes; 3 | use ethers::abi; 4 | use ethers::types::{Transaction, H160, U256, U64}; 5 | use ethers_providers::Middleware; 6 | use foundry_evm::{ 7 | executor::{ 8 | fork::{BlockchainDb, BlockchainDbMeta, SharedBackend}, 9 | Bytecode, ExecutionResult, Output, TransactTo, 10 | }, 11 | revm::{ 12 | db::{CacheDB, Database}, 13 | primitives::{keccak256, AccountInfo, U256 as rU256}, 14 | EVM, 15 | }, 16 | }; 17 | use std::{collections::BTreeSet, str::FromStr, sync::Arc}; 18 | 19 | use crate::constants::SIMULATOR_CODE; 20 | use crate::interfaces::{pool::V2PoolABI, simulator::SimulatorABI, token::TokenABI}; 21 | 22 | #[derive(Clone)] 23 | pub struct EvmSimulator { 24 | pub provider: Arc, 25 | pub owner: H160, 26 | pub evm: EVM>, 27 | pub block_number: U64, 28 | 29 | pub token: TokenABI, 30 | pub v2_pool: V2PoolABI, 31 | pub simulator: SimulatorABI, 32 | 33 | pub simulator_address: H160, 34 | } 35 | 36 | #[derive(Debug, Clone)] 37 | pub struct Tx { 38 | pub caller: H160, 39 | pub transact_to: H160, 40 | pub data: Bytes, 41 | pub value: U256, 42 | pub gas_limit: u64, 43 | } 44 | 45 | #[derive(Debug, Clone)] 46 | pub struct TxResult { 47 | pub output: Bytes, 48 | pub gas_used: u64, 49 | pub gas_refunded: u64, 50 | } 51 | 52 | impl EvmSimulator { 53 | pub fn new(provider: Arc, owner: H160, block_number: U64) -> Self { 54 | let shared_backend = SharedBackend::spawn_backend_thread( 55 | provider.clone(), 56 | BlockchainDb::new( 57 | BlockchainDbMeta { 58 | cfg_env: Default::default(), 59 | block_env: Default::default(), 60 | hosts: BTreeSet::from(["".to_string()]), 61 | }, 62 | None, 63 | ), 64 | Some(block_number.into()), 65 | ); 66 | let db = CacheDB::new(shared_backend); 67 | 68 | let mut evm = EVM::new(); 69 | evm.database(db); 70 | 71 | evm.env.cfg.limit_contract_code_size = Some(0x100000); 72 | evm.env.cfg.disable_block_gas_limit = true; 73 | evm.env.cfg.disable_base_fee = true; 74 | 75 | evm.env.block.number = rU256::from(block_number.as_u64() + 1); 76 | 77 | Self { 78 | provider, 79 | owner, 80 | evm, 81 | block_number, 82 | 83 | token: TokenABI::new(), 84 | v2_pool: V2PoolABI::new(), 85 | simulator: SimulatorABI::new(), 86 | 87 | simulator_address: H160::from_str("0x4E17607Fb72C01C280d7b5c41Ba9A2109D74a32C") 88 | .unwrap(), 89 | } 90 | } 91 | 92 | pub fn inject_db(&mut self, db: CacheDB) { 93 | self.evm.database(db); 94 | } 95 | 96 | pub fn run_pending_tx(&mut self, tx: &Transaction) -> Result { 97 | // We simply need to commit changes to the DB 98 | self.evm.env.tx.caller = tx.from.0.into(); 99 | self.evm.env.tx.transact_to = TransactTo::Call(tx.to.unwrap_or_default().0.into()); 100 | self.evm.env.tx.data = tx.input.0.clone(); 101 | self.evm.env.tx.value = tx.value.into(); 102 | self.evm.env.tx.chain_id = tx.chain_id.map(|id| id.as_u64()); 103 | self.evm.env.tx.gas_limit = tx.gas.as_u64(); 104 | 105 | match tx.transaction_type { 106 | Some(U64([0])) => self.evm.env.tx.gas_price = tx.gas_price.unwrap_or_default().into(), 107 | Some(_) => { 108 | self.evm.env.tx.gas_priority_fee = 109 | tx.max_priority_fee_per_gas.map(|mpf| mpf.into()); 110 | self.evm.env.tx.gas_price = tx.max_fee_per_gas.unwrap_or_default().into(); 111 | } 112 | None => self.evm.env.tx.gas_price = tx.gas_price.unwrap_or_default().into(), 113 | } 114 | 115 | let result = match self.evm.transact_commit() { 116 | Ok(result) => result, 117 | Err(e) => return Err(anyhow!("EVM call failed: {:?}", e)), 118 | }; 119 | 120 | let output = match result { 121 | ExecutionResult::Success { 122 | gas_used, 123 | gas_refunded, 124 | output, 125 | .. 126 | } => match output { 127 | Output::Call(o) => TxResult { 128 | output: o, 129 | gas_used, 130 | gas_refunded, 131 | }, 132 | Output::Create(o, _) => TxResult { 133 | output: o, 134 | gas_used, 135 | gas_refunded, 136 | }, 137 | }, 138 | ExecutionResult::Revert { gas_used, output } => { 139 | return Err(anyhow!( 140 | "EVM REVERT: {:?} / Gas used: {:?}", 141 | output, 142 | gas_used 143 | )) 144 | } 145 | ExecutionResult::Halt { reason, .. } => return Err(anyhow!("EVM HALT: {:?}", reason)), 146 | }; 147 | 148 | Ok(output) 149 | } 150 | 151 | pub fn _call(&mut self, tx: Tx, commit: bool) -> Result { 152 | self.evm.env.tx.caller = tx.caller.into(); 153 | self.evm.env.tx.transact_to = TransactTo::Call(tx.transact_to.into()); 154 | self.evm.env.tx.data = tx.data; 155 | self.evm.env.tx.value = tx.value.into(); 156 | self.evm.env.tx.gas_limit = 5000000; 157 | 158 | let result; 159 | 160 | if commit { 161 | result = match self.evm.transact_commit() { 162 | Ok(result) => result, 163 | Err(e) => return Err(anyhow!("EVM call failed: {:?}", e)), 164 | }; 165 | } else { 166 | let ref_tx = self 167 | .evm 168 | .transact_ref() 169 | .map_err(|e| anyhow!("EVM staticcall failed: {:?}", e))?; 170 | result = ref_tx.result; 171 | } 172 | 173 | let output = match result { 174 | ExecutionResult::Success { 175 | gas_used, 176 | gas_refunded, 177 | output, 178 | .. 179 | } => match output { 180 | Output::Call(o) => TxResult { 181 | output: o, 182 | gas_used, 183 | gas_refunded, 184 | }, 185 | Output::Create(o, _) => TxResult { 186 | output: o, 187 | gas_used, 188 | gas_refunded, 189 | }, 190 | }, 191 | ExecutionResult::Revert { gas_used, output } => { 192 | return Err(anyhow!( 193 | "EVM REVERT: {:?} / Gas used: {:?}", 194 | output, 195 | gas_used 196 | )) 197 | } 198 | ExecutionResult::Halt { reason, .. } => return Err(anyhow!("EVM HALT: {:?}", reason)), 199 | }; 200 | 201 | Ok(output) 202 | } 203 | 204 | pub fn staticcall(&mut self, tx: Tx) -> Result { 205 | self._call(tx, false) 206 | } 207 | 208 | pub fn call(&mut self, tx: Tx) -> Result { 209 | self._call(tx, true) 210 | } 211 | 212 | pub fn get_eth_balance(&mut self) -> U256 { 213 | let acc = self 214 | .evm 215 | .db 216 | .as_mut() 217 | .unwrap() 218 | .basic(self.owner.into()) 219 | .unwrap() 220 | .unwrap(); 221 | acc.balance.into() 222 | } 223 | 224 | pub fn set_eth_balance(&mut self, balance: u32) { 225 | let user_balance = rU256::from(balance) 226 | .checked_mul(rU256::from(10).pow(rU256::from(18))) 227 | .unwrap(); 228 | let user_info = AccountInfo::new(user_balance, 0, Bytecode::default()); 229 | self.evm 230 | .db 231 | .as_mut() 232 | .unwrap() 233 | .insert_account_info(self.owner.into(), user_info); 234 | } 235 | 236 | // ERC-20 Token functions 237 | pub fn set_token_balance( 238 | &mut self, 239 | account: H160, 240 | token: H160, 241 | decimals: u8, 242 | slot: u32, 243 | balance: u32, 244 | ) { 245 | let slot = keccak256(&abi::encode(&[ 246 | abi::Token::Address(account.into()), 247 | abi::Token::Uint(U256::from(slot)), 248 | ])); 249 | let target_balance = rU256::from(balance) 250 | .checked_mul(rU256::from(10).pow(rU256::from(decimals))) 251 | .unwrap(); 252 | self.evm 253 | .db 254 | .as_mut() 255 | .unwrap() 256 | .insert_account_storage(token.into(), slot.into(), target_balance) 257 | .unwrap(); 258 | } 259 | 260 | pub fn token_balance_of(&mut self, token: H160, account: H160) -> Result { 261 | let calldata = self.token.balance_of_input(account)?; 262 | let value = self.staticcall(Tx { 263 | caller: self.owner.into(), 264 | transact_to: token, 265 | data: calldata.0, 266 | value: U256::zero(), 267 | gas_limit: 0, 268 | })?; 269 | let out = self.token.balance_of_output(value.output)?; 270 | Ok(out) 271 | } 272 | 273 | // V2 Pool functions 274 | pub fn set_v2_pool_reserves(&mut self, pool: H160, reserves: rU256) { 275 | let slot = rU256::from(8); 276 | self.evm 277 | .db 278 | .as_mut() 279 | .unwrap() 280 | .insert_account_storage(pool.into(), slot.into(), reserves) 281 | .unwrap(); 282 | } 283 | 284 | pub fn v2_pool_get_reserves(&mut self, pool: H160) -> Result<(u128, u128, u32)> { 285 | let calldata = self.v2_pool.get_reserves_input()?; 286 | let value = self.staticcall(Tx { 287 | caller: self.owner, 288 | transact_to: pool, 289 | data: calldata.0, 290 | value: U256::zero(), 291 | gas_limit: 0, 292 | })?; 293 | let out = self.v2_pool.get_reserves_output(value.output)?; 294 | Ok(out) 295 | } 296 | 297 | // Simulator functions 298 | pub fn deploy_simulator(&mut self) { 299 | let contract_info = AccountInfo::new( 300 | rU256::ZERO, 301 | 0, 302 | Bytecode::new_raw((*SIMULATOR_CODE.0).into()), 303 | ); 304 | self.evm 305 | .db 306 | .as_mut() 307 | .unwrap() 308 | .insert_account_info(self.simulator_address.into(), contract_info); 309 | } 310 | 311 | pub fn v2_simulate_swap( 312 | &mut self, 313 | amount_in: U256, 314 | target_pool: H160, 315 | input_token: H160, 316 | output_token: H160, 317 | commit: bool, 318 | ) -> Result<(U256, U256)> { 319 | let calldata = self.simulator.v2_simulate_swap_input( 320 | amount_in, 321 | target_pool, 322 | input_token, 323 | output_token, 324 | )?; 325 | let tx = Tx { 326 | caller: self.owner, 327 | transact_to: self.simulator_address, 328 | data: calldata.0, 329 | value: U256::zero(), 330 | gas_limit: 5000000, 331 | }; 332 | let value = if commit { 333 | self.call(tx)? 334 | } else { 335 | self.staticcall(tx)? 336 | }; 337 | let out = self.simulator.v2_simulate_swap_output(value.output)?; 338 | Ok(out) 339 | } 340 | 341 | pub fn get_amount_out( 342 | &mut self, 343 | amount_in: U256, 344 | reserve_in: U256, 345 | reserve_out: U256, 346 | ) -> Result { 347 | let calldata = self 348 | .simulator 349 | .get_amount_out_input(amount_in, reserve_in, reserve_out)?; 350 | let value = self.staticcall(Tx { 351 | caller: self.owner, 352 | transact_to: self.simulator_address, 353 | data: calldata.0, 354 | value: U256::zero(), 355 | gas_limit: 5000000, 356 | })?; 357 | let out = self.simulator.get_amount_out_output(value.output)?; 358 | Ok(out) 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /src/strategy.rs: -------------------------------------------------------------------------------- 1 | use anvil::eth::fees::calculate_next_block_base_fee; 2 | use anyhow::Result; 3 | use cfmms::dex::DexVariant; 4 | use colored::Colorize; 5 | use ethers::{ 6 | prelude::*, 7 | providers::{Middleware, Provider, Ws}, 8 | types::{BlockId, BlockNumber, H160, U256, U64}, 9 | }; 10 | use foundry_evm::revm::primitives::keccak256; 11 | use log::info; 12 | use std::{collections::HashMap, str::FromStr, sync::Arc}; 13 | use tokio::sync::broadcast::Sender; 14 | 15 | use crate::constants::Env; 16 | use crate::honeypot::HoneypotFilter; 17 | use crate::pools::{load_all_pools, Pool}; 18 | use crate::sandwich::{simulate_sandwich_bundle, Sandwich, SandwichSimulator}; 19 | use crate::streams::{Event, NewBlock}; 20 | 21 | #[macro_export] 22 | macro_rules! log_info_warning { 23 | ($($arg:tt)*) => { 24 | info!("{}", format_args!($($arg)*).to_string().magenta()); 25 | }; 26 | } 27 | 28 | pub async fn get_touched_pools( 29 | provider: Arc>, 30 | tx: &Transaction, 31 | block_number: U64, 32 | verified_pools_map: &HashMap, 33 | honeypot_filter: &HoneypotFilter, 34 | ) -> Result>> { 35 | // you don't know what transaction will touch the pools you're interested in 36 | // thus, you need to trace all pending transactions you receive 37 | // evm tracing can sometimes take a very long time as can be seen from: 38 | // https://banteg.mirror.xyz/3dbuIlaHh30IPITWzfT1MFfSg6fxSssMqJ7TcjaWecM 39 | 40 | // Also check: https://github.com/ethereum/go-ethereum/pull/25422#discussion_r978789901 for diffMode 41 | let trace = provider 42 | .debug_trace_call( 43 | tx, 44 | Some(BlockId::Number(BlockNumber::Number(block_number))), 45 | GethDebugTracingCallOptions { 46 | tracing_options: GethDebugTracingOptions { 47 | disable_storage: None, 48 | disable_stack: None, 49 | enable_memory: None, 50 | enable_return_data: None, 51 | tracer: Some(GethDebugTracerType::BuiltInTracer( 52 | GethDebugBuiltInTracerType::PreStateTracer, 53 | )), 54 | tracer_config: Some(GethDebugTracerConfig::BuiltInTracer( 55 | GethDebugBuiltInTracerConfig::PreStateTracer(PreStateConfig { 56 | diff_mode: Some(true), 57 | }), 58 | )), 59 | timeout: None, 60 | }, 61 | state_overrides: None, 62 | }, 63 | ) 64 | .await?; 65 | 66 | let mut sandwichable_pools = HashMap::new(); 67 | 68 | match trace { 69 | GethTrace::Known(known) => match known { 70 | GethTraceFrame::PreStateTracer(prestate) => match prestate { 71 | PreStateFrame::Diff(diff) => { 72 | // Step 1: Check if any of the pools I'm monitoring were touched 73 | let mut touched_pools = Vec::new(); 74 | for (acc, _) in &diff.post { 75 | if verified_pools_map.contains_key(&acc) { 76 | touched_pools.push(*acc); 77 | sandwichable_pools.insert(*acc, None); 78 | } 79 | } 80 | 81 | if touched_pools.is_empty() { 82 | return Ok(sandwichable_pools); 83 | } 84 | 85 | let safe_token_info = &honeypot_filter.safe_token_info; 86 | let balance_slots = &honeypot_filter.balance_slots; 87 | 88 | // Step 2: Check if the transaction increases the pool's safe token balance (weth/usdt/usdc/dai) 89 | // This means that the safe token price will go down, and the other token price will go up 90 | // Thus, we buy the token in our frontrunning tx, and sell the token in our backrunning tx 91 | for (_, safe_token) in safe_token_info { 92 | let token_prestate = diff.pre.get(&safe_token.address); 93 | match token_prestate { 94 | Some(prestate) => match &prestate.storage { 95 | Some(pre_storage) => { 96 | let slot = *balance_slots.get(&safe_token.address).unwrap(); 97 | for pool in &touched_pools { 98 | let balance_slot = keccak256(&abi::encode(&[ 99 | abi::Token::Address((*pool).into()), 100 | abi::Token::Uint(U256::from(slot)), 101 | ])); 102 | if pre_storage.contains_key(&balance_slot.into()) { 103 | let pre_balance = U256::from( 104 | pre_storage 105 | .get(&balance_slot.into()) 106 | .unwrap() 107 | .to_fixed_bytes(), 108 | ); 109 | 110 | let token_poststate = 111 | diff.post.get(&safe_token.address).unwrap(); 112 | let post_storage = &token_poststate.storage; 113 | let post_balance = U256::from( 114 | post_storage 115 | .as_ref() 116 | .unwrap() 117 | .get(&balance_slot.into()) 118 | .unwrap() 119 | .to_fixed_bytes(), 120 | ); 121 | 122 | if pre_balance < post_balance { 123 | sandwichable_pools 124 | .insert(*pool, Some(safe_token.address)); 125 | } 126 | } 127 | } 128 | } 129 | None => {} 130 | }, 131 | None => {} 132 | } 133 | } 134 | } 135 | _ => {} 136 | }, 137 | _ => {} 138 | }, 139 | _ => {} 140 | } 141 | 142 | Ok(sandwichable_pools) 143 | } 144 | 145 | pub async fn event_handler(provider: Arc>, event_sender: Sender) { 146 | let env = Env::new(); 147 | let factories = vec![( 148 | // Sushiswap V2 149 | "0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac", 150 | DexVariant::UniswapV2, 151 | 10794229u64, 152 | )]; 153 | let pools = load_all_pools(env.wss_url.clone(), factories) 154 | .await 155 | .unwrap(); 156 | 157 | let block = provider 158 | .get_block(BlockNumber::Latest) 159 | .await 160 | .unwrap() 161 | .unwrap(); 162 | 163 | let mut honeypot_filter = HoneypotFilter::new(provider.clone(), block.clone()); 164 | honeypot_filter.setup().await; 165 | honeypot_filter 166 | .filter_tokens(&pools[0..3000].to_vec()) 167 | .await; 168 | 169 | // filter out pools that use unverified tokens 170 | let verified_pools: Vec = pools 171 | .into_iter() 172 | .filter(|pool| { 173 | let token0_verified = honeypot_filter.safe_token_info.contains_key(&pool.token0) 174 | || honeypot_filter.token_info.contains_key(&pool.token0); 175 | let token1_verified = honeypot_filter.safe_token_info.contains_key(&pool.token1) 176 | || honeypot_filter.token_info.contains_key(&pool.token1); 177 | token0_verified && token1_verified 178 | }) 179 | .collect(); 180 | info!("Verified pools only: {:?} pools", verified_pools.len()); 181 | 182 | let mut verified_pools_map = HashMap::new(); 183 | for pool in &verified_pools { 184 | verified_pools_map.insert(pool.address, pool.clone()); 185 | } 186 | 187 | let mut event_receiver = event_sender.subscribe(); 188 | 189 | let mut new_block = NewBlock { 190 | block_number: block.number.unwrap(), 191 | base_fee: block.base_fee_per_gas.unwrap_or_default(), 192 | next_base_fee: U256::from(calculate_next_block_base_fee( 193 | block.gas_used.as_u64(), 194 | block.gas_limit.as_u64(), 195 | block.base_fee_per_gas.unwrap_or_default().as_u64(), 196 | )), 197 | }; 198 | 199 | loop { 200 | match event_receiver.recv().await { 201 | Ok(event) => match event { 202 | Event::Block(block) => { 203 | new_block = block; 204 | info!("⛓ New Block: {:?}", block); 205 | } 206 | Event::PendingTx(tx) => { 207 | let base_fee_condition = 208 | tx.max_fee_per_gas.unwrap_or_default() < new_block.base_fee; 209 | 210 | if base_fee_condition { 211 | continue; 212 | } 213 | 214 | match get_touched_pools( 215 | provider.clone(), 216 | &tx, 217 | new_block.block_number, 218 | &verified_pools_map, 219 | &honeypot_filter, 220 | ) 221 | .await 222 | { 223 | Ok(touched_pools) => { 224 | if touched_pools.len() > 0 { 225 | info!( 226 | "[🌯🥪🌯🥪🌯] Sandwichable pools detected: {:?}", 227 | touched_pools 228 | ); 229 | 230 | let owner = 231 | H160::from_str("0x001a06BF8cE4afdb3f5618f6bafe35e9Fc09F187") 232 | .unwrap(); 233 | 234 | for (touched_pool, use_token) in &touched_pools { 235 | match use_token { 236 | Some(safe_token) => { 237 | let target_token = honeypot_filter 238 | .safe_token_info 239 | .get(safe_token) 240 | .unwrap(); 241 | let target_pool = 242 | verified_pools_map.get(touched_pool).unwrap(); 243 | let balance_slot = honeypot_filter 244 | .balance_slots 245 | .get(safe_token) 246 | .unwrap(); 247 | let amount_in = U256::from(1) 248 | .checked_mul( 249 | U256::from(10) 250 | .pow(U256::from(target_token.decimals)), 251 | ) 252 | .unwrap(); 253 | 254 | let sandwich = Sandwich { 255 | amount_in, 256 | balance_slot: *balance_slot, 257 | target_token: target_token.clone(), 258 | target_pool: target_pool.clone(), 259 | meat_tx: tx.clone(), 260 | }; 261 | 262 | match simulate_sandwich_bundle( 263 | sandwich, 264 | provider.clone(), 265 | owner, 266 | new_block.block_number, 267 | None, 268 | ) { 269 | Ok(profit) => info!( 270 | "Simulation was successful. Profit: {:?}", 271 | profit 272 | ), 273 | Err(e) => { 274 | info!("Simulation failed. Error: {:?}", e) 275 | } 276 | } 277 | } 278 | None => {} 279 | } 280 | } 281 | } 282 | } 283 | Err(_) => {} 284 | } 285 | } 286 | Event::Log(_) => {} 287 | }, 288 | Err(_) => {} 289 | } 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/streams.rs: -------------------------------------------------------------------------------- 1 | use anvil::eth::fees::calculate_next_block_base_fee; 2 | use ethers::{ 3 | providers::{Provider, Ws}, 4 | types::{Log, Transaction, U256, U64}, 5 | }; 6 | use ethers_providers::Middleware; 7 | use std::sync::Arc; 8 | use tokio::sync::broadcast::Sender; 9 | use tokio_stream::StreamExt; 10 | 11 | #[derive(Default, Debug, Clone, Copy)] 12 | pub struct NewBlock { 13 | pub block_number: U64, 14 | pub base_fee: U256, 15 | pub next_base_fee: U256, 16 | } 17 | 18 | #[derive(Debug, Clone)] 19 | pub enum Event { 20 | Block(NewBlock), 21 | PendingTx(Transaction), 22 | Log(Log), 23 | } 24 | 25 | pub async fn stream_new_blocks(provider: Arc>, event_sender: Sender) { 26 | let stream = provider.subscribe_blocks().await.unwrap(); 27 | let mut stream = stream.filter_map(|block| match block.number { 28 | Some(number) => Some(NewBlock { 29 | block_number: number, 30 | base_fee: block.base_fee_per_gas.unwrap_or_default(), 31 | next_base_fee: U256::from(calculate_next_block_base_fee( 32 | block.gas_used.as_u64(), 33 | block.gas_limit.as_u64(), 34 | block.base_fee_per_gas.unwrap_or_default().as_u64(), 35 | )), 36 | }), 37 | None => None, 38 | }); 39 | 40 | while let Some(block) = stream.next().await { 41 | match event_sender.send(Event::Block(block)) { 42 | Ok(_) => {} 43 | Err(_) => {} 44 | } 45 | } 46 | } 47 | 48 | pub async fn stream_pending_transactions(provider: Arc>, event_sender: Sender) { 49 | let stream = provider.subscribe_pending_txs().await.unwrap(); 50 | let mut stream = stream.transactions_unordered(256).fuse(); 51 | 52 | while let Some(result) = stream.next().await { 53 | match result { 54 | Ok(tx) => match event_sender.send(Event::PendingTx(tx)) { 55 | Ok(_) => {} 56 | Err(_) => {} 57 | }, 58 | Err(_) => {} 59 | }; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/tokens.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use csv::StringRecord; 3 | use ethers::{abi::parse_abi, prelude::*}; 4 | use ethers_contract::{Contract, Multicall}; 5 | use ethers_core::types::{BlockId, BlockNumber, TxHash, H160, U256}; 6 | use std::{str::FromStr, sync::Arc}; 7 | use tokio::task::JoinSet; 8 | 9 | use crate::constants::ZERO_ADDRESS; 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct Token { 13 | pub address: H160, 14 | pub implementation: Option, 15 | pub name: String, 16 | pub symbol: String, 17 | pub decimals: u8, 18 | } 19 | 20 | impl From for Token { 21 | fn from(record: StringRecord) -> Self { 22 | Self { 23 | address: H160::from_str(record.get(0).unwrap()).unwrap(), 24 | implementation: match record.get(1) { 25 | Some(raw_impl_addr) => match raw_impl_addr { 26 | "" => None, 27 | _ => Some(H160::from_str(raw_impl_addr).unwrap()), 28 | }, 29 | None => None, 30 | }, 31 | name: String::from(record.get(2).unwrap()), 32 | symbol: String::from(record.get(3).unwrap()), 33 | decimals: record.get(4).unwrap().parse::().unwrap(), 34 | } 35 | } 36 | } 37 | 38 | impl Token { 39 | pub fn add_implementation(&mut self, implementation: Option) { 40 | self.implementation = implementation; 41 | } 42 | 43 | pub fn cache_row(&self) -> (String, String, String, String, u8) { 44 | ( 45 | format!("{:?}", self.address), 46 | match self.implementation { 47 | Some(implementation) => format!("{:?}", implementation), 48 | None => String::from(""), 49 | }, 50 | self.name.clone(), 51 | self.symbol.clone(), 52 | self.decimals, 53 | ) 54 | } 55 | } 56 | 57 | pub async fn get_implementation( 58 | provider: Arc, 59 | token: H160, 60 | block_number: U64, 61 | ) -> Result> { 62 | // adapted from: https://github.com/gnosis/evm-proxy-detection/blob/main/src/index.ts 63 | let eip_1967_logic_slot = 64 | U256::from("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"); 65 | let eip_1967_beacon_slot = 66 | U256::from("0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50"); 67 | let open_zeppelin_implementation_slot = 68 | U256::from("0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3"); 69 | let eip_1822_logic_slot = 70 | U256::from("0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7"); 71 | 72 | let implementation_slots = vec![ 73 | eip_1967_logic_slot, 74 | eip_1967_beacon_slot, 75 | open_zeppelin_implementation_slot, 76 | eip_1822_logic_slot, 77 | ]; 78 | 79 | let mut set = JoinSet::new(); 80 | 81 | for slot in implementation_slots { 82 | let _provider = provider.clone(); 83 | let fut = tokio::spawn(async move { 84 | _provider 85 | .get_storage_at( 86 | token, 87 | TxHash::from_uint(&slot), 88 | Some(BlockId::Number(BlockNumber::Number(block_number))), 89 | ) 90 | .await 91 | }); 92 | set.spawn(fut); 93 | } 94 | 95 | while let Some(res) = set.join_next().await { 96 | let out = res???; 97 | let implementation = H160::from(out); 98 | if implementation != *ZERO_ADDRESS { 99 | return Ok(Some(implementation)); 100 | } 101 | } 102 | 103 | Ok(None) 104 | } 105 | 106 | pub async fn get_token_info( 107 | provider: Arc, 108 | token: H160, 109 | ) -> Result { 110 | let erc20_contract = BaseContract::from( 111 | parse_abi(&[ 112 | "function name() external view returns (string)", 113 | "function symbol() external view returns (string)", 114 | "function decimals() external view returns (uint8)", 115 | ]) 116 | .unwrap(), 117 | ); 118 | 119 | let mut multicall = Multicall::new(provider.clone(), None).await?; 120 | let contract = Contract::new(token, erc20_contract.abi().clone(), provider.clone()); 121 | 122 | let name_call = contract.method::<_, String>("name", ())?; 123 | let symbol_call = contract.method::<_, String>("symbol", ())?; 124 | let decimals_call = contract.method::<_, u8>("decimals", ())?; 125 | 126 | multicall.add_call(name_call, true); 127 | multicall.add_call(symbol_call, true); 128 | multicall.add_call(decimals_call, true); 129 | 130 | let result: (String, String, u8) = multicall.call().await?; 131 | let token_info = Token { 132 | address: token, 133 | implementation: None, 134 | name: result.0, 135 | symbol: result.1, 136 | decimals: result.2, 137 | }; 138 | 139 | Ok(token_info) 140 | } 141 | -------------------------------------------------------------------------------- /src/trace.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use ethers::{ 3 | abi::{self, parse_abi}, 4 | prelude::*, 5 | types::transaction::eip2930::AccessList, 6 | }; 7 | use ethers_providers::Middleware; 8 | use foundry_evm::revm::primitives::keccak256; 9 | use std::sync::Arc; 10 | 11 | pub struct EvmTracer { 12 | provider: Arc, 13 | } 14 | 15 | impl EvmTracer { 16 | pub fn new(provider: Arc) -> Self { 17 | Self { provider } 18 | } 19 | 20 | pub async fn get_state_diff( 21 | &self, 22 | tx: Eip1559TransactionRequest, 23 | block_number: u64, 24 | ) -> Result { 25 | let trace = self 26 | .provider 27 | .debug_trace_call( 28 | tx, 29 | Some(BlockId::Number(BlockNumber::Number(block_number.into()))), 30 | GethDebugTracingCallOptions { 31 | tracing_options: GethDebugTracingOptions { 32 | disable_storage: None, 33 | disable_stack: None, 34 | enable_memory: None, 35 | enable_return_data: None, 36 | tracer: Some(GethDebugTracerType::BuiltInTracer( 37 | GethDebugBuiltInTracerType::PreStateTracer, 38 | )), 39 | tracer_config: None, 40 | timeout: None, 41 | }, 42 | state_overrides: None, 43 | }, 44 | ) 45 | .await 46 | .unwrap(); 47 | 48 | Ok(trace) 49 | } 50 | 51 | pub async fn find_balance_slot( 52 | &self, 53 | token: H160, 54 | owner: H160, 55 | nonce: U256, 56 | chain_id: U64, 57 | block_number: u64, 58 | ) -> Result<(bool, u32)> { 59 | // A brute force way of finding the storage slot value of an ERC-20 token 60 | // Calling balanceOf and tracing the call using "debug_traceCall" will give us access to the 61 | // storage slot of "balances" 62 | let erc20_contract = BaseContract::from( 63 | parse_abi(&["function balanceOf(address) external view returns (uint256)"]).unwrap(), 64 | ); 65 | let calldata = erc20_contract.encode("balanceOf", owner).unwrap(); 66 | let tx = Eip1559TransactionRequest { 67 | to: Some(NameOrAddress::Address(token)), 68 | from: Some(owner), 69 | data: Some(calldata.0.into()), 70 | value: Some(U256::zero()), 71 | chain_id: Some(chain_id), 72 | max_priority_fee_per_gas: None, 73 | max_fee_per_gas: None, 74 | gas: None, 75 | nonce: Some(nonce), 76 | access_list: AccessList::default(), 77 | }; 78 | let trace = self.get_state_diff(tx, block_number).await.unwrap(); 79 | match trace { 80 | GethTrace::Known(known) => match known { 81 | GethTraceFrame::PreStateTracer(prestate) => match prestate { 82 | PreStateFrame::Default(prestate_mode) => { 83 | let token_info = 84 | prestate_mode.0.get(&token).ok_or(anyhow!("no token key"))?; 85 | let touched_storage = token_info 86 | .storage 87 | .clone() 88 | .ok_or(anyhow!("no storage values"))?; 89 | for i in 0..20 { 90 | let slot = keccak256(&abi::encode(&[ 91 | abi::Token::Address(owner.into()), 92 | abi::Token::Uint(U256::from(i)), 93 | ])); 94 | match touched_storage.get(&slot.into()) { 95 | Some(_) => { 96 | return Ok((true, i)); 97 | } 98 | None => {} 99 | } 100 | } 101 | Ok((false, 0)) 102 | } 103 | _ => Ok((false, 0)), 104 | }, 105 | _ => Ok((false, 0)), 106 | }, 107 | _ => Ok((false, 0)), 108 | } 109 | } 110 | 111 | pub async fn find_v2_reserves_slot( 112 | &self, 113 | pool: H160, 114 | owner: H160, 115 | nonce: U256, 116 | chain_id: U64, 117 | block_number: u64, 118 | ) -> Result<(bool, u32)> { 119 | let v2_pool_contract = BaseContract::from( 120 | parse_abi(&["function getReserves() external view returns (uint112,uint112,uint32)"]) 121 | .unwrap(), 122 | ); 123 | let calldata = v2_pool_contract.encode("getReserves", ()).unwrap(); 124 | let tx = Eip1559TransactionRequest { 125 | to: Some(NameOrAddress::Address(pool)), 126 | from: Some(owner), 127 | data: Some(calldata.0.into()), 128 | value: Some(U256::zero()), 129 | chain_id: Some(chain_id), 130 | max_priority_fee_per_gas: None, 131 | max_fee_per_gas: None, 132 | gas: None, 133 | nonce: Some(nonce), 134 | access_list: AccessList::default(), 135 | }; 136 | let trace = self.get_state_diff(tx, block_number).await.unwrap(); 137 | match trace { 138 | GethTrace::Known(known) => match known { 139 | GethTraceFrame::PreStateTracer(prestate) => match prestate { 140 | PreStateFrame::Default(prestate_mode) => { 141 | let token_info = 142 | prestate_mode.0.get(&pool).ok_or(anyhow!("no token key"))?; 143 | let touched_storage = token_info 144 | .storage 145 | .clone() 146 | .ok_or(anyhow!("no storage values"))?; 147 | let slot = touched_storage 148 | .keys() 149 | .next() 150 | .ok_or(anyhow!("no slot value in storage"))?; 151 | Ok((true, slot.to_low_u64_be() as u32)) 152 | } 153 | _ => Ok((false, 0)), 154 | }, 155 | _ => Ok((false, 0)), 156 | }, 157 | _ => Ok((false, 0)), 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{self, Result}; 2 | use fern::colors::{Color, ColoredLevelConfig}; 3 | use log::LevelFilter; 4 | 5 | pub fn setup_logger() -> Result<()> { 6 | let colors = ColoredLevelConfig { 7 | trace: Color::Cyan, 8 | debug: Color::Magenta, 9 | info: Color::Green, 10 | warn: Color::Red, 11 | error: Color::BrightRed, 12 | ..ColoredLevelConfig::new() 13 | }; 14 | 15 | fern::Dispatch::new() 16 | .format(move |out, message, record| { 17 | out.finish(format_args!( 18 | "{}[{}] {}", 19 | chrono::Local::now().format("[%H:%M:%S]"), 20 | colors.color(record.level()), 21 | message 22 | )) 23 | }) 24 | .chain(std::io::stdout()) 25 | .level(log::LevelFilter::Error) 26 | .level_for("evm_simulation", LevelFilter::Info) 27 | .apply()?; 28 | 29 | Ok(()) 30 | } 31 | --------------------------------------------------------------------------------