├── .gitignore ├── Cargo.toml ├── README.md └── src ├── constants.rs ├── main.rs ├── tax_checker.sol └── utils.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | Cargo.lock 4 | .vscode 5 | .env 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "reth-tax" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap = "4.4.11" 10 | jsonrpsee = "0.20.3" 11 | serde = "1.0.193" 12 | 13 | thiserror = "1.0.50" 14 | eyre = "0.6.10" 15 | anyhow = "1.0.75" 16 | 17 | reth = { git = "https://github.com/paradigmxyz/reth", branch = "main" } 18 | alloy-dyn-abi = "0.5.2" 19 | alloy-sol-types = "0.5.2" 20 | alloy-primitives = "0.5.2" 21 | async-trait = "0.1.74" 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reth token tax 2 | 3 | Having recently taken an interest in Rust and Reth, I decided to take advantage of its modularity to add some custom code. 4 | This code adds a new custom eth_tokenTax api to the rpc eth namespace. 5 | At the moment, only token/weth pairs on UniswapV2 are supported. 6 | 7 | ### Acknowledgements 8 | This project is based on the [Univ2-Tri-Arb repo from duoxehyon](https://github.com/duoxehyon/univ2-tri-arb) for retrieving univ2 token taxes, as well as on the examples provided in the [reth repo](https://github.com/paradigmxyz/reth). 9 | 10 | ### Build 11 | ```rust 12 | cargo run -- node --http --http.api web3,eth,trace 13 | ``` 14 | 15 | And call the method with [cast](https://github.com/foundry-rs/foundry) or curl: 16 | 17 | ```bash 18 | cast rpc eth_tokenTax '["0xaC1419Ee74F203C6b9DAa3635ad7169b7ebb5C1A","0x1396D6F2e9056954DFc2775204bB3e2Eb8ab8a5B","0x1151CB3d861920e07a38e03eEAd12C32178567F6","0x72e4f9F808C49A2a61dE9C5896298920Dc4EEEa9"]' | jq 19 | ``` 20 | 21 | ```json 22 | [ 23 | { 24 | "Success": { 25 | "token": "0xac1419ee74f203c6b9daa3635ad7169b7ebb5c1a", 26 | "buy": "0x5", 27 | "sell": "0x5" 28 | } 29 | }, 30 | { 31 | "Success": { 32 | "token": "0x1396d6f2e9056954dfc2775204bb3e2eb8ab8a5b", 33 | "buy": "0x1", 34 | "sell": "0x1" 35 | } 36 | }, 37 | { 38 | "CallError": { 39 | "PairAddressDoesNotExist": "0x1151cb3d861920e07a38e03eead12c32178567f6" 40 | } 41 | }, 42 | { 43 | "Success": { 44 | "token": "0x72e4f9f808c49a2a61de9c5896298920dc4eeea9", 45 | "buy": null, 46 | "sell": null 47 | } 48 | } 49 | ] 50 | ``` 51 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | use reth::primitives::{address, Address, Bytes}; 2 | 3 | pub fn tax_checker_address() -> Address { 4 | address!("00000000000000000000000000000000F3370000") 5 | } 6 | 7 | // Holds constant value representing braindance caller 8 | pub fn tax_checker_controller_address() -> Address { 9 | address!("000000000000000000000000000000000420BABE") 10 | } 11 | 12 | pub fn get_tax_checker_code() -> Bytes { 13 | "608060405234801561000f575f80fd5b5060043610610029575f3560e01c8063d7ad21ac1461002d575b5f80fd5b61004061003b366004610dca565b610059565b6040805192835260208301919091520160405180910390f35b6040805160e0810182525f818301819052606080830182905260a0830182905260c083018290526001600160a01b038781168085529087166020850152608084018690528451630240bc6b60e21b8152945192948594939192630902f1ac926004808401938290030181865afa1580156100d5573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906100f99190610e23565b506001600160701b0390811660c08401521660a082015280516040805163d21220a760e01b815290516001600160a01b039092169163d21220a7916004808201926020929091908290030181865afa158015610157573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061017b9190610e6f565b6001600160a01b031681602001516001600160a01b0316036101b65760c08101805160a0830180516001600160701b03908116909352911690525b80602001516001600160a01b03166323b872dd825f0151306127108560a001516101e09190610eb9565b6040516001600160e01b031960e086901b1681526001600160a01b0393841660048201529290911660248301526001600160701b031660448201526064016020604051808303815f875af115801561023a573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061025e9190610ede565b50805f01516001600160a01b031663fff6cae96040518163ffffffff1660e01b81526004015f604051808303815f87803b15801561029a575f80fd5b505af11580156102ac573d5f803e3d5ffd5b50505050805f01516001600160a01b0316630902f1ac6040518163ffffffff1660e01b8152600401606060405180830381865afa1580156102ef573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103139190610e23565b506001600160701b0390811660c08401521660a08201526020808201516001600160a01b039081166040808501919091528351815163d21220a760e01b81529151600194919093169263d21220a79260048082019392918290030181865afa158015610381573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103a59190610e6f565b6001600160a01b031682602001516001600160a01b03160361045f5760c08201805160a0840180516001600160701b0390811690935291169052815160408051630dfe168160e01b815290516001600160a01b0390921691630dfe1681916004808201926020929091908290030181865afa158015610426573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061044a9190610e6f565b6001600160a01b03166060830152505f6104d1565b815f01516001600160a01b031663d21220a76040518163ffffffff1660e01b8152600401602060405180830381865afa15801561049e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104c29190610e6f565b6001600160a01b031660608301525b5f6104dc83836105dc565b6104e7906001610efd565b9050825f01516001600160a01b0316630902f1ac6040518163ffffffff1660e01b8152600401606060405180830381865afa158015610528573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061054c9190610e23565b506001600160701b0390811660c08601521660a0840152816105875760c08301805160a0850180516001600160701b03908116909352911690525b5f61059284846109df565b60408051848152602081018390529192507fbcf2857ab072dd1bb2474056a1d6cd22f44ddef1f02199e5003cef746a37be34910160405180910390a1909890975095505050505050565b60208201516040516370a0823160e01b81523060048201525f9182916001600160a01b03909116906370a0823190602401602060405180830381865afa158015610628573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061064c9190610f16565b90505f610679828660a001516001600160701b03168760c001516001600160701b03168860800151610d65565b6040868101518751915163a9059cbb60e01b81526001600160a01b03928316600482015260248101869052929350839291169063a9059cbb906044016020604051808303815f875af11580156106d1573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106f59190610ede565b5060a0860151604080880151885191516370a0823160e01b81526001600160a01b03928316600482015286936001600160701b031692909116906370a0823190602401602060405180830381865afa158015610753573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107779190610f16565b6107819190610f2d565b1461083c5760a0860151604080880151885191516370a0823160e01b81526001600160a01b039283166004820152610839936001600160701b031692909116906370a0823190602401602060405180830381865afa1580156107e5573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108099190610f16565b6108139190610f2d565b8760a001516001600160701b03168860c001516001600160701b03168960800151610d65565b91505b6108486101f483610f2d565b915084156108c2578551604080516020810182525f808252915163022c0d9f60e01b81526001600160a01b039093169263022c0d9f9261089092909187913091600401610f83565b5f604051808303815f87803b1580156108a7575f80fd5b505af11580156108b9573d5f803e3d5ffd5b5050505061092f565b8551604080516020810182525f808252915163022c0d9f60e01b81526001600160a01b039093169263022c0d9f92610901928792309190600401610f83565b5f604051808303815f87803b158015610918575f80fd5b505af115801561092a573d5f803e3d5ffd5b505050505b60608601516040516370a0823160e01b81523060048201525f916001600160a01b0316906370a08231906024015b602060405180830381865afa158015610978573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061099c9190610f16565b6109a69083610f2d565b9050805f036109b7575f94506109d5565b816109c482612710610faf565b6109ce9190610fc6565b61ffff1694505b5050505092915050565b60608201516040516370a0823160e01b81523060048201525f9182916001600160a01b03909116906370a0823190602401602060405180830381865afa158015610a2b573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a4f9190610f16565b90505f610a7c828660c001516001600160701b03168760a001516001600160701b03168860800151610d65565b6060860151865160405163a9059cbb60e01b81526001600160a01b03918216600482015260248101869052929350839291169063a9059cbb906044016020604051808303815f875af1158015610ad4573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610af89190610ede565b5060c0860151606087015187516040516370a0823160e01b81526001600160a01b03918216600482015286936001600160701b03169291909116906370a0823190602401602060405180830381865afa158015610b57573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b7b9190610f16565b610b859190610f2d565b14610c415760c0860151606087015187516040516370a0823160e01b81526001600160a01b039182166004820152610c3e936001600160701b03169291909116906370a0823190602401602060405180830381865afa158015610bea573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c0e9190610f16565b610c189190610f2d565b8760c001516001600160701b03168860a001516001600160701b03168960800151610d65565b91505b610c4c600583610f2d565b91508415610cc5578551604080516020810182525f808252915163022c0d9f60e01b81526001600160a01b039093169263022c0d9f92610c93928792309190600401610f83565b5f604051808303815f87803b158015610caa575f80fd5b505af1158015610cbc573d5f803e3d5ffd5b50505050610d33565b8551604080516020810182525f808252915163022c0d9f60e01b81526001600160a01b039093169263022c0d9f92610d0592909187913091600401610f83565b5f604051808303815f87803b158015610d1c575f80fd5b505af1158015610d2e573d5f803e3d5ffd5b505050505b60408087015190516370a0823160e01b81523060048201525f916001600160a01b0316906370a082319060240161095d565b5f80610d718387610faf565b905080610d80866103e8610faf565b610d8a9190610efd565b610d948583610faf565b610d9e9190610fc6565b610da9906001610efd565b9695505050505050565b6001600160a01b0381168114610dc7575f80fd5b50565b5f805f60608486031215610ddc575f80fd5b8335610de781610db3565b92506020840135610df781610db3565b929592945050506040919091013590565b80516001600160701b0381168114610e1e575f80fd5b919050565b5f805f60608486031215610e35575f80fd5b610e3e84610e08565b9250610e4c60208501610e08565b9150604084015163ffffffff81168114610e64575f80fd5b809150509250925092565b5f60208284031215610e7f575f80fd5b8151610e8a81610db3565b9392505050565b634e487b7160e01b5f52601260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b5f6001600160701b0380841680610ed257610ed2610e91565b92169190910492915050565b5f60208284031215610eee575f80fd5b81518015158114610e8a575f80fd5b80820180821115610f1057610f10610ea5565b92915050565b5f60208284031215610f26575f80fd5b5051919050565b81810381811115610f1057610f10610ea5565b5f81518084525f5b81811015610f6457602081850181015186830182015201610f48565b505f602082860101526020601f19601f83011685010191505092915050565b84815283602082015260018060a01b0383166040820152608060608201525f610da96080830184610f40565b8082028115828204841417610f1057610f10610ea5565b5f82610fd457610fd4610e91565b50049056fea26469706673582212208a70148d097f0fab5b4e8249127bc3cedb57d4e543a9437d40c5573bfda63a1964736f6c63430008140033".parse().unwrap() 14 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod constants; 2 | mod utils; 3 | use utils::*; 4 | use constants::*; 5 | 6 | use clap::Parser; 7 | use jsonrpsee::{core::RpcResult, proc_macros::rpc, types::ErrorObject}; 8 | use reth::cli::{ 9 | components::{RethNodeComponents, RethRpcComponents}, 10 | config::RethRpcConfig, 11 | ext::{RethCliExt, RethNodeCommandConfig}, 12 | Cli, 13 | }; 14 | use reth::rpc::eth::EthTransactions; 15 | use reth::primitives::{BlockId, BlockNumberOrTag, keccak256, KECCAK_EMPTY}; 16 | use reth::revm::database::StateProviderDatabase; 17 | use reth::revm::primitives::{Address, Env, ResultAndState, TxEnv, AccountInfo, U256, Bytecode, TransactTo}; 18 | use reth::revm::db::CacheDB; 19 | use reth::revm::EVM; 20 | 21 | use alloy_primitives::utils::parse_ether; 22 | 23 | use async_trait::async_trait; 24 | use std::str::FromStr; 25 | use std::sync::Arc; 26 | 27 | fn main() { 28 | Cli::::parse().run().unwrap(); 29 | } 30 | 31 | struct MyRethCliExt; 32 | 33 | impl RethCliExt for MyRethCliExt { 34 | type Node = RethCliEthExt; 35 | } 36 | 37 | #[derive(Debug, Clone, Copy, Default, clap::Args)] 38 | struct RethCliEthExt {} 39 | 40 | impl RethNodeCommandConfig for RethCliEthExt { 41 | fn extend_rpc_modules( 42 | &mut self, 43 | _config: &Conf, 44 | _components: &Reth, 45 | rpc_components: RethRpcComponents<'_, Reth>, 46 | ) -> eyre::Result<()> 47 | where 48 | Conf: RethRpcConfig, 49 | Reth: RethNodeComponents, 50 | { 51 | let eth_api = rpc_components.registry.eth_api().clone(); 52 | let ext = EthTokenTax { eth_api: Arc::new(eth_api) }; 53 | 54 | rpc_components.modules.merge_configured(ext.into_rpc())?; 55 | Ok(()) 56 | } 57 | } 58 | 59 | #[derive(Debug)] 60 | struct EthTokenTax { 61 | eth_api: Arc, 62 | } 63 | 64 | #[rpc(server, namespace = "eth")] 65 | #[async_trait] 66 | pub trait EthTokenTaxApi { 67 | /// Returns the buy and sell tax of given erc20 tokens 68 | #[method(name = "tokenTax")] 69 | async fn token_tax(&self, addresses: Vec
) -> RpcResult>; 70 | } 71 | 72 | #[async_trait] 73 | impl EthTokenTaxApiServer for EthTokenTax 74 | where 75 | Eth: EthTransactions + 'static 76 | { 77 | async fn token_tax(&self, addresses: Vec
) -> RpcResult> { 78 | 79 | if addresses.is_empty() { 80 | return Err(ErrorObject::owned( 81 | -1, 82 | "No token provided", 83 | None::<()>, 84 | )); 85 | } 86 | // get env configuration 87 | let latest_block_id = BlockId::Number(BlockNumberOrTag::Latest); 88 | let (cfg, block_env, at) = self.eth_api.evm_env_at(latest_block_id).await?; 89 | 90 | let res = self.eth_api 91 | .spawn_with_state_at_block(at, move |state| { 92 | let mut env = Env { cfg, block: block_env, tx: TxEnv::default() }; 93 | let mut db = CacheDB::new(StateProviderDatabase::new(state)); 94 | 95 | // insert tax checker contract account in db 96 | let tax_checker_code = get_tax_checker_code(); 97 | let code_hash = keccak256(&tax_checker_code); 98 | let tax_checker_bytecode = Bytecode::new_raw(tax_checker_code); 99 | let contract_account = AccountInfo::new(U256::from(0), 0, code_hash, tax_checker_bytecode); 100 | db.insert_account_info(tax_checker_address(), contract_account); 101 | 102 | // insert tax checker controller address in db 103 | let controller_account = AccountInfo::new(parse_ether("69").unwrap(), 0, KECCAK_EMPTY, Bytecode::default()); 104 | db.insert_account_info(tax_checker_controller_address(), controller_account); 105 | 106 | env.tx.caller = tax_checker_controller_address(); 107 | env.tx.gas_limit = 7000000; 108 | env.tx.gas_price = U256::from_str("100000000000").unwrap(); 109 | 110 | let mut evm = EVM::with_env(env); 111 | evm.database(db); 112 | let mut results: Vec = Vec::with_capacity(addresses.len()); 113 | 114 | let mut addresses = addresses.into_iter().peekable(); 115 | 116 | while let Some(token) = addresses.next() { 117 | 118 | let mut token_tax_info = TaxInfo::new(token); 119 | 120 | // get pair address. Expect that token LP contract is UniswapV2 Factory 121 | let pair_address = match token_tax_info.get_pair_address(&mut evm) { 122 | Ok(pair) => pair, 123 | Err(e) => { 124 | results.push(TaxCallResult::CallError(e)); 125 | continue; 126 | } 127 | }; 128 | 129 | // insert the fake allowance to transfer tokens to the contract 130 | insert_fake_approval(token, pair_address, evm.db().unwrap()); 131 | evm.env.tx.transact_to = TransactTo::Call(tax_checker_address()); 132 | 133 | // getTax calldata 134 | let encoded_call = token_tax_info.encode_call(pair_address).into(); 135 | evm.env.tx.data = encoded_call; 136 | 137 | // execute the call 138 | let ResultAndState { result, ..} = evm.transact()?; 139 | // handle the result 140 | if result.is_success() { 141 | let output = result.into_output().unwrap_or_default(); 142 | token_tax_info.decode_success_call(output); 143 | results.push(TaxCallResult::Success(token_tax_info)); 144 | } else { 145 | let revert = result.into_output().unwrap_or_default(); 146 | results.push(TaxCallResult::CallError(TaxCallError::CallingTaxError(token, revert.to_string()))); 147 | }; 148 | } 149 | 150 | 151 | Ok(results) 152 | }) 153 | .await; 154 | 155 | res.map_err(|err| ErrorObject::from(err)) 156 | } 157 | } -------------------------------------------------------------------------------- /src/tax_checker.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | 4 | interface IUniswapV2Pair { 5 | function balanceOf(address owner) external view returns (uint256); 6 | function approve(address spender, uint256 value) external returns (bool); 7 | function transfer(address to, uint value) external returns (bool); 8 | function transferFrom(address from, address to, uint256 value) external returns (bool); 9 | function token0() external view returns (address); 10 | function token1() external view returns (address); 11 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 12 | function sync() external; 13 | function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external; 14 | } 15 | 16 | contract TaxChecker { 17 | 18 | event TaxSucceed(uint256 buyTax, uint256 sellTax); 19 | 20 | struct Config { 21 | address pair; 22 | address tokenIn; 23 | address token0; 24 | address token1; 25 | uint256 dexFee; 26 | uint112 reserve0; 27 | uint112 reserve1; 28 | } 29 | 30 | function getTax( 31 | address pair, 32 | address tokenIn, 33 | uint256 dexFee 34 | ) external returns (uint256 buyTax, uint256 sellTax) { 35 | 36 | Config memory config; 37 | config.pair = pair; 38 | config.tokenIn = tokenIn; 39 | config.dexFee = dexFee; 40 | // Get initial balance 41 | (config.reserve0, config.reserve1, ) = IUniswapV2Pair(config.pair).getReserves(); 42 | 43 | if (config.tokenIn == IUniswapV2Pair(config.pair).token1()) { 44 | (config.reserve0, config.reserve1) = (config.reserve1, config.reserve0); 45 | } 46 | 47 | IUniswapV2Pair(config.tokenIn).transferFrom(config.pair, address(this), config.reserve0 / 10000); 48 | IUniswapV2Pair(config.pair).sync(); 49 | 50 | (config.reserve0, config.reserve1,) = IUniswapV2Pair(config.pair).getReserves(); 51 | 52 | config.token0 = config.tokenIn; 53 | config.token1; 54 | 55 | bool swapInDefault = true; // if 0 then token0 == token 56 | 57 | if (config.tokenIn == IUniswapV2Pair(config.pair).token1()) { 58 | (config.reserve0, config.reserve1) = (config.reserve1, config.reserve0); 59 | config.token1 = IUniswapV2Pair(config.pair).token0(); 60 | swapInDefault = false; 61 | } else { 62 | config.token1 = IUniswapV2Pair(config.pair).token1(); 63 | } 64 | 65 | uint256 taxIn = _buyTax(config, swapInDefault) + 1; 66 | 67 | (config.reserve0, config.reserve1,) = IUniswapV2Pair(config.pair).getReserves(); 68 | if (!swapInDefault) { 69 | (config.reserve0, config.reserve1) = (config.reserve1, config.reserve0); 70 | } 71 | 72 | uint256 taxOut = _sellTax(config, swapInDefault); 73 | emit TaxSucceed(taxIn, taxOut); 74 | return (taxIn, taxOut); 75 | } 76 | 77 | function _buyTax(Config memory config, bool swapInDefault) 78 | internal 79 | returns (uint256 taxIn) 80 | { 81 | uint256 amountIn = IUniswapV2Pair(config.tokenIn).balanceOf(address(this)); 82 | // Calc amountOut and transfer in amountIn to pair 83 | 84 | uint256 amountOut = getAmountOut(amountIn, config.reserve0, config.reserve1, config.dexFee); 85 | uint256 amountOutExpected = amountOut; 86 | 87 | IUniswapV2Pair(config.token0).transfer(config.pair, amountIn); 88 | 89 | // Check if this transfer was taxed 90 | if (IUniswapV2Pair(config.token0).balanceOf(config.pair) - config.reserve0 != amountIn) { 91 | // If yes then re calculate amountOut 92 | amountOut = getAmountOut( 93 | (IUniswapV2Pair(config.token0).balanceOf(config.pair) - config.reserve0), 94 | config.reserve0, 95 | config.reserve1, 96 | config.dexFee 97 | ); 98 | } 99 | 100 | // Do Swap 101 | amountOut -= 500; // Prevent Rounding error 102 | if (swapInDefault) { 103 | IUniswapV2Pair(config.pair).swap(0, amountOut, address(this), bytes("")); 104 | } else { 105 | IUniswapV2Pair(config.pair).swap(amountOut, 0, address(this), bytes("")); 106 | } 107 | 108 | uint256 difference = (amountOutExpected - IUniswapV2Pair(config.token1).balanceOf(address(this))); 109 | if (difference == 0) { 110 | taxIn = 0; 111 | } else { 112 | taxIn = uint16((difference * 10000) / amountOutExpected); 113 | } 114 | } 115 | 116 | function _sellTax(Config memory config, bool swapInDefault) internal returns (uint256 taxOut) { 117 | uint amountIn = IUniswapV2Pair(config.token1).balanceOf(address(this)); 118 | 119 | // Calc amountOut and transfer in amountIn to pair 120 | uint amountOut = getAmountOut(amountIn, config.reserve1, config.reserve0, config.dexFee); 121 | uint amountOutExpected = amountOut; 122 | IUniswapV2Pair(config.token1).transfer(config.pair, amountIn); 123 | 124 | // Check if this transfer was taxed 125 | if (IUniswapV2Pair(config.token1).balanceOf(config.pair) - config.reserve1 != amountIn) { 126 | amountOut = getAmountOut( 127 | (IUniswapV2Pair(config.token1).balanceOf(config.pair) - config.reserve1), 128 | config.reserve1, 129 | config.reserve0, 130 | config.dexFee 131 | ); 132 | } 133 | 134 | // Do Swap 135 | amountOut -= 5; // Prevent Rounding error 136 | if (swapInDefault) { 137 | IUniswapV2Pair(config.pair).swap(amountOut, 0, address(this), bytes("")); 138 | } else { 139 | IUniswapV2Pair(config.pair).swap(0 , amountOut, address(this), bytes("")); 140 | } 141 | 142 | uint difference = (amountOutExpected - IUniswapV2Pair(config.token0).balanceOf(address(this))); 143 | if (difference == 0) { 144 | taxOut = 0; 145 | } else { 146 | taxOut = uint16((difference * 10000) / amountOutExpected); 147 | } 148 | } 149 | 150 | function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut, uint256 dexFee) 151 | internal 152 | pure 153 | returns (uint256) 154 | { 155 | uint256 amountInWithFee = amountIn * dexFee; 156 | return amountInWithFee * reserveOut / ((reserveIn * 1000) + amountInWithFee) + 1; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use super::constants::*; 2 | 3 | use jsonrpsee::core::Serialize; 4 | use reth::providers::StateProvider; 5 | use reth::revm::database::StateProviderDatabase; 6 | use reth::revm::db::{CacheDB, DatabaseRef}; 7 | use reth::revm::EVM; 8 | use reth::primitives::{Address, U256, Bytes, keccak256}; 9 | use alloy_dyn_abi::DynSolValue; 10 | use alloy_sol_types::{SolCall, sol}; 11 | use reth::revm::primitives::{TransactTo, ResultAndState, ExecutionResult}; 12 | use serde::Deserialize; 13 | 14 | type StateProviderBox = Box; 15 | type StateProviderDB = StateProviderDatabase; 16 | type CacheDBStateProvider = CacheDB; 17 | type EvmStateProvider = EVM; 18 | 19 | #[derive(Debug, Clone, Serialize, Deserialize)] 20 | pub enum TaxCallResult { 21 | Success(TaxInfo), 22 | CallError(TaxCallError) 23 | } 24 | 25 | #[derive(Debug, Clone, Serialize, Deserialize)] 26 | pub struct TaxInfo { 27 | pub token: Address, 28 | pub buy: Option, 29 | pub sell: Option, 30 | } 31 | 32 | #[derive(Debug, thiserror::Error, Clone, Serialize, Deserialize)] 33 | pub enum TaxCallError { 34 | #[error("No pair address found for token {0:?}")] 35 | PairAddressDoesNotExist(Address), 36 | #[error("Failed to fetch pair address for token {0:}: {1:?}")] 37 | CallingPairReverts(Address, String), 38 | #[error("Get pair call halt")] 39 | CallingPairHalt, 40 | #[error("Failed to fetch token tax {0:}: {1:?}")] 41 | CallingTaxError(Address, String), 42 | } 43 | 44 | impl TaxInfo { 45 | pub fn new(token: Address) -> Self { 46 | Self { 47 | token, 48 | buy: None, 49 | sell: None, 50 | } 51 | } 52 | 53 | // atm pair address can only be found from the uniwapv2 factory contract 54 | pub fn get_pair_address(&self, evm: &mut EvmStateProvider) -> Result 55 | { 56 | sol! { 57 | function getPair(address token0, address token1) returns (address pair); 58 | } 59 | 60 | let weth: Address = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2".parse().unwrap(); 61 | let univ2_factory: Address = "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f".parse().unwrap(); 62 | let (token_0, token_1) = if weth < self.token { 63 | (weth, self.token) 64 | } else { 65 | (self.token, weth) 66 | }; 67 | 68 | let call = getPairCall { 69 | token0: token_0, 70 | token1: token_1, 71 | }; 72 | let tx_data = call.abi_encode(); 73 | 74 | evm.env.tx.data = tx_data.into(); 75 | evm.env.tx.transact_to = TransactTo::Call(univ2_factory); 76 | let ResultAndState { result, .. } = evm.transact().unwrap(); 77 | 78 | match result { 79 | ExecutionResult::Success { output, .. } => { 80 | let decoded = getPairCall::abi_decode_returns(output.data(), true).unwrap(); 81 | let pair_address = decoded.pair; 82 | if pair_address != Address::ZERO { 83 | return Ok(pair_address); 84 | } else { 85 | return Err(TaxCallError::PairAddressDoesNotExist(self.token).into()) 86 | } 87 | }, 88 | ExecutionResult::Revert { gas_used: _, output } => { 89 | return Err(TaxCallError::CallingPairReverts(self.token, output.to_string())) 90 | }, 91 | ExecutionResult::Halt { .. } => { 92 | return Err(TaxCallError::CallingPairHalt) 93 | } 94 | } 95 | } 96 | 97 | pub fn encode_call(&self, pair: Address) -> Vec { 98 | let call = getTaxCall { 99 | pair, 100 | tokenIn: self.token, 101 | dexFee: U256::from(997), 102 | }; 103 | call.abi_encode() 104 | } 105 | 106 | pub fn decode_success_call(&mut self, output: Bytes) { 107 | let decoded = getTaxCall::abi_decode_returns(&output, true).unwrap(); 108 | let buy_tax = decoded.buyTax; 109 | let sell_tax = decoded.sellTax; 110 | if buy_tax > U256::from(99) { 111 | self.buy = Some(buy_tax / U256::from(100)) 112 | } 113 | if sell_tax > U256::from(99) { 114 | self.sell = Some(sell_tax / U256::from(100)) 115 | } 116 | } 117 | } 118 | 119 | 120 | 121 | pub fn insert_fake_approval(token: Address, pair: Address, db: &mut CacheDB) 122 | where 123 | ExtDB: DatabaseRef, 124 | ::Error: std::fmt::Debug, 125 | { 126 | for i in 0..100 { 127 | let slot_new = map_location(U256::from(i), pair, tax_checker_address()); 128 | let max_uint = U256::MAX; 129 | db.insert_account_storage(token, slot_new, max_uint).unwrap(); 130 | } 131 | } 132 | 133 | pub fn map_location(slot: U256, key: Address, key_after: Address) -> U256 { 134 | let input = [DynSolValue::Address(key), DynSolValue::Uint(slot, 32)]; 135 | let input = DynSolValue::Tuple(input.to_vec()); 136 | let key_slot_hash: U256 = keccak256(input.abi_encode()).into(); 137 | 138 | let input = [DynSolValue::Address(key_after), DynSolValue::Uint(key_slot_hash, 32)]; 139 | let input = DynSolValue::Tuple(input.to_vec()); 140 | let slot: U256 = keccak256(input.abi_encode()).into(); 141 | slot 142 | } 143 | 144 | sol! { 145 | #[derive(Debug)] 146 | function getTax( 147 | address pair, 148 | address tokenIn, 149 | uint256 dexFee 150 | ) returns (uint256 buyTax, uint256 sellTax); 151 | } --------------------------------------------------------------------------------