├── .github ├── ISSUE_TEMPLATE │ ├── BUG-FORM.yml │ └── FEATURE-FORM.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── Cargo.toml ├── README.md ├── benches ├── erc_4626.rs ├── uniswap_v2.rs └── uniswap_v3.rs ├── build.rs ├── contracts ├── foundry.toml ├── src │ ├── Balancer │ │ └── GetBalancerPoolDataBatchRequest.sol │ ├── ERC20 │ │ └── GetTokenDecimalsBatchRequest.sol │ ├── ERC4626 │ │ └── GetERC4626VaultDataBatchRequest.sol │ ├── UniswapV2 │ │ ├── GetUniswapV2PairsBatchRequest.sol │ │ └── GetUniswapV2PoolDataBatchRequest.sol │ ├── UniswapV3 │ │ ├── FixedPoint.sol │ │ ├── GetUniswapV3PoolDataBatchRequest.sol │ │ ├── GetUniswapV3PoolSlot0BatchRequest.sol │ │ ├── GetUniswapV3PoolTickBitmapBatchRequest.sol │ │ ├── GetUniswapV3PoolTickDataBatchRequest.sol │ │ └── interfaces │ │ │ ├── IBalancer.sol │ │ │ ├── IUniswapV2.sol │ │ │ ├── IUniswapV3.sol │ │ │ └── Token.sol │ ├── filters │ │ ├── WethValueInPools.sol │ │ └── WethValueInPoolsBatchRequest.sol │ └── interfaces │ │ ├── IBalancer.sol │ │ ├── IUniswapV2.sol │ │ ├── IUniswapV3.sol │ │ └── Token.sol └── test │ ├── FixedPoint.t.sol │ ├── WethValueInPools.t.sol │ └── WethValueInPoolsBatchRequest.t.sol ├── dependabot.yml ├── examples ├── filters.rs ├── simulate_swap.rs ├── state_space_builder.rs ├── subscribe.rs ├── swap_calldata.rs └── sync_macro.rs └── src ├── amms ├── abi │ ├── GetBalancerPoolDataBatchRequest.json │ ├── GetERC4626VaultDataBatchRequest.json │ ├── GetTokenDecimalsBatchRequest.json │ ├── GetUniswapV2PairsBatchRequest.json │ ├── GetUniswapV2PoolDataBatchRequest.json │ ├── GetUniswapV3PoolDataBatchRequest.json │ ├── GetUniswapV3PoolSlot0BatchRequest.json │ ├── GetUniswapV3PoolTickBitmapBatchRequest.json │ ├── GetUniswapV3PoolTickDataBatchRequest.json │ ├── WethValueInPools.json │ └── WethValueInPoolsBatchRequest.json ├── amm.rs ├── balancer │ ├── bmath.rs │ └── mod.rs ├── consts.rs ├── erc_4626 │ └── mod.rs ├── error.rs ├── factory.rs ├── float.rs ├── mod.rs ├── uniswap_v2 │ └── mod.rs └── uniswap_v3 │ └── mod.rs ├── lib.rs └── state_space ├── cache.rs ├── discovery.rs ├── error.rs ├── filters ├── blacklist.rs ├── mod.rs ├── value.rs └── whitelist.rs └── mod.rs /.github/ISSUE_TEMPLATE/BUG-FORM.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: File a bug report 3 | labels: ["bug"] 4 | title: "Bug: " 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Please ensure that the bug has not already been filed in the issue tracker. 10 | 11 | Thanks for taking the time to report this bug! 12 | - type: textarea 13 | attributes: 14 | label: Describe the bug 15 | description: Please include code snippets as well if relevant. 16 | validations: 17 | required: true 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE-FORM.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest a feature 3 | labels: ["enhancement"] 4 | title: "Feat: " 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Please ensure that the feature has not already been requested in the issue tracker. 10 | - type: textarea 11 | attributes: 12 | label: Describe the feature you would like 13 | description: 14 | Please also describe your goals for the feature. What problems it solves, how it would 15 | be used, etc. 16 | validations: 17 | required: true 18 | - type: textarea 19 | attributes: 20 | label: Additional context 21 | description: Add any other context to the feature (screenshots, resources, etc.) 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | ## Motivation 12 | 13 | 18 | 19 | ## Solution 20 | 21 | 25 | 26 | ## PR Checklist 27 | 28 | - [ ] Added Tests 29 | - [ ] Added Documentation 30 | - [ ] Breaking changes 31 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "docker" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | - package-ecosystem: "github-actions" 12 | directory: "/" 13 | schedule: 14 | interval: "weekly" -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | types: [opened,synchronize,reopened] 9 | branches: 10 | - main 11 | 12 | env: 13 | ETHEREUM_PROVIDER: ${{ secrets.ETHEREUM_PROVIDER }} 14 | RUST_VERSION: "1.84" 15 | NIGHTLY_VERSION: nightly-2024-11-01 16 | CARGO_TERM_COLOR: always 17 | # Skip incremental build and debug info generation in CI 18 | CARGO_INCREMENTAL: 0 19 | CARGO_PROFILE_DEV_DEBUG: 0 20 | 21 | jobs: 22 | lint: 23 | name: Lint 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v2 27 | - uses: actions-rs/toolchain@v1 28 | with: 29 | profile: minimal 30 | toolchain: stable 31 | override: true 32 | - run: rustup component add rustfmt 33 | - uses: actions-rs/cargo@v1 34 | with: 35 | command: fmt 36 | args: --all -- --check 37 | 38 | test: 39 | name: Test 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: Checkout repository 43 | uses: actions/checkout@v4 44 | - name: Install rust 45 | uses: actions-rs/toolchain@v1 46 | with: 47 | profile: minimal 48 | toolchain: ${{ env.NIGHTLY_VERSION }} 49 | override: true 50 | - name: Install protobuf-compiler 51 | run: sudo apt-get install -y protobuf-compiler 52 | - name: Cache 53 | uses: actions/cache@v3 54 | continue-on-error: false 55 | with: 56 | path: | 57 | ~/.cargo/registry/index/ 58 | ~/.cargo/registry/cache/ 59 | ~/.cargo/git/db/ 60 | target/ 61 | key: ${{ env.RUST_VERSION }}-${{ env.NIGHTLY_VERSION }}-cargo-test-${{ hashFiles('**/Cargo.lock') }} 62 | restore-keys: ${{ env.RUST_VERSION }}-${{ env.NIGHTLY_VERSION }}-cargo-test- 63 | - name: Install Foundry 64 | uses: foundry-rs/foundry-toolchain@v1 65 | with: 66 | version: nightly 67 | - name: Install latest nextest release 68 | uses: taiki-e/install-action@nextest 69 | - name: Build tests 70 | uses: actions-rs/cargo@v1 71 | with: 72 | command: nextest 73 | args: run --workspace --no-run 74 | - name: Run tests 75 | uses: actions-rs/cargo@v1 76 | with: 77 | command: nextest 78 | args: run --workspace 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | 4 | # Foundry 5 | 6 | # Compiler files 7 | contracts/cache/ 8 | contracts/out/ 9 | contracts/lib/ 10 | contracts/src/flattened/ 11 | 12 | # Ignores development broadcast logs 13 | !/broadcast 14 | /broadcast/*/31337/ 15 | /broadcast/**/dry-run/ 16 | 17 | # Dotenv file 18 | *.env 19 | 20 | # Temp checkpoint 21 | *.temp-checkpoint.json 22 | 23 | # intellij 24 | *.idea/ -------------------------------------------------------------------------------- /.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 = "amms" 3 | version = "0.7.2" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "A library to interact with automated market makers across EVM chains." 7 | readme = "README.md" 8 | homepage = "https://github.com/darkforestry/amms-rs" 9 | repository = "https://github.com/darkforestry/amms-rs" 10 | keywords = ["ethereum", "amm", "mev"] 11 | exclude = ["target/*", ".github/*", ".gitignore", "build.rs", "contracts/*"] 12 | 13 | 14 | [dependencies] 15 | # darkforest 16 | uniswap_v3_math = "0.6.1" 17 | 18 | # alloy 19 | alloy = { version = "1.0.3", features = [ 20 | "contract", 21 | "network", 22 | "rpc", 23 | "rpc-types", 24 | "provider-ws", 25 | "rpc-types-eth", 26 | "signer-local", 27 | ] } 28 | 29 | # tracing 30 | eyre = "0.6" 31 | tracing = "0.1" 32 | 33 | # async 34 | tokio = { version = "1.42", default-features = false } 35 | futures = "0.3" 36 | async-trait = "0.1" 37 | 38 | # misc 39 | arraydeque = "0.5" 40 | thiserror = "1.0" 41 | rug = "1.24.1" 42 | itertools = "0.13.0" 43 | rayon = "1.10.0" 44 | async-stream = "0.3.6" 45 | serde = "1.0" 46 | 47 | 48 | [dev-dependencies] 49 | rand = "0.8.5" 50 | tracing-subscriber = "0.3" 51 | criterion = "0.5" 52 | tokio = { version = "1.42", default-features = false, features = [ 53 | "rt-multi-thread", 54 | ] } 55 | alloy = { version = "1.0.3", features = ["rpc-client"] } 56 | alloy-provider = { version = "1.0.3", features = ["throttle"] } 57 | 58 | 59 | [build-dependencies] 60 | serde_json = "1.0" 61 | rayon = "1" 62 | 63 | 64 | [profile.release] 65 | opt-level = 3 66 | lto = true 67 | codegen-units = 1 68 | panic = "abort" 69 | 70 | [profile.dev] 71 | opt-level = 3 72 | lto = true 73 | codegen-units = 1 74 | debug = "full" 75 | 76 | 77 | [[bench]] 78 | name = "uniswap_v2" 79 | harness = false 80 | 81 | [[bench]] 82 | name = "uniswap_v3" 83 | harness = false 84 | 85 | [[bench]] 86 | name = "erc_4626" 87 | harness = false 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # amms-rs [![Github Actions][gha-badge]][gha] [![Chat][tg-badge]][tg-url] 2 | 3 | [gha]: https://github.com/darkforestry/amms-rs/actions 4 | [gha-badge]: https://github.com/darkforestry/amms-rs/actions/workflows/ci.yml/badge.svg 5 | [tg-url]: https://t.me/amms_rs 6 | [tg-badge]: https://img.shields.io/badge/chat-telegram-blue 7 | 8 | `amms-rs` is a Rust library to interact with automated market makers across EVM chains. 9 | 10 | 13 | 14 | 15 | ## Supported AMMs 16 | 17 | | AMM | Status | 18 | | --------------- | ------ | 19 | | UniswapV2 | ✅ | 20 | | UniswapV3 | ✅ | 21 | | Balancer | ✅ | 22 | | ERC4626 Vaults | ✅ | 23 | -------------------------------------------------------------------------------- /benches/erc_4626.rs: -------------------------------------------------------------------------------- 1 | use alloy::primitives::{address, U256}; 2 | use amms::amms::{amm::AutomatedMarketMaker, erc_4626::ERC4626Vault}; 3 | use criterion::{criterion_group, criterion_main, Criterion}; 4 | use rand::Rng; 5 | 6 | fn simulate_swap(c: &mut Criterion) { 7 | let token_a = address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); 8 | let token_b = address!("fc0d6cf33e38bce7ca7d89c0e292274031b7157a"); 9 | 10 | let pool = ERC4626Vault { 11 | vault_token: token_a, 12 | vault_token_decimals: 18, 13 | asset_token: token_b, 14 | asset_token_decimals: 18, 15 | vault_reserve: U256::from(20_000_000_u128), 16 | asset_reserve: U256::from(20_000_000_u128), 17 | deposit_fee: 300, 18 | withdraw_fee: 300, 19 | }; 20 | 21 | let mut rng = rand::thread_rng(); 22 | c.bench_function("erc4626_simulate_swap", |b| { 23 | b.iter_with_setup( 24 | || U256::from(rng.gen_range(1_000..=1e24 as u128)), 25 | |amount| { 26 | let _ = pool.simulate_swap(token_a, token_b, amount).unwrap(); 27 | }, 28 | ); 29 | }); 30 | } 31 | 32 | criterion_group!(erc_4626, simulate_swap); 33 | criterion_main!(erc_4626); 34 | -------------------------------------------------------------------------------- /benches/uniswap_v2.rs: -------------------------------------------------------------------------------- 1 | use alloy::primitives::{address, U256}; 2 | use amms::amms::{amm::AutomatedMarketMaker, uniswap_v2::UniswapV2Pool, Token}; 3 | use criterion::{criterion_group, criterion_main, Criterion}; 4 | use rand::Rng; 5 | 6 | fn simulate_swap(c: &mut Criterion) { 7 | let token_a = address!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); 8 | let token_b = address!("fc0d6cf33e38bce7ca7d89c0e292274031b7157a"); 9 | 10 | let pool = UniswapV2Pool { 11 | token_a: Token::new_with_decimals(token_a, 18), 12 | token_b: Token::new_with_decimals(token_b, 18), 13 | reserve_0: 20_000_000_u128, 14 | reserve_1: 20_000_000_u128, 15 | fee: 300, 16 | ..Default::default() 17 | }; 18 | 19 | let mut rng = rand::thread_rng(); 20 | c.bench_function("uniswap_v2_simulate_swap", |b| { 21 | b.iter_with_setup( 22 | || U256::from(rng.gen_range(1_000..=1e24 as u128)), 23 | |amount| { 24 | let _ = pool.simulate_swap(token_a, token_b, amount).unwrap(); 25 | }, 26 | ); 27 | }); 28 | } 29 | 30 | criterion_group!(uniswap_v2, simulate_swap); 31 | criterion_main!(uniswap_v2); 32 | -------------------------------------------------------------------------------- /benches/uniswap_v3.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use alloy::{ 4 | eips::BlockId, 5 | primitives::{address, U256}, 6 | providers::ProviderBuilder, 7 | rpc::client::ClientBuilder, 8 | transports::layers::{RetryBackoffLayer, ThrottleLayer}, 9 | }; 10 | use amms::amms::{amm::AutomatedMarketMaker, uniswap_v3::UniswapV3Pool}; 11 | use criterion::{criterion_group, criterion_main, Criterion}; 12 | use rand::Rng; 13 | use tokio::runtime::Runtime; 14 | 15 | fn simulate_swap(c: &mut Criterion) { 16 | let rpc_endpoint = std::env::var("ETHEREUM_PROVIDER").expect("Could not get rpc endpoint"); 17 | 18 | let client = ClientBuilder::default() 19 | .layer(ThrottleLayer::new(500)) 20 | .layer(RetryBackoffLayer::new(5, 200, 330)) 21 | .http(rpc_endpoint.parse().unwrap()); 22 | 23 | let provider = Arc::new(ProviderBuilder::new().connect_client(client)); 24 | 25 | let token_a = address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); 26 | let token_b = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); 27 | 28 | let runtime = Runtime::new().expect("Failed to create Tokio runtime"); 29 | let pool = runtime.block_on(async { 30 | UniswapV3Pool::new(address!("88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")) 31 | .init(BlockId::latest(), provider.clone()) 32 | .await 33 | .expect("Could not init pool") 34 | }); 35 | 36 | let mut rng = rand::thread_rng(); 37 | c.bench_function("uniswap_v3_simulate_swap", |b| { 38 | b.iter_with_setup( 39 | || U256::from(rng.gen_range(1_000..=1e24 as u128)), 40 | |amount| { 41 | let _ = pool.simulate_swap(token_a, token_b, amount).unwrap(); 42 | }, 43 | ); 44 | }); 45 | } 46 | 47 | criterion_group!(uniswap_v3, simulate_swap); 48 | criterion_main!(uniswap_v3); 49 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; 2 | use serde_json::Value; 3 | use std::{ 4 | fs, 5 | hash::{DefaultHasher, Hash, Hasher}, 6 | path::PathBuf, 7 | process::Command, 8 | }; 9 | 10 | const TARGET_CONTRACTS: &[&str] = &[ 11 | "GetERC4626VaultDataBatchRequest", 12 | "GetTokenDecimalsBatchRequest", 13 | "GetBalancerPoolDataBatchRequest", 14 | "WethValueInPools", 15 | "WethValueInPoolsBatchRequest", 16 | "GetUniswapV2PairsBatchRequest", 17 | "GetUniswapV2PoolDataBatchRequest", 18 | "GetUniswapV3PoolDataBatchRequest", 19 | "GetUniswapV3PoolSlot0BatchRequest", 20 | "GetUniswapV3PoolTickBitmapBatchRequest", 21 | "GetUniswapV3PoolTickDataBatchRequest", 22 | ]; 23 | 24 | fn main() -> Result<(), Box> { 25 | let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 26 | 27 | let status = Command::new("forge") 28 | .arg("build") 29 | .current_dir("contracts") 30 | .status()?; 31 | 32 | if !status.success() { 33 | panic!("forge build failed"); 34 | } 35 | 36 | let forge_out_dir = manifest_dir.join("contracts/out"); 37 | let abi_out_dir = manifest_dir.join("src/amms/abi/"); 38 | fs::create_dir_all(&abi_out_dir)?; 39 | 40 | TARGET_CONTRACTS.par_iter().for_each(|contract| { 41 | let new_abi = forge_out_dir 42 | .join(format!("{contract}.sol")) 43 | .join(format!("{contract}.json")); 44 | let prev_abi = abi_out_dir.join(format!("{contract}.json")); 45 | 46 | if !prev_abi.exists() { 47 | fs::copy(&new_abi, &prev_abi).unwrap(); 48 | return; 49 | } 50 | 51 | let prev_contents: Value = 52 | serde_json::from_str(&fs::read_to_string(&prev_abi).unwrap()).unwrap(); 53 | let new_contents: Value = 54 | serde_json::from_str(&fs::read_to_string(&new_abi).unwrap()).unwrap(); 55 | 56 | let prev_bytecode = prev_contents["bytecode"]["object"] 57 | .as_str() 58 | .expect("Missing prev bytecode"); 59 | let new_bytecode = new_contents["bytecode"]["object"] 60 | .as_str() 61 | .expect("Missing new bytecode"); 62 | 63 | if hash(prev_bytecode) != hash(new_bytecode) { 64 | fs::copy(&new_abi, &prev_abi).unwrap(); 65 | } 66 | }); 67 | 68 | println!("cargo:rerun-if-changed=contracts"); 69 | 70 | Ok(()) 71 | } 72 | 73 | fn hash(value: &str) -> u64 { 74 | let mut hasher = DefaultHasher::new(); 75 | value.hash(&mut hasher); 76 | hasher.finish() 77 | } 78 | -------------------------------------------------------------------------------- /contracts/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | optimizer = false 3 | evm_version = "cancun" 4 | remappings = ["forge-std=lib/forge-std/src/"] 5 | fs_permissions = [{ access = "read-write", path = "./" }] 6 | -------------------------------------------------------------------------------- /contracts/src/Balancer/GetBalancerPoolDataBatchRequest.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IBPool { 5 | function getCurrentTokens() external returns (address[] memory); 6 | function getDenormalizedWeight(address token) external returns (uint); 7 | function getSwapFee() external returns (uint); 8 | function getBalance(address token) external returns (uint); 9 | } 10 | 11 | interface IERC20 { 12 | function decimals() external view returns (uint8); 13 | } 14 | 15 | /** 16 | * @dev This contract is not meant to be deployed. Instead, use a static call with the 17 | * deployment bytecode as payload. 18 | */ 19 | contract GetBalancerPoolDataBatchRequest { 20 | struct PoolData { 21 | address[] tokens; 22 | uint8[] decimals; 23 | uint256[] liquidity; 24 | uint256[] weights; 25 | uint32 fee; 26 | } 27 | 28 | constructor(address[] memory pools) { 29 | PoolData[] memory allPoolData = new PoolData[](pools.length); 30 | 31 | for (uint256 i = 0; i < pools.length; ++i) { 32 | address poolAddress = pools[i]; 33 | 34 | if (codeSizeIsZero(poolAddress)) continue; 35 | 36 | PoolData memory poolData; 37 | 38 | // Get the tokens 39 | address[] memory tokens = IBPool(poolAddress).getCurrentTokens(); 40 | uint8[] memory decimals = new uint8[](tokens.length); 41 | uint256[] memory liquidity = new uint256[](tokens.length); 42 | uint256[] memory weights = new uint256[](tokens.length); 43 | 44 | for (uint256 j = 0; j < tokens.length; ++j) { 45 | if (codeSizeIsZero(tokens[j])) { 46 | continue; 47 | } 48 | } 49 | 50 | // Grab the decimals/liquidity 51 | for (uint256 j = 0; j < tokens.length; ++j) { 52 | uint8 tokenDecimals = getTokenDecimals(tokens[j]); 53 | if (tokenDecimals == 0) { 54 | continue; 55 | } else { 56 | decimals[j] = tokenDecimals; 57 | } 58 | weights[j] = IBPool(poolAddress).getDenormalizedWeight( 59 | tokens[j] 60 | ); 61 | liquidity[j] = IBPool(poolAddress).getBalance(tokens[j]); 62 | } 63 | 64 | // Grab the swap fee 65 | poolData.fee = uint32(IBPool(poolAddress).getSwapFee()); 66 | poolData.tokens = tokens; 67 | poolData.decimals = decimals; 68 | poolData.liquidity = liquidity; 69 | poolData.weights = weights; 70 | allPoolData[i] = poolData; 71 | } 72 | 73 | bytes memory _abiEncodedData = abi.encode(allPoolData); 74 | assembly { 75 | // Return from the start of the data (discarding the original data address) 76 | // up to the end of the memory used 77 | let dataStart := add(_abiEncodedData, 0x20) 78 | return(dataStart, sub(msize(), dataStart)) 79 | } 80 | } 81 | 82 | function getTokenDecimals(address token) internal returns (uint8) { 83 | (bool success, bytes memory data) = token.call( 84 | abi.encodeWithSignature("decimals()") 85 | ); 86 | 87 | if (success) { 88 | uint256 decimals; 89 | if (data.length == 32) { 90 | (decimals) = abi.decode(data, (uint256)); 91 | if (decimals == 0 || decimals > 255) { 92 | return 0; 93 | } else { 94 | return uint8(decimals); 95 | } 96 | } else { 97 | return 0; 98 | } 99 | } else { 100 | return 0; 101 | } 102 | } 103 | 104 | function codeSizeIsZero(address target) internal view returns (bool) { 105 | if (target.code.length == 0) { 106 | return true; 107 | } else { 108 | return false; 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /contracts/src/ERC20/GetTokenDecimalsBatchRequest.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract GetTokenDecimalsBatchRequest { 5 | constructor(address[] memory tokens) { 6 | uint8[] memory decimals = new uint8[](tokens.length); 7 | 8 | for (uint256 i = 0; i < tokens.length; ++i) { 9 | address token = tokens[i]; 10 | 11 | if (codeSizeIsZero(token)) continue; 12 | 13 | (bool tokenDecimalsSuccess, bytes memory tokenDecimalsData) = token 14 | .call{gas: 20000}(abi.encodeWithSignature("decimals()")); 15 | 16 | if (tokenDecimalsSuccess) { 17 | uint256 tokenDecimals; 18 | 19 | if (tokenDecimalsData.length == 32) { 20 | (tokenDecimals) = abi.decode(tokenDecimalsData, (uint256)); 21 | 22 | if (tokenDecimals == 0 || tokenDecimals > 255) { 23 | continue; 24 | } else { 25 | decimals[i] = uint8(tokenDecimals); 26 | } 27 | } else { 28 | continue; 29 | } 30 | } else { 31 | continue; 32 | } 33 | } 34 | 35 | bytes memory _abiEncodedData = abi.encode(decimals); 36 | assembly { 37 | // Return from the start of the data (discarding the original data address) 38 | // up to the end of the memory used 39 | let dataStart := add(_abiEncodedData, 0x20) 40 | return(dataStart, sub(msize(), dataStart)) 41 | } 42 | } 43 | } 44 | 45 | function codeSizeIsZero(address target) view returns (bool) { 46 | if (target.code.length == 0) { 47 | return true; 48 | } else { 49 | return false; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /contracts/src/ERC4626/GetERC4626VaultDataBatchRequest.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IERC4626Vault { 5 | function asset() external view returns (address); 6 | 7 | function decimals() external view returns (uint8); 8 | 9 | function totalSupply() external view returns (uint256); 10 | 11 | function totalAssets() external view returns (uint256); 12 | 13 | function convertToShares(uint256 assets) external view returns (uint256); 14 | 15 | function convertToAssets(uint256 shares) external view returns (uint256); 16 | 17 | function previewDeposit(uint256 assets) external view returns (uint256); 18 | 19 | function previewRedeem(uint256 shares) external view returns (uint256); 20 | } 21 | 22 | interface IERC20 { 23 | function decimals() external view returns (uint8); 24 | } 25 | 26 | /** 27 | * @dev This contract is not meant to be deployed. Instead, use a static call with the 28 | * deployment bytecode as payload. 29 | */ 30 | contract GetERC4626VaultDataBatchRequest { 31 | struct VaultData { 32 | address vaultToken; 33 | uint8 vaultTokenDecimals; 34 | address assetToken; 35 | uint8 assetTokenDecimals; 36 | uint256 vaultTokenReserve; 37 | uint256 assetTokenReserve; 38 | uint256 depositFeeDelta1; 39 | uint256 depositFeeDelta2; 40 | uint256 depositNoFee; 41 | uint256 withdrawFeeDelta1; 42 | uint256 withdrawFeeDelta2; 43 | uint256 withdrawNoFee; 44 | } 45 | 46 | constructor(address[] memory vaults) { 47 | VaultData[] memory allVaultData = new VaultData[](vaults.length); 48 | 49 | for (uint256 i = 0; i < vaults.length; ++i) { 50 | address vaultAddress = vaults[i]; 51 | 52 | if (codeSizeIsZero(vaultAddress)) continue; 53 | 54 | address assetToken = IERC4626Vault(vaultAddress).asset(); 55 | // Check that assetToken exists and get assetTokenDecimals 56 | if (codeSizeIsZero(assetToken)) continue; 57 | ( 58 | bool assetTokenDecimalsSuccess, 59 | bytes memory assetTokenDecimalsData 60 | ) = assetToken.call{gas: 20000}( 61 | abi.encodeWithSignature("decimals()") 62 | ); 63 | 64 | if ( 65 | !assetTokenDecimalsSuccess || 66 | assetTokenDecimalsData.length == 32 67 | ) { 68 | continue; 69 | } 70 | 71 | uint256 assetTokenDecimals = abi.decode( 72 | assetTokenDecimalsData, 73 | (uint256) 74 | ); 75 | if (assetTokenDecimals == 0 || assetTokenDecimals > 255) { 76 | continue; 77 | } 78 | 79 | VaultData memory vaultData; 80 | 81 | // Get tokens 82 | vaultData.vaultToken = vaultAddress; 83 | vaultData.assetToken = assetToken; 84 | 85 | // Get vault token decimals 86 | vaultData.vaultTokenDecimals = IERC4626Vault(vaultAddress) 87 | .decimals(); 88 | // Get asset token decimals 89 | vaultData.assetTokenDecimals = uint8(assetTokenDecimals); 90 | 91 | // Get token reserves 92 | vaultData.vaultTokenReserve = IERC4626Vault(vaultAddress) 93 | .totalSupply(); 94 | vaultData.assetTokenReserve = IERC4626Vault(vaultAddress) 95 | .totalAssets(); 96 | 97 | // Get fee deltas 98 | // Deposit fee delta 1 - 100 asset tokens 99 | vaultData.depositFeeDelta1 = 100 | IERC4626Vault(vaultAddress).convertToShares( 101 | 100 * 10 ** vaultData.assetTokenDecimals 102 | ) - 103 | IERC4626Vault(vaultAddress).previewDeposit( 104 | 100 * 10 ** vaultData.assetTokenDecimals 105 | ); 106 | 107 | // Deposit fee delta 2 - 200 asset tokens 108 | vaultData.depositFeeDelta2 = 109 | IERC4626Vault(vaultAddress).convertToShares( 110 | 200 * 10 ** vaultData.assetTokenDecimals 111 | ) - 112 | IERC4626Vault(vaultAddress).previewDeposit( 113 | 200 * 10 ** vaultData.assetTokenDecimals 114 | ); 115 | 116 | vaultData.depositNoFee = IERC4626Vault(vaultAddress) 117 | .convertToShares(100 * 10 ** vaultData.assetTokenDecimals); 118 | 119 | // Withdraw fee delta 1 - 100 vault tokens 120 | vaultData.withdrawFeeDelta1 = 121 | IERC4626Vault(vaultAddress).convertToAssets( 122 | 100 * 10 ** vaultData.vaultTokenDecimals 123 | ) - 124 | IERC4626Vault(vaultAddress).previewRedeem( 125 | 100 * 10 ** vaultData.vaultTokenDecimals 126 | ); 127 | 128 | // Withdraw fee delta 2 - 200 vault tokens 129 | vaultData.withdrawFeeDelta2 = 130 | IERC4626Vault(vaultAddress).convertToAssets( 131 | 200 * 10 ** vaultData.vaultTokenDecimals 132 | ) - 133 | IERC4626Vault(vaultAddress).previewRedeem( 134 | 200 * 10 ** vaultData.vaultTokenDecimals 135 | ); 136 | 137 | vaultData.withdrawNoFee = IERC4626Vault(vaultAddress) 138 | .convertToAssets(100 * 10 ** vaultData.vaultTokenDecimals); 139 | 140 | allVaultData[i] = vaultData; 141 | } 142 | 143 | // ensure abi encoding, not needed here but increase reusability for different return types 144 | // note: abi.encode add a first 32 bytes word with the address of the original data 145 | bytes memory _abiEncodedData = abi.encode(allVaultData); 146 | 147 | assembly { 148 | // Return from the start of the data (discarding the original data address) 149 | // up to the end of the memory used 150 | let dataStart := add(_abiEncodedData, 0x20) 151 | return(dataStart, sub(msize(), dataStart)) 152 | } 153 | } 154 | 155 | function codeSizeIsZero(address target) internal view returns (bool) { 156 | return target.code.length == 0; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /contracts/src/UniswapV2/GetUniswapV2PairsBatchRequest.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IFactory { 5 | function allPairs(uint256 idx) external returns (address); 6 | function allPairsLength() external returns (uint256); 7 | } 8 | 9 | /** 10 | * @dev This contract is not meant to be deployed. Instead, use a static call with the 11 | * deployment bytecode as payload. 12 | */ 13 | contract GetUniswapV2PairsBatchRequest { 14 | constructor(uint256 from, uint256 step, address factory) { 15 | uint256 allPairsLength = IFactory(factory).allPairsLength(); 16 | 17 | step = from + step > allPairsLength ? allPairsLength - from : step; 18 | 19 | // There is a max number of pool as a too big returned data times out the rpc 20 | address[] memory allPairs = new address[](step); 21 | 22 | for (uint256 i = 0; i < step; ++i) { 23 | allPairs[i] = IFactory(factory).allPairs(from + i); 24 | } 25 | 26 | // ensure abi encoding, not needed here but increase reusability for different return types 27 | // note: abi.encode add a first 32 bytes word with the address of the original data 28 | bytes memory _abiEncodedData = abi.encode(allPairs); 29 | 30 | assembly { 31 | // Return from the start of the data (discarding the original data address) 32 | // up to the end of the memory used 33 | let dataStart := add(_abiEncodedData, 0x20) 34 | return(dataStart, sub(msize(), dataStart)) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /contracts/src/UniswapV2/GetUniswapV2PoolDataBatchRequest.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IUniswapV2Pair { 5 | function token0() external view returns (address); 6 | 7 | function token1() external view returns (address); 8 | 9 | function getReserves() 10 | external 11 | view 12 | returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 13 | } 14 | 15 | interface IERC20 { 16 | function decimals() external view returns (uint8); 17 | } 18 | 19 | /** 20 | * @dev This contract is not meant to be deployed. Instead, use a static call with the 21 | * deployment bytecode as payload. 22 | */ 23 | contract GetUniswapV2PoolDataBatchRequest { 24 | struct PoolData { 25 | address tokenA; 26 | address tokenB; 27 | uint112 reserve0; 28 | uint112 reserve1; 29 | uint8 tokenADecimals; 30 | uint8 tokenBDecimals; 31 | } 32 | 33 | constructor(address[] memory pools) { 34 | PoolData[] memory allPoolData = new PoolData[](pools.length); 35 | 36 | for (uint256 i = 0; i < pools.length; ++i) { 37 | address poolAddress = pools[i]; 38 | 39 | if (codeSizeIsZero(poolAddress)) continue; 40 | 41 | PoolData memory poolData; 42 | 43 | // Get tokens A and B 44 | poolData.tokenA = IUniswapV2Pair(poolAddress).token0(); 45 | poolData.tokenB = IUniswapV2Pair(poolAddress).token1(); 46 | 47 | // Check that tokenA and tokenB do not have codesize of 0 48 | if (codeSizeIsZero(poolData.tokenA)) continue; 49 | if (codeSizeIsZero(poolData.tokenB)) continue; 50 | 51 | // Get tokenA decimals 52 | ( 53 | bool tokenADecimalsSuccess, 54 | bytes memory tokenADecimalsData 55 | ) = poolData.tokenA.call{gas: 20000}( 56 | abi.encodeWithSignature("decimals()") 57 | ); 58 | 59 | if (tokenADecimalsSuccess) { 60 | uint256 tokenADecimals; 61 | 62 | if (tokenADecimalsData.length == 32) { 63 | (tokenADecimals) = abi.decode( 64 | tokenADecimalsData, 65 | (uint256) 66 | ); 67 | 68 | if (tokenADecimals == 0 || tokenADecimals > 255) { 69 | continue; 70 | } else { 71 | poolData.tokenADecimals = uint8(tokenADecimals); 72 | } 73 | } else { 74 | continue; 75 | } 76 | } else { 77 | continue; 78 | } 79 | 80 | // Get tokenB decimals 81 | ( 82 | bool tokenBDecimalsSuccess, 83 | bytes memory tokenBDecimalsData 84 | ) = poolData.tokenB.call{gas: 20000}( 85 | abi.encodeWithSignature("decimals()") 86 | ); 87 | 88 | if (tokenBDecimalsSuccess) { 89 | uint256 tokenBDecimals; 90 | 91 | if (tokenBDecimalsData.length == 32) { 92 | (tokenBDecimals) = abi.decode( 93 | tokenBDecimalsData, 94 | (uint256) 95 | ); 96 | 97 | if (tokenBDecimals == 0 || tokenBDecimals > 255) { 98 | continue; 99 | } else { 100 | poolData.tokenBDecimals = uint8(tokenBDecimals); 101 | } 102 | } else { 103 | continue; 104 | } 105 | } else { 106 | continue; 107 | } 108 | 109 | // Get reserves 110 | (poolData.reserve0, poolData.reserve1, ) = IUniswapV2Pair( 111 | poolAddress 112 | ).getReserves(); 113 | 114 | allPoolData[i] = poolData; 115 | } 116 | 117 | // ensure abi encoding, not needed here but increase reusability for different return types 118 | // note: abi.encode add a first 32 bytes word with the address of the original data 119 | bytes memory _abiEncodedData = abi.encode(allPoolData); 120 | 121 | assembly { 122 | // Return from the start of the data (discarding the original data address) 123 | // up to the end of the memory used 124 | let dataStart := add(_abiEncodedData, 0x20) 125 | return(dataStart, sub(msize(), dataStart)) 126 | } 127 | } 128 | 129 | function codeSizeIsZero(address target) internal view returns (bool) { 130 | if (target.code.length == 0) { 131 | return true; 132 | } else { 133 | return false; 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /contracts/src/UniswapV3/FixedPoint.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | library FixedPointMath { 5 | uint256 internal constant Q96 = 0x1000000000000000000000000; 6 | 7 | /// @notice helper function to multiply unsigned 64.64 fixed point number by a unsigned integer 8 | /// @param x 64.64 unsigned fixed point number 9 | /// @param y uint256 unsigned integer 10 | /// @return unsigned 11 | function mul64u(uint128 x, uint256 y) internal pure returns (uint256) { 12 | unchecked { 13 | if (y == 0 || x == 0) { 14 | return 0; 15 | } 16 | 17 | uint256 lo = (uint256(x) * (y & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) >> 64; 18 | uint256 hi = uint256(x) * (y >> 128); 19 | 20 | if (hi > 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) { 21 | return 0; 22 | } 23 | hi <<= 64; 24 | 25 | if (hi > 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - lo) { 26 | return 0; 27 | } 28 | return hi + lo; 29 | } 30 | } 31 | 32 | /// @notice helper to divide two unsigned integers 33 | /// @param x uint256 unsigned integer 34 | /// @param y uint256 unsigned integer 35 | /// @return unsigned 64.64 fixed point number 36 | function divuu(uint256 x, uint256 y) internal pure returns (uint128) { 37 | unchecked { 38 | if (y == 0) return 0; 39 | 40 | uint256 answer; 41 | 42 | if (x <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) { 43 | answer = (x << 64) / y; 44 | } else { 45 | uint256 msb = 192; 46 | uint256 xc = x >> 192; 47 | if (xc >= 0x100000000) { 48 | xc >>= 32; 49 | msb += 32; 50 | } 51 | if (xc >= 0x10000) { 52 | xc >>= 16; 53 | msb += 16; 54 | } 55 | if (xc >= 0x100) { 56 | xc >>= 8; 57 | msb += 8; 58 | } 59 | if (xc >= 0x10) { 60 | xc >>= 4; 61 | msb += 4; 62 | } 63 | if (xc >= 0x4) { 64 | xc >>= 2; 65 | msb += 2; 66 | } 67 | if (xc >= 0x2) msb += 1; // No need to shift xc anymore 68 | 69 | answer = (x << (255 - msb)) / (((y - 1) >> (msb - 191)) + 1); 70 | 71 | // require( 72 | // answer <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 73 | // "overflow in divuu" 74 | // ); 75 | 76 | // We ignore pools that have a price that is too high because it is likely that the reserves are too low to be accurate 77 | // There is almost certainly not a pool that has a price of token/weth > 2^128 78 | if (answer > 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) { 79 | return 0; 80 | } 81 | 82 | uint256 hi = answer * (y >> 128); 83 | uint256 lo = answer * (y & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); 84 | 85 | uint256 xh = x >> 192; 86 | uint256 xl = x << 64; 87 | 88 | if (xl < lo) xh -= 1; 89 | xl -= lo; // We rely on overflow behavior here 90 | lo = hi << 128; 91 | if (xl < lo) xh -= 1; 92 | xl -= lo; // We rely on overflow behavior here 93 | 94 | assert(xh == hi >> 128); 95 | 96 | answer += xl / y; 97 | } 98 | 99 | // We ignore pools that have a price that is too high because it is likely that the reserves are too low to be accurate 100 | // There is almost certainly not a pool that has a price of token/weth > 2^128 101 | if (answer > 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) { 102 | return 0; 103 | } 104 | 105 | return uint128(answer); 106 | } 107 | } 108 | 109 | function fromSqrtX96(uint160 sqrtPriceX96, bool token0IsReserve0, int8 token0Decimals, int8 token1Decimals) 110 | internal 111 | pure 112 | returns (uint256 priceX128) 113 | { 114 | unchecked { 115 | ///@notice Cache the difference between the input and output token decimals. p=y/x ==> p*10**(x_decimals-y_decimals)>>Q192 will be the proper price in base 10. 116 | int8 decimalShift = token0Decimals - token1Decimals; 117 | ///@notice Square the sqrtPrice ratio and normalize the value based on decimalShift. 118 | uint256 priceSquaredX96 = decimalShift < 0 119 | ? uint256(sqrtPriceX96) ** 2 / uint256(10) ** (uint8(-decimalShift)) 120 | : uint256(sqrtPriceX96) ** 2 * 10 ** uint8(decimalShift); 121 | if (Q96 > priceSquaredX96) { 122 | return 0; 123 | } 124 | ///@notice The first value is a Q96 representation of p_token0, the second is 128X fixed point representation of p_token1. 125 | uint256 priceSquaredShiftQ96 = token0IsReserve0 126 | ? priceSquaredX96 / Q96 127 | : (Q96 * 0xffffffffffffffffffffffffffffffff) / (priceSquaredX96 / Q96); 128 | 129 | ///@notice Convert the first value to 128X fixed point by shifting it left 128 bits and normalizing the value by Q96. 130 | priceX128 = token0IsReserve0 131 | ? (uint256(priceSquaredShiftQ96) * 0xffffffffffffffffffffffffffffffff) / Q96 132 | : priceSquaredShiftQ96; 133 | 134 | if (priceX128 > type(uint256).max) { 135 | // Essentially 0 liquidity. 136 | return 0; 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /contracts/src/UniswapV3/GetUniswapV3PoolDataBatchRequest.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /** 5 | * @dev This contract is not meant to be deployed. Instead, use a static call with the 6 | * deployment bytecode as payload. 7 | */ 8 | contract GetUniswapV3PoolDataBatchRequest { 9 | struct PoolInfo { 10 | address pool; 11 | address tokenA; 12 | address tokenB; 13 | int24 tickSpacing; 14 | int16 minWord; 15 | int16 maxWord; 16 | } 17 | 18 | struct TickInfo { 19 | uint128 liquidityGross; 20 | int128 liquidityNet; 21 | bool initialized; 22 | } 23 | 24 | struct PoolData { 25 | uint256[] tickBitmap; 26 | int24[] tickIndices; 27 | TickInfo[] ticks; 28 | } 29 | 30 | constructor(PoolInfo[] memory poolInfo) { 31 | PoolData[] memory allPoolData = new PoolData[](poolInfo.length); 32 | 33 | for (uint256 i = 0; i < poolInfo.length; ++i) { 34 | PoolInfo memory info = poolInfo[i]; 35 | IUniswapV3PoolState pool = IUniswapV3PoolState(info.pool); 36 | 37 | PoolData memory poolData = allPoolData[i]; 38 | uint256 wordRange = uint256(int256(info.maxWord - info.minWord)) + 1; 39 | 40 | poolData.tickBitmap = new uint256[](wordRange); 41 | 42 | TickInfo[] memory tickInfo = new TickInfo[](256 * wordRange); 43 | int24[] memory tickIdxs = new int24[](256 * wordRange); 44 | 45 | uint256 tickArrayIndex = 0; 46 | 47 | // Loop from min to max word inclusive and get all tick bitmaps 48 | uint256 wordRangeIdx = 0; 49 | for (int16 j = info.minWord; j <= info.maxWord; ++j) { 50 | uint256 tickBitmap = pool.tickBitmap(j); 51 | 52 | if (tickBitmap == 0) { 53 | continue; 54 | } 55 | 56 | for (uint256 k = 0; k < 256; ++k) { 57 | uint256 bit = 1 << k; 58 | 59 | bool initialized = (tickBitmap & bit) != 0; 60 | if (initialized) { 61 | int24 tickIndex = int24(int256(wordRangeIdx * 256 + k * uint256(int256(info.tickSpacing)))); 62 | 63 | IUniswapV3PoolState.TickInfo memory tick = pool.ticks(tickIndex); 64 | 65 | tickIdxs[tickArrayIndex] = tickIndex; 66 | tickInfo[tickArrayIndex] = TickInfo({ 67 | liquidityGross: tick.liquidityGross, 68 | liquidityNet: tick.liquidityNet, 69 | initialized: tick.initialized 70 | }); 71 | 72 | ++tickArrayIndex; 73 | } 74 | } 75 | 76 | poolData.tickBitmap[wordRangeIdx] = tickBitmap; 77 | ++wordRangeIdx; 78 | } 79 | 80 | assembly { 81 | mstore(tickInfo, tickArrayIndex) 82 | mstore(tickIdxs, tickArrayIndex) 83 | } 84 | 85 | poolData.ticks = tickInfo; 86 | poolData.tickIndices = tickIdxs; 87 | allPoolData[i] = poolData; 88 | } 89 | 90 | // ensure abi encoding, not needed here but increase reusability for different return types 91 | // note: abi.encode add a first 32 bytes word with the address of the original data 92 | bytes memory abiEncodedData = abi.encode(allPoolData); 93 | 94 | assembly { 95 | // Return from the start of the data (discarding the original data address) 96 | // up to the end of the memory used 97 | let dataStart := add(abiEncodedData, 0x20) 98 | return(dataStart, sub(msize(), dataStart)) 99 | } 100 | } 101 | } 102 | 103 | function codeSizeIsZero(address target) view returns (bool) { 104 | if (target.code.length == 0) { 105 | return true; 106 | } else { 107 | return false; 108 | } 109 | } 110 | 111 | /// @title Pool state that can change 112 | /// @notice These methods compose the pool's state, and can change with any frequency including multiple times 113 | /// per transaction 114 | interface IUniswapV3PoolState { 115 | struct TickInfo { 116 | // the total position liquidity that references this tick 117 | uint128 liquidityGross; 118 | // amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left), 119 | int128 liquidityNet; 120 | // fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) 121 | // only has relative meaning, not absolute — the value depends on when the tick is initialized 122 | uint256 feeGrowthOutside0X128; 123 | uint256 feeGrowthOutside1X128; 124 | // the cumulative tick value on the other side of the tick 125 | int56 tickCumulativeOutside; 126 | // the seconds per unit of liquidity on the _other_ side of this tick (relative to the current tick) 127 | // only has relative meaning, not absolute — the value depends on when the tick is initialized 128 | uint160 secondsPerLiquidityOutsideX128; 129 | // the seconds spent on the other side of the tick (relative to the current tick) 130 | // only has relative meaning, not absolute — the value depends on when the tick is initialized 131 | uint32 secondsOutside; 132 | // true iff the tick is initialized, i.e. the value is exactly equivalent to the expression liquidityGross != 0 133 | // these 8 bits are set to prevent fresh sstores when crossing newly initialized ticks 134 | bool initialized; 135 | } 136 | 137 | function ticks(int24 tick) external view returns (TickInfo memory); 138 | 139 | /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information 140 | function tickBitmap(int16 wordPosition) external view returns (uint256); 141 | 142 | function slot0() 143 | external 144 | view 145 | returns ( 146 | uint160 sqrtPriceX96, 147 | int24 tick, 148 | uint16 observationIndex, 149 | uint16 observationCardinality, 150 | uint16 observationCardinalityNext, 151 | uint8 feeProtocol, 152 | bool unlocked 153 | ); 154 | 155 | function liquidity() external view returns (uint128); 156 | } 157 | -------------------------------------------------------------------------------- /contracts/src/UniswapV3/GetUniswapV3PoolSlot0BatchRequest.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /** 5 | * @dev This contract is not meant to be deployed. Instead, use a static call with the 6 | * deployment bytecode as payload. 7 | */ 8 | contract GetUniswapV3PoolSlot0BatchRequest { 9 | struct Slot0Data { 10 | int24 tick; 11 | uint128 liquidity; 12 | uint256 sqrtPrice; 13 | } 14 | 15 | constructor(address[] memory pools) { 16 | Slot0Data[] memory allSlot0Data = new Slot0Data[](pools.length); 17 | 18 | for (uint256 i = 0; i < pools.length; ++i) { 19 | Slot0Data memory slot0Data = allSlot0Data[i]; 20 | address poolAddress = pools[i]; 21 | 22 | IUniswapV3PoolState pool = IUniswapV3PoolState(poolAddress); 23 | slot0Data.liquidity = pool.liquidity(); 24 | 25 | (slot0Data.sqrtPrice, slot0Data.tick, , , , , ) = pool.slot0(); 26 | 27 | allSlot0Data[i] = slot0Data; 28 | } 29 | 30 | // ensure abi encoding, not needed here but increase reusability for different return types 31 | // note: abi.encode add a first 32 bytes word with the address of the original data 32 | bytes memory abiEncodedData = abi.encode(allSlot0Data); 33 | 34 | assembly { 35 | // Return from the start of the data (discarding the original data address) 36 | // up to the end of the memory used 37 | let dataStart := add(abiEncodedData, 0x20) 38 | return(dataStart, sub(msize(), dataStart)) 39 | } 40 | } 41 | } 42 | 43 | /// @title Pool state that can change 44 | /// @notice These methods compose the pool's state, and can change with any frequency including multiple times 45 | /// per transaction 46 | interface IUniswapV3PoolState { 47 | struct TickInfo { 48 | // the total position liquidity that references this tick 49 | uint128 liquidityGross; 50 | // amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left), 51 | int128 liquidityNet; 52 | // fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) 53 | // only has relative meaning, not absolute — the value depends on when the tick is initialized 54 | uint256 feeGrowthOutside0X128; 55 | uint256 feeGrowthOutside1X128; 56 | // the cumulative tick value on the other side of the tick 57 | int56 tickCumulativeOutside; 58 | // the seconds per unit of liquidity on the _other_ side of this tick (relative to the current tick) 59 | // only has relative meaning, not absolute — the value depends on when the tick is initialized 60 | uint160 secondsPerLiquidityOutsideX128; 61 | // the seconds spent on the other side of the tick (relative to the current tick) 62 | // only has relative meaning, not absolute — the value depends on when the tick is initialized 63 | uint32 secondsOutside; 64 | // true iff the tick is initialized, i.e. the value is exactly equivalent to the expression liquidityGross != 0 65 | // these 8 bits are set to prevent fresh sstores when crossing newly initialized ticks 66 | bool initialized; 67 | } 68 | 69 | function ticks(int24 tick) external view returns (TickInfo memory); 70 | 71 | /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information 72 | function tickBitmap(int16 wordPosition) external view returns (uint256); 73 | 74 | function slot0() 75 | external 76 | view 77 | returns ( 78 | uint160 sqrtPriceX96, 79 | int24 tick, 80 | uint16 observationIndex, 81 | uint16 observationCardinality, 82 | uint16 observationCardinalityNext, 83 | uint8 feeProtocol, 84 | bool unlocked 85 | ); 86 | 87 | function liquidity() external view returns (uint128); 88 | } 89 | -------------------------------------------------------------------------------- /contracts/src/UniswapV3/GetUniswapV3PoolTickBitmapBatchRequest.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /** 5 | * @dev This contract is not meant to be deployed. Instead, use a static call with the 6 | * deployment bytecode as payload. 7 | */ 8 | contract GetUniswapV3PoolTickBitmapBatchRequest { 9 | struct TickBitmapInfo { 10 | address pool; 11 | int16 minWord; 12 | int16 maxWord; 13 | } 14 | 15 | struct TickBitmaps { 16 | int16[] wordPositions; 17 | uint256[] tickBitmaps; 18 | } 19 | 20 | constructor(TickBitmapInfo[] memory allPoolInfo) { 21 | uint256[][] memory allTickBitmaps = new uint256[][](allPoolInfo.length); 22 | 23 | for (uint256 i = 0; i < allPoolInfo.length; ++i) { 24 | TickBitmapInfo memory info = allPoolInfo[i]; 25 | IUniswapV3PoolState pool = IUniswapV3PoolState(info.pool); 26 | 27 | uint256[] memory tickBitmaps = new uint256[](uint16(info.maxWord - info.minWord) + 1); 28 | 29 | uint256 wordIdx = 0; 30 | for (int16 j = info.minWord; j <= info.maxWord; ++j) { 31 | uint256 tickBitmap = pool.tickBitmap(j); 32 | 33 | if (tickBitmap == 0) { 34 | continue; 35 | } 36 | 37 | tickBitmaps[wordIdx] = uint256(int256(j)); 38 | ++wordIdx; 39 | 40 | tickBitmaps[wordIdx] = tickBitmap; 41 | ++wordIdx; 42 | } 43 | 44 | assembly { 45 | mstore(tickBitmaps, wordIdx) 46 | } 47 | 48 | allTickBitmaps[i] = tickBitmaps; 49 | } 50 | 51 | // ensure abi encoding, not needed here but increase reusability for different return types 52 | // note: abi.encode add a first 32 bytes word with the address of the original data 53 | bytes memory abiEncodedData = abi.encode(allTickBitmaps); 54 | 55 | assembly { 56 | // Return from the start of the data (discarding the original data address) 57 | // up to the end of the memory used 58 | let dataStart := add(abiEncodedData, 0x20) 59 | return(dataStart, sub(msize(), dataStart)) 60 | } 61 | } 62 | } 63 | 64 | /// @title Pool state that can change 65 | /// @notice These methods compose the pool's state, and can change with any frequency including multiple times 66 | /// per transaction 67 | interface IUniswapV3PoolState { 68 | /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information 69 | function tickBitmap(int16 wordPosition) external view returns (uint256); 70 | function tickSpacing() external view returns (int24); 71 | } 72 | -------------------------------------------------------------------------------- /contracts/src/UniswapV3/GetUniswapV3PoolTickDataBatchRequest.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /** 5 | * @dev This contract is not meant to be deployed. Instead, use a static call with the 6 | * deployment bytecode as payload. 7 | */ 8 | 9 | contract GetUniswapV3PoolTickDataBatchRequest { 10 | struct TickDataInfo { 11 | address pool; 12 | int24[] ticks; 13 | } 14 | 15 | struct Info { 16 | bool initialized; 17 | uint128 liquidityGross; 18 | int128 liquidityNet; 19 | } 20 | 21 | constructor(TickDataInfo[] memory allPoolInfo) { 22 | Info[][] memory tickInfoReturn = new Info[][](allPoolInfo.length); 23 | 24 | for (uint256 i = 0; i < allPoolInfo.length; ++i) { 25 | Info[] memory tickInfo = new Info[](allPoolInfo[i].ticks.length); 26 | for (uint256 j = 0; j < allPoolInfo[i].ticks.length; ++j) { 27 | IUniswapV3PoolState.Info memory tick = IUniswapV3PoolState( 28 | allPoolInfo[i].pool 29 | ).ticks(allPoolInfo[i].ticks[j]); 30 | 31 | tickInfo[j] = Info({ 32 | liquidityGross: tick.liquidityGross, 33 | liquidityNet: tick.liquidityNet, 34 | initialized: tick.initialized 35 | }); 36 | } 37 | tickInfoReturn[i] = tickInfo; 38 | } 39 | 40 | // ensure abi encoding, not needed here but increase reusability for different return types 41 | // note: abi.encode add a first 32 bytes word with the address of the original data 42 | bytes memory abiEncodedData = abi.encode(tickInfoReturn); 43 | 44 | assembly { 45 | // Return from the start of the data (discarding the original data address) 46 | // up to the end of the memory used 47 | let dataStart := add(abiEncodedData, 0x20) 48 | return(dataStart, sub(msize(), dataStart)) 49 | } 50 | } 51 | } 52 | 53 | /// @title Pool state that can change 54 | /// @notice These methods compose the pool's state, and can change with any frequency including multiple times 55 | /// per transaction 56 | interface IUniswapV3PoolState { 57 | struct Info { 58 | // the total position liquidity that references this tick 59 | uint128 liquidityGross; 60 | // amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left), 61 | int128 liquidityNet; 62 | // fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick) 63 | // only has relative meaning, not absolute — the value depends on when the tick is initialized 64 | uint256 feeGrowthOutside0X128; 65 | uint256 feeGrowthOutside1X128; 66 | // the cumulative tick value on the other side of the tick 67 | int56 tickCumulativeOutside; 68 | // the seconds per unit of liquidity on the _other_ side of this tick (relative to the current tick) 69 | // only has relative meaning, not absolute — the value depends on when the tick is initialized 70 | uint160 secondsPerLiquidityOutsideX128; 71 | // the seconds spent on the other side of the tick (relative to the current tick) 72 | // only has relative meaning, not absolute — the value depends on when the tick is initialized 73 | uint32 secondsOutside; 74 | // true iff the tick is initialized, i.e. the value is exactly equivalent to the expression liquidityGross != 0 75 | // these 8 bits are set to prevent fresh sstores when crossing newly initialized ticks 76 | bool initialized; 77 | } 78 | 79 | /// @notice Returns 256 packed tick initialized boolean values. See TickBitmap for more information 80 | function tickBitmap(int16 wordPosition) external view returns (uint256); 81 | function ticks(int24 tick) external view returns (Info memory); 82 | } 83 | -------------------------------------------------------------------------------- /contracts/src/UniswapV3/interfaces/IBalancer.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IBPool { 5 | function getCurrentTokens() external returns (address[] memory); 6 | function getDenormalizedWeight(address token) external returns (uint256); 7 | function getSwapFee() external returns (uint256); 8 | function getBalance(address token) external returns (uint256); 9 | } 10 | -------------------------------------------------------------------------------- /contracts/src/UniswapV3/interfaces/IUniswapV2.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IUniswapV2Pair { 5 | function token0() external view returns (address); 6 | function token1() external view returns (address); 7 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 8 | } 9 | 10 | interface IUniswapV2Factory { 11 | function allPairs(uint256 idx) external returns (address); 12 | function getPair(address tokenA, address tokenB) external view returns (address pair); 13 | } 14 | -------------------------------------------------------------------------------- /contracts/src/UniswapV3/interfaces/IUniswapV3.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IUniswapV3Pool { 5 | function fee() external view returns (uint24); 6 | function tickSpacing() external view returns (int24); 7 | function liquidity() external view returns (uint128); 8 | function slot0() 9 | external 10 | view 11 | returns ( 12 | uint160 sqrtPriceX96, 13 | int24 tick, 14 | uint16 observationIndex, 15 | uint16 observationCardinality, 16 | uint16 observationCardinalityNext, 17 | uint8 feeProtocol, 18 | bool unlocked 19 | ); 20 | function ticks(int24 tick) 21 | external 22 | view 23 | returns ( 24 | uint128 liquidityGross, 25 | int128 liquidityNet, 26 | uint256 feeGrowthOutside0X128, 27 | uint256 feeGrowthOutside1X128, 28 | int56 tickCumulativeOutside, 29 | uint160 secondsPerLiquidityOutsideX128, 30 | uint32 secondsOutside, 31 | bool initialized 32 | ); 33 | } 34 | 35 | interface IUniswapV3Factory { 36 | function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool); 37 | } 38 | -------------------------------------------------------------------------------- /contracts/src/UniswapV3/interfaces/Token.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IERC20 { 5 | function decimals() external view returns (uint8); 6 | function balanceOf(address) external view returns (uint256); 7 | } 8 | -------------------------------------------------------------------------------- /contracts/src/filters/WethValueInPools.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.26; 3 | 4 | import {IBPool} from "../interfaces/IBalancer.sol"; 5 | import {IUniswapV2Pair} from "../interfaces/IUniswapV2.sol"; 6 | import {IUniswapV2Factory} from "../interfaces/IUniswapV2.sol"; 7 | import {IUniswapV3Pool} from "../interfaces/IUniswapV3.sol"; 8 | import {IUniswapV3Factory} from "../interfaces/IUniswapV3.sol"; 9 | import {IERC20} from "../interfaces/Token.sol"; 10 | import {FixedPointMath} from "../UniswapV3/FixedPoint.sol"; 11 | 12 | contract WethValueInPools { 13 | /// @notice Address of Uniswap V2 factory 14 | /// @dev Used as the first priority for quoting WETH value 15 | address UNISWAP_V2_FACTORY; 16 | // 17 | /// @notice Address of Uniswap V3 factory 18 | /// @dev Used as the second priority for quoting WETH value 19 | address UNISWAP_V3_FACTORY; 20 | 21 | /// @notice Address of WETH 22 | address WETH; 23 | 24 | /// @notice The minimum WETH liquidity to consider a `quote` valid. 25 | uint256 private constant MIN_WETH_LIQUIDITY = 1 ether; 26 | 27 | address private constant ADDRESS_ZERO = address(0); 28 | 29 | uint8 private constant WETH_DECIMALS = 18; 30 | 31 | constructor(address _uniswapV2Factory, address _uniswapV3Factory, address _weth) { 32 | UNISWAP_V2_FACTORY = _uniswapV2Factory; 33 | UNISWAP_V3_FACTORY = _uniswapV3Factory; 34 | WETH = _weth; 35 | } 36 | 37 | /// @notice Enum for pool types 38 | enum PoolType { 39 | Balancer, 40 | UniswapV2, 41 | UniswapV3 42 | } 43 | 44 | /// @notice Struct for pool info 45 | struct PoolInfo { 46 | PoolType poolType; 47 | address poolAddress; 48 | } 49 | 50 | /// @notice Struct for pool info return 51 | struct PoolInfoReturn { 52 | PoolType poolType; 53 | address poolAddress; 54 | uint256 wethValue; 55 | } 56 | 57 | /// @notice Returns an array of `PoolInfoReturn` for the consumer to determine wether to filter or not to save gas. 58 | /// @dev We require a 1 ETH minimum liquidity in the quoting pool for it to be considered. 59 | function getWethValueInPools(PoolInfo[] memory pools) public returns (PoolInfoReturn[] memory) { 60 | PoolInfoReturn[] memory poolInfoReturns = new PoolInfoReturn[](pools.length); 61 | for (uint256 i = 0; i < pools.length; i++) { 62 | PoolInfo memory info = pools[i]; 63 | if (info.poolType == PoolType.Balancer) { 64 | uint256 wethValue = handleBalancerPool(info.poolAddress); 65 | poolInfoReturns[i] = PoolInfoReturn(info.poolType, info.poolAddress, wethValue); 66 | } else if (info.poolType == PoolType.UniswapV2) { 67 | uint256 wethValue = handleUniswapV2Pool(info.poolAddress); 68 | poolInfoReturns[i] = PoolInfoReturn(info.poolType, info.poolAddress, wethValue); 69 | } else if (info.poolType == PoolType.UniswapV3) { 70 | uint256 wethValue = handleUniswapV3Pool(info.poolAddress); 71 | poolInfoReturns[i] = PoolInfoReturn(info.poolType, info.poolAddress, wethValue); 72 | } 73 | } 74 | return poolInfoReturns; 75 | } 76 | 77 | function handleBalancerPool(address pool) internal returns (uint256) { 78 | // Get pool tokens 79 | address[] memory tokens; 80 | try IBPool(pool).getCurrentTokens() returns (address[] memory _tokens) { 81 | tokens = _tokens; 82 | } catch { 83 | return 0; 84 | } 85 | 86 | // First check if we have WETH in the pool. If so, return Weth Value * # of tokens in the pool. 87 | for (uint256 i = 0; i < tokens.length; i++) { 88 | if (tokens[i] == WETH) { 89 | try IBPool(pool).getBalance(tokens[i]) returns (uint256 _balance) { 90 | // Obviously assuming an even distribution of value. Which is a "good enough" approximation. 91 | // For a value filter. 92 | return _balance * tokens.length; 93 | } catch { 94 | return 0; 95 | } 96 | } 97 | } 98 | 99 | address baseToken = tokens[0]; 100 | uint256 balance; 101 | try IBPool(pool).getBalance(baseToken) returns (uint256 _balance) { 102 | balance = _balance; 103 | } catch { 104 | return 0; 105 | } 106 | 107 | uint256 wethValue = quoteTokenToWethValue(baseToken, balance); 108 | return wethValue * tokens.length; 109 | } 110 | 111 | function handleUniswapV2Pool(address pool) internal returns (uint256) { 112 | address token0; 113 | try IUniswapV2Pair(pool).token0() returns (address _token0) { 114 | token0 = _token0; 115 | } catch { 116 | return 0; 117 | } 118 | address token1; 119 | try IUniswapV2Pair(pool).token1() returns (address _token1) { 120 | token1 = _token1; 121 | } catch { 122 | return 0; 123 | } 124 | try IUniswapV2Pair(pool).getReserves() returns (uint112 reserve0, uint112 reserve1, uint32) { 125 | if (token0 == WETH) { 126 | return reserve0 * 2; 127 | } else if (token1 == WETH) { 128 | return reserve1 * 2; 129 | } 130 | // No WETH in the pool Quote token0. 131 | uint256 wethValue = quoteTokenToWethValue(token0, reserve0); 132 | return wethValue * 2; 133 | } catch { 134 | return 0; 135 | } 136 | } 137 | 138 | function handleUniswapV3Pool(address pool) internal returns (uint256) { 139 | address token0; 140 | try IUniswapV2Pair(address(pool)).token0() returns (address _token0) { 141 | token0 = _token0; 142 | } catch { 143 | return 0; 144 | } 145 | address token1; 146 | try IUniswapV2Pair(address(pool)).token1() returns (address _token1) { 147 | token1 = _token1; 148 | } catch { 149 | return 0; 150 | } 151 | 152 | if (token0 == WETH) { 153 | try IERC20(token0).balanceOf(address(pool)) returns (uint256 balance) { 154 | return balance * 2; 155 | } catch { 156 | return 0; 157 | } 158 | } else if (token1 == WETH) { 159 | try IERC20(token1).balanceOf(address(pool)) returns (uint256 balance) { 160 | return balance * 2; 161 | } catch { 162 | return 0; 163 | } 164 | } 165 | 166 | // No WETH in the pool Quote token0. 167 | try IERC20(token0).balanceOf(address(pool)) returns (uint256 balance) { 168 | uint256 wethValue = quoteTokenToWethValue(token0, balance); 169 | return wethValue * 2; 170 | } catch { 171 | return 0; 172 | } 173 | } 174 | 175 | /// @dev Returns the value of `amount` of `token` in terms of WETH. 176 | function quoteTokenToWethValue(address token, uint256 amount) internal returns (uint256) { 177 | // Try Uniswap V2. 178 | uint128 price = quoteToken(token); 179 | if (price > 0) { 180 | return FixedPointMath.mul64u(price, amount); 181 | } else { 182 | return price; 183 | } 184 | } 185 | 186 | /// @dev Quotes a Q64 quote of `token` in terms of WETH. 187 | function quoteToken(address token) internal returns (uint128) { 188 | // Get the token decimals 189 | uint128 price; 190 | // Try Uniswap V2. 191 | price = quoteTokenUniswapV2(token); 192 | if (price > 0) { 193 | return price; 194 | } 195 | // Try Uniswap V3. 196 | price = quoteTokenUniswapV3(token); 197 | return price; 198 | } 199 | 200 | function quoteTokenUniswapV2(address token) internal returns (uint128 price) { 201 | // Get the pair 202 | IUniswapV2Pair pair = IUniswapV2Pair(IUniswapV2Factory(UNISWAP_V2_FACTORY).getPair(token, WETH)); 203 | if (address(pair) == ADDRESS_ZERO) { 204 | return 0; 205 | } 206 | 207 | // Get the reserves 208 | // (uint112 reserve0, uint112 reserve1, ) = pair.getReserves(); 209 | uint112 reserve0; 210 | uint112 reserve1; 211 | try pair.getReserves() returns (uint112 _reserve0, uint112 _reserve1, uint32) { 212 | reserve0 = _reserve0; 213 | reserve1 = _reserve1; 214 | } catch { 215 | return 0; 216 | } 217 | if (reserve0 == 0 || reserve1 == 0) { 218 | return 0; 219 | } 220 | 221 | // Get the decimals of token. 222 | (uint8 tokenDecimals, bool tokenDecimalsSuccess) = getTokenDecimalsUnsafe(token); 223 | if (!tokenDecimalsSuccess) { 224 | return 0; 225 | } 226 | 227 | // Normalize r0/r1 to 18 decimals. 228 | uint112 reserveWeth = token < WETH ? reserve1 : reserve0; 229 | uint112 reserveToken = token < WETH ? reserve0 : reserve1; 230 | 231 | reserveToken = tokenDecimals <= WETH_DECIMALS 232 | ? uint112(reserveToken * 10 ** (WETH_DECIMALS - tokenDecimals)) 233 | : uint112(reserveToken / 10 ** (tokenDecimals - WETH_DECIMALS)); 234 | price = FixedPointMath.divuu(reserveWeth, reserveToken); 235 | } 236 | 237 | function quoteTokenUniswapV3(address token) internal returns (uint128) { 238 | uint16[3] memory feeTiers = [500, 3000, 10000]; 239 | IUniswapV3Pool pool; 240 | for (uint256 i = 0; i < feeTiers.length; ++i) { 241 | // Get the pool 242 | IUniswapV3Pool pair = 243 | IUniswapV3Pool(IUniswapV3Factory(UNISWAP_V3_FACTORY).getPool(token, WETH, feeTiers[i])); 244 | if (address(pool) != ADDRESS_ZERO) { 245 | pool = pair; 246 | break; 247 | } 248 | } 249 | 250 | if (address(pool) == ADDRESS_ZERO) { 251 | return 0; 252 | } 253 | 254 | // Get slot 0 sqrtPriceX96 255 | uint160 sqrtPriceX96; 256 | try pool.slot0() returns (uint160 _sqrtPriceX96, int24, uint16, uint16, uint16, uint8, bool) { 257 | sqrtPriceX96 = _sqrtPriceX96; 258 | } catch { 259 | return 0; 260 | } 261 | 262 | bool token0IsReserve0 = token < WETH; 263 | (uint8 tokenDecimals, bool token0DecimalsSuccess) = getTokenDecimalsUnsafe(token); 264 | if (!token0DecimalsSuccess) { 265 | return 0; 266 | } 267 | // Q128 -> Q64 268 | return uint128( 269 | FixedPointMath.fromSqrtX96( 270 | sqrtPriceX96, 271 | token0IsReserve0, 272 | token0IsReserve0 ? int8(tokenDecimals) : int8(WETH_DECIMALS), 273 | token0IsReserve0 ? int8(WETH_DECIMALS) : int8(tokenDecimals) 274 | ) >> 64 275 | ); 276 | } 277 | 278 | /// @notice returns true as the second return value if the token decimals can be successfully retrieved 279 | function getTokenDecimalsUnsafe(address token) internal returns (uint8, bool) { 280 | (bool tokenDecimalsSuccess, bytes memory tokenDecimalsData) = 281 | token.call{gas: 20000}(abi.encodeWithSignature("decimals()")); 282 | 283 | if (tokenDecimalsSuccess) { 284 | uint256 tokenDecimals; 285 | 286 | if (tokenDecimalsData.length == 32) { 287 | (tokenDecimals) = abi.decode(tokenDecimalsData, (uint256)); 288 | 289 | if (tokenDecimals == 0 || tokenDecimals > 255) { 290 | return (0, false); 291 | } else { 292 | return (uint8(tokenDecimals), true); 293 | } 294 | } else { 295 | return (0, false); 296 | } 297 | } else { 298 | return (0, false); 299 | } 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /contracts/src/filters/WethValueInPoolsBatchRequest.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.26; 3 | 4 | import "./WethValueInPools.sol"; 5 | 6 | contract WethValueInPoolsBatchRequest is WethValueInPools { 7 | constructor( 8 | address _uniswapV2Factory, 9 | address _uniswapV3Factory, 10 | address _weth, 11 | WethValueInPools.PoolInfo[] memory pools 12 | ) WethValueInPools(_uniswapV2Factory, _uniswapV3Factory, _weth) { 13 | WethValueInPools.PoolInfoReturn[] memory poolInfoReturn = getWethValueInPools(pools); 14 | // insure abi encoding, not needed here but increase reusability for different return types 15 | // note: abi.encode add a first 32 bytes word with the address of the original data 16 | bytes memory abiEncodedData = abi.encode(poolInfoReturn); 17 | assembly { 18 | // Return from the start of the data (discarding the original data address) 19 | // up to the end of the memory used 20 | let dataStart := add(abiEncodedData, 0x20) 21 | return(dataStart, sub(msize(), dataStart)) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/src/interfaces/IBalancer.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IBPool { 5 | function getCurrentTokens() external returns (address[] memory); 6 | function getDenormalizedWeight(address token) external returns (uint256); 7 | function getSwapFee() external returns (uint256); 8 | function getBalance(address token) external returns (uint256); 9 | } 10 | -------------------------------------------------------------------------------- /contracts/src/interfaces/IUniswapV2.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IUniswapV2Pair { 5 | function token0() external view returns (address); 6 | function token1() external view returns (address); 7 | function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); 8 | } 9 | 10 | interface IUniswapV2Factory { 11 | function allPairs(uint256 idx) external returns (address); 12 | function getPair(address tokenA, address tokenB) external view returns (address pair); 13 | } 14 | -------------------------------------------------------------------------------- /contracts/src/interfaces/IUniswapV3.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IUniswapV3Pool { 5 | function fee() external view returns (uint24); 6 | function tickSpacing() external view returns (int24); 7 | function liquidity() external view returns (uint128); 8 | function slot0() 9 | external 10 | view 11 | returns ( 12 | uint160 sqrtPriceX96, 13 | int24 tick, 14 | uint16 observationIndex, 15 | uint16 observationCardinality, 16 | uint16 observationCardinalityNext, 17 | uint8 feeProtocol, 18 | bool unlocked 19 | ); 20 | function ticks(int24 tick) 21 | external 22 | view 23 | returns ( 24 | uint128 liquidityGross, 25 | int128 liquidityNet, 26 | uint256 feeGrowthOutside0X128, 27 | uint256 feeGrowthOutside1X128, 28 | int56 tickCumulativeOutside, 29 | uint160 secondsPerLiquidityOutsideX128, 30 | uint32 secondsOutside, 31 | bool initialized 32 | ); 33 | } 34 | 35 | interface IUniswapV3Factory { 36 | function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool); 37 | } 38 | -------------------------------------------------------------------------------- /contracts/src/interfaces/Token.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IERC20 { 5 | function decimals() external view returns (uint8); 6 | function balanceOf(address) external view returns (uint256); 7 | } 8 | -------------------------------------------------------------------------------- /contracts/test/FixedPoint.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../src/UniswapV3/FixedPoint.sol"; 6 | 7 | contract FixedPointTest is Test { 8 | /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK) 9 | uint160 internal constant MIN_SQRT_RATIO = 4295128739; 10 | /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) 11 | uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; 12 | 13 | function setUp() public {} 14 | 15 | function test_divuu_never_reverts(uint128 a, uint128 b) public pure { 16 | FixedPointMath.divuu(a, b); 17 | } 18 | 19 | function test_mul64u_never_reverts(uint128 a, uint256 b) public pure { 20 | FixedPointMath.mul64u(a, b); 21 | } 22 | 23 | function test_from_sqrt_x_96_never_reverts( 24 | uint160 x, 25 | bool token0IsReserve0, 26 | int8 token0Decimals, 27 | int8 token1Decimals 28 | ) public pure { 29 | // Bound x from min_sqrt_x_96 to max_sqrt_x_96 30 | if (x >= MIN_SQRT_RATIO || x <= MAX_SQRT_RATIO) { 31 | FixedPointMath.fromSqrtX96(x, token0IsReserve0, token0Decimals, token1Decimals); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/test/WethValueInPools.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../src/filters/WethValueInPools.sol"; 6 | 7 | contract WethValueInPoolsTest is Test { 8 | WethValueInPools public wethValueInPools; 9 | address uniswapV2Factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; 10 | address uniswapV3Factory = 0x1F98431c8aD98523631AE4a59f267346ea31F984; 11 | address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 12 | address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; 13 | address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 14 | 15 | function setUp() public { 16 | wethValueInPools = new WethValueInPools(uniswapV2Factory, uniswapV3Factory, WETH); 17 | } 18 | 19 | function test_getWethValueInPools_validWeth() public { 20 | WethValueInPools.PoolInfo[] memory testFixtureValidWeth = new WethValueInPools.PoolInfo[](3); 21 | testFixtureValidWeth[0] = WethValueInPools.PoolInfo({ 22 | poolType: WethValueInPools.PoolType.Balancer, 23 | poolAddress: 0x8a649274E4d777FFC6851F13d23A86BBFA2f2Fbf 24 | }); 25 | testFixtureValidWeth[1] = WethValueInPools.PoolInfo({ 26 | poolType: WethValueInPools.PoolType.UniswapV2, 27 | poolAddress: 0x397FF1542f962076d0BFE58eA045FfA2d347ACa0 28 | }); 29 | testFixtureValidWeth[2] = WethValueInPools.PoolInfo({ 30 | poolType: WethValueInPools.PoolType.UniswapV3, 31 | poolAddress: 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 32 | }); 33 | WethValueInPools.PoolInfoReturn[] memory pools = wethValueInPools.getWethValueInPools(testFixtureValidWeth); 34 | assertEq(pools.length, 3); 35 | // Check weth value > 0 36 | assertGt(pools[0].wethValue, 0); 37 | assertGt(pools[1].wethValue, 0); 38 | assertGt(pools[2].wethValue, 0); 39 | } 40 | 41 | function test_getWethValueInPools_validNoWeth() public { 42 | WethValueInPools.PoolInfo[] memory testFixtureValidNoWeth = new WethValueInPools.PoolInfo[](3); 43 | testFixtureValidNoWeth[0] = WethValueInPools.PoolInfo({ 44 | poolType: WethValueInPools.PoolType.Balancer, 45 | poolAddress: 0xE5D1fAB0C5596ef846DCC0958d6D0b20E1Ec4498 46 | }); 47 | testFixtureValidNoWeth[1] = WethValueInPools.PoolInfo({ 48 | poolType: WethValueInPools.PoolType.UniswapV2, 49 | poolAddress: 0xAE461cA67B15dc8dc81CE7615e0320dA1A9aB8D5 50 | }); 51 | testFixtureValidNoWeth[2] = WethValueInPools.PoolInfo({ 52 | poolType: WethValueInPools.PoolType.UniswapV3, 53 | poolAddress: 0x6c6Bc977E13Df9b0de53b251522280BB72383700 54 | }); 55 | WethValueInPools.PoolInfoReturn[] memory pools = wethValueInPools.getWethValueInPools(testFixtureValidNoWeth); 56 | assertEq(pools.length, 3); 57 | // Check weth value > 0 58 | assertGt(pools[0].wethValue, 0); 59 | assertGt(pools[1].wethValue, 0); 60 | assertGt(pools[2].wethValue, 0); 61 | } 62 | 63 | function test_getWethValueInPools_invalid_no_revert() public { 64 | WethValueInPools.PoolInfo[] memory testFixtureInvalid = new WethValueInPools.PoolInfo[](3); 65 | testFixtureInvalid[0] = WethValueInPools.PoolInfo({ 66 | poolType: WethValueInPools.PoolType.Balancer, 67 | poolAddress: 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f 68 | }); 69 | testFixtureInvalid[1] = WethValueInPools.PoolInfo({ 70 | poolType: WethValueInPools.PoolType.UniswapV2, 71 | poolAddress: 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f 72 | }); 73 | testFixtureInvalid[2] = WethValueInPools.PoolInfo({ 74 | poolType: WethValueInPools.PoolType.UniswapV3, 75 | poolAddress: 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f 76 | }); 77 | WethValueInPools.PoolInfoReturn[] memory pools = wethValueInPools.getWethValueInPools(testFixtureInvalid); 78 | assertEq(pools.length, 3); 79 | // Should all be zero 80 | assertEq(pools[0].wethValue, 0); 81 | assertEq(pools[1].wethValue, 0); 82 | assertEq(pools[2].wethValue, 0); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /contracts/test/WethValueInPoolsBatchRequest.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../src/filters/WethValueInPoolsBatchRequest.sol"; 6 | 7 | contract WethValueInPoolsBatchRequestTest is Test { 8 | address uniswapV2Factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; 9 | address uniswapV3Factory = 0x1F98431c8aD98523631AE4a59f267346ea31F984; 10 | address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 11 | address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; 12 | address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 13 | 14 | function setUp() public {} 15 | 16 | function test_WethValueInPoolsBatchRequest_validWeth() public { 17 | WethValueInPools.PoolInfo[] memory testFixtureValidWeth = new WethValueInPools.PoolInfo[](3); 18 | testFixtureValidWeth[0] = WethValueInPools.PoolInfo({ 19 | poolType: WethValueInPools.PoolType.Balancer, 20 | poolAddress: 0x8a649274E4d777FFC6851F13d23A86BBFA2f2Fbf 21 | }); 22 | testFixtureValidWeth[1] = WethValueInPools.PoolInfo({ 23 | poolType: WethValueInPools.PoolType.UniswapV2, 24 | poolAddress: 0x397FF1542f962076d0BFE58eA045FfA2d347ACa0 25 | }); 26 | testFixtureValidWeth[2] = WethValueInPools.PoolInfo({ 27 | poolType: WethValueInPools.PoolType.UniswapV3, 28 | poolAddress: 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 29 | }); 30 | 31 | bytes memory returnData = address( 32 | new WethValueInPoolsBatchRequest(uniswapV2Factory, uniswapV3Factory, WETH, testFixtureValidWeth) 33 | ).code; 34 | WethValueInPools.PoolInfoReturn[] memory pools = abi.decode(returnData, (WethValueInPools.PoolInfoReturn[])); 35 | 36 | assertEq(pools.length, 3); 37 | // Check weth value > 0 and valid pool address and pool type 38 | for (uint256 i = 0; i < pools.length; i++) { 39 | assertGt(pools[i].wethValue, 0); 40 | assertEq(uint8(pools[i].poolType), uint8(testFixtureValidWeth[i].poolType)); 41 | assertEq(pools[i].poolAddress, testFixtureValidWeth[i].poolAddress); 42 | } 43 | } 44 | 45 | function test_WethValueInPoolsBatchRequest_validNoWeth() public { 46 | WethValueInPools.PoolInfo[] memory testFixtureValidNoWeth = new WethValueInPools.PoolInfo[](3); 47 | testFixtureValidNoWeth[0] = WethValueInPools.PoolInfo({ 48 | poolType: WethValueInPools.PoolType.Balancer, 49 | poolAddress: 0xE5D1fAB0C5596ef846DCC0958d6D0b20E1Ec4498 50 | }); 51 | testFixtureValidNoWeth[1] = WethValueInPools.PoolInfo({ 52 | poolType: WethValueInPools.PoolType.UniswapV2, 53 | poolAddress: 0xAE461cA67B15dc8dc81CE7615e0320dA1A9aB8D5 54 | }); 55 | testFixtureValidNoWeth[2] = WethValueInPools.PoolInfo({ 56 | poolType: WethValueInPools.PoolType.UniswapV3, 57 | poolAddress: 0x6c6Bc977E13Df9b0de53b251522280BB72383700 58 | }); 59 | 60 | bytes memory returnData = address( 61 | new WethValueInPoolsBatchRequest(uniswapV2Factory, uniswapV3Factory, WETH, testFixtureValidNoWeth) 62 | ).code; 63 | WethValueInPools.PoolInfoReturn[] memory pools = abi.decode(returnData, (WethValueInPools.PoolInfoReturn[])); 64 | 65 | assertEq(pools.length, 3); 66 | // Check weth value > 0 and valid pool address and pool type 67 | for (uint256 i = 0; i < pools.length; i++) { 68 | assertGt(pools[i].wethValue, 0); 69 | assertEq(uint8(pools[i].poolType), uint8(testFixtureValidNoWeth[i].poolType)); 70 | assertEq(pools[i].poolAddress, testFixtureValidNoWeth[i].poolAddress); 71 | } 72 | } 73 | 74 | function test_WethValueInPoolsBatchRequest_invalid_no_revert() public { 75 | WethValueInPools.PoolInfo[] memory testFixtureInvalid = new WethValueInPools.PoolInfo[](3); 76 | testFixtureInvalid[0] = WethValueInPools.PoolInfo({ 77 | poolType: WethValueInPools.PoolType.Balancer, 78 | poolAddress: 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f 79 | }); 80 | testFixtureInvalid[1] = WethValueInPools.PoolInfo({ 81 | poolType: WethValueInPools.PoolType.UniswapV2, 82 | poolAddress: 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f 83 | }); 84 | testFixtureInvalid[2] = WethValueInPools.PoolInfo({ 85 | poolType: WethValueInPools.PoolType.UniswapV3, 86 | poolAddress: 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f 87 | }); 88 | 89 | bytes memory returnData = 90 | address(new WethValueInPoolsBatchRequest(uniswapV2Factory, uniswapV3Factory, WETH, testFixtureInvalid)).code; 91 | WethValueInPools.PoolInfoReturn[] memory pools = abi.decode(returnData, (WethValueInPools.PoolInfoReturn[])); 92 | 93 | assertEq(pools.length, 3); 94 | // All weth values should be zero 95 | for (uint256 i = 0; i < pools.length; i++) { 96 | assertEq(pools[i].wethValue, 0); 97 | assertEq(uint8(pools[i].poolType), uint8(testFixtureInvalid[i].poolType)); 98 | assertEq(pools[i].poolAddress, testFixtureInvalid[i].poolAddress); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | - package-ecosystem: "docker" 8 | directory: "/" 9 | schedule: 10 | interval: "monthly" 11 | - package-ecosystem: "github-actions" 12 | directory: "/" 13 | schedule: 14 | interval: "monthly" -------------------------------------------------------------------------------- /examples/filters.rs: -------------------------------------------------------------------------------- 1 | use alloy::{ 2 | primitives::address, 3 | providers::ProviderBuilder, 4 | rpc::client::ClientBuilder, 5 | transports::layers::{RetryBackoffLayer, ThrottleLayer}, 6 | }; 7 | use amms::{ 8 | amms::uniswap_v2::UniswapV2Factory, 9 | state_space::{ 10 | filters::whitelist::{PoolWhitelistFilter, TokenWhitelistFilter}, 11 | StateSpaceBuilder, 12 | }, 13 | }; 14 | use std::sync::Arc; 15 | 16 | #[tokio::main] 17 | async fn main() -> eyre::Result<()> { 18 | tracing_subscriber::fmt::init(); 19 | let rpc_endpoint = std::env::var("ETHEREUM_PROVIDER")?; 20 | 21 | let client = ClientBuilder::default() 22 | .layer(ThrottleLayer::new(500)) 23 | .layer(RetryBackoffLayer::new(5, 200, 330)) 24 | .http(rpc_endpoint.parse()?); 25 | 26 | let provider = Arc::new(ProviderBuilder::new().connect_client(client)); 27 | 28 | let factories = vec![ 29 | // UniswapV2 30 | UniswapV2Factory::new( 31 | address!("5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"), 32 | 300, 33 | 10000835, 34 | ) 35 | .into(), 36 | ]; 37 | 38 | /* PoolFilters are applied all AMMs when syncing the state space. 39 | Filters have two "stages", `FilterStage::Discovery` or `FilterStage::Sync`. 40 | Discovery filters are applied to AMMs after the `StateSpaceManager` has processed all pool created events. 41 | Sync filters are applied to AMMs after the `StateSpaceManager` has processed all pool sync events. 42 | This allows for efficient syncing of the state space by minimizing the amount of pools that need to sync state. 43 | In the following example, the `PoolWhitelistFilter` is applied to the `Discovery` stage 44 | and the `TokenWhitelistFilter` is applied to the `Sync` stage. Rather than syncing all pools from the factory, 45 | only the whitelisted pools are synced. The `TokenWhitelistFilter` is applied after syncing since pool creation logs 46 | do not always emit the tokens included in the pool, but this data will always be populated after syncing. 47 | */ 48 | let filters = vec![ 49 | PoolWhitelistFilter::new(vec![address!("88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")]).into(), 50 | TokenWhitelistFilter::new(vec![address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")]) 51 | .into(), 52 | ]; 53 | 54 | let _state_space_manager = StateSpaceBuilder::new(provider.clone()) 55 | .with_factories(factories) 56 | .with_filters(filters) 57 | .sync() 58 | .await; 59 | 60 | Ok(()) 61 | } 62 | -------------------------------------------------------------------------------- /examples/simulate_swap.rs: -------------------------------------------------------------------------------- 1 | use alloy::eips::BlockId; 2 | use alloy::primitives::{Address, U256}; 3 | use alloy::transports::layers::ThrottleLayer; 4 | use alloy::{ 5 | primitives::address, providers::ProviderBuilder, rpc::client::ClientBuilder, 6 | transports::layers::RetryBackoffLayer, 7 | }; 8 | use amms::amms::amm::AutomatedMarketMaker; 9 | use amms::amms::uniswap_v3::UniswapV3Pool; 10 | use std::sync::Arc; 11 | 12 | #[tokio::main] 13 | async fn main() -> eyre::Result<()> { 14 | let rpc_endpoint = std::env::var("ETHEREUM_PROVIDER")?; 15 | let client = ClientBuilder::default() 16 | .layer(ThrottleLayer::new(50)) 17 | .layer(RetryBackoffLayer::new(5, 200, 330)) 18 | .http(rpc_endpoint.parse()?); 19 | 20 | let provider = Arc::new(ProviderBuilder::new().connect_client(client)); 21 | 22 | let pool = UniswapV3Pool::new(address!("88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")) 23 | .init(BlockId::latest(), provider) 24 | .await?; 25 | 26 | // Note that the token out does not need to be specified when 27 | // simulating a swap for pools with only two tokens. 28 | let amount_out = pool.simulate_swap( 29 | pool.token_a.address, 30 | Address::default(), 31 | U256::from(1000000), 32 | )?; 33 | println!("Amount out: {:?}", amount_out); 34 | 35 | Ok(()) 36 | } 37 | -------------------------------------------------------------------------------- /examples/state_space_builder.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use alloy::{ 4 | primitives::address, 5 | providers::ProviderBuilder, 6 | rpc::client::ClientBuilder, 7 | transports::layers::{RetryBackoffLayer, ThrottleLayer}, 8 | }; 9 | use amms::{ 10 | amms::{ 11 | erc_4626::ERC4626Vault, 12 | uniswap_v2::{UniswapV2Factory, UniswapV2Pool}, 13 | uniswap_v3::{UniswapV3Factory, UniswapV3Pool}, 14 | }, 15 | state_space::StateSpaceBuilder, 16 | }; 17 | 18 | #[tokio::main] 19 | async fn main() -> eyre::Result<()> { 20 | tracing_subscriber::fmt::init(); 21 | let rpc_endpoint = std::env::var("ETHEREUM_PROVIDER")?; 22 | 23 | let client = ClientBuilder::default() 24 | .layer(ThrottleLayer::new(500)) 25 | .layer(RetryBackoffLayer::new(5, 200, 330)) 26 | .http(rpc_endpoint.parse()?); 27 | 28 | let provider = Arc::new(ProviderBuilder::new().connect_client(client)); 29 | 30 | /* 31 | The `StateSpaceBuilder` is used to sync a state space of AMMs. 32 | 33 | When specifying a set of factories to sync from, the `sync()` method fetches all pool creation logs 34 | from the factory contracts specified and syncs all pools to the latest block. This method returns a 35 | `StateSpaceManager` which can be used to subscribe to state changes and interact with AMMs 36 | the state space. 37 | */ 38 | let factories = vec![ 39 | // UniswapV2 40 | UniswapV2Factory::new( 41 | address!("5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"), 42 | 300, 43 | 10000835, 44 | ) 45 | .into(), 46 | // UniswapV3 47 | UniswapV3Factory::new( 48 | address!("1F98431c8aD98523631AE4a59f267346ea31F984"), 49 | 12369621, 50 | ) 51 | .into(), 52 | ]; 53 | 54 | let _state_space_manager = StateSpaceBuilder::new(provider.clone()) 55 | .with_factories(factories.clone()) 56 | .sync() 57 | .await?; 58 | 59 | // ======================================================================================== // 60 | 61 | /* 62 | You can also sync pools directly without specifying factories. This is great for when you only 63 | need to track a handful of specific pools. 64 | */ 65 | let amms = vec![ 66 | UniswapV2Pool::new(address!("B4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"), 300).into(), 67 | UniswapV3Pool::new(address!("88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")).into(), 68 | ]; 69 | 70 | let _state_space_manager = StateSpaceBuilder::new(provider.clone()) 71 | .with_amms(amms) 72 | .sync() 73 | .await?; 74 | 75 | // ======================================================================================== // 76 | 77 | /* 78 | Additionally, you can specify specific factories to discover and sync pools from, as well as 79 | specify specific AMMs to sync. This can be helpful when there isnt a factory for a given AMM 80 | as is the case with ERC4626 vaults. 81 | */ 82 | let amms = vec![ERC4626Vault::new(address!("163538E22F4d38c1eb21B79939f3d2ee274198Ff")).into()]; 83 | 84 | let _state_space_manager = StateSpaceBuilder::new(provider.clone()) 85 | .with_factories(factories) 86 | .with_amms(amms) 87 | .sync() 88 | .await?; 89 | 90 | Ok(()) 91 | } 92 | -------------------------------------------------------------------------------- /examples/subscribe.rs: -------------------------------------------------------------------------------- 1 | use alloy::{ 2 | primitives::address, 3 | providers::ProviderBuilder, 4 | rpc::client::ClientBuilder, 5 | transports::layers::{RetryBackoffLayer, ThrottleLayer}, 6 | }; 7 | use amms::{amms::uniswap_v2::UniswapV2Factory, state_space::StateSpaceBuilder}; 8 | use futures::StreamExt; 9 | use std::sync::Arc; 10 | 11 | #[tokio::main] 12 | async fn main() -> eyre::Result<()> { 13 | tracing_subscriber::fmt::init(); 14 | let rpc_endpoint = std::env::var("ETHEREUM_PROVIDER")?; 15 | let client = ClientBuilder::default() 16 | .layer(ThrottleLayer::new(500)) 17 | .layer(RetryBackoffLayer::new(5, 200, 330)) 18 | .http(rpc_endpoint.parse()?); 19 | 20 | let sync_provider = Arc::new(ProviderBuilder::new().connect_client(client)); 21 | 22 | let factories = vec![UniswapV2Factory::new( 23 | address!("5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"), 24 | 300, 25 | 10000835, 26 | ) 27 | .into()]; 28 | 29 | let state_space_manager = StateSpaceBuilder::new(sync_provider.clone()) 30 | .with_factories(factories) 31 | .sync() 32 | .await?; 33 | 34 | /* 35 | The subscribe method listens for new blocks and fetches 36 | all logs matching any `sync_events()` specified by the AMM variants in the state space. 37 | Under the hood, this method applies all state changes to any affected AMMs and returns a Vec of 38 | addresses, indicating which AMMs have been updated. 39 | */ 40 | let mut stream = state_space_manager.subscribe().await?.take(5); 41 | while let Some(updated_amms) = stream.next().await { 42 | if let Ok(amms) = updated_amms { 43 | println!("Updated AMMs: {:?}", amms); 44 | } 45 | } 46 | 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /examples/swap_calldata.rs: -------------------------------------------------------------------------------- 1 | use alloy::eips::BlockId; 2 | use alloy::primitives::U256; 3 | use alloy::transports::layers::ThrottleLayer; 4 | use alloy::{ 5 | primitives::address, providers::ProviderBuilder, rpc::client::ClientBuilder, 6 | transports::layers::RetryBackoffLayer, 7 | }; 8 | use amms::amms::{amm::AutomatedMarketMaker, uniswap_v2::UniswapV2Pool}; 9 | use std::sync::Arc; 10 | 11 | #[tokio::main] 12 | async fn main() -> eyre::Result<()> { 13 | let rpc_endpoint = std::env::var("ETHEREUM_PROVIDER")?; 14 | let client = ClientBuilder::default() 15 | .layer(ThrottleLayer::new(500)) 16 | .layer(RetryBackoffLayer::new(5, 200, 330)) 17 | .http(rpc_endpoint.parse()?); 18 | 19 | let provider = Arc::new(ProviderBuilder::new().connect_client(client)); 20 | 21 | let pool = UniswapV2Pool::new(address!("B4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"), 300) 22 | .init(BlockId::latest(), provider) 23 | .await?; 24 | 25 | let to_address = address!("DecafC0ffee15BadDecafC0ffee15BadDecafC0f"); 26 | let swap_calldata = pool.swap_calldata(U256::from(10000), U256::ZERO, to_address, vec![]); 27 | 28 | println!("Swap calldata: {:?}", swap_calldata); 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /examples/sync_macro.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use alloy::{ 4 | primitives::address, 5 | providers::ProviderBuilder, 6 | rpc::client::ClientBuilder, 7 | transports::layers::{RetryBackoffLayer, ThrottleLayer}, 8 | }; 9 | use amms::{ 10 | amms::{uniswap_v2::UniswapV2Factory, uniswap_v3::UniswapV3Factory}, 11 | state_space::{ 12 | filters::{ 13 | whitelist::{PoolWhitelistFilter, TokenWhitelistFilter}, 14 | PoolFilter, 15 | }, 16 | StateSpaceBuilder, 17 | }, 18 | sync, 19 | }; 20 | 21 | #[tokio::main] 22 | async fn main() -> eyre::Result<()> { 23 | tracing_subscriber::fmt::init(); 24 | let rpc_endpoint = std::env::var("ETHEREUM_PROVIDER")?; 25 | 26 | let client = ClientBuilder::default() 27 | .layer(ThrottleLayer::new(500)) 28 | .layer(RetryBackoffLayer::new(5, 200, 330)) 29 | .http(rpc_endpoint.parse()?); 30 | 31 | let provider = Arc::new(ProviderBuilder::new().connect_client(client)); 32 | 33 | let factories = vec![ 34 | // UniswapV2 35 | UniswapV2Factory::new( 36 | address!("5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"), 37 | 300, 38 | 10000835, 39 | ) 40 | .into(), 41 | // UniswapV3 42 | UniswapV3Factory::new( 43 | address!("1F98431c8aD98523631AE4a59f267346ea31F984"), 44 | 12369621, 45 | ) 46 | .into(), 47 | ]; 48 | 49 | let filters: Vec = vec![ 50 | PoolWhitelistFilter::new(vec![address!("88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640")]).into(), 51 | TokenWhitelistFilter::new(vec![address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")]) 52 | .into(), 53 | ]; 54 | 55 | let _state_space_manager = sync!(factories, filters, provider); 56 | 57 | Ok(()) 58 | } 59 | -------------------------------------------------------------------------------- /src/amms/abi/GetTokenDecimalsBatchRequest.json: -------------------------------------------------------------------------------- 1 | {"abi":[{"type":"constructor","inputs":[{"name":"tokens","type":"address[]","internalType":"address[]"}],"stateMutability":"nonpayable"}],"bytecode":{"object":"0x608060405234801561000f575f5ffd5b5060405161068138038061068183398181016040528101906100319190610463565b5f815167ffffffffffffffff81111561004d5761004c6102cd565b5b60405190808252806020026020018201604052801561007b5781602001602082028036833780820191505090505b5090505f5f90505b8251811015610249575f8382815181106100a05761009f6104aa565b5b602002602001015190506100b98161027760201b60201c565b156100c4575061023e565b5f5f8273ffffffffffffffffffffffffffffffffffffffff16614e206040516024016040516020818303038152906040527f313ce567000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516101709190610529565b5f604051808303815f8787f1925050503d805f81146101aa576040519150601f19603f3d011682016040523d82523d5f602084013e6101af565b606091505b50915091508115610232575f602082510361022357818060200190518101906101d89190610572565b90505f8114806101e8575060ff81115b156101f6575050505061023e565b8086868151811061020a576102096104aa565b5b602002602001019060ff16908160ff168152505061022c565b5050505061023e565b5061023a565b50505061023e565b5050505b806001019050610083565b505f8160405160200161025c9190610660565b60405160208183030381529060405290506020810180590381f35b5f5f8273ffffffffffffffffffffffffffffffffffffffff163b0361029f57600190506102a3565b5f90505b919050565b5f604051905090565b5f5ffd5b5f5ffd5b5f5ffd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b610303826102bd565b810181811067ffffffffffffffff82111715610322576103216102cd565b5b80604052505050565b5f6103346102a8565b905061034082826102fa565b919050565b5f67ffffffffffffffff82111561035f5761035e6102cd565b5b602082029050602081019050919050565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61039d82610374565b9050919050565b6103ad81610393565b81146103b7575f5ffd5b50565b5f815190506103c8816103a4565b92915050565b5f6103e06103db84610345565b61032b565b9050808382526020820190506020840283018581111561040357610402610370565b5b835b8181101561042c578061041888826103ba565b845260208401935050602081019050610405565b5050509392505050565b5f82601f83011261044a576104496102b9565b5b815161045a8482602086016103ce565b91505092915050565b5f60208284031215610478576104776102b1565b5b5f82015167ffffffffffffffff811115610495576104946102b5565b5b6104a184828501610436565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f81519050919050565b5f81905092915050565b8281835e5f83830152505050565b5f610503826104d7565b61050d81856104e1565b935061051d8185602086016104eb565b80840191505092915050565b5f61053482846104f9565b915081905092915050565b5f819050919050565b6105518161053f565b811461055b575f5ffd5b50565b5f8151905061056c81610548565b92915050565b5f60208284031215610587576105866102b1565b5b5f6105948482850161055e565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f60ff82169050919050565b6105db816105c6565b82525050565b5f6105ec83836105d2565b60208301905092915050565b5f602082019050919050565b5f61060e8261059d565b61061881856105a7565b9350610623836105b7565b805f5b8381101561065357815161063a88826105e1565b9750610645836105f8565b925050600181019050610626565b5085935050505092915050565b5f6020820190508181035f8301526106788184610604565b90509291505056fe","sourceMap":"56:1363:24:-:0;;;100:1317;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;147:23;185:6;:13;173:26;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;147:52;;215:9;227:1;215:13;;210:863;234:6;:13;230:1;:17;210:863;;;268:13;284:6;291:1;284:9;;;;;;;;:::i;:::-;;;;;;;;268:25;;312:21;327:5;312:14;;;:21;;:::i;:::-;308:35;;;335:8;;;308:35;359:25;386:30;420:5;:27;;453:5;460:37;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;420:78;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;358:140;;;;517:20;513:550;;;557:21;629:2;601:17;:24;:30;597:405;;684:17;673:40;;;;;;;;;;;;:::i;:::-;655:58;;757:1;740:13;:18;:41;;;;778:3;762:13;:19;740:41;736:193;;;809:8;;;;;;736:193;892:13;872:8;881:1;872:11;;;;;;;;:::i;:::-;;;;;;;:34;;;;;;;;;;;597:405;;;975:8;;;;;;597:405;539:477;513:550;;;1040:8;;;;;513:550;254:819;;;210:863;249:3;;;;;210:863;;;;1083:28;1125:8;1114:20;;;;;;;;:::i;:::-;;;;;;;;;;;;;1083:51;;1341:4;1324:15;1320:26;1390:9;1381:7;1377:23;1366:9;1359:42;1421:160;1475:4;1513:1;1491:6;:18;;;:23;1487:92;;1533:4;1526:11;;;;1487:92;1567:5;1560:12;;1421:160;;;;:::o;7:75:41:-;40:6;73:2;67:9;57:19;;7:75;:::o;88:117::-;197:1;194;187:12;211:117;320:1;317;310:12;334:117;443:1;440;433:12;457:102;498:6;549:2;545:7;540:2;533:5;529:14;525:28;515:38;;457:102;;;:::o;565:180::-;613:77;610:1;603:88;710:4;707:1;700:15;734:4;731:1;724:15;751:281;834:27;856:4;834:27;:::i;:::-;826:6;822:40;964:6;952:10;949:22;928:18;916:10;913:34;910:62;907:88;;;975:18;;:::i;:::-;907:88;1015:10;1011:2;1004:22;794:238;751:281;;:::o;1038:129::-;1072:6;1099:20;;:::i;:::-;1089:30;;1128:33;1156:4;1148:6;1128:33;:::i;:::-;1038:129;;;:::o;1173:311::-;1250:4;1340:18;1332:6;1329:30;1326:56;;;1362:18;;:::i;:::-;1326:56;1412:4;1404:6;1400:17;1392:25;;1472:4;1466;1462:15;1454:23;;1173:311;;;:::o;1490:117::-;1599:1;1596;1589:12;1613:126;1650:7;1690:42;1683:5;1679:54;1668:65;;1613:126;;;:::o;1745:96::-;1782:7;1811:24;1829:5;1811:24;:::i;:::-;1800:35;;1745:96;;;:::o;1847:122::-;1920:24;1938:5;1920:24;:::i;:::-;1913:5;1910:35;1900:63;;1959:1;1956;1949:12;1900:63;1847:122;:::o;1975:143::-;2032:5;2063:6;2057:13;2048:22;;2079:33;2106:5;2079:33;:::i;:::-;1975:143;;;;:::o;2141:732::-;2248:5;2273:81;2289:64;2346:6;2289:64;:::i;:::-;2273:81;:::i;:::-;2264:90;;2374:5;2403:6;2396:5;2389:21;2437:4;2430:5;2426:16;2419:23;;2490:4;2482:6;2478:17;2470:6;2466:30;2519:3;2511:6;2508:15;2505:122;;;2538:79;;:::i;:::-;2505:122;2653:6;2636:231;2670:6;2665:3;2662:15;2636:231;;;2745:3;2774:48;2818:3;2806:10;2774:48;:::i;:::-;2769:3;2762:61;2852:4;2847:3;2843:14;2836:21;;2712:155;2696:4;2691:3;2687:14;2680:21;;2636:231;;;2640:21;2254:619;;2141:732;;;;;:::o;2896:385::-;2978:5;3027:3;3020:4;3012:6;3008:17;3004:27;2994:122;;3035:79;;:::i;:::-;2994:122;3145:6;3139:13;3170:105;3271:3;3263:6;3256:4;3248:6;3244:17;3170:105;:::i;:::-;3161:114;;2984:297;2896:385;;;;:::o;3287:554::-;3382:6;3431:2;3419:9;3410:7;3406:23;3402:32;3399:119;;;3437:79;;:::i;:::-;3399:119;3578:1;3567:9;3563:17;3557:24;3608:18;3600:6;3597:30;3594:117;;;3630:79;;:::i;:::-;3594:117;3735:89;3816:7;3807:6;3796:9;3792:22;3735:89;:::i;:::-;3725:99;;3528:306;3287:554;;;;:::o;3847:180::-;3895:77;3892:1;3885:88;3992:4;3989:1;3982:15;4016:4;4013:1;4006:15;4033:98;4084:6;4118:5;4112:12;4102:22;;4033:98;;;:::o;4137:147::-;4238:11;4275:3;4260:18;;4137:147;;;;:::o;4290:139::-;4379:6;4374:3;4369;4363:23;4420:1;4411:6;4406:3;4402:16;4395:27;4290:139;;;:::o;4435:386::-;4539:3;4567:38;4599:5;4567:38;:::i;:::-;4621:88;4702:6;4697:3;4621:88;:::i;:::-;4614:95;;4718:65;4776:6;4771:3;4764:4;4757:5;4753:16;4718:65;:::i;:::-;4808:6;4803:3;4799:16;4792:23;;4543:278;4435:386;;;;:::o;4827:271::-;4957:3;4979:93;5068:3;5059:6;4979:93;:::i;:::-;4972:100;;5089:3;5082:10;;4827:271;;;;:::o;5104:77::-;5141:7;5170:5;5159:16;;5104:77;;;:::o;5187:122::-;5260:24;5278:5;5260:24;:::i;:::-;5253:5;5250:35;5240:63;;5299:1;5296;5289:12;5240:63;5187:122;:::o;5315:143::-;5372:5;5403:6;5397:13;5388:22;;5419:33;5446:5;5419:33;:::i;:::-;5315:143;;;;:::o;5464:351::-;5534:6;5583:2;5571:9;5562:7;5558:23;5554:32;5551:119;;;5589:79;;:::i;:::-;5551:119;5709:1;5734:64;5790:7;5781:6;5770:9;5766:22;5734:64;:::i;:::-;5724:74;;5680:128;5464:351;;;;:::o;5821:112::-;5886:6;5920:5;5914:12;5904:22;;5821:112;;;:::o;5939:182::-;6036:11;6070:6;6065:3;6058:19;6110:4;6105:3;6101:14;6086:29;;5939:182;;;;:::o;6127:130::-;6192:4;6215:3;6207:11;;6245:4;6240:3;6236:14;6228:22;;6127:130;;;:::o;6263:86::-;6298:7;6338:4;6331:5;6327:16;6316:27;;6263:86;;;:::o;6355:102::-;6428:22;6444:5;6428:22;:::i;:::-;6423:3;6416:35;6355:102;;:::o;6463:171::-;6528:10;6549:42;6587:3;6579:6;6549:42;:::i;:::-;6623:4;6618:3;6614:14;6600:28;;6463:171;;;;:::o;6640:111::-;6708:4;6740;6735:3;6731:14;6723:22;;6640:111;;;:::o;6783:716::-;6898:3;6927:52;6973:5;6927:52;:::i;:::-;6995:84;7072:6;7067:3;6995:84;:::i;:::-;6988:91;;7103:54;7151:5;7103:54;:::i;:::-;7180:7;7211:1;7196:278;7221:6;7218:1;7215:13;7196:278;;;7297:6;7291:13;7324:59;7379:3;7364:13;7324:59;:::i;:::-;7317:66;;7406:58;7457:6;7406:58;:::i;:::-;7396:68;;7256:218;7243:1;7240;7236:9;7231:14;;7196:278;;;7200:14;7490:3;7483:10;;6903:596;;;6783:716;;;;:::o;7505:365::-;7644:4;7682:2;7671:9;7667:18;7659:26;;7731:9;7725:4;7721:20;7717:1;7706:9;7702:17;7695:47;7759:104;7858:4;7849:6;7759:104;:::i;:::-;7751:112;;7505:365;;;;:::o","linkReferences":{}},"deployedBytecode":{"object":"0x60806040525f5ffdfea164736f6c634300081c000a","sourceMap":"56:1363:24:-:0;;;;;","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"tokens\",\"type\":\"address[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/ERC20/GetTokenDecimalsBatchRequest.sol\":\"GetTokenDecimalsBatchRequest\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":forge-std/=lib/forge-std/src/\"]},\"sources\":{\"src/ERC20/GetTokenDecimalsBatchRequest.sol\":{\"keccak256\":\"0xc7540761024525cf0d1371c70d9890b2beb0d8e5c996a238139e61541fc36ec5\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://fa59b893b03692814beb0d442247b028099e8a8582e5b3757bf3e5dd8231ae98\",\"dweb:/ipfs/QmUXeRAb4EAsaLRXCx7rHaJmyDA9dPnKoJbPoBoTWZfTZr\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.28+commit.7893614a"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address[]","name":"tokens","type":"address[]"}],"stateMutability":"nonpayable","type":"constructor"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["forge-std/=lib/forge-std/src/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"src/ERC20/GetTokenDecimalsBatchRequest.sol":"GetTokenDecimalsBatchRequest"},"evmVersion":"cancun","libraries":{}},"sources":{"src/ERC20/GetTokenDecimalsBatchRequest.sol":{"keccak256":"0xc7540761024525cf0d1371c70d9890b2beb0d8e5c996a238139e61541fc36ec5","urls":["bzz-raw://fa59b893b03692814beb0d442247b028099e8a8582e5b3757bf3e5dd8231ae98","dweb:/ipfs/QmUXeRAb4EAsaLRXCx7rHaJmyDA9dPnKoJbPoBoTWZfTZr"],"license":"MIT"}},"version":1},"id":24} -------------------------------------------------------------------------------- /src/amms/abi/GetUniswapV2PairsBatchRequest.json: -------------------------------------------------------------------------------- 1 | {"abi":[{"type":"constructor","inputs":[{"name":"from","type":"uint256","internalType":"uint256"},{"name":"step","type":"uint256","internalType":"uint256"},{"name":"factory","type":"address","internalType":"address"}],"stateMutability":"nonpayable"}],"bytecode":{"object":"0x608060405234801561000f575f5ffd5b50604051610550380380610550833981810160405281019061003191906102bd565b5f8173ffffffffffffffffffffffffffffffffffffffff1663574f2ba36040518163ffffffff1660e01b81526004016020604051808303815f875af115801561007c573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906100a0919061030d565b90508083856100af9190610365565b116100ba57826100c7565b83816100c69190610398565b5b92505f8367ffffffffffffffff8111156100e4576100e36103cb565b5b6040519080825280602002602001820160405280156101125781602001602082028036833780820191505090505b5090505f5f90505b848110156101fe578373ffffffffffffffffffffffffffffffffffffffff16631e3dd18b828861014a9190610365565b6040518263ffffffff1660e01b81526004016101669190610407565b6020604051808303815f875af1158015610182573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906101a69190610420565b8282815181106101b9576101b861044b565b5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff168152505080600101905061011a565b505f81604051602001610211919061052f565b60405160208183030381529060405290506020810180590381f35b5f5ffd5b5f819050919050565b61024281610230565b811461024c575f5ffd5b50565b5f8151905061025d81610239565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61028c82610263565b9050919050565b61029c81610282565b81146102a6575f5ffd5b50565b5f815190506102b781610293565b92915050565b5f5f5f606084860312156102d4576102d361022c565b5b5f6102e18682870161024f565b93505060206102f28682870161024f565b9250506040610303868287016102a9565b9150509250925092565b5f602082840312156103225761032161022c565b5b5f61032f8482850161024f565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61036f82610230565b915061037a83610230565b925082820190508082111561039257610391610338565b5b92915050565b5f6103a282610230565b91506103ad83610230565b92508282039050818111156103c5576103c4610338565b5b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61040181610230565b82525050565b5f60208201905061041a5f8301846103f8565b92915050565b5f602082840312156104355761043461022c565b5b5f610442848285016102a9565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b6104aa81610282565b82525050565b5f6104bb83836104a1565b60208301905092915050565b5f602082019050919050565b5f6104dd82610478565b6104e78185610482565b93506104f283610492565b805f5b8381101561052257815161050988826104b0565b9750610514836104c7565b9250506001810190506104f5565b5085935050505092915050565b5f6020820190508181035f83015261054781846104d3565b90509291505056fe","sourceMap":"337:1052:26:-:0;;;382:1005;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;449:22;483:7;474:32;;;:34;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;449:59;;540:14;533:4;526;:11;;;;:::i;:::-;:28;:59;;581:4;526:59;;;574:4;557:14;:21;;;;:::i;:::-;526:59;519:66;;682:25;724:4;710:19;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;682:47;;745:9;757:1;745:13;;740:110;764:4;760:1;:8;740:110;;;812:7;803:26;;;837:1;830:4;:8;;;;:::i;:::-;803:36;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;789:8;798:1;789:11;;;;;;;;:::i;:::-;;;;;;;:50;;;;;;;;;;;770:3;;;;;740:110;;;;1052:28;1094:8;1083:20;;;;;;;;:::i;:::-;;;;;;;;;;;;;1052:51;;1311:4;1294:15;1290:26;1360:9;1351:7;1347:23;1336:9;1329:42;88:117:41;197:1;194;187:12;334:77;371:7;400:5;389:16;;334:77;;;:::o;417:122::-;490:24;508:5;490:24;:::i;:::-;483:5;480:35;470:63;;529:1;526;519:12;470:63;417:122;:::o;545:143::-;602:5;633:6;627:13;618:22;;649:33;676:5;649:33;:::i;:::-;545:143;;;;:::o;694:126::-;731:7;771:42;764:5;760:54;749:65;;694:126;;;:::o;826:96::-;863:7;892:24;910:5;892:24;:::i;:::-;881:35;;826:96;;;:::o;928:122::-;1001:24;1019:5;1001:24;:::i;:::-;994:5;991:35;981:63;;1040:1;1037;1030:12;981:63;928:122;:::o;1056:143::-;1113:5;1144:6;1138:13;1129:22;;1160:33;1187:5;1160:33;:::i;:::-;1056:143;;;;:::o;1205:663::-;1293:6;1301;1309;1358:2;1346:9;1337:7;1333:23;1329:32;1326:119;;;1364:79;;:::i;:::-;1326:119;1484:1;1509:64;1565:7;1556:6;1545:9;1541:22;1509:64;:::i;:::-;1499:74;;1455:128;1622:2;1648:64;1704:7;1695:6;1684:9;1680:22;1648:64;:::i;:::-;1638:74;;1593:129;1761:2;1787:64;1843:7;1834:6;1823:9;1819:22;1787:64;:::i;:::-;1777:74;;1732:129;1205:663;;;;;:::o;1874:351::-;1944:6;1993:2;1981:9;1972:7;1968:23;1964:32;1961:119;;;1999:79;;:::i;:::-;1961:119;2119:1;2144:64;2200:7;2191:6;2180:9;2176:22;2144:64;:::i;:::-;2134:74;;2090:128;1874:351;;;;:::o;2231:180::-;2279:77;2276:1;2269:88;2376:4;2373:1;2366:15;2400:4;2397:1;2390:15;2417:191;2457:3;2476:20;2494:1;2476:20;:::i;:::-;2471:25;;2510:20;2528:1;2510:20;:::i;:::-;2505:25;;2553:1;2550;2546:9;2539:16;;2574:3;2571:1;2568:10;2565:36;;;2581:18;;:::i;:::-;2565:36;2417:191;;;;:::o;2614:194::-;2654:4;2674:20;2692:1;2674:20;:::i;:::-;2669:25;;2708:20;2726:1;2708:20;:::i;:::-;2703:25;;2752:1;2749;2745:9;2737:17;;2776:1;2770:4;2767:11;2764:37;;;2781:18;;:::i;:::-;2764:37;2614:194;;;;:::o;2814:180::-;2862:77;2859:1;2852:88;2959:4;2956:1;2949:15;2983:4;2980:1;2973:15;3000:118;3087:24;3105:5;3087:24;:::i;:::-;3082:3;3075:37;3000:118;;:::o;3124:222::-;3217:4;3255:2;3244:9;3240:18;3232:26;;3268:71;3336:1;3325:9;3321:17;3312:6;3268:71;:::i;:::-;3124:222;;;;:::o;3352:351::-;3422:6;3471:2;3459:9;3450:7;3446:23;3442:32;3439:119;;;3477:79;;:::i;:::-;3439:119;3597:1;3622:64;3678:7;3669:6;3658:9;3654:22;3622:64;:::i;:::-;3612:74;;3568:128;3352:351;;;;:::o;3709:180::-;3757:77;3754:1;3747:88;3854:4;3851:1;3844:15;3878:4;3875:1;3868:15;3895:114;3962:6;3996:5;3990:12;3980:22;;3895:114;;;:::o;4015:184::-;4114:11;4148:6;4143:3;4136:19;4188:4;4183:3;4179:14;4164:29;;4015:184;;;;:::o;4205:132::-;4272:4;4295:3;4287:11;;4325:4;4320:3;4316:14;4308:22;;4205:132;;;:::o;4343:108::-;4420:24;4438:5;4420:24;:::i;:::-;4415:3;4408:37;4343:108;;:::o;4457:179::-;4526:10;4547:46;4589:3;4581:6;4547:46;:::i;:::-;4625:4;4620:3;4616:14;4602:28;;4457:179;;;;:::o;4642:113::-;4712:4;4744;4739:3;4735:14;4727:22;;4642:113;;;:::o;4791:732::-;4910:3;4939:54;4987:5;4939:54;:::i;:::-;5009:86;5088:6;5083:3;5009:86;:::i;:::-;5002:93;;5119:56;5169:5;5119:56;:::i;:::-;5198:7;5229:1;5214:284;5239:6;5236:1;5233:13;5214:284;;;5315:6;5309:13;5342:63;5401:3;5386:13;5342:63;:::i;:::-;5335:70;;5428:60;5481:6;5428:60;:::i;:::-;5418:70;;5274:224;5261:1;5258;5254:9;5249:14;;5214:284;;;5218:14;5514:3;5507:10;;4915:608;;;4791:732;;;;:::o;5529:373::-;5672:4;5710:2;5699:9;5695:18;5687:26;;5759:9;5753:4;5749:20;5745:1;5734:9;5730:17;5723:47;5787:108;5890:4;5881:6;5787:108;:::i;:::-;5779:116;;5529:373;;;;:::o","linkReferences":{}},"deployedBytecode":{"object":"0x60806040525f5ffdfea164736f6c634300081c000a","sourceMap":"337:1052:26:-:0;;;;;","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"from\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"step\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"factory\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}],\"devdoc\":{\"details\":\"This contract is not meant to be deployed. Instead, use a static call with the deployment bytecode as payload.\",\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/UniswapV2/GetUniswapV2PairsBatchRequest.sol\":\"GetUniswapV2PairsBatchRequest\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":forge-std/=lib/forge-std/src/\"]},\"sources\":{\"src/UniswapV2/GetUniswapV2PairsBatchRequest.sol\":{\"keccak256\":\"0x44d0d290e07e6cc3f6a365f4c907b81419593172a9b47dd1a64e3d2942d20229\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9f90e9388ee75d7b9c82f144e7f440de704bbe058c0287db83db0677f3819707\",\"dweb:/ipfs/QmfP1KFxDHdAFZSNYK9oP2prKypbw5FbqufxFGp3BVPMGB\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.28+commit.7893614a"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"uint256","name":"from","type":"uint256"},{"internalType":"uint256","name":"step","type":"uint256"},{"internalType":"address","name":"factory","type":"address"}],"stateMutability":"nonpayable","type":"constructor"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["forge-std/=lib/forge-std/src/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"src/UniswapV2/GetUniswapV2PairsBatchRequest.sol":"GetUniswapV2PairsBatchRequest"},"evmVersion":"cancun","libraries":{}},"sources":{"src/UniswapV2/GetUniswapV2PairsBatchRequest.sol":{"keccak256":"0x44d0d290e07e6cc3f6a365f4c907b81419593172a9b47dd1a64e3d2942d20229","urls":["bzz-raw://9f90e9388ee75d7b9c82f144e7f440de704bbe058c0287db83db0677f3819707","dweb:/ipfs/QmfP1KFxDHdAFZSNYK9oP2prKypbw5FbqufxFGp3BVPMGB"],"license":"MIT"}},"version":1},"id":26} -------------------------------------------------------------------------------- /src/amms/abi/GetUniswapV3PoolSlot0BatchRequest.json: -------------------------------------------------------------------------------- 1 | {"abi":[{"type":"constructor","inputs":[{"name":"pools","type":"address[]","internalType":"address[]"}],"stateMutability":"nonpayable"}],"bytecode":{"object":"0x608060405234801561000f575f5ffd5b5060405161082a38038061082a83398181016040528101906100319190610468565b5f815167ffffffffffffffff81111561004d5761004c6102d2565b5b60405190808252806020026020018201604052801561008657816020015b61007361027a565b81526020019060019003908161006b5790505b5090505f5f90505b825181101561024c575f8282815181106100ab576100aa6104af565b5b602002602001015190505f8483815181106100c9576100c86104af565b5b602002602001015190505f8190508073ffffffffffffffffffffffffffffffffffffffff16631a6865026040518163ffffffff1660e01b8152600401602060405180830381865afa158015610120573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906101449190610521565b83602001906fffffffffffffffffffffffffffffffff1690816fffffffffffffffffffffffffffffffff16815250508073ffffffffffffffffffffffffffffffffffffffff16633850c7bd6040518163ffffffff1660e01b815260040160e060405180830381865afa1580156101bc573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906101e0919061064e565b8673ffffffffffffffffffffffffffffffffffffffff16965090919293509091925090915090505084604001855f018260020b60020b81525082815250505082858581518110610233576102326104af565b5b602002602001018190525050505080600101905061008e565b505f8160405160200161025f9190610809565b60405160208183030381529060405290506020810180590381f35b60405180606001604052805f60020b81526020015f6fffffffffffffffffffffffffffffffff1681526020015f81525090565b5f604051905090565b5f5ffd5b5f5ffd5b5f5ffd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b610308826102c2565b810181811067ffffffffffffffff82111715610327576103266102d2565b5b80604052505050565b5f6103396102ad565b905061034582826102ff565b919050565b5f67ffffffffffffffff821115610364576103636102d2565b5b602082029050602081019050919050565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6103a282610379565b9050919050565b6103b281610398565b81146103bc575f5ffd5b50565b5f815190506103cd816103a9565b92915050565b5f6103e56103e08461034a565b610330565b9050808382526020820190506020840283018581111561040857610407610375565b5b835b81811015610431578061041d88826103bf565b84526020840193505060208101905061040a565b5050509392505050565b5f82601f83011261044f5761044e6102be565b5b815161045f8482602086016103d3565b91505092915050565b5f6020828403121561047d5761047c6102b6565b5b5f82015167ffffffffffffffff81111561049a576104996102ba565b5b6104a68482850161043b565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f6fffffffffffffffffffffffffffffffff82169050919050565b610500816104dc565b811461050a575f5ffd5b50565b5f8151905061051b816104f7565b92915050565b5f60208284031215610536576105356102b6565b5b5f6105438482850161050d565b91505092915050565b61055581610379565b811461055f575f5ffd5b50565b5f815190506105708161054c565b92915050565b5f8160020b9050919050565b61058b81610576565b8114610595575f5ffd5b50565b5f815190506105a681610582565b92915050565b5f61ffff82169050919050565b6105c2816105ac565b81146105cc575f5ffd5b50565b5f815190506105dd816105b9565b92915050565b5f60ff82169050919050565b6105f8816105e3565b8114610602575f5ffd5b50565b5f81519050610613816105ef565b92915050565b5f8115159050919050565b61062d81610619565b8114610637575f5ffd5b50565b5f8151905061064881610624565b92915050565b5f5f5f5f5f5f5f60e0888a031215610669576106686102b6565b5b5f6106768a828b01610562565b97505060206106878a828b01610598565b96505060406106988a828b016105cf565b95505060606106a98a828b016105cf565b94505060806106ba8a828b016105cf565b93505060a06106cb8a828b01610605565b92505060c06106dc8a828b0161063a565b91505092959891949750929550565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b61071d81610576565b82525050565b61072c816104dc565b82525050565b5f819050919050565b61074481610732565b82525050565b606082015f82015161075e5f850182610714565b5060208201516107716020850182610723565b506040820151610784604085018261073b565b50505050565b5f610795838361074a565b60608301905092915050565b5f602082019050919050565b5f6107b7826106eb565b6107c181856106f5565b93506107cc83610705565b805f5b838110156107fc5781516107e3888261078a565b97506107ee836107a1565b9250506001810190506107cf565b5085935050505092915050565b5f6020820190508181035f83015261082181846107ad565b90509291505056fe","sourceMap":"192:1215:30:-:0;;;345:1060;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;391:31;441:5;:12;425:29;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;391:63;;470:9;482:1;470:13;;465:401;489:5;:12;485:1;:16;465:401;;;522:26;551:12;564:1;551:15;;;;;;;;:::i;:::-;;;;;;;;522:44;;580:19;602:5;608:1;602:8;;;;;;;;:::i;:::-;;;;;;;;580:30;;625:24;672:11;625:59;;720:4;:14;;;:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;698:9;:19;;:38;;;;;;;;;;;801:4;:10;;;:12;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;751:62;;;;;;;;;;;;;;;;;;;;752:9;:19;;773:9;:14;;751:62;;;;;;;;;;;;;;846:9;828:12;841:1;828:15;;;;;;;;:::i;:::-;;;;;;;:27;;;;508:358;;;503:3;;;;;465:401;;;;1068:27;1109:12;1098:24;;;;;;;;:::i;:::-;;;;;;;;;;;;;1068:54;;1329:4;1313:14;1309:25;1378:9;1369:7;1365:23;1354:9;1347:42;192:1215;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;7:75:41:-;40:6;73:2;67:9;57:19;;7:75;:::o;88:117::-;197:1;194;187:12;211:117;320:1;317;310:12;334:117;443:1;440;433:12;457:102;498:6;549:2;545:7;540:2;533:5;529:14;525:28;515:38;;457:102;;;:::o;565:180::-;613:77;610:1;603:88;710:4;707:1;700:15;734:4;731:1;724:15;751:281;834:27;856:4;834:27;:::i;:::-;826:6;822:40;964:6;952:10;949:22;928:18;916:10;913:34;910:62;907:88;;;975:18;;:::i;:::-;907:88;1015:10;1011:2;1004:22;794:238;751:281;;:::o;1038:129::-;1072:6;1099:20;;:::i;:::-;1089:30;;1128:33;1156:4;1148:6;1128:33;:::i;:::-;1038:129;;;:::o;1173:311::-;1250:4;1340:18;1332:6;1329:30;1326:56;;;1362:18;;:::i;:::-;1326:56;1412:4;1404:6;1400:17;1392:25;;1472:4;1466;1462:15;1454:23;;1173:311;;;:::o;1490:117::-;1599:1;1596;1589:12;1613:126;1650:7;1690:42;1683:5;1679:54;1668:65;;1613:126;;;:::o;1745:96::-;1782:7;1811:24;1829:5;1811:24;:::i;:::-;1800:35;;1745:96;;;:::o;1847:122::-;1920:24;1938:5;1920:24;:::i;:::-;1913:5;1910:35;1900:63;;1959:1;1956;1949:12;1900:63;1847:122;:::o;1975:143::-;2032:5;2063:6;2057:13;2048:22;;2079:33;2106:5;2079:33;:::i;:::-;1975:143;;;;:::o;2141:732::-;2248:5;2273:81;2289:64;2346:6;2289:64;:::i;:::-;2273:81;:::i;:::-;2264:90;;2374:5;2403:6;2396:5;2389:21;2437:4;2430:5;2426:16;2419:23;;2490:4;2482:6;2478:17;2470:6;2466:30;2519:3;2511:6;2508:15;2505:122;;;2538:79;;:::i;:::-;2505:122;2653:6;2636:231;2670:6;2665:3;2662:15;2636:231;;;2745:3;2774:48;2818:3;2806:10;2774:48;:::i;:::-;2769:3;2762:61;2852:4;2847:3;2843:14;2836:21;;2712:155;2696:4;2691:3;2687:14;2680:21;;2636:231;;;2640:21;2254:619;;2141:732;;;;;:::o;2896:385::-;2978:5;3027:3;3020:4;3012:6;3008:17;3004:27;2994:122;;3035:79;;:::i;:::-;2994:122;3145:6;3139:13;3170:105;3271:3;3263:6;3256:4;3248:6;3244:17;3170:105;:::i;:::-;3161:114;;2984:297;2896:385;;;;:::o;3287:554::-;3382:6;3431:2;3419:9;3410:7;3406:23;3402:32;3399:119;;;3437:79;;:::i;:::-;3399:119;3578:1;3567:9;3563:17;3557:24;3608:18;3600:6;3597:30;3594:117;;;3630:79;;:::i;:::-;3594:117;3735:89;3816:7;3807:6;3796:9;3792:22;3735:89;:::i;:::-;3725:99;;3528:306;3287:554;;;;:::o;3847:180::-;3895:77;3892:1;3885:88;3992:4;3989:1;3982:15;4016:4;4013:1;4006:15;4033:118;4070:7;4110:34;4103:5;4099:46;4088:57;;4033:118;;;:::o;4157:122::-;4230:24;4248:5;4230:24;:::i;:::-;4223:5;4220:35;4210:63;;4269:1;4266;4259:12;4210:63;4157:122;:::o;4285:143::-;4342:5;4373:6;4367:13;4358:22;;4389:33;4416:5;4389:33;:::i;:::-;4285:143;;;;:::o;4434:351::-;4504:6;4553:2;4541:9;4532:7;4528:23;4524:32;4521:119;;;4559:79;;:::i;:::-;4521:119;4679:1;4704:64;4760:7;4751:6;4740:9;4736:22;4704:64;:::i;:::-;4694:74;;4650:128;4434:351;;;;:::o;4791:122::-;4864:24;4882:5;4864:24;:::i;:::-;4857:5;4854:35;4844:63;;4903:1;4900;4893:12;4844:63;4791:122;:::o;4919:143::-;4976:5;5007:6;5001:13;4992:22;;5023:33;5050:5;5023:33;:::i;:::-;4919:143;;;;:::o;5068:90::-;5103:7;5146:5;5143:1;5132:20;5121:31;;5068:90;;;:::o;5164:118::-;5235:22;5251:5;5235:22;:::i;:::-;5228:5;5225:33;5215:61;;5272:1;5269;5262:12;5215:61;5164:118;:::o;5288:139::-;5343:5;5374:6;5368:13;5359:22;;5390:31;5415:5;5390:31;:::i;:::-;5288:139;;;;:::o;5433:89::-;5469:7;5509:6;5502:5;5498:18;5487:29;;5433:89;;;:::o;5528:120::-;5600:23;5617:5;5600:23;:::i;:::-;5593:5;5590:34;5580:62;;5638:1;5635;5628:12;5580:62;5528:120;:::o;5654:141::-;5710:5;5741:6;5735:13;5726:22;;5757:32;5783:5;5757:32;:::i;:::-;5654:141;;;;:::o;5801:86::-;5836:7;5876:4;5869:5;5865:16;5854:27;;5801:86;;;:::o;5893:118::-;5964:22;5980:5;5964:22;:::i;:::-;5957:5;5954:33;5944:61;;6001:1;5998;5991:12;5944:61;5893:118;:::o;6017:139::-;6072:5;6103:6;6097:13;6088:22;;6119:31;6144:5;6119:31;:::i;:::-;6017:139;;;;:::o;6162:90::-;6196:7;6239:5;6232:13;6225:21;6214:32;;6162:90;;;:::o;6258:116::-;6328:21;6343:5;6328:21;:::i;:::-;6321:5;6318:32;6308:60;;6364:1;6361;6354:12;6308:60;6258:116;:::o;6380:137::-;6434:5;6465:6;6459:13;6450:22;;6481:30;6505:5;6481:30;:::i;:::-;6380:137;;;;:::o;6523:1271::-;6637:6;6645;6653;6661;6669;6677;6685;6734:3;6722:9;6713:7;6709:23;6705:33;6702:120;;;6741:79;;:::i;:::-;6702:120;6861:1;6886:64;6942:7;6933:6;6922:9;6918:22;6886:64;:::i;:::-;6876:74;;6832:128;6999:2;7025:62;7079:7;7070:6;7059:9;7055:22;7025:62;:::i;:::-;7015:72;;6970:127;7136:2;7162:63;7217:7;7208:6;7197:9;7193:22;7162:63;:::i;:::-;7152:73;;7107:128;7274:2;7300:63;7355:7;7346:6;7335:9;7331:22;7300:63;:::i;:::-;7290:73;;7245:128;7412:3;7439:63;7494:7;7485:6;7474:9;7470:22;7439:63;:::i;:::-;7429:73;;7383:129;7551:3;7578:62;7632:7;7623:6;7612:9;7608:22;7578:62;:::i;:::-;7568:72;;7522:128;7689:3;7716:61;7769:7;7760:6;7749:9;7745:22;7716:61;:::i;:::-;7706:71;;7660:127;6523:1271;;;;;;;;;;:::o;7800:142::-;7895:6;7929:5;7923:12;7913:22;;7800:142;;;:::o;7948:212::-;8075:11;8109:6;8104:3;8097:19;8149:4;8144:3;8140:14;8125:29;;7948:212;;;;:::o;8166:160::-;8261:4;8284:3;8276:11;;8314:4;8309:3;8305:14;8297:22;;8166:160;;;:::o;8332:102::-;8405:22;8421:5;8405:22;:::i;:::-;8400:3;8393:35;8332:102;;:::o;8440:108::-;8517:24;8535:5;8517:24;:::i;:::-;8512:3;8505:37;8440:108;;:::o;8554:77::-;8591:7;8620:5;8609:16;;8554:77;;;:::o;8637:108::-;8714:24;8732:5;8714:24;:::i;:::-;8709:3;8702:37;8637:108;;:::o;8863:683::-;9006:4;9001:3;8997:14;9093:4;9086:5;9082:16;9076:23;9112:59;9165:4;9160:3;9156:14;9142:12;9112:59;:::i;:::-;9021:160;9268:4;9261:5;9257:16;9251:23;9287:63;9344:4;9339:3;9335:14;9321:12;9287:63;:::i;:::-;9191:169;9447:4;9440:5;9436:16;9430:23;9466:63;9523:4;9518:3;9514:14;9500:12;9466:63;:::i;:::-;9370:169;8975:571;8863:683;;:::o;9552:291::-;9677:10;9698:102;9796:3;9788:6;9698:102;:::i;:::-;9832:4;9827:3;9823:14;9809:28;;9552:291;;;;:::o;9849:141::-;9947:4;9979;9974:3;9970:14;9962:22;;9849:141;;;:::o;10112:956::-;10287:3;10316:82;10392:5;10316:82;:::i;:::-;10414:114;10521:6;10516:3;10414:114;:::i;:::-;10407:121;;10552:84;10630:5;10552:84;:::i;:::-;10659:7;10690:1;10675:368;10700:6;10697:1;10694:13;10675:368;;;10776:6;10770:13;10803:119;10918:3;10903:13;10803:119;:::i;:::-;10796:126;;10945:88;11026:6;10945:88;:::i;:::-;10935:98;;10735:308;10722:1;10719;10715:9;10710:14;;10675:368;;;10679:14;11059:3;11052:10;;10292:776;;;10112:956;;;;:::o;11074:485::-;11273:4;11311:2;11300:9;11296:18;11288:26;;11360:9;11354:4;11350:20;11346:1;11335:9;11331:17;11324:47;11388:164;11547:4;11538:6;11388:164;:::i;:::-;11380:172;;11074:485;;;;:::o","linkReferences":{}},"deployedBytecode":{"object":"0x60806040525f5ffdfea164736f6c634300081c000a","sourceMap":"192:1215:30:-:0;;;;;","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"pools\",\"type\":\"address[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}],\"devdoc\":{\"details\":\"This contract is not meant to be deployed. Instead, use a static call with the deployment bytecode as payload.\",\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/UniswapV3/GetUniswapV3PoolSlot0BatchRequest.sol\":\"GetUniswapV3PoolSlot0BatchRequest\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":forge-std/=lib/forge-std/src/\"]},\"sources\":{\"src/UniswapV3/GetUniswapV3PoolSlot0BatchRequest.sol\":{\"keccak256\":\"0xaf8a81b6f2f914fd54a9adbfe170f17e2d39b2bb1dc5a52d89f42477fe21c28d\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://39a49464263df97fd16380057c325b99a91d5a6a24ef0720a844d0dec0e4098a\",\"dweb:/ipfs/QmPbDwPDsQ5n6SGJ5KNE3d47tXj3zosmebkXwEbagYPRH9\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.28+commit.7893614a"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address[]","name":"pools","type":"address[]"}],"stateMutability":"nonpayable","type":"constructor"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["forge-std/=lib/forge-std/src/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"src/UniswapV3/GetUniswapV3PoolSlot0BatchRequest.sol":"GetUniswapV3PoolSlot0BatchRequest"},"evmVersion":"cancun","libraries":{}},"sources":{"src/UniswapV3/GetUniswapV3PoolSlot0BatchRequest.sol":{"keccak256":"0xaf8a81b6f2f914fd54a9adbfe170f17e2d39b2bb1dc5a52d89f42477fe21c28d","urls":["bzz-raw://39a49464263df97fd16380057c325b99a91d5a6a24ef0720a844d0dec0e4098a","dweb:/ipfs/QmPbDwPDsQ5n6SGJ5KNE3d47tXj3zosmebkXwEbagYPRH9"],"license":"MIT"}},"version":1},"id":30} -------------------------------------------------------------------------------- /src/amms/abi/GetUniswapV3PoolTickBitmapBatchRequest.json: -------------------------------------------------------------------------------- 1 | {"abi":[{"type":"constructor","inputs":[{"name":"allPoolInfo","type":"tuple[]","internalType":"struct GetUniswapV3PoolTickBitmapBatchRequest.TickBitmapInfo[]","components":[{"name":"pool","type":"address","internalType":"address"},{"name":"minWord","type":"int16","internalType":"int16"},{"name":"maxWord","type":"int16","internalType":"int16"}]}],"stateMutability":"nonpayable"}],"bytecode":{"object":"0x608060405234801561000f575f5ffd5b506040516108b43803806108b4833981810160405281019061003191906104ef565b5f815167ffffffffffffffff81111561004d5761004c6102be565b5b60405190808252806020026020018201604052801561008057816020015b606081526020019060019003908161006b5790505b5090505f5f90505b825181101561026b575f8382815181106100a5576100a4610536565b5b602002602001015190505f815f015190505f6001836020015184604001516100cd9190610590565b6100d791906105f6565b61ffff1667ffffffffffffffff8111156100f4576100f36102be565b5b6040519080825280602002602001820160405280156101225781602001602082028036833780820191505090505b5090505f5f90505f846020015190505b846040015160010b8160010b13610239575f8473ffffffffffffffffffffffffffffffffffffffff16635339c296836040518263ffffffff1660e01b815260040161017d919061063a565b602060405180830381865afa158015610198573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906101bc9190610686565b90505f81036101cb5750610228565b8160010b8484815181106101e2576101e1610536565b5b602002602001018181525050826101f8906106b1565b92508084848151811061020e5761020d610536565b5b60200260200101818152505082610224906106b1565b9250505b80610232906106f8565b9050610132565b508082528186868151811061025157610250610536565b5b602002602001018190525050505050806001019050610088565b505f8160405160200161027e9190610893565b60405160208183030381529060405290506020810180590381f35b5f604051905090565b5f5ffd5b5f5ffd5b5f5ffd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6102f4826102ae565b810181811067ffffffffffffffff82111715610313576103126102be565b5b80604052505050565b5f610325610299565b905061033182826102eb565b919050565b5f67ffffffffffffffff8211156103505761034f6102be565b5b602082029050602081019050919050565b5f5ffd5b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61039282610369565b9050919050565b6103a281610388565b81146103ac575f5ffd5b50565b5f815190506103bd81610399565b92915050565b5f8160010b9050919050565b6103d8816103c3565b81146103e2575f5ffd5b50565b5f815190506103f3816103cf565b92915050565b5f6060828403121561040e5761040d610365565b5b610418606061031c565b90505f610427848285016103af565b5f83015250602061043a848285016103e5565b602083015250604061044e848285016103e5565b60408301525092915050565b5f61046c61046784610336565b61031c565b9050808382526020820190506060840283018581111561048f5761048e610361565b5b835b818110156104b857806104a488826103f9565b845260208401935050606081019050610491565b5050509392505050565b5f82601f8301126104d6576104d56102aa565b5b81516104e684826020860161045a565b91505092915050565b5f60208284031215610504576105036102a2565b5b5f82015167ffffffffffffffff811115610521576105206102a6565b5b61052d848285016104c2565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61059a826103c3565b91506105a5836103c3565b92508282039050617fff81137fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8000821217156105e3576105e2610563565b5b92915050565b5f61ffff82169050919050565b5f610600826105e9565b915061060b836105e9565b9250828201905061ffff81111561062557610624610563565b5b92915050565b610634816103c3565b82525050565b5f60208201905061064d5f83018461062b565b92915050565b5f819050919050565b61066581610653565b811461066f575f5ffd5b50565b5f815190506106808161065c565b92915050565b5f6020828403121561069b5761069a6102a2565b5b5f6106a884828501610672565b91505092915050565b5f6106bb82610653565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036106ed576106ec610563565b5b600182019050919050565b5f610702826103c3565b9150617fff820361071657610715610563565b5b600182019050919050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b61077c81610653565b82525050565b5f61078d8383610773565b60208301905092915050565b5f602082019050919050565b5f6107af8261074a565b6107b98185610754565b93506107c483610764565b805f5b838110156107f45781516107db8882610782565b97506107e683610799565b9250506001810190506107c7565b5085935050505092915050565b5f61080c83836107a5565b905092915050565b5f602082019050919050565b5f61082a82610721565b610834818561072b565b9350836020820285016108468561073b565b805f5b8581101561088157848403895281516108628582610801565b945061086d83610814565b925060208a01995050600181019050610849565b50829750879550505050505092915050565b5f6020820190508181035f8301526108ab8184610820565b90509291505056fe","sourceMap":"192:1778:31:-:0;;;443:1525;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;502:33;554:11;:18;538:35;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;502:71;;589:9;601:1;589:13;;584:843;608:11;:18;604:1;:22;584:843;;;647:26;676:11;688:1;676:14;;;;;;;;:::i;:::-;;;;;;;;647:43;;704:24;751:4;:9;;;704:57;;776:28;859:1;843:4;:12;;;828:4;:12;;;:27;;;;:::i;:::-;821:39;;;;:::i;:::-;807:54;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;776:85;;876:15;894:1;876:19;;914:7;924:4;:12;;;914:22;;909:379;943:4;:12;;;938:17;;:1;:17;;;909:379;;980:18;1001:4;:15;;;1017:1;1001:18;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;980:39;;1056:1;1042:10;:15;1038:70;;1081:8;;;1038:70;1164:1;1157:9;;1126:11;1138:7;1126:20;;;;;;;;:::i;:::-;;;;;;;:41;;;;;1185:9;;;;:::i;:::-;;;1236:10;1213:11;1225:7;1213:20;;;;;;;;:::i;:::-;;;;;;;:33;;;;;1264:9;;;;:::i;:::-;;;962:326;909:379;957:3;;;;:::i;:::-;;;909:379;;;;1349:7;1336:11;1329:28;1405:11;1385:14;1400:1;1385:17;;;;;;;;:::i;:::-;;;;;;;:31;;;;633:794;;;;628:3;;;;;584:843;;;;1629:27;1670:14;1659:26;;;;;;;;:::i;:::-;;;;;;;;;;;;;1629:56;;1892:4;1876:14;1872:25;1941:9;1932:7;1928:23;1917:9;1910:42;7:75:41;40:6;73:2;67:9;57:19;;7:75;:::o;88:117::-;197:1;194;187:12;211:117;320:1;317;310:12;334:117;443:1;440;433:12;457:102;498:6;549:2;545:7;540:2;533:5;529:14;525:28;515:38;;457:102;;;:::o;565:180::-;613:77;610:1;603:88;710:4;707:1;700:15;734:4;731:1;724:15;751:281;834:27;856:4;834:27;:::i;:::-;826:6;822:40;964:6;952:10;949:22;928:18;916:10;913:34;910:62;907:88;;;975:18;;:::i;:::-;907:88;1015:10;1011:2;1004:22;794:238;751:281;;:::o;1038:129::-;1072:6;1099:20;;:::i;:::-;1089:30;;1128:33;1156:4;1148:6;1128:33;:::i;:::-;1038:129;;;:::o;1173:344::-;1283:4;1373:18;1365:6;1362:30;1359:56;;;1395:18;;:::i;:::-;1359:56;1445:4;1437:6;1433:17;1425:25;;1505:4;1499;1495:15;1487:23;;1173:344;;;:::o;1523:117::-;1632:1;1629;1622:12;1646:117;1755:1;1752;1745:12;1892:126;1929:7;1969:42;1962:5;1958:54;1947:65;;1892:126;;;:::o;2024:96::-;2061:7;2090:24;2108:5;2090:24;:::i;:::-;2079:35;;2024:96;;;:::o;2126:122::-;2199:24;2217:5;2199:24;:::i;:::-;2192:5;2189:35;2179:63;;2238:1;2235;2228:12;2179:63;2126:122;:::o;2254:143::-;2311:5;2342:6;2336:13;2327:22;;2358:33;2385:5;2358:33;:::i;:::-;2254:143;;;;:::o;2403:90::-;2438:7;2481:5;2478:1;2467:20;2456:31;;2403:90;;;:::o;2499:118::-;2570:22;2586:5;2570:22;:::i;:::-;2563:5;2560:33;2550:61;;2607:1;2604;2597:12;2550:61;2499:118;:::o;2623:139::-;2678:5;2709:6;2703:13;2694:22;;2725:31;2750:5;2725:31;:::i;:::-;2623:139;;;;:::o;2836:789::-;2929:5;2973:4;2961:9;2956:3;2952:19;2948:30;2945:117;;;2981:79;;:::i;:::-;2945:117;3080:21;3096:4;3080:21;:::i;:::-;3071:30;;3160:1;3200:60;3256:3;3247:6;3236:9;3232:22;3200:60;:::i;:::-;3193:4;3186:5;3182:16;3175:86;3111:161;3334:2;3375:58;3429:3;3420:6;3409:9;3405:22;3375:58;:::i;:::-;3368:4;3361:5;3357:16;3350:84;3282:163;3507:2;3548:58;3602:3;3593:6;3582:9;3578:22;3548:58;:::i;:::-;3541:4;3534:5;3530:16;3523:84;3455:163;2836:789;;;;:::o;3701:831::-;3841:5;3866:114;3882:97;3972:6;3882:97;:::i;:::-;3866:114;:::i;:::-;3857:123;;4000:5;4029:6;4022:5;4015:21;4063:4;4056:5;4052:16;4045:23;;4116:4;4108:6;4104:17;4096:6;4092:30;4145:3;4137:6;4134:15;4131:122;;;4164:79;;:::i;:::-;4131:122;4279:6;4262:264;4296:6;4291:3;4288:15;4262:264;;;4371:3;4400:81;4477:3;4465:10;4400:81;:::i;:::-;4395:3;4388:94;4511:4;4506:3;4502:14;4495:21;;4338:188;4322:4;4317:3;4313:14;4306:21;;4262:264;;;4266:21;3847:685;;3701:831;;;;;:::o;4608:451::-;4723:5;4772:3;4765:4;4757:6;4753:17;4749:27;4739:122;;4780:79;;:::i;:::-;4739:122;4890:6;4884:13;4915:138;5049:3;5041:6;5034:4;5026:6;5022:17;4915:138;:::i;:::-;4906:147;;4729:330;4608:451;;;;:::o;5065:620::-;5193:6;5242:2;5230:9;5221:7;5217:23;5213:32;5210:119;;;5248:79;;:::i;:::-;5210:119;5389:1;5378:9;5374:17;5368:24;5419:18;5411:6;5408:30;5405:117;;;5441:79;;:::i;:::-;5405:117;5546:122;5660:7;5651:6;5640:9;5636:22;5546:122;:::i;:::-;5536:132;;5339:339;5065:620;;;;:::o;5691:180::-;5739:77;5736:1;5729:88;5836:4;5833:1;5826:15;5860:4;5857:1;5850:15;5877:180;5925:77;5922:1;5915:88;6022:4;6019:1;6012:15;6046:4;6043:1;6036:15;6063:311;6101:4;6121:18;6137:1;6121:18;:::i;:::-;6116:23;;6153:18;6169:1;6153:18;:::i;:::-;6148:23;;6195:1;6192;6188:9;6180:17;;6327:6;6321:4;6317:17;6236:66;6230:4;6226:77;6210:134;6207:160;;;6347:18;;:::i;:::-;6207:160;6063:311;;;;:::o;6380:89::-;6416:7;6456:6;6449:5;6445:18;6434:29;;6380:89;;;:::o;6475:193::-;6514:3;6533:19;6550:1;6533:19;:::i;:::-;6528:24;;6566:19;6583:1;6566:19;:::i;:::-;6561:24;;6608:1;6605;6601:9;6594:16;;6631:6;6626:3;6623:15;6620:41;;;6641:18;;:::i;:::-;6620:41;6475:193;;;;:::o;6674:112::-;6757:22;6773:5;6757:22;:::i;:::-;6752:3;6745:35;6674:112;;:::o;6792:214::-;6881:4;6919:2;6908:9;6904:18;6896:26;;6932:67;6996:1;6985:9;6981:17;6972:6;6932:67;:::i;:::-;6792:214;;;;:::o;7012:77::-;7049:7;7078:5;7067:16;;7012:77;;;:::o;7095:122::-;7168:24;7186:5;7168:24;:::i;:::-;7161:5;7158:35;7148:63;;7207:1;7204;7197:12;7148:63;7095:122;:::o;7223:143::-;7280:5;7311:6;7305:13;7296:22;;7327:33;7354:5;7327:33;:::i;:::-;7223:143;;;;:::o;7372:351::-;7442:6;7491:2;7479:9;7470:7;7466:23;7462:32;7459:119;;;7497:79;;:::i;:::-;7459:119;7617:1;7642:64;7698:7;7689:6;7678:9;7674:22;7642:64;:::i;:::-;7632:74;;7588:128;7372:351;;;;:::o;7729:233::-;7768:3;7791:24;7809:5;7791:24;:::i;:::-;7782:33;;7837:66;7830:5;7827:77;7824:103;;7907:18;;:::i;:::-;7824:103;7954:1;7947:5;7943:13;7936:20;;7729:233;;;:::o;7968:169::-;8005:3;8028:22;8044:5;8028:22;:::i;:::-;8019:31;;8072:6;8065:5;8062:17;8059:43;;8082:18;;:::i;:::-;8059:43;8129:1;8122:5;8118:13;8111:20;;7968:169;;;:::o;8143:139::-;8235:6;8269:5;8263:12;8253:22;;8143:139;;;:::o;8288:209::-;8412:11;8446:6;8441:3;8434:19;8486:4;8481:3;8477:14;8462:29;;8288:209;;;;:::o;8503:157::-;8595:4;8618:3;8610:11;;8648:4;8643:3;8639:14;8631:22;;8503:157;;;:::o;8666:114::-;8733:6;8767:5;8761:12;8751:22;;8666:114;;;:::o;8786:174::-;8875:11;8909:6;8904:3;8897:19;8949:4;8944:3;8940:14;8925:29;;8786:174;;;;:::o;8966:132::-;9033:4;9056:3;9048:11;;9086:4;9081:3;9077:14;9069:22;;8966:132;;;:::o;9104:108::-;9181:24;9199:5;9181:24;:::i;:::-;9176:3;9169:37;9104:108;;:::o;9218:179::-;9287:10;9308:46;9350:3;9342:6;9308:46;:::i;:::-;9386:4;9381:3;9377:14;9363:28;;9218:179;;;;:::o;9403:113::-;9473:4;9505;9500:3;9496:14;9488:22;;9403:113;;;:::o;9552:712::-;9661:3;9690:54;9738:5;9690:54;:::i;:::-;9760:76;9829:6;9824:3;9760:76;:::i;:::-;9753:83;;9860:56;9910:5;9860:56;:::i;:::-;9939:7;9970:1;9955:284;9980:6;9977:1;9974:13;9955:284;;;10056:6;10050:13;10083:63;10142:3;10127:13;10083:63;:::i;:::-;10076:70;;10169:60;10222:6;10169:60;:::i;:::-;10159:70;;10015:224;10002:1;9999;9995:9;9990:14;;9955:284;;;9959:14;10255:3;10248:10;;9666:598;;;9552:712;;;;:::o;10270:256::-;10389:10;10424:96;10516:3;10508:6;10424:96;:::i;:::-;10410:110;;10270:256;;;;:::o;10532:138::-;10627:4;10659;10654:3;10650:14;10642:22;;10532:138;;;:::o;10710:1111::-;10879:3;10908:79;10981:5;10908:79;:::i;:::-;11003:111;11107:6;11102:3;11003:111;:::i;:::-;10996:118;;11140:3;11185:4;11177:6;11173:17;11168:3;11164:27;11215:81;11290:5;11215:81;:::i;:::-;11319:7;11350:1;11335:441;11360:6;11357:1;11354:13;11335:441;;;11431:9;11425:4;11421:20;11416:3;11409:33;11482:6;11476:13;11510:114;11619:4;11604:13;11510:114;:::i;:::-;11502:122;;11647:85;11725:6;11647:85;:::i;:::-;11637:95;;11761:4;11756:3;11752:14;11745:21;;11395:381;11382:1;11379;11375:9;11370:14;;11335:441;;;11339:14;11792:4;11785:11;;11812:3;11805:10;;10884:937;;;;;10710:1111;;;;:::o;11827:473::-;12020:4;12058:2;12047:9;12043:18;12035:26;;12107:9;12101:4;12097:20;12093:1;12082:9;12078:17;12071:47;12135:158;12288:4;12279:6;12135:158;:::i;:::-;12127:166;;11827:473;;;;:::o","linkReferences":{}},"deployedBytecode":{"object":"0x60806040525f5ffdfea164736f6c634300081c000a","sourceMap":"192:1778:31:-:0;;;;;","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.28+commit.7893614a\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"},{\"internalType\":\"int16\",\"name\":\"minWord\",\"type\":\"int16\"},{\"internalType\":\"int16\",\"name\":\"maxWord\",\"type\":\"int16\"}],\"internalType\":\"struct GetUniswapV3PoolTickBitmapBatchRequest.TickBitmapInfo[]\",\"name\":\"allPoolInfo\",\"type\":\"tuple[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"}],\"devdoc\":{\"details\":\"This contract is not meant to be deployed. Instead, use a static call with the deployment bytecode as payload.\",\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/UniswapV3/GetUniswapV3PoolTickBitmapBatchRequest.sol\":\"GetUniswapV3PoolTickBitmapBatchRequest\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[\":forge-std/=lib/forge-std/src/\"]},\"sources\":{\"src/UniswapV3/GetUniswapV3PoolTickBitmapBatchRequest.sol\":{\"keccak256\":\"0x1d72e66cf86ebcfc9b92460c7ef6b4964d191b3f6e67cd008cab226fd377af64\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f8364aaeb50f6d6809de6e95dab98260d7abe6b2b7bc4a5b5386f2a65f928184\",\"dweb:/ipfs/QmRJFENg5eFBoSqRgskPL354vRaNoYh5Vgyqp47QTmdcP5\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.28+commit.7893614a"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"struct GetUniswapV3PoolTickBitmapBatchRequest.TickBitmapInfo[]","name":"allPoolInfo","type":"tuple[]","components":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"int16","name":"minWord","type":"int16"},{"internalType":"int16","name":"maxWord","type":"int16"}]}],"stateMutability":"nonpayable","type":"constructor"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["forge-std/=lib/forge-std/src/"],"optimizer":{"enabled":false,"runs":200},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"src/UniswapV3/GetUniswapV3PoolTickBitmapBatchRequest.sol":"GetUniswapV3PoolTickBitmapBatchRequest"},"evmVersion":"cancun","libraries":{}},"sources":{"src/UniswapV3/GetUniswapV3PoolTickBitmapBatchRequest.sol":{"keccak256":"0x1d72e66cf86ebcfc9b92460c7ef6b4964d191b3f6e67cd008cab226fd377af64","urls":["bzz-raw://f8364aaeb50f6d6809de6e95dab98260d7abe6b2b7bc4a5b5386f2a65f928184","dweb:/ipfs/QmRJFENg5eFBoSqRgskPL354vRaNoYh5Vgyqp47QTmdcP5"],"license":"MIT"}},"version":1},"id":31} -------------------------------------------------------------------------------- /src/amms/amm.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | balancer::BalancerPool, erc_4626::ERC4626Vault, error::AMMError, uniswap_v2::UniswapV2Pool, 3 | uniswap_v3::UniswapV3Pool, 4 | }; 5 | use alloy::{ 6 | eips::BlockId, 7 | network::Network, 8 | primitives::{Address, B256, U256}, 9 | providers::Provider, 10 | rpc::types::Log, 11 | }; 12 | use eyre::Result; 13 | use serde::{Deserialize, Serialize}; 14 | use std::hash::{Hash, Hasher}; 15 | 16 | #[allow(async_fn_in_trait)] 17 | pub trait AutomatedMarketMaker { 18 | /// Address of the AMM 19 | fn address(&self) -> Address; 20 | 21 | /// Event signatures that indicate when the AMM should be synced 22 | fn sync_events(&self) -> Vec; 23 | 24 | /// Syncs the AMM state 25 | fn sync(&mut self, log: &Log) -> Result<(), AMMError>; 26 | 27 | /// Returns a list of token addresses used in the AMM 28 | fn tokens(&self) -> Vec
; 29 | 30 | /// Calculates the price of `base_token` in terms of `quote_token` 31 | fn calculate_price(&self, base_token: Address, quote_token: Address) -> Result; 32 | 33 | /// Simulate a swap 34 | /// Returns the amount_out in `quote token` for a given `amount_in` of `base_token` 35 | fn simulate_swap( 36 | &self, 37 | base_token: Address, 38 | quote_token: Address, 39 | amount_in: U256, 40 | ) -> Result; 41 | 42 | /// Simulate a swap, mutating the AMM state 43 | /// Returns the amount_out in `quote token` for a given `amount_in` of `base_token` 44 | fn simulate_swap_mut( 45 | &mut self, 46 | base_token: Address, 47 | quote_token: Address, 48 | amount_in: U256, 49 | ) -> Result; 50 | 51 | // Initializes an empty pool and syncs state up to `block_number` 52 | async fn init(self, block_number: BlockId, provider: P) -> Result 53 | where 54 | Self: Sized, 55 | N: Network, 56 | P: Provider + Clone; 57 | } 58 | 59 | macro_rules! amm { 60 | ($($pool_type:ident),+ $(,)?) => { 61 | #[derive(Debug, Clone, Serialize, Deserialize)] 62 | pub enum AMM { 63 | $($pool_type($pool_type),)+ 64 | } 65 | 66 | impl AutomatedMarketMaker for AMM { 67 | fn address(&self) -> Address{ 68 | match self { 69 | $(AMM::$pool_type(pool) => pool.address(),)+ 70 | } 71 | } 72 | 73 | fn sync_events(&self) -> Vec { 74 | match self { 75 | $(AMM::$pool_type(pool) => pool.sync_events(),)+ 76 | } 77 | } 78 | 79 | fn sync(&mut self, log: &Log) -> Result<(), AMMError> { 80 | match self { 81 | $(AMM::$pool_type(pool) => pool.sync(log),)+ 82 | } 83 | } 84 | 85 | fn simulate_swap(&self, base_token: Address, quote_token: Address,amount_in: U256) -> Result { 86 | match self { 87 | $(AMM::$pool_type(pool) => pool.simulate_swap(base_token, quote_token, amount_in),)+ 88 | } 89 | } 90 | 91 | fn simulate_swap_mut(&mut self, base_token: Address, quote_token: Address, amount_in: U256) -> Result { 92 | match self { 93 | $(AMM::$pool_type(pool) => pool.simulate_swap_mut(base_token, quote_token, amount_in),)+ 94 | } 95 | } 96 | 97 | fn tokens(&self) -> Vec
{ 98 | match self { 99 | $(AMM::$pool_type(pool) => pool.tokens(),)+ 100 | } 101 | } 102 | 103 | fn calculate_price(&self, base_token: Address, quote_token: Address) -> Result { 104 | match self { 105 | $(AMM::$pool_type(pool) => pool.calculate_price(base_token, quote_token),)+ 106 | } 107 | } 108 | 109 | async fn init(self, block_number: BlockId, provider: P) -> Result 110 | where 111 | Self: Sized, 112 | N: Network, 113 | P: Provider + Clone, 114 | { 115 | match self { 116 | $(AMM::$pool_type(pool) => pool.init(block_number, provider).await.map(AMM::$pool_type),)+ 117 | } 118 | } 119 | } 120 | 121 | 122 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] 123 | pub enum Variant { 124 | $($pool_type,)+ 125 | } 126 | 127 | impl AMM { 128 | pub fn variant(&self) -> Variant { 129 | match self { 130 | $(AMM::$pool_type(_) => Variant::$pool_type,)+ 131 | } 132 | } 133 | } 134 | 135 | impl Hash for AMM { 136 | fn hash(&self, state: &mut H) { 137 | self.address().hash(state); 138 | } 139 | } 140 | 141 | impl PartialEq for AMM { 142 | fn eq(&self, other: &Self) -> bool { 143 | self.address() == other.address() 144 | } 145 | } 146 | 147 | impl Eq for AMM {} 148 | 149 | $( 150 | impl From<$pool_type> for AMM { 151 | fn from(amm: $pool_type) -> Self { 152 | AMM::$pool_type(amm) 153 | } 154 | } 155 | )+ 156 | }; 157 | } 158 | 159 | amm!(UniswapV2Pool, UniswapV3Pool, ERC4626Vault, BalancerPool); 160 | -------------------------------------------------------------------------------- /src/amms/balancer/bmath.rs: -------------------------------------------------------------------------------- 1 | use alloy::primitives::U256; 2 | 3 | use crate::amms::consts::{BONE, U256_1, U256_10E_10, U256_2}; 4 | 5 | use super::BalancerError; 6 | 7 | pub fn btoi(a: U256) -> U256 { 8 | a / BONE 9 | } 10 | 11 | #[inline] 12 | pub fn badd(a: U256, b: U256) -> Result { 13 | let c = a + b; 14 | if c < a { 15 | return Err(BalancerError::AddOverflow); 16 | } 17 | Ok(c) 18 | } 19 | 20 | #[inline] 21 | pub fn bpowi(a: U256, n: U256) -> Result { 22 | let mut z = if n % U256_2 != U256::ZERO { a } else { BONE }; 23 | 24 | let mut a = a; 25 | let mut n = n / U256_2; 26 | while n != U256::ZERO { 27 | a = bmul(a, a)?; 28 | if n % U256_2 != U256::ZERO { 29 | z = bmul(z, a)?; 30 | } 31 | n /= U256_2; 32 | } 33 | Ok(z) 34 | } 35 | 36 | #[inline] 37 | pub fn bpow(base: U256, exp: U256) -> Result { 38 | let whole = bfloor(exp); 39 | let remain = bsub(exp, whole)?; 40 | let whole_pow = bpowi(base, btoi(whole))?; 41 | if remain == U256::ZERO { 42 | return Ok(whole_pow); 43 | } 44 | let precision = BONE / U256_10E_10; 45 | let partial_result = bpow_approx(base, remain, precision)?; 46 | bmul(whole_pow, partial_result) 47 | } 48 | 49 | #[inline] 50 | pub fn bpow_approx(base: U256, exp: U256, precision: U256) -> Result { 51 | let a = exp; 52 | let (x, xneg) = bsub_sign(base, BONE); 53 | let mut term = BONE; 54 | let mut sum = term; 55 | let mut negative = false; 56 | let mut i = U256_1; 57 | while term >= precision { 58 | let big_k = U256::from(i) * BONE; 59 | let (c, cneg) = bsub_sign(a, bsub(big_k, BONE)?); 60 | term = bmul(term, bmul(c, x)?)?; 61 | term = bdiv(term, big_k)?; 62 | if term == U256::ZERO { 63 | break; 64 | } 65 | negative ^= xneg ^ cneg; 66 | if negative { 67 | sum = bsub(sum, term)?; 68 | } else { 69 | sum = badd(sum, term)?; 70 | } 71 | i += U256_1; 72 | } 73 | Ok(sum) 74 | } 75 | 76 | #[inline] 77 | pub fn bfloor(a: U256) -> U256 { 78 | btoi(a) * BONE 79 | } 80 | 81 | // Reference: 82 | // https://github.com/balancer/balancer-core/blob/f4ed5d65362a8d6cec21662fb6eae233b0babc1f/contracts/BNum.sol#L75 83 | #[inline] 84 | pub fn bdiv(a: U256, b: U256) -> Result { 85 | if b == U256::ZERO { 86 | return Err(BalancerError::DivZero); 87 | } 88 | let c0 = a * BONE; 89 | if a != U256::ZERO && c0 / a != BONE { 90 | return Err(BalancerError::DivInternal); 91 | } 92 | let c1 = c0 + (b / U256_2); 93 | if c1 < c0 { 94 | return Err(BalancerError::DivInternal); 95 | } 96 | Ok(c1 / b) 97 | } 98 | 99 | // Reference: 100 | // https://github.com/balancer/balancer-core/blob/f4ed5d65362a8d6cec21662fb6eae233b0babc1f/contracts/BNum.sol#L43 101 | #[inline] 102 | pub fn bsub(a: U256, b: U256) -> Result { 103 | let (c, flag) = bsub_sign(a, b); 104 | if flag { 105 | return Err(BalancerError::SubUnderflow); 106 | } 107 | Ok(c) 108 | } 109 | 110 | // Reference: 111 | // https://github.com/balancer/balancer-core/blob/f4ed5d65362a8d6cec21662fb6eae233b0babc1f/contracts/BNum.sol#L52 112 | #[inline] 113 | pub fn bsub_sign(a: U256, b: U256) -> (U256, bool) { 114 | if a >= b { 115 | (a - b, false) 116 | } else { 117 | (b - a, true) 118 | } 119 | } 120 | 121 | // Reference: 122 | // https://github.com/balancer/balancer-core/blob/f4ed5d65362a8d6cec21662fb6eae233b0babc1f/contracts/BNum.sol#L63C4-L73C6 123 | #[inline] 124 | pub fn bmul(a: U256, b: U256) -> Result { 125 | let c0 = a * b; 126 | if a != U256::ZERO && c0 / a != b { 127 | return Err(BalancerError::MulOverflow); 128 | } 129 | let c1 = c0 + (BONE / U256_2); 130 | if c1 < c0 { 131 | return Err(BalancerError::MulOverflow); 132 | } 133 | Ok(c1 / BONE) 134 | } 135 | 136 | /********************************************************************************************** 137 | // calcSpotPrice // 138 | // sP = spotPrice // 139 | // bI = tokenBalanceIn ( bI / wI ) 1 // 140 | // bO = tokenBalanceOut sP = ----------- * ---------- // 141 | // wI = tokenWeightIn ( bO / wO ) ( 1 - sF ) // 142 | // wO = tokenWeightOut // 143 | // sF = swapFee // 144 | **********************************************************************************************/ 145 | pub fn calculate_price( 146 | b_i: U256, 147 | w_i: U256, 148 | b_o: U256, 149 | w_o: U256, 150 | s_f: U256, 151 | ) -> Result { 152 | let numer = bdiv(b_i, w_i)?; 153 | let denom = bdiv(b_o, w_o)?; 154 | let ratio = bdiv(numer, denom)?; 155 | let scale = bdiv(BONE, bsub(BONE, s_f)?)?; 156 | bmul(ratio, scale) 157 | } 158 | 159 | /********************************************************************************************** 160 | // calcOutGivenIn // 161 | // aO = tokenAmountOut // 162 | // bO = tokenBalanceOut // 163 | // bI = tokenBalanceIn / / bI \ (wI / wO) \ // 164 | // aI = tokenAmountIn aO = bO * | 1 - | -------------------------- | ^ | // 165 | // wI = tokenWeightIn \ \ ( bI + ( aI * ( 1 - sF )) / / // 166 | // wO = tokenWeightOut // 167 | // sF = swapFee // 168 | **********************************************************************************************/ 169 | pub fn calculate_out_given_in( 170 | token_balance_in: U256, 171 | token_weight_in: U256, 172 | token_balance_out: U256, 173 | token_weight_out: U256, 174 | token_amount_in: U256, 175 | swap_fee: U256, 176 | ) -> Result { 177 | let weight_ratio = bdiv(token_weight_in, token_weight_out)?; 178 | let adjusted_in = bsub(BONE, swap_fee)?; 179 | let adjusted_in = bmul(token_amount_in, adjusted_in)?; 180 | let y = bdiv(token_balance_in, badd(token_balance_in, adjusted_in)?)?; 181 | let x = bpow(y, weight_ratio)?; 182 | let z = bsub(BONE, x)?; 183 | bmul(token_balance_out, z) 184 | } 185 | -------------------------------------------------------------------------------- /src/amms/consts.rs: -------------------------------------------------------------------------------- 1 | use alloy::primitives::U256; 2 | 3 | // commonly used U256s 4 | pub const U256_10E_10: U256 = U256::from_limbs([10000000000, 0, 0, 0]); 5 | pub const U256_0X100000000: U256 = U256::from_limbs([4294967296, 0, 0, 0]); 6 | pub const U256_0X10000: U256 = U256::from_limbs([65536, 0, 0, 0]); 7 | pub const U256_0X100: U256 = U256::from_limbs([256, 0, 0, 0]); 8 | pub const U256_1000: U256 = U256::from_limbs([1000, 0, 0, 0]); 9 | pub const U256_10000: U256 = U256::from_limbs([10000, 0, 0, 0]); 10 | pub const U256_100000: U256 = U256::from_limbs([100000, 0, 0, 0]); 11 | pub const U256_255: U256 = U256::from_limbs([255, 0, 0, 0]); 12 | pub const U256_192: U256 = U256::from_limbs([192, 0, 0, 0]); 13 | pub const U256_191: U256 = U256::from_limbs([191, 0, 0, 0]); 14 | pub const U256_128: U256 = U256::from_limbs([128, 0, 0, 0]); 15 | pub const U256_64: U256 = U256::from_limbs([64, 0, 0, 0]); 16 | pub const U256_32: U256 = U256::from_limbs([32, 0, 0, 0]); 17 | pub const U256_16: U256 = U256::from_limbs([16, 0, 0, 0]); 18 | pub const U256_10: U256 = U256::from_limbs([10, 0, 0, 0]); 19 | pub const U256_8: U256 = U256::from_limbs([8, 0, 0, 0]); 20 | pub const U256_4: U256 = U256::from_limbs([4, 0, 0, 0]); 21 | pub const U256_2: U256 = U256::from_limbs([2, 0, 0, 0]); 22 | pub const U256_1: U256 = U256::from_limbs([1, 0, 0, 0]); 23 | 24 | // Uniswap V3 specific 25 | pub const POPULATE_TICK_DATA_STEP: u64 = 100000; 26 | pub const Q128: U256 = U256::from_limbs([0, 0, 1, 0]); 27 | pub const Q224: U256 = U256::from_limbs([0, 0, 0, 4294967296]); 28 | 29 | // Balancer V2 specific 30 | pub const BONE: U256 = U256::from_limbs([0xDE0B6B3A7640000, 0, 0, 0]); 31 | 32 | // Others 33 | pub const U128_0X10000000000000000: u128 = 18446744073709551616; 34 | pub const U256_0XFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF: U256 = U256::from_limbs([ 35 | 18446744073709551615, 36 | 18446744073709551615, 37 | 18446744073709551615, 38 | 0, 39 | ]); 40 | pub const U256_0XFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF: U256 = 41 | U256::from_limbs([18446744073709551615, 18446744073709551615, 0, 0]); 42 | 43 | pub const DECIMAL_RADIX: i32 = 10; 44 | pub const MPFR_T_PRECISION: u32 = 70; 45 | -------------------------------------------------------------------------------- /src/amms/erc_4626/mod.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | amm::AutomatedMarketMaker, 3 | consts::{U128_0X10000000000000000, U256_10000, U256_2}, 4 | error::AMMError, 5 | float::q64_to_float, 6 | uniswap_v2::div_uu, 7 | }; 8 | use alloy::{ 9 | eips::BlockId, 10 | network::Network, 11 | primitives::{Address, B256, U256}, 12 | providers::Provider, 13 | rpc::types::Log, 14 | sol, 15 | sol_types::{SolEvent, SolValue}, 16 | }; 17 | use serde::{Deserialize, Serialize}; 18 | use std::cmp::Ordering; 19 | use thiserror::Error; 20 | use tracing::info; 21 | 22 | sol! { 23 | /// Interface of the IERC4626Valut contract 24 | #[derive(Debug, PartialEq, Eq)] 25 | #[sol(rpc)] 26 | contract IERC4626Vault { 27 | event Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares); 28 | event Deposit(address indexed sender,address indexed owner, uint256 assets, uint256 shares); 29 | function totalAssets() external view returns (uint256); 30 | function totalSupply() external view returns (uint256); 31 | } 32 | } 33 | 34 | sol! { 35 | #[allow(missing_docs)] 36 | #[sol(rpc)] 37 | IGetERC4626VaultDataBatchRequest, 38 | "src/amms/abi/GetERC4626VaultDataBatchRequest.json", 39 | } 40 | 41 | #[derive(Error, Debug)] 42 | pub enum ERC4626VaultError { 43 | #[error("Non relative or zero fee")] 44 | NonRelativeOrZeroFee, 45 | #[error("Division by zero")] 46 | DivisionByZero, 47 | #[error("Initialization error")] 48 | InitializationError, 49 | } 50 | 51 | #[derive(Debug, Clone, Default, Serialize, Deserialize)] 52 | pub struct ERC4626Vault { 53 | /// Token received from depositing, i.e. shares token 54 | pub vault_token: Address, 55 | pub vault_token_decimals: u8, 56 | /// Token received from withdrawing, i.e. underlying token 57 | pub asset_token: Address, 58 | pub asset_token_decimals: u8, 59 | /// Total supply of vault tokens 60 | pub vault_reserve: U256, 61 | /// Total balance of asset tokens held by vault 62 | pub asset_reserve: U256, 63 | /// Deposit fee in basis points 64 | pub deposit_fee: u32, 65 | /// Withdrawal fee in basis points 66 | pub withdraw_fee: u32, 67 | } 68 | 69 | impl AutomatedMarketMaker for ERC4626Vault { 70 | fn address(&self) -> Address { 71 | self.vault_token 72 | } 73 | 74 | fn sync_events(&self) -> Vec { 75 | vec![ 76 | IERC4626Vault::Deposit::SIGNATURE_HASH, 77 | IERC4626Vault::Withdraw::SIGNATURE_HASH, 78 | ] 79 | } 80 | 81 | fn sync(&mut self, log: &Log) -> Result<(), AMMError> { 82 | let event_signature = log.data().topics()[0]; 83 | match event_signature { 84 | IERC4626Vault::Deposit::SIGNATURE_HASH => { 85 | let deposit_event = IERC4626Vault::Deposit::decode_log(log.as_ref())?; 86 | self.asset_reserve += deposit_event.assets; 87 | self.vault_reserve += deposit_event.shares; 88 | 89 | info!( 90 | target = "amms::erc_4626::sync", 91 | address = ?self.vault_token, 92 | asset_reserve = ?self.asset_reserve, 93 | vault_reserve = ?self.vault_reserve, 94 | "Deposit" 95 | ); 96 | } 97 | 98 | IERC4626Vault::Withdraw::SIGNATURE_HASH => { 99 | let withdraw_event = IERC4626Vault::Withdraw::decode_log(log.as_ref())?; 100 | self.asset_reserve -= withdraw_event.assets; 101 | self.vault_reserve -= withdraw_event.shares; 102 | 103 | info!( 104 | target = "amms::erc_4626::sync", 105 | address = ?self.vault_token, 106 | asset_reserve = ?self.asset_reserve, 107 | vault_reserve = ?self.vault_reserve, 108 | "Withdraw" 109 | ); 110 | } 111 | 112 | _ => { 113 | return Err(AMMError::UnrecognizedEventSignature(event_signature)); 114 | } 115 | } 116 | 117 | Ok(()) 118 | } 119 | 120 | fn tokens(&self) -> Vec
{ 121 | vec![self.vault_token, self.asset_token] 122 | } 123 | 124 | fn calculate_price(&self, base_token: Address, _quote_token: Address) -> Result { 125 | q64_to_float(self.calculate_price_64_x_64(base_token)?) 126 | } 127 | 128 | fn simulate_swap( 129 | &self, 130 | base_token: Address, 131 | _quote_token: Address, 132 | amount_in: U256, 133 | ) -> Result { 134 | if self.vault_token == base_token { 135 | Ok(self.get_amount_out(amount_in, self.vault_reserve, self.asset_reserve)?) 136 | } else { 137 | Ok(self.get_amount_out(amount_in, self.asset_reserve, self.vault_reserve)?) 138 | } 139 | } 140 | 141 | fn simulate_swap_mut( 142 | &mut self, 143 | base_token: Address, 144 | _quote_token: Address, 145 | amount_in: U256, 146 | ) -> Result { 147 | if self.vault_token == base_token { 148 | let amount_out = 149 | self.get_amount_out(amount_in, self.vault_reserve, self.asset_reserve)?; 150 | 151 | self.vault_reserve -= amount_in; 152 | self.asset_reserve -= amount_out; 153 | 154 | Ok(amount_out) 155 | } else { 156 | let amount_out = 157 | self.get_amount_out(amount_in, self.asset_reserve, self.vault_reserve)?; 158 | 159 | self.asset_reserve += amount_in; 160 | self.vault_reserve += amount_out; 161 | 162 | Ok(amount_out) 163 | } 164 | } 165 | 166 | // TODO: clean up this function 167 | async fn init(mut self, block_number: BlockId, provider: P) -> Result 168 | where 169 | N: Network, 170 | P: Provider + Clone, 171 | { 172 | let deployer = 173 | IGetERC4626VaultDataBatchRequest::deploy_builder(provider, vec![self.vault_token]); 174 | let res = deployer.call_raw().block(block_number).await?; 175 | 176 | let data = as SolValue>::abi_decode(&res)?; 190 | let ( 191 | vault_token, 192 | vault_token_dec, 193 | asset_token, 194 | asset_token_dec, 195 | vault_reserve, 196 | asset_reserve, 197 | deposit_fee_delta_1, 198 | deposit_fee_delta_2, 199 | deposit_no_fee, 200 | withdraw_fee_delta_1, 201 | withdraw_fee_delta_2, 202 | withdraw_no_fee, 203 | ) = if !data.is_empty() { 204 | data[0] 205 | } else { 206 | return Err(ERC4626VaultError::InitializationError)?; 207 | }; 208 | 209 | // If both deltas are zero, the fee is zero 210 | if deposit_fee_delta_1.is_zero() && deposit_fee_delta_2.is_zero() { 211 | self.deposit_fee = 0; 212 | 213 | // Assuming 18 decimals, if the delta of 1e20 is half the delta of 2e20, relative fee. 214 | // Delta / (amount without fee / 10000) to give us the fee in basis points 215 | } else if deposit_fee_delta_1 * U256_2 == deposit_fee_delta_2 { 216 | self.deposit_fee = (deposit_fee_delta_1 / (deposit_no_fee / U256::from(10_000))).to(); 217 | } else { 218 | todo!("Handle error") 219 | } 220 | 221 | // If both deltas are zero, the fee is zero 222 | if withdraw_fee_delta_1.is_zero() && withdraw_fee_delta_2.is_zero() { 223 | self.withdraw_fee = 0; 224 | // Assuming 18 decimals, if the delta of 1e20 is half the delta of 2e20, relative fee. 225 | // Delta / (amount without fee / 10000) to give us the fee in basis points 226 | } else if withdraw_fee_delta_1 * U256::from(2) == withdraw_fee_delta_2 { 227 | self.withdraw_fee = 228 | (withdraw_fee_delta_1 / (withdraw_no_fee / U256::from(10_000))).to(); 229 | } else { 230 | // If not a relative fee or zero, ignore vault 231 | return Err(ERC4626VaultError::NonRelativeOrZeroFee.into()); 232 | } 233 | 234 | // if above does not error => populate the vault 235 | self.vault_token = vault_token; 236 | self.vault_token_decimals = vault_token_dec as u8; 237 | self.asset_token = asset_token; 238 | self.asset_token_decimals = asset_token_dec as u8; 239 | self.vault_reserve = vault_reserve; 240 | self.asset_reserve = asset_reserve; 241 | 242 | Ok(self) 243 | } 244 | } 245 | 246 | // TODO: swap calldata 247 | impl ERC4626Vault { 248 | // Returns a new, unsynced ERC4626 vault 249 | pub fn new(address: Address) -> Self { 250 | Self { 251 | vault_token: address, 252 | ..Default::default() 253 | } 254 | } 255 | 256 | pub fn get_amount_out( 257 | &self, 258 | amount_in: U256, 259 | reserve_in: U256, 260 | reserve_out: U256, 261 | ) -> Result { 262 | if amount_in.is_zero() { 263 | return Ok(U256::ZERO); 264 | } 265 | 266 | if self.vault_reserve.is_zero() { 267 | return Ok(amount_in); 268 | } 269 | 270 | let fee = if reserve_in == self.vault_reserve { 271 | self.withdraw_fee 272 | } else { 273 | self.deposit_fee 274 | }; 275 | 276 | if reserve_in.is_zero() || 10000 - fee == 0 { 277 | return Err(ERC4626VaultError::DivisionByZero.into()); 278 | } 279 | 280 | Ok(amount_in * reserve_out / reserve_in * U256::from(10000 - fee) / U256_10000) 281 | } 282 | 283 | pub fn calculate_price_64_x_64(&self, base_token: Address) -> Result { 284 | let decimal_shift = self.vault_token_decimals as i8 - self.asset_token_decimals as i8; 285 | 286 | // Normalize reserves by decimal shift 287 | let (r_v, r_a) = match decimal_shift.cmp(&0) { 288 | Ordering::Less => ( 289 | self.vault_reserve * U256::from(10u128.pow(decimal_shift.unsigned_abs() as u32)), 290 | self.asset_reserve, 291 | ), 292 | _ => ( 293 | self.vault_reserve, 294 | self.asset_reserve * U256::from(10u128.pow(decimal_shift as u32)), 295 | ), 296 | }; 297 | 298 | // Withdraw 299 | if base_token == self.vault_token { 300 | if r_v.is_zero() { 301 | // Return 1 in Q64 302 | Ok(U128_0X10000000000000000) 303 | } else { 304 | Ok(div_uu(r_a, r_v)?) 305 | } 306 | // Deposit 307 | } else if r_a.is_zero() { 308 | // Return 1 in Q64 309 | Ok(U128_0X10000000000000000) 310 | } else { 311 | Ok(div_uu(r_v, r_a)?) 312 | } 313 | } 314 | 315 | pub async fn get_reserves( 316 | &self, 317 | provider: P, 318 | block_number: BlockId, 319 | ) -> Result<(U256, U256), AMMError> 320 | where 321 | N: Network, 322 | P: Provider + Clone + Clone, 323 | { 324 | let vault = IERC4626Vault::new(self.vault_token, provider); 325 | 326 | let total_assets = vault.totalAssets().block(block_number).call().await?; 327 | 328 | let total_supply = vault.totalSupply().block(block_number).call().await?; 329 | 330 | Ok((total_supply, total_assets)) 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /src/amms/error.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | balancer::BalancerError, erc_4626::ERC4626VaultError, uniswap_v2::UniswapV2Error, 3 | uniswap_v3::UniswapV3Error, 4 | }; 5 | use alloy::{primitives::FixedBytes, transports::TransportErrorKind}; 6 | use thiserror::Error; 7 | 8 | #[derive(Error, Debug)] 9 | pub enum AMMError { 10 | #[error(transparent)] 11 | TransportError(#[from] alloy::transports::RpcError), 12 | #[error(transparent)] 13 | ContractError(#[from] alloy::contract::Error), 14 | #[error(transparent)] 15 | ABIError(#[from] alloy::dyn_abi::Error), 16 | #[error(transparent)] 17 | SolTypesError(#[from] alloy::sol_types::Error), 18 | #[error(transparent)] 19 | UniswapV2Error(#[from] UniswapV2Error), 20 | #[error(transparent)] 21 | UniswapV3Error(#[from] UniswapV3Error), 22 | #[error(transparent)] 23 | BalancerError(#[from] BalancerError), 24 | #[error(transparent)] 25 | ERC4626VaultError(#[from] ERC4626VaultError), 26 | #[error(transparent)] 27 | BatchContractError(#[from] BatchContractError), 28 | #[error(transparent)] 29 | ParseFloatError(#[from] rug::float::ParseFloatError), 30 | #[error("Unrecognized Event Signature {0}")] 31 | UnrecognizedEventSignature(FixedBytes<32>), 32 | #[error(transparent)] 33 | JoinError(#[from] tokio::task::JoinError), 34 | } 35 | 36 | #[derive(Error, Debug)] 37 | pub enum BatchContractError { 38 | #[error(transparent)] 39 | ContractError(#[from] alloy::contract::Error), 40 | #[error(transparent)] 41 | DynABIError(#[from] alloy::dyn_abi::Error), 42 | } 43 | -------------------------------------------------------------------------------- /src/amms/factory.rs: -------------------------------------------------------------------------------- 1 | use super::{amm::Variant, uniswap_v2::UniswapV2Factory, uniswap_v3::UniswapV3Factory}; 2 | use super::{ 3 | amm::{AutomatedMarketMaker, AMM}, 4 | balancer::BalancerFactory, 5 | error::AMMError, 6 | }; 7 | use alloy::{ 8 | eips::BlockId, 9 | network::Network, 10 | primitives::{Address, B256, U256}, 11 | providers::Provider, 12 | rpc::types::eth::Log, 13 | }; 14 | use eyre::Result; 15 | use serde::{Deserialize, Serialize}; 16 | use std::{ 17 | future::Future, 18 | hash::{Hash, Hasher}, 19 | }; 20 | 21 | pub trait DiscoverySync { 22 | fn discover( 23 | &self, 24 | to_block: BlockId, 25 | provider: P, 26 | ) -> impl Future, AMMError>> 27 | where 28 | N: Network, 29 | P: Provider + Clone; 30 | 31 | fn sync( 32 | &self, 33 | amms: Vec, 34 | to_block: BlockId, 35 | provider: P, 36 | ) -> impl Future, AMMError>> 37 | where 38 | N: Network, 39 | P: Provider + Clone; 40 | } 41 | 42 | pub trait AutomatedMarketMakerFactory: DiscoverySync { 43 | type PoolVariant: AutomatedMarketMaker + Default; 44 | 45 | /// Address of the factory contract 46 | fn address(&self) -> Address; 47 | 48 | /// Creates an unsynced pool from a creation log. 49 | fn create_pool(&self, log: Log) -> Result; 50 | 51 | /// Returns the block number at which the factory was created. 52 | fn creation_block(&self) -> u64; 53 | 54 | /// Event signature that indicates when a new pool was created 55 | fn pool_creation_event(&self) -> B256; 56 | 57 | /// Event signatures signifying when a pool created by the factory should be synced 58 | fn pool_events(&self) -> Vec { 59 | Self::PoolVariant::default().sync_events() 60 | } 61 | 62 | fn pool_variant(&self) -> Self::PoolVariant { 63 | Self::PoolVariant::default() 64 | } 65 | } 66 | 67 | macro_rules! factory { 68 | ($($factory_type:ident),+ $(,)?) => { 69 | #[derive(Debug, Clone, Serialize, Deserialize)] 70 | pub enum Factory { 71 | $($factory_type($factory_type),)+ 72 | } 73 | 74 | impl Factory { 75 | pub fn address(&self) -> Address { 76 | match self { 77 | $(Factory::$factory_type(factory) => factory.address(),)+ 78 | } 79 | } 80 | 81 | pub fn discovery_event(&self) -> B256 { 82 | match self { 83 | $(Factory::$factory_type(factory) => factory.pool_creation_event(),)+ 84 | } 85 | } 86 | 87 | pub fn create_pool(&self, log: Log) -> Result { 88 | match self { 89 | $(Factory::$factory_type(factory) => factory.create_pool(log),)+ 90 | } 91 | } 92 | 93 | pub fn creation_block(&self) -> u64 { 94 | match self { 95 | $(Factory::$factory_type(factory) => factory.creation_block(),)+ 96 | } 97 | } 98 | 99 | pub fn pool_events(&self) -> Vec { 100 | match self { 101 | $(Factory::$factory_type(factory) => factory.pool_events(),)+ 102 | } 103 | } 104 | 105 | pub fn variant(&self) -> Variant { 106 | match self { 107 | $(Factory::$factory_type(factory) => AMM::from(factory.pool_variant()).variant(),)+ 108 | } 109 | } 110 | } 111 | 112 | impl Hash for Factory { 113 | fn hash(&self, state: &mut H) { 114 | self.address().hash(state); 115 | } 116 | } 117 | 118 | impl PartialEq for Factory { 119 | fn eq(&self, other: &Self) -> bool { 120 | self.address() == other.address() 121 | } 122 | } 123 | 124 | impl Eq for Factory {} 125 | 126 | 127 | impl Factory { 128 | pub async fn discover< N, P>(&self, to_block: BlockId, provider: P) -> Result, AMMError> 129 | where 130 | N: Network, 131 | P: Provider + Clone, 132 | { 133 | match self { 134 | $(Factory::$factory_type(factory) => factory.discover(to_block, provider).await,)+ 135 | } 136 | } 137 | 138 | pub async fn sync< N, P>(&self, amms: Vec, to_block: BlockId, provider: P) -> Result, AMMError> 139 | where 140 | N: Network, 141 | P: Provider + Clone, 142 | { 143 | match self { 144 | $(Factory::$factory_type(factory) => factory.sync(amms, to_block, provider).await,)+ 145 | } 146 | } 147 | } 148 | 149 | $( 150 | impl From<$factory_type> for Factory { 151 | fn from(factory: $factory_type) -> Self { 152 | Factory::$factory_type(factory) 153 | } 154 | } 155 | )+ 156 | }; 157 | } 158 | 159 | factory!(UniswapV2Factory, UniswapV3Factory, BalancerFactory); 160 | 161 | #[derive(Default)] 162 | pub struct NoopAMM; 163 | impl AutomatedMarketMaker for NoopAMM { 164 | fn address(&self) -> Address { 165 | unreachable!() 166 | } 167 | 168 | fn sync_events(&self) -> Vec { 169 | unreachable!() 170 | } 171 | 172 | fn sync(&mut self, _log: &Log) -> Result<(), AMMError> { 173 | unreachable!() 174 | } 175 | 176 | fn simulate_swap( 177 | &self, 178 | _base_token: Address, 179 | _quote_token: Address, 180 | _amount_in: U256, 181 | ) -> Result { 182 | unreachable!() 183 | } 184 | 185 | fn simulate_swap_mut( 186 | &mut self, 187 | _base_token: Address, 188 | _quote_token: Address, 189 | _amount_in: U256, 190 | ) -> Result { 191 | unreachable!() 192 | } 193 | fn calculate_price( 194 | &self, 195 | _base_token: Address, 196 | _quote_token: Address, 197 | ) -> Result { 198 | unreachable!() 199 | } 200 | 201 | fn tokens(&self) -> Vec
{ 202 | unreachable!() 203 | } 204 | 205 | async fn init(self, _block_number: BlockId, _provider: P) -> Result 206 | where 207 | N: Network, 208 | P: Provider + Clone, 209 | { 210 | unreachable!() 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/amms/float.rs: -------------------------------------------------------------------------------- 1 | use alloy::primitives::U256; 2 | use rug::Float; 3 | 4 | use super::{ 5 | consts::{MPFR_T_PRECISION, U128_0X10000000000000000}, 6 | error::AMMError, 7 | }; 8 | 9 | pub fn q64_to_float(num: u128) -> Result { 10 | let float_num = u128_to_float(num)?; 11 | let divisor = u128_to_float(U128_0X10000000000000000)?; 12 | Ok((float_num / divisor).to_f64()) 13 | } 14 | 15 | pub fn u128_to_float(num: u128) -> Result { 16 | let value_string = num.to_string(); 17 | let parsed_value = Float::parse_radix(value_string, 10)?; 18 | Ok(Float::with_val(MPFR_T_PRECISION, parsed_value)) 19 | } 20 | 21 | pub fn u256_to_float(num: U256) -> Result { 22 | let value_string = num.to_string(); 23 | let parsed_value = Float::parse_radix(value_string, 10)?; 24 | Ok(Float::with_val(MPFR_T_PRECISION, parsed_value)) 25 | } 26 | -------------------------------------------------------------------------------- /src/amms/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | hash::{Hash, Hasher}, 4 | }; 5 | 6 | use alloy::{dyn_abi::DynSolType, network::Network, primitives::Address, providers::Provider, sol}; 7 | use error::{AMMError, BatchContractError}; 8 | use futures::{stream::FuturesUnordered, StreamExt}; 9 | use serde::{Deserialize, Serialize}; 10 | 11 | pub mod amm; 12 | pub mod balancer; 13 | pub mod consts; 14 | pub mod erc_4626; 15 | pub mod error; 16 | pub mod factory; 17 | pub mod float; 18 | pub mod uniswap_v2; 19 | pub mod uniswap_v3; 20 | 21 | sol! { 22 | #[sol(rpc)] 23 | GetTokenDecimalsBatchRequest, 24 | "src/amms/abi/GetTokenDecimalsBatchRequest.json", 25 | } 26 | 27 | sol!( 28 | #[derive(Debug, PartialEq, Eq)] 29 | #[sol(rpc)] 30 | contract IERC20 { 31 | function decimals() external view returns (uint8); 32 | }); 33 | 34 | #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] 35 | pub struct Token { 36 | pub address: Address, 37 | pub decimals: u8, 38 | // TODO: add optional tax 39 | } 40 | 41 | impl Token { 42 | pub async fn new(address: Address, provider: P) -> Result 43 | where 44 | N: Network, 45 | P: Provider + Clone, 46 | { 47 | let decimals = IERC20::new(address, provider).decimals().call().await?; 48 | 49 | Ok(Self { address, decimals }) 50 | } 51 | 52 | pub const fn new_with_decimals(address: Address, decimals: u8) -> Self { 53 | Self { address, decimals } 54 | } 55 | 56 | pub const fn address(&self) -> &Address { 57 | &self.address 58 | } 59 | 60 | pub const fn decimals(&self) -> u8 { 61 | self.decimals 62 | } 63 | } 64 | 65 | impl From
for Token { 66 | fn from(address: Address) -> Self { 67 | Self { 68 | address, 69 | decimals: 0, 70 | } 71 | } 72 | } 73 | 74 | impl Hash for Token { 75 | fn hash(&self, state: &mut H) { 76 | self.address.hash(state); 77 | } 78 | } 79 | 80 | /// Fetches the decimal precision for a list of ERC-20 tokens. 81 | /// 82 | /// # Returns 83 | /// A map of token addresses to their decimal precision. 84 | pub async fn get_token_decimals( 85 | tokens: Vec
, 86 | provider: P, 87 | ) -> Result, BatchContractError> 88 | where 89 | N: Network, 90 | P: Provider + Clone + Clone, 91 | { 92 | let step = 765; 93 | 94 | let mut futures = FuturesUnordered::new(); 95 | tokens.chunks(step).for_each(|group| { 96 | let provider = provider.clone(); 97 | 98 | futures.push(async move { 99 | ( 100 | group, 101 | GetTokenDecimalsBatchRequest::deploy_builder(provider, group.to_vec()) 102 | .call_raw() 103 | .await, 104 | ) 105 | }); 106 | }); 107 | 108 | let mut token_decimals = HashMap::new(); 109 | let return_type = DynSolType::Array(Box::new(DynSolType::Uint(8))); 110 | 111 | while let Some(res) = futures.next().await { 112 | let (token_addresses, return_data) = res; 113 | 114 | let return_data = return_type.abi_decode_sequence(&return_data?)?; 115 | 116 | if let Some(tokens_arr) = return_data.as_array() { 117 | for (decimals, token_address) in tokens_arr.iter().zip(token_addresses.iter()) { 118 | token_decimals.insert( 119 | *token_address, 120 | decimals.as_uint().expect("Could not get uint").0.to::(), 121 | ); 122 | } 123 | } 124 | } 125 | Ok(token_decimals) 126 | } 127 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(test), warn(unused_crate_dependencies))] 2 | 3 | pub mod amms; 4 | pub mod state_space; 5 | -------------------------------------------------------------------------------- /src/state_space/cache.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::amms::amm::{AutomatedMarketMaker, AMM}; 4 | use arraydeque::ArrayDeque; 5 | 6 | #[derive(Debug)] 7 | 8 | pub struct StateChangeCache { 9 | oldest_block: u64, 10 | cache: ArrayDeque, 11 | } 12 | 13 | impl Default for StateChangeCache { 14 | fn default() -> Self { 15 | Self::new() 16 | } 17 | } 18 | 19 | impl StateChangeCache { 20 | pub fn new() -> Self { 21 | StateChangeCache { 22 | oldest_block: 0, 23 | cache: ArrayDeque::new(), 24 | } 25 | } 26 | 27 | pub fn is_empty(&self) -> bool { 28 | self.cache.is_empty() 29 | } 30 | 31 | pub fn push(&mut self, state_change: StateChange) { 32 | let cache = &mut self.cache; 33 | 34 | if cache.is_full() { 35 | cache.pop_back(); 36 | self.oldest_block = cache.back().unwrap().block_number; 37 | } 38 | 39 | // We can unwrap here since we check if the cache is full 40 | cache.push_front(state_change).unwrap(); 41 | } 42 | 43 | /// Unwinds the state changes up to the given block number 44 | /// Returns the state of the affected AMMs at the block number provided 45 | pub fn unwind_state_changes(&mut self, block_to_unwind: u64) -> Vec { 46 | let cache = &mut self.cache; 47 | 48 | if block_to_unwind < self.oldest_block { 49 | panic!("Block to unwind < oldest block in cache"); 50 | } 51 | 52 | // If the block to unwind is greater than the latest state change in the block, exit early 53 | if cache 54 | .front() 55 | .is_none_or(|latest| block_to_unwind > latest.block_number) 56 | { 57 | return vec![]; 58 | } 59 | 60 | let pivot_idx = cache 61 | .iter() 62 | .position(|state_change| state_change.block_number < block_to_unwind); 63 | 64 | let state_changes = if let Some(pivot_idx) = pivot_idx { 65 | cache.drain(..pivot_idx).collect() 66 | } else { 67 | cache.drain(..).collect::>() 68 | }; 69 | 70 | self.flatten_state_changes(state_changes) 71 | } 72 | 73 | fn flatten_state_changes(&self, state_changes: Vec) -> Vec { 74 | state_changes 75 | .into_iter() 76 | .rev() 77 | .fold(HashMap::new(), |mut amms, state_change| { 78 | for amm in state_change.state_change { 79 | amms.entry(amm.address()).or_insert(amm); 80 | } 81 | amms 82 | }) 83 | .into_values() 84 | .collect() 85 | } 86 | } 87 | 88 | // NOTE: we can probably make this more efficient and create a state change struct for each amm rather than 89 | // cloning each amm when caching 90 | #[derive(Debug, Clone)] 91 | pub struct StateChange { 92 | pub state_change: Vec, 93 | pub block_number: u64, 94 | } 95 | 96 | impl StateChange { 97 | pub fn new(state_change: Vec, block_number: u64) -> Self { 98 | Self { 99 | block_number, 100 | state_change, 101 | } 102 | } 103 | } 104 | // TODO: add tests 105 | -------------------------------------------------------------------------------- /src/state_space/discovery.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | 3 | use alloy::primitives::{Address, FixedBytes}; 4 | 5 | use crate::amms::factory::Factory; 6 | 7 | use super::filters::PoolFilter; 8 | 9 | #[derive(Debug, Default, Clone)] 10 | pub struct DiscoveryManager { 11 | pub factories: HashMap, 12 | pub pool_filters: Option>, 13 | pub token_decimals: HashMap, 14 | } 15 | 16 | impl DiscoveryManager { 17 | pub fn new(factories: Vec) -> Self { 18 | let factories = factories 19 | .into_iter() 20 | .map(|factory| { 21 | let address = factory.address(); 22 | (address, factory) 23 | }) 24 | .collect(); 25 | Self { 26 | factories, 27 | ..Default::default() 28 | } 29 | } 30 | 31 | pub fn with_pool_filters(self, pool_filters: Vec) -> Self { 32 | Self { 33 | pool_filters: Some(pool_filters), 34 | ..self 35 | } 36 | } 37 | 38 | pub fn disc_events(&self) -> HashSet> { 39 | self.factories 40 | .iter() 41 | .fold(HashSet::new(), |mut events_set, (_, factory)| { 42 | events_set.extend([factory.discovery_event()]); 43 | events_set 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/state_space/error.rs: -------------------------------------------------------------------------------- 1 | use alloy::transports::TransportErrorKind; 2 | use thiserror::Error; 3 | 4 | use crate::amms::error::AMMError; 5 | 6 | #[derive(Error, Debug)] 7 | pub enum StateSpaceError { 8 | #[error(transparent)] 9 | AMMError(#[from] AMMError), 10 | #[error(transparent)] 11 | TransportError(#[from] alloy::transports::RpcError), 12 | #[error(transparent)] 13 | JoinError(#[from] tokio::task::JoinError), 14 | #[error("Block Number Does not Exist")] 15 | MissingBlockNumber, 16 | } 17 | -------------------------------------------------------------------------------- /src/state_space/filters/blacklist.rs: -------------------------------------------------------------------------------- 1 | use alloy::primitives::Address; 2 | use async_trait::async_trait; 3 | 4 | use crate::amms::{ 5 | amm::{AutomatedMarketMaker, AMM}, 6 | error::AMMError, 7 | }; 8 | 9 | use super::{AMMFilter, FilterStage}; 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct BlacklistFilter { 13 | /// A blacklist of addresses to exclusively disallow 14 | blacklist: Vec
, 15 | } 16 | 17 | impl BlacklistFilter { 18 | pub fn new(blacklist: Vec
) -> Self { 19 | Self { blacklist } 20 | } 21 | } 22 | 23 | #[async_trait] 24 | impl AMMFilter for BlacklistFilter { 25 | /// Filter for any AMMs or tokens not in the blacklist 26 | async fn filter(&self, amms: Vec) -> Result, AMMError> { 27 | Ok(amms 28 | .into_iter() 29 | .filter(|amm| { 30 | !self.blacklist.contains(&amm.address()) 31 | && amm.tokens().iter().all(|t| !self.blacklist.contains(t)) 32 | }) 33 | .collect()) 34 | } 35 | 36 | /// Filter stage is set to after the sync stage to ensure the blacklist is applied to all 37 | /// pool and token addresses after the pools have been fully populated 38 | fn stage(&self) -> FilterStage { 39 | FilterStage::Sync 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/state_space/filters/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod blacklist; 2 | pub mod value; 3 | pub mod whitelist; 4 | 5 | use async_trait::async_trait; 6 | use blacklist::BlacklistFilter; 7 | use whitelist::{PoolWhitelistFilter, TokenWhitelistFilter}; 8 | 9 | use crate::amms::{amm::AMM, error::AMMError}; 10 | #[async_trait] 11 | pub trait AMMFilter { 12 | async fn filter(&self, amms: Vec) -> Result, AMMError>; 13 | fn stage(&self) -> FilterStage; 14 | } 15 | 16 | #[derive(Debug, Clone, PartialEq, Eq)] 17 | pub enum FilterStage { 18 | Discovery, 19 | Sync, 20 | } 21 | 22 | macro_rules! filter { 23 | ($($filter_type:ident),+ $(,)?) => { 24 | #[derive(Debug, Clone)] 25 | pub enum PoolFilter { 26 | $($filter_type($filter_type),)+ 27 | } 28 | 29 | #[async_trait] 30 | impl AMMFilter for PoolFilter { 31 | async fn filter(&self, amms: Vec) -> Result, AMMError> { 32 | match self { 33 | $(PoolFilter::$filter_type(filter) => filter.filter(amms).await,)+ 34 | } 35 | } 36 | 37 | fn stage(&self) -> FilterStage { 38 | match self { 39 | $(PoolFilter::$filter_type(filter) => filter.stage(),)+ 40 | } 41 | } 42 | } 43 | 44 | $( 45 | impl From<$filter_type> for PoolFilter { 46 | fn from(filter: $filter_type) -> Self { 47 | PoolFilter::$filter_type(filter) 48 | } 49 | } 50 | )+ 51 | }; 52 | } 53 | 54 | filter!( 55 | BlacklistFilter, 56 | PoolWhitelistFilter, 57 | TokenWhitelistFilter, 58 | // ValueFilter 59 | ); 60 | -------------------------------------------------------------------------------- /src/state_space/filters/value.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, marker::PhantomData}; 2 | 3 | use super::{AMMFilter, FilterStage}; 4 | use crate::amms::{ 5 | amm::{AutomatedMarketMaker, AMM}, 6 | error::AMMError, 7 | }; 8 | use alloy::{ 9 | network::Network, 10 | primitives::{Address, U256}, 11 | providers::Provider, 12 | sol, 13 | sol_types::SolValue, 14 | }; 15 | use async_trait::async_trait; 16 | use WethValueInPools::{PoolInfo, PoolInfoReturn}; 17 | 18 | sol! { 19 | #[sol(rpc)] 20 | WethValueInPoolsBatchRequest, 21 | "src/amms/abi/WethValueInPoolsBatchRequest.json" 22 | } 23 | 24 | pub struct ValueFilter 25 | where 26 | N: Network, 27 | P: Provider + Clone, 28 | { 29 | pub uniswap_v2_factory: Address, 30 | pub uniswap_v3_factory: Address, 31 | pub weth: Address, 32 | pub min_weth_threshold: U256, 33 | pub provider: P, 34 | phantom: PhantomData, 35 | } 36 | 37 | impl ValueFilter 38 | where 39 | N: Network, 40 | P: Provider + Clone, 41 | { 42 | pub fn new( 43 | uniswap_v2_factory: Address, 44 | uniswap_v3_factory: Address, 45 | weth: Address, 46 | min_weth_threshold: U256, 47 | provider: P, 48 | ) -> Self { 49 | Self { 50 | uniswap_v2_factory, 51 | uniswap_v3_factory, 52 | weth, 53 | min_weth_threshold, 54 | provider, 55 | phantom: PhantomData, 56 | } 57 | } 58 | 59 | pub async fn get_weth_value_in_pools( 60 | &self, 61 | pools: Vec, 62 | ) -> Result, AMMError> { 63 | let deployer = WethValueInPoolsBatchRequest::deploy_builder( 64 | self.provider.clone(), 65 | self.uniswap_v2_factory, 66 | self.uniswap_v3_factory, 67 | self.weth, 68 | pools, 69 | ); 70 | 71 | let res = deployer.call_raw().await?; 72 | let return_data = as SolValue>::abi_decode(&res)?; 73 | 74 | Ok(return_data 75 | .into_iter() 76 | .map(|pool_info| (pool_info.poolAddress, pool_info)) 77 | .collect()) 78 | } 79 | } 80 | 81 | #[async_trait] 82 | impl AMMFilter for ValueFilter 83 | where 84 | N: Network, 85 | P: Provider + Clone, 86 | { 87 | async fn filter(&self, amms: Vec) -> Result, AMMError> { 88 | let pool_infos = amms 89 | .iter() 90 | .cloned() 91 | .map(|amm| { 92 | let pool_address = amm.address(); 93 | let pool_type = match amm { 94 | AMM::BalancerPool(_) => 0, 95 | AMM::UniswapV2Pool(_) => 1, 96 | AMM::UniswapV3Pool(_) => 2, 97 | // TODO: At the moment, filters are not compatible with vaults 98 | AMM::ERC4626Vault(_) => todo!(), 99 | }; 100 | 101 | PoolInfo { 102 | poolType: pool_type, 103 | poolAddress: pool_address, 104 | } 105 | }) 106 | .collect::>(); 107 | 108 | let mut pool_info_returns = HashMap::new(); 109 | let futs = pool_infos 110 | .chunks(CHUNK_SIZE) 111 | .map(|chunk| async { self.get_weth_value_in_pools(chunk.to_vec()).await }) 112 | .collect::>(); 113 | 114 | let results = futures::future::join_all(futs).await; 115 | for result in results { 116 | pool_info_returns.extend(result?); 117 | } 118 | 119 | let filtered_amms = amms 120 | .into_iter() 121 | .filter(|amm| { 122 | let pool_address = amm.address(); 123 | pool_info_returns 124 | .get(&pool_address) 125 | .is_some_and(|pool_info_return| { 126 | pool_info_return.wethValue > self.min_weth_threshold 127 | }) 128 | }) 129 | .collect::>(); 130 | Ok(filtered_amms) 131 | } 132 | 133 | fn stage(&self) -> FilterStage { 134 | FilterStage::Sync 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/state_space/filters/whitelist.rs: -------------------------------------------------------------------------------- 1 | use alloy::primitives::Address; 2 | use async_trait::async_trait; 3 | 4 | use crate::amms::{ 5 | amm::{AutomatedMarketMaker, AMM}, 6 | error::AMMError, 7 | }; 8 | 9 | use super::{AMMFilter, FilterStage}; 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct PoolWhitelistFilter { 13 | pools: Vec
, 14 | } 15 | 16 | impl PoolWhitelistFilter { 17 | pub fn new(pools: Vec
) -> Self { 18 | Self { pools } 19 | } 20 | } 21 | 22 | #[async_trait] 23 | impl AMMFilter for PoolWhitelistFilter { 24 | /// Filter for any AMMs or tokens in the whitelist 25 | async fn filter(&self, amms: Vec) -> Result, AMMError> { 26 | Ok(amms 27 | .into_iter() 28 | .filter(|amm| self.pools.contains(&amm.address())) 29 | .collect()) 30 | } 31 | 32 | fn stage(&self) -> FilterStage { 33 | FilterStage::Discovery 34 | } 35 | } 36 | 37 | #[derive(Debug, Clone)] 38 | pub struct TokenWhitelistFilter { 39 | tokens: Vec
, 40 | } 41 | 42 | impl TokenWhitelistFilter { 43 | pub fn new(tokens: Vec
) -> Self { 44 | Self { tokens } 45 | } 46 | } 47 | 48 | #[async_trait] 49 | impl AMMFilter for TokenWhitelistFilter { 50 | /// Filter for any AMMs or tokens in the whitelist 51 | async fn filter(&self, amms: Vec) -> Result, AMMError> { 52 | Ok(amms 53 | .into_iter() 54 | .filter(|amm| amm.tokens().iter().any(|t| self.tokens.contains(t))) 55 | .collect()) 56 | } 57 | 58 | fn stage(&self) -> FilterStage { 59 | FilterStage::Sync 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/state_space/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cache; 2 | pub mod discovery; 3 | pub mod error; 4 | pub mod filters; 5 | 6 | use crate::amms::amm::AutomatedMarketMaker; 7 | use crate::amms::amm::AMM; 8 | use crate::amms::error::AMMError; 9 | use crate::amms::factory::Factory; 10 | 11 | use alloy::consensus::BlockHeader; 12 | use alloy::eips::BlockId; 13 | use alloy::rpc::types::{Block, Filter, FilterSet, Log}; 14 | use alloy::{ 15 | network::Network, 16 | primitives::{Address, FixedBytes}, 17 | providers::Provider, 18 | }; 19 | use async_stream::stream; 20 | use cache::StateChange; 21 | use cache::StateChangeCache; 22 | 23 | use error::StateSpaceError; 24 | use filters::AMMFilter; 25 | use filters::PoolFilter; 26 | use futures::stream::FuturesUnordered; 27 | use futures::Stream; 28 | use futures::StreamExt; 29 | use std::collections::HashSet; 30 | use std::pin::Pin; 31 | use std::sync::atomic::AtomicU64; 32 | use std::sync::atomic::Ordering; 33 | use std::{collections::HashMap, marker::PhantomData, sync::Arc}; 34 | use tokio::sync::RwLock; 35 | use tracing::debug; 36 | use tracing::info; 37 | 38 | pub const CACHE_SIZE: usize = 30; 39 | 40 | #[derive(Clone)] 41 | pub struct StateSpaceManager { 42 | pub state: Arc>, 43 | pub latest_block: Arc, 44 | // discovery_manager: Option, 45 | pub block_filter: Filter, 46 | pub provider: P, 47 | phantom: PhantomData, 48 | // TODO: add support for caching 49 | } 50 | 51 | impl StateSpaceManager { 52 | pub async fn subscribe( 53 | &self, 54 | ) -> Result< 55 | Pin, StateSpaceError>> + Send>>, 56 | StateSpaceError, 57 | > 58 | where 59 | P: Provider + Clone + 'static, 60 | N: Network, 61 | { 62 | let provider = self.provider.clone(); 63 | let latest_block = self.latest_block.clone(); 64 | let state = self.state.clone(); 65 | let mut block_filter = self.block_filter.clone(); 66 | 67 | let block_stream = provider.subscribe_blocks().await?.into_stream(); 68 | 69 | Ok(Box::pin(stream! { 70 | tokio::pin!(block_stream); 71 | 72 | while let Some(block) = block_stream.next().await { 73 | let block_number = block.number(); 74 | block_filter = block_filter.select(block_number); 75 | 76 | 77 | let logs = provider.get_logs(&block_filter).await?; 78 | 79 | let affected_amms = state.write().await.sync(&logs)?; 80 | latest_block.store(block_number, Ordering::Relaxed); 81 | 82 | yield Ok(affected_amms); 83 | } 84 | })) 85 | } 86 | } 87 | 88 | // TODO: Drop impl, create a checkpoint 89 | #[derive(Debug, Default)] 90 | pub struct StateSpaceBuilder { 91 | pub provider: P, 92 | pub latest_block: u64, 93 | pub factories: Vec, 94 | pub amms: Vec, 95 | pub filters: Vec, 96 | phantom: PhantomData, 97 | // TODO: add support for caching 98 | } 99 | 100 | impl StateSpaceBuilder 101 | where 102 | N: Network, 103 | P: Provider + Clone + 'static, 104 | { 105 | pub fn new(provider: P) -> StateSpaceBuilder { 106 | Self { 107 | provider, 108 | latest_block: 0, 109 | factories: vec![], 110 | amms: vec![], 111 | filters: vec![], 112 | // discovery: false, 113 | phantom: PhantomData, 114 | } 115 | } 116 | 117 | pub fn block(self, latest_block: u64) -> StateSpaceBuilder { 118 | StateSpaceBuilder { 119 | latest_block, 120 | ..self 121 | } 122 | } 123 | 124 | pub fn with_factories(self, factories: Vec) -> StateSpaceBuilder { 125 | StateSpaceBuilder { factories, ..self } 126 | } 127 | 128 | pub fn with_amms(self, amms: Vec) -> StateSpaceBuilder { 129 | StateSpaceBuilder { amms, ..self } 130 | } 131 | 132 | pub fn with_filters(self, filters: Vec) -> StateSpaceBuilder { 133 | StateSpaceBuilder { filters, ..self } 134 | } 135 | 136 | pub async fn sync(self) -> Result, AMMError> { 137 | let chain_tip = BlockId::from(self.provider.get_block_number().await?); 138 | let factories = self.factories.clone(); 139 | let mut futures = FuturesUnordered::new(); 140 | 141 | let mut filter_set = HashSet::new(); 142 | for factory in &self.factories { 143 | for event in factory.pool_events() { 144 | filter_set.insert(event); 145 | } 146 | } 147 | 148 | for amm in self.amms.iter() { 149 | for event in amm.sync_events() { 150 | filter_set.insert(event); 151 | } 152 | } 153 | 154 | let block_filter = Filter::new().event_signature(FilterSet::from( 155 | filter_set.into_iter().collect::>>(), 156 | )); 157 | let mut amm_variants = HashMap::new(); 158 | for amm in self.amms.into_iter() { 159 | amm_variants 160 | .entry(amm.variant()) 161 | .or_insert_with(Vec::new) 162 | .push(amm); 163 | } 164 | 165 | for factory in factories { 166 | let provider = self.provider.clone(); 167 | let filters = self.filters.clone(); 168 | 169 | let extension = amm_variants.remove(&factory.variant()); 170 | futures.push(tokio::spawn(async move { 171 | let mut discovered_amms = factory.discover(chain_tip, provider.clone()).await?; 172 | 173 | if let Some(amms) = extension { 174 | discovered_amms.extend(amms); 175 | } 176 | 177 | // Apply discovery filters 178 | for filter in filters.iter() { 179 | if filter.stage() == filters::FilterStage::Discovery { 180 | let pre_filter_len = discovered_amms.len(); 181 | discovered_amms = filter.filter(discovered_amms).await?; 182 | 183 | info!( 184 | target: "state_space::sync", 185 | factory = %factory.address(), 186 | pre_filter_len, 187 | post_filter_len = discovered_amms.len(), 188 | filter = ?filter, 189 | "Discovery filter" 190 | ); 191 | } 192 | } 193 | 194 | discovered_amms = factory.sync(discovered_amms, chain_tip, provider).await?; 195 | 196 | // Apply sync filters 197 | for filter in filters.iter() { 198 | if filter.stage() == filters::FilterStage::Sync { 199 | let pre_filter_len = discovered_amms.len(); 200 | discovered_amms = filter.filter(discovered_amms).await?; 201 | 202 | info!( 203 | target: "state_space::sync", 204 | factory = %factory.address(), 205 | pre_filter_len, 206 | post_filter_len = discovered_amms.len(), 207 | filter = ?filter, 208 | "Sync filter" 209 | ); 210 | } 211 | } 212 | 213 | Ok::, AMMError>(discovered_amms) 214 | })); 215 | } 216 | 217 | let mut state_space = StateSpace::default(); 218 | while let Some(res) = futures.next().await { 219 | let synced_amms = res??; 220 | 221 | for amm in synced_amms { 222 | state_space.state.insert(amm.address(), amm); 223 | } 224 | } 225 | 226 | // Sync remaining AMM variants 227 | for (_, remaining_amms) in amm_variants.drain() { 228 | for mut amm in remaining_amms { 229 | let address = amm.address(); 230 | amm = amm.init(chain_tip, self.provider.clone()).await?; 231 | state_space.state.insert(address, amm); 232 | } 233 | } 234 | 235 | Ok(StateSpaceManager { 236 | latest_block: Arc::new(AtomicU64::new(self.latest_block)), 237 | state: Arc::new(RwLock::new(state_space)), 238 | block_filter, 239 | provider: self.provider, 240 | phantom: PhantomData, 241 | }) 242 | } 243 | } 244 | 245 | #[derive(Debug, Default)] 246 | pub struct StateSpace { 247 | pub state: HashMap, 248 | pub latest_block: Arc, 249 | cache: StateChangeCache, 250 | } 251 | 252 | impl StateSpace { 253 | pub fn get(&self, address: &Address) -> Option<&AMM> { 254 | self.state.get(address) 255 | } 256 | 257 | pub fn get_mut(&mut self, address: &Address) -> Option<&mut AMM> { 258 | self.state.get_mut(address) 259 | } 260 | 261 | pub fn sync(&mut self, logs: &[Log]) -> Result, StateSpaceError> { 262 | let latest = self.latest_block.load(Ordering::Relaxed); 263 | let Some(mut block_number) = logs 264 | .first() 265 | .map(|log| log.block_number.ok_or(StateSpaceError::MissingBlockNumber)) 266 | .transpose()? 267 | else { 268 | return Ok(vec![]); 269 | }; 270 | 271 | // Check if there is a reorg and unwind to state before block_number 272 | if latest >= block_number { 273 | info!( 274 | target: "state_space::sync", 275 | from = %latest, 276 | to = %block_number - 1, 277 | "Unwinding state changes" 278 | ); 279 | 280 | let cached_state = self.cache.unwind_state_changes(block_number); 281 | for amm in cached_state { 282 | debug!(target: "state_space::sync", ?amm, "Reverting AMM state"); 283 | self.state.insert(amm.address(), amm); 284 | } 285 | } 286 | 287 | let mut cached_amms = HashSet::new(); 288 | let mut affected_amms = HashSet::new(); 289 | for log in logs { 290 | // If the block number is updated, cache the current block state changes 291 | let log_block_number = log 292 | .block_number 293 | .ok_or(StateSpaceError::MissingBlockNumber)?; 294 | if log_block_number != block_number { 295 | let amms = cached_amms.drain().collect::>(); 296 | affected_amms.extend(amms.iter().map(|amm| amm.address())); 297 | let state_change = StateChange::new(amms, block_number); 298 | 299 | debug!( 300 | target: "state_space::sync", 301 | state_change = ?state_change, 302 | "Caching state change" 303 | ); 304 | 305 | self.cache.push(state_change); 306 | block_number = log_block_number; 307 | } 308 | 309 | // If the AMM is in the state space add the current state to cache and sync from log 310 | let address = log.address(); 311 | if let Some(amm) = self.state.get_mut(&address) { 312 | cached_amms.insert(amm.clone()); 313 | amm.sync(log)?; 314 | 315 | info!( 316 | target: "state_space::sync", 317 | ?amm, 318 | "Synced AMM" 319 | ); 320 | } 321 | } 322 | 323 | if !cached_amms.is_empty() { 324 | let amms = cached_amms.drain().collect::>(); 325 | affected_amms.extend(amms.iter().map(|amm| amm.address())); 326 | let state_change = StateChange::new(amms, block_number); 327 | 328 | debug!( 329 | target: "state_space::sync", 330 | state_change = ?state_change, 331 | "Caching state change" 332 | ); 333 | 334 | self.cache.push(state_change); 335 | } 336 | 337 | Ok(affected_amms.into_iter().collect()) 338 | } 339 | } 340 | 341 | #[macro_export] 342 | macro_rules! sync { 343 | // Sync factories with provider 344 | ($factories:expr, $provider:expr) => {{ 345 | StateSpaceBuilder::new($provider.clone()) 346 | .with_factories($factories) 347 | .sync() 348 | .await? 349 | }}; 350 | 351 | // Sync factories with filters 352 | ($factories:expr, $filters:expr, $provider:expr) => {{ 353 | StateSpaceBuilder::new($provider.clone()) 354 | .with_factories($factories) 355 | .with_filters($filters) 356 | .sync() 357 | .await? 358 | }}; 359 | 360 | ($factories:expr, $amms:expr, $filters:expr, $provider:expr) => {{ 361 | StateSpaceBuilder::new($provider.clone()) 362 | .with_factories($factories) 363 | .with_amms($amms) 364 | .with_filters($filters) 365 | .sync() 366 | .await? 367 | }}; 368 | } 369 | --------------------------------------------------------------------------------