diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c9d1930..64f8e142 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,7 @@ jobs: run: forge test --isolate -vvv env: FOUNDRY_PROFILE: pr - FORGE_SNAPSHOT_CHECK: true + FORGE_SNAPSHOT_CHECK: false MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }} UNICHAIN_RPC_URL: ${{ secrets.UNICHAIN_RPC_URL }} ARBITRUM_RPC_URL: ${{ secrets.ARBITRUM_RPC_URL }} diff --git a/.gitmodules b/.gitmodules index 4a8855bd..9b6490e2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,6 @@ [submodule "lib/native-token-transfers"] path = lib/native-token-transfers url = https://github.com/wormhole-foundation/native-token-transfers +[submodule "lib/govkit"] + path = lib/govkit + url = https://github.com/Uniswap/govkit diff --git a/.records/DeployFeeInfraRobinhood.json b/.records/DeployFeeInfraRobinhood.json new file mode 100644 index 00000000..31aa6992 --- /dev/null +++ b/.records/DeployFeeInfraRobinhood.json @@ -0,0 +1,8 @@ +{ + "4663": { + "ArbitrumOrbitDeployer": "0x81405C50595f09175d282ccEc657e090EE54AB2E", + "TokenJar": "0x2aC03e14Cfe755426DaAEe0a4994184Ce81482F8", + "Releaser": "0x7a8f74C2585F84c781F951B7f2FF21337D5b630b", + "V3OpenFeeAdapter": "0x05C420bC4823e039AA4dA645eDde743486dAAA25" + } +} \ No newline at end of file diff --git a/.records/DeployV4FeeInfra.json b/.records/DeployV4FeeInfra.json new file mode 100644 index 00000000..fdc50e3f --- /dev/null +++ b/.records/DeployV4FeeInfra.json @@ -0,0 +1,34 @@ +{ + "8453": { + "V4FeeAdapter": "0xc53DD825dedBE7814562528A1A30e8e5BCbB4b55", + "V4FeePolicy": "0xf963bdbe8BA2aABBB3190fF2268F3d833393ab71" + }, + "1": { + "V4FeeAdapter": "0x89A5D5bF00a27D55c02951E49078a5C5771051dB", + "V4FeePolicy": "0x1cd822b70a0591420F65E94b9B3A0D0b0fB3a314" + }, + "42161": { + "V4FeeAdapter": "0x6b3C04567CC62fc5D6AB8D50f1bc1Dee161eb2Cc", + "V4FeePolicy": "0x8F6a5A19Ff1adc6AA9456d6b201fA4FC7589BD0B" + }, + "56": { + "V4FeeAdapter": "0x66276D2e80784180E4B9b859e4327b9C5646B87c", + "V4FeePolicy": "0x020B1Ad79037f02feC357Ec4601fFbe5f7ED6872" + }, + "42220": { + "V4FeeAdapter": "0xfd39ac616e630e9db03EFB2c9a4Fe63eDB949233", + "V4FeePolicy": "0xe1Adf3f130Abf616a7aB04AEeBf8D0470F6B6a16" + }, + "10": { + "V4FeeAdapter": "0xd85393784234fB2ee4f077867A8B78E1aD4cE56B", + "V4FeePolicy": "0x13d9D19802660b8167B3c84C1fcDCD0163672eB8" + }, + "4663": { + "V4FeeAdapter": "0x6d0009504D129CF5002Dba61D9Ae8575AA79314c", + "V4FeePolicy": "0x6ee984309ba26c224f12Bb85D6F7f3C019483DFc" + }, + "137": { + "V4FeeAdapter": "0x59e84309Afa65F084F9c03668583d231afB12cc6", + "V4FeePolicy": "0x600c29d3Ce95Ce503FAb43753cbcb5Fa87EE6093" + } +} \ No newline at end of file diff --git a/README.md b/README.md index 0fc6cf67..c18d6c74 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ - [2. Fee Sources](#2-fee-sources) - [Push vs Pull Models](#push-vs-pull-models) - [Supported Protocols](#supported-protocols) + - [V4 Fee Resolution](#v4-fee-resolution) - [3. Releasers](#3-releasers) - [UNI Burn (Mainnet)](#uni-burn-mainnet) - [Cross-Chain UNI Burn (OP Stack L2s)](#cross-chain-uni-burn-op-stack-l2s) @@ -33,6 +34,7 @@ - [Base (Chain ID: 8453)](#base-chain-id-8453) - [Polygon (Chain ID: 137)](#polygon-chain-id-137) - [BNB Chain (Chain ID: 56)](#bnb-chain-chain-id-56) + - [Robinhood Chain (Chain ID: 4663)](#robinhood-chain-chain-id-4663) - [Development](#development) - [Prerequisites](#prerequisites) - [Installation](#installation) @@ -289,6 +291,8 @@ Ethereum Mainnet | Agreement Anchor 3 | [`0xF9F85a17cC6De9150Cd139f64b127976a1dE91D1`](https://etherscan.io/address/0xF9F85a17cC6De9150Cd139f64b127976a1dE91D1) | | Ntt Manager | [`0x6569925Aac77D6B8Bb085F31F9828ff80D5a0c44`](https://etherscan.io/address/0x6569925Aac77D6B8Bb085F31F9828ff80D5a0c44) | | Wormhole Transceiver | [`0x7597C40Fd3df66b750C14ad4D90524e247499011`](https://etherscan.io/address/0x7597C40Fd3df66b750C14ad4D90524e247499011) | +| V4FeeAdapter | [`0x89A5D5bF00a27D55c02951E49078a5C5771051dB`](https://etherscan.io/address/0x89A5D5bF00a27D55c02951E49078a5C5771051dB) | +| V4FeePolicy | [`0x1cd822b70a0591420F65E94b9B3A0D0b0fB3a314`](https://etherscan.io/address/0x1cd822b70a0591420F65E94b9B3A0D0b0fB3a314) | ### Unichain (Chain ID: 130) @@ -326,6 +330,8 @@ Ethereum Mainnet | TokenJar | [`0x190c22c5085640D1cB60CeC88a4F736Acb59bb6B`](https://celoscan.io/address/0x190c22c5085640D1cB60CeC88a4F736Acb59bb6B) | | Releaser (OptimismBridgedResourceFirepit) | [`0x2758FbaA228D7d3c41dD139F47dab1a27bF9bc25`](https://celoscan.io/address/0x2758FbaA228D7d3c41dD139F47dab1a27bF9bc25) | | V3OpenFeeAdapter | [`0xB9952C01830306ea2fAAe1505f6539BD260Bfc48`](https://celoscan.io/address/0xB9952C01830306ea2fAAe1505f6539BD260Bfc48) | +| V4FeeAdapter | [`0xfd39ac616e630e9db03EFB2c9a4Fe63eDB949233`](https://celoscan.io/address/0xfd39ac616e630e9db03EFB2c9a4Fe63eDB949233) | +| V4FeePolicy | [`0xe1Adf3f130Abf616a7aB04AEeBf8D0470F6B6a16`](https://celoscan.io/address/0xe1Adf3f130Abf616a7aB04AEeBf8D0470F6B6a16) | ### Zora (Chain ID: 7777777) @@ -354,6 +360,9 @@ Ethereum Mainnet | TokenJar | [`0x95E337C5B155385945D407f5396387D0c2a3A263`](https://arbiscan.io/address/0x95E337C5B155385945D407f5396387D0c2a3A263) | | Releaser | [`0xB8018422bcE25D82E70cB98FdA96a4f502D89427`](https://arbiscan.io/address/0xB8018422bcE25D82E70cB98FdA96a4f502D89427) | | V3OpenFeeAdapter | [`0xFF7aD5dA31fECdC678796c88B05926dB896b0699`](https://arbiscan.io/address/0xFF7aD5dA31fECdC678796c88B05926dB896b0699) | +| V4FeeAdapter | [`0x66276D2e80784180E4B9b859e4327b9C5646B87c`](https://arbiscan.io/address/0x66276D2e80784180E4B9b859e4327b9C5646B87c) | +| V4FeePolicy | [`0x020B1Ad79037f02feC357Ec4601fFbe5f7ED6872`](https://arbiscan.io/address/0x8F6a5A19Ff1adc6AA9456d6b201fA4FC7589BD0B) | + ### OP Mainnet (Chain ID: 10) @@ -363,6 +372,8 @@ Ethereum Mainnet | TokenJar | [`0xb13285DF724ea75f3f1E9912010B7e491dCd5EE3`](https://optimistic.etherscan.io/address/0xb13285DF724ea75f3f1E9912010B7e491dCd5EE3) | | Releaser (OptimismBridgedResourceFirepit) | [`0x94460443Ca27FFC1baeCa61165fde18346C91AbD`](https://optimistic.etherscan.io/address/0x94460443Ca27FFC1baeCa61165fde18346C91AbD) | | V3OpenFeeAdapter | [`0xec23Cf5A1db3dcC6595385D28B2a4D9B52503Be4`](https://optimistic.etherscan.io/address/0xec23Cf5A1db3dcC6595385D28B2a4D9B52503Be4) | +| V4FeeAdapter | [`0xd85393784234fB2ee4f077867A8B78E1aD4cE56B`](https://optimistic.etherscan.io/address/0xd85393784234fB2ee4f077867A8B78E1aD4cE56B) | +| V4FeePolicy | [`0x13d9D19802660b8167B3c84C1fcDCD0163672eB8`](https://optimistic.etherscan.io/address/0x13d9D19802660b8167B3c84C1fcDCD0163672eB8) | ### Base (Chain ID: 8453) @@ -372,6 +383,8 @@ Ethereum Mainnet | TokenJar | [`0x9bD25e67bF390437C8fAF480AC735a27BcF6168c`](https://basescan.org/address/0x9bD25e67bF390437C8fAF480AC735a27BcF6168c) | | Releaser (OptimismBridgedResourceFirepit) | [`0xFf77c0ED0B6b13A20446969107E5867abc46f53a`](https://basescan.org/address/0xFf77c0ED0B6b13A20446969107E5867abc46f53a) | | V3OpenFeeAdapter | [`0xaBEA76658b205696d49B5F91b2a03536cB8A3bE1`](https://basescan.org/address/0xaBEA76658b205696d49B5F91b2a03536cB8A3bE1) | +| V4FeeAdapter | [`0xc53DD825dedBE7814562528A1A30e8e5BCbB4b55`](https://basescan.org/address/0xc53DD825dedBE7814562528A1A30e8e5BCbB4b55) | +| V4FeePolicy | [`0xf963bdbe8BA2aABBB3190fF2268F3d833393ab71`](https://basescan.org/address/0xf963bdbe8BA2aABBB3190fF2268F3d833393ab71) | ### Polygon (Chain ID: 137) @@ -383,6 +396,8 @@ Ethereum Mainnet | tokenJar | [`0xc6Ae6373CEcc9e595A6C8b9fe581925a8c84f70A`](https://polygonscan.com/address/0xc6Ae6373CEcc9e595A6C8b9fe581925a8c84f70A) | | wormholeReleaser | [`0xa59FfbB55D91Fc32b44A06F0b9cc6036a4afbcE2`](https://polygonscan.com/address/0xa59FfbB55D91Fc32b44A06F0b9cc6036a4afbcE2) | | v3OpenFeeAdapter | [`0x3F07F08b45912dCd6691C5B9412975D5113B2910`](https://polygonscan.com/address/0x3F07F08b45912dCd6691C5B9412975D5113B2910) | +| V4FeeAdapter | [`0x59e84309Afa65F084F9c03668583d231afB12cc6`](https://polygonscan.com/address/0x59e84309Afa65F084F9c03668583d231afB12cc6) | +| V4FeePolicy | [`0x600c29d3Ce95Ce503FAb43753cbcb5Fa87EE6093`](https://polygonscan.com/address/0x600c29d3Ce95Ce503FAb43753cbcb5Fa87EE6093) | ### BNB Chain (Chain ID: 56) @@ -394,6 +409,19 @@ Ethereum Mainnet | tokenJar | [`0xc6Ae6373CEcc9e595A6C8b9fe581925a8c84f70A`](https://bscscan.com/address/0xc6Ae6373CEcc9e595A6C8b9fe581925a8c84f70A) | | wormholeReleaser | [`0xa59FfbB55D91Fc32b44A06F0b9cc6036a4afbcE2`](https://bscscan.com/address/0xa59FfbB55D91Fc32b44A06F0b9cc6036a4afbcE2) | | v3OpenFeeAdapter | [`0x3F07F08b45912dCd6691C5B9412975D5113B2910`](https://bscscan.com/address/0x3F07F08b45912dCd6691C5B9412975D5113B2910) | +| V4FeeAdapter | [`0x66276D2e80784180E4B9b859e4327b9C5646B87c`](https://bscscan.com/address/0x66276D2e80784180E4B9b859e4327b9C5646B87c) | +| V4FeePolicy | [`0x020B1Ad79037f02feC357Ec4601fFbe5f7ED6872`](https://bscscan.com/address/0x020B1Ad79037f02feC357Ec4601fFbe5f7ED6872) | + +### Robinhood Chain (Chain ID: 4663) + +| Contract | Address | +| ---------------- | ------------------------------------------------------------------------------------------------------------------- | +| Deployer | [`0x81405C50595f09175d282ccEc657e090EE54AB2E`](https://robinhoodchain.blockscout.com/address/0x81405C50595f09175d282ccEc657e090EE54AB2E) | +| TokenJar | [`0x2aC03e14Cfe755426DaAEe0a4994184Ce81482F8`](https://robinhoodchain.blockscout.com/address/0x2aC03e14Cfe755426DaAEe0a4994184Ce81482F8) | +| Releaser | [`0x7a8f74C2585F84c781F951B7f2FF21337D5b630b`](https://robinhoodchain.blockscout.com/address/0x7a8f74C2585F84c781F951B7f2FF21337D5b630b) | +| V3OpenFeeAdapter | [`0x05C420bC4823e039AA4dA645eDde743486dAAA25`](https://robinhoodchain.blockscout.com/address/0x05C420bC4823e039AA4dA645eDde743486dAAA25) | +| V4FeeAdapter | [`0x6d0009504D129CF5002Dba61D9Ae8575AA79314c`](https://robinhoodchain.blockscout.com/address/0x6d0009504D129CF5002Dba61D9Ae8575AA79314c) | +| V4FeePolicy | [`0x6ee984309ba26c224f12Bb85D6F7f3C019483DFc`](https://robinhoodchain.blockscout.com/address/0x6ee984309ba26c224f12Bb85D6F7f3C019483DFc) | ## Development diff --git a/foundry.toml b/foundry.toml index a285e9bd..3307aad1 100644 --- a/foundry.toml +++ b/foundry.toml @@ -15,7 +15,11 @@ compilation_restrictions = [ { paths = "lib/native-token-transfers/evm/src/NttManager/NttManagerNoRateLimiting.sol", optimizer_runs = 200 } ] -fs_permissions = [{ access = "read", path = "./broadcast"}] +fs_permissions = [ + { access = "read", path = "./broadcast"}, + { access = "read-write", path = "./.records/" }, + { access = "read-write", path = "out/.seatbelt" } +] [rpc_endpoints] mainnet = "${MAINNET_RPC_URL}" @@ -56,7 +60,11 @@ quote_style = "double" single_line_statement_blocks = "single" tab_width = 2 wrap_comments = true -ignore = ["script/proposal-4/ProposalDescription.sol"] +ignore = [ + "script/proposal-4/ProposalDescription.sol", + "script/proposal-5/Description.sol", + "script/proposal-6/Description.sol" + ] [lint] exclude_lints = ["mixed-case-variable", "mixed-case-function"] diff --git a/lib/govkit b/lib/govkit new file mode 160000 index 00000000..bd521852 --- /dev/null +++ b/lib/govkit @@ -0,0 +1 @@ +Subproject commit bd521852d54cb2b9119c1c9bf675e286f521b8fc diff --git a/remappings.txt b/remappings.txt index 4f0f371f..c67842e0 100644 --- a/remappings.txt +++ b/remappings.txt @@ -7,3 +7,4 @@ src/AgreementAnchorFactory.sol=lib/dao-signer/src/AgreementAnchorFactory.sol src/AgreementAnchor.sol=lib/dao-signer/src/AgreementAnchor.sol src/interfaces/IAgreementAnchorFactory.sol=lib/dao-signer/src/interfaces/IAgreementAnchorFactory.sol openzeppelin-contracts/contracts/=lib/native-token-transfers/evm/lib/openzeppelin-contracts/contracts/ +govkit/=lib/govkit/src/ diff --git a/script/deployers/ArbitrumOrbitDeployer.sol b/script/deployers/ArbitrumOrbitDeployer.sol new file mode 100644 index 00000000..a883d54a --- /dev/null +++ b/script/deployers/ArbitrumOrbitDeployer.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.29; + +import {ITokenJar} from "../../src/interfaces/ITokenJar.sol"; +import {TokenJar} from "../../src/TokenJar.sol"; +import {IReleaser} from "../../src/interfaces/IReleaser.sol"; +import {IV3OpenFeeAdapter} from "../../src/interfaces/IV3OpenFeeAdapter.sol"; +import {V3OpenFeeAdapter} from "../../src/feeAdapters/V3OpenFeeAdapter.sol"; +import {IOwned} from "../../src/interfaces/base/IOwned.sol"; +import {ArbitrumOrbitResourceFirepit} from "../../src/releasers/ArbitrumOrbitResourceFirepit.sol"; + +/// @title ArbitrumOrbitDeployer +/// @notice Deployer for TokenJar + ArbitrumOrbitResourceFirepit + V3OpenFeeAdapter on Arbitrum +/// Orbit chain +/// @dev Deploys and configures the fee collection system with Arbitrum Orbit-specific parameters. +/// The `_owner` parameter is expected to be the **aliased** L1 Timelock address. +/// Arbitrum aliases all L1->L2 messages from contracts at the protocol level — retryable +/// tickets via `Inbox.createRetryableTicket()` are the canonical messaging path and always +/// produce `msg.sender == aliasedAddress` on L2, satisfying the `onlyOwner` check. +/// Unlike OP Stack, there is no alternative un-aliased route for contracts on Arbitrum Orbit +/// chain. +contract ArbitrumOrbitDeployer { + error ZeroAddress(); + error ZeroThreshold(); + + ITokenJar public immutable TOKEN_JAR; + IReleaser public immutable RELEASER; + IV3OpenFeeAdapter public immutable V3_OPEN_FEE_ADAPTER; + + bytes32 public constant SALT_TOKEN_JAR = bytes32(uint256(1)); + bytes32 public constant SALT_RELEASER = bytes32(uint256(2)); + bytes32 public constant SALT_FEE_ADAPTER = bytes32(uint256(3)); + + // Protocol fee defaults — same as mainnet + uint8 constant DEFAULT_FEE_100 = 4 << 4 | 4; // 1/4 for 0.01% tier + uint8 constant DEFAULT_FEE_500 = 4 << 4 | 4; // 1/4 for 0.05% tier + uint8 constant DEFAULT_FEE_3000 = 6 << 4 | 6; // 1/6 for 0.30% tier + uint8 constant DEFAULT_FEE_10000 = 6 << 4 | 6; // 1/6 for 1.00% tier + + /// @notice Deploys TokenJar, ArbitrumOrbitResourceFirepit, and V3OpenFeeAdapter + /// @param _resource The bridged UNI token address on Arbitrum Orbit chain (L2) + /// @param _l1Resource The UNI token address on Ethereum mainnet (L1) + /// @param _threshold The minimum UNI amount required per release + /// @param _owner The aliased owner address — must be the L1 Timelock address + 0x1111...1111 + /// @param _v3Factory The Uniswap V3 Factory address on Arbitrum Orbit chain + constructor( + address _l2GatewayRouter, + address _resource, + address _l1Resource, + uint256 _threshold, + address _owner, + address _v3Factory + ) { + require(_l2GatewayRouter != address(0), ZeroAddress()); + require(_resource != address(0), ZeroAddress()); + require(_l1Resource != address(0), ZeroAddress()); + require(_threshold > 0, ZeroThreshold()); + require(_owner != address(0), ZeroAddress()); + require(_v3Factory != address(0), ZeroAddress()); + + /// 1. Deploy the TokenJar + TOKEN_JAR = new TokenJar{salt: SALT_TOKEN_JAR}(); + + /// 2. Deploy the Releaser + RELEASER = new ArbitrumOrbitResourceFirepit{salt: SALT_RELEASER}( + _l2GatewayRouter, _resource, _l1Resource, _threshold, address(TOKEN_JAR) + ); + + /// 3. Set the releaser on the token jar + TOKEN_JAR.setReleaser(address(RELEASER)); + + /// 4. Update the owner on the token jar + IOwned(address(TOKEN_JAR)).transferOwnership(_owner); + + /// 5. Update the thresholdSetter on the releaser to the owner + RELEASER.setThresholdSetter(_owner); + + /// 6. Update the owner on the releaser + IOwned(address(RELEASER)).transferOwnership(_owner); + + /// 7. Deploy and configure V3OpenFeeAdapter + V3OpenFeeAdapter feeAdapter = + new V3OpenFeeAdapter{salt: SALT_FEE_ADAPTER}(_v3Factory, address(TOKEN_JAR)); + + // Configure fee tier defaults + feeAdapter.setFeeSetter(address(this)); + + // Set default fee (applied when no tier or pool override is set) + feeAdapter.setDefaultFee(DEFAULT_FEE_100); + + // Set fee tier defaults + feeAdapter.setFeeTierDefault(100, DEFAULT_FEE_100); + feeAdapter.setFeeTierDefault(500, DEFAULT_FEE_500); + feeAdapter.setFeeTierDefault(3000, DEFAULT_FEE_3000); + feeAdapter.setFeeTierDefault(10_000, DEFAULT_FEE_10000); + + // Store fee tiers + feeAdapter.storeFeeTier(100); + feeAdapter.storeFeeTier(500); + feeAdapter.storeFeeTier(3000); + feeAdapter.storeFeeTier(10_000); + + // Transfer feeSetter and ownership to aliased Timelock + feeAdapter.setFeeSetter(_owner); + feeAdapter.transferOwnership(_owner); + + V3_OPEN_FEE_ADAPTER = IV3OpenFeeAdapter(address(feeAdapter)); + } +} diff --git a/script/proposal-5/Constants.sol b/script/proposal-5/Constants.sol new file mode 100644 index 00000000..34447aa6 --- /dev/null +++ b/script/proposal-5/Constants.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: AGPl-3.0-only +pragma solidity 0.8.29; + +library Ethereum { + address constant RH_L1_ERC20_GATEWAY = 0x85001CC4867C5e1C22dA4B79BB8852B9e2a06da0; + address constant RH_INBOX = 0x1A07cc4BD17E0118BdB54D70990D2158AbAD7a2D; +} + +library Robinhood { + uint256 constant CHAIN_ID = 4663; + + address constant V2_FACTORY = 0x8bcEaA40B9AcdfAedF85AdF4FF01F5Ad6517937f; + address constant V3_FACTORY = 0x1f7d7550B1b028f7571E69A784071F0205FD2EfA; + address constant POOL_MANAGER = 0x8366a39CC670B4001A1121B8F6A443A643e40951; + address constant UNI = 0xF177d86a28b520e3E396E4F3B96cd8e72D7dabd8; + address constant L2_GATEWAY_ROUTER = 0x1E324B9316138CA9a73F960213621AD1aaf01B89; + address constant TOKEN_JAR = 0x2aC03e14Cfe755426DaAEe0a4994184Ce81482F8; +} diff --git a/script/proposal-5/Description.sol b/script/proposal-5/Description.sol new file mode 100644 index 00000000..4917a03f --- /dev/null +++ b/script/proposal-5/Description.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.29; + +string constant DESCRIPTION = "# Protocol Fee Expansion: Robinhood Chain\n" +"\n" +"*This proposal is part of the protocol fee rollout, following proposals [#93](https://vote.uniswapfoundation.org/proposals/93), [#94](https://vote.uniswapfoundation.org/proposals/94), [#95](https://vote.uniswapfoundation.org/proposals/95), and [#96](https://vote.uniswapfoundation.org/proposals/96). It uses the expedited governance process [approved](https://gov.uniswap.org/t/unification-proposal/25881#p-57882-protocol-fee-rollout-4) in UNIfication, where fee parameter update proposals can bypass the RFC stage and go directly to a five-day Snapshot followed by an onchain vote.*\n" +"\n" +"Since protocol fees went live on Ethereum mainnet in late December last year, the rollout has extended to ten additional chains: Arbitrum, Base, OP Mainnet, Worldchain, X Layer, Soneium, Zora, Celo, BNB Chain, and Polygon. The burn system is working as designed, with fees accumulating in TokenJars across chains. From there, searchers claim them in exchange for burning UNI by bridging it back to mainnet and sending it to the burn address.\n" +"\n" +"Uniswap [launched on Robinhood Chain](https://blog.uniswap.org/robinhood-chain-is-live) at the chain's July 1, 2026 mainnet debut, with v2, v3, and v4 all live. As of July 10, the deployments have crossed $6b of cumulative swap volume. This proposal:\n" +"\n" +"- Extends the infrastructure for collecting and burning protocol fees to Robinhood Chain\n" +"- Enables v2, v3, and v4 protocol fees on Robinhood Chain. \n" +"\n" +"Please note that this onchain vote enables v2 and v3 fees specifically, while v4 fees will be activated as a part of a separate proposal covering part 1 of the v4 fee activation.\n" +"\n" +"## Implementation Details\n" +"\n" +"Fees on Robinhood Chain will be routed to the TokenJar on that chain. UNI burned on Robinhood Chain is bridged back to Ethereum mainnet and sent to the burn address.\n" +"\n" +"Robinhood Chain is an Arbitrum Orbit chain, so this proposal reuses the pattern from the Arbitrum One activation in proposal 94.\n" +"\n" +"**Governance path.** Same as Arbitrum One: cross-chain governance messages are delivered as retryable tickets through Robinhood Chain's Inbox contract on Ethereum and executed on Robinhood Chain by the L2 alias of the governance Timelock. The aliased Timelock already controls both factories and the v4 PoolManager on Robinhood Chain (it is the v2 factory's feeToSetter and the owner of the v3 factory and the PoolManager), so no ownership migration is required.\n" +"\n" +"**Burn path.** The releaser, ArbitrumOrbitResourceFirepit, is the Arbitrum One firepit generalized for Orbit chains. A searcher pays bridged UNI on Robinhood Chain to claim the TokenJar's accumulated fees, and the contract withdraws that UNI to the burn address on mainnet through the canonical gateway. As on Arbitrum One, the withdrawal finalizes on mainnet after Robinhood Chain's challenge period.\n" +"\n" +"The TokenJar, Releaser, V3OpenFeeAdapter, V4FeeAdapter, and V4FeePolicy will be deployed ahead of the onchain votes, with ownership verified against the aliased Timelock. This post will be updated with those addresses when they've been deployed and a link to the repo containing the contracts and deployment scripts once it has been merged.\n" +"\n" +"Implementation details for the v4 fee system are in the [v4 fee activation temp check](https://gov.uniswap.org/t/temp-check-activate-v4-protocol-fees/26162). v2 and v3 protocol fee levels are the same as on all other chains where fees are live, see breakdown [here](https://developers.uniswap.org/docs/protocols/protocol-fee/concepts/fees#fee-split-table).\n" +"\n" +"## Proposal Spec\n" +"\n" +"If passed, the Robinhood Fee Activation proposal will execute two calls, each creating a retryable ticket in Robinhood Chain's Inbox on Ethereum.\n" +"\n" +"`RH_INBOX.createRetryableTicket(...) x2`\n" +"\n" +"One call will encode:\n" +"\n" +"`V2_FACTORY.setFeeTo(TOKEN_JAR)`\n" +"\n" +"The other will encode:\n" +"\n" +"`V3_FACTORY.setOwner(V3_OPEN_FEE_ADAPTER)`\n" +"\n" +"Once executed on Robinhood Chain, they set the fee collector of UniswapV2Factory to TokenJar and transfer ownership of UniswapV3Factory to V3OpenFeeAdapter.\n" +"\n" +"Robinhood's v4 activation will be included in the V4 Fee Activation (Part 1) proposal described below. Matching the spec of the [v4 fee activation temp check](https://gov.uniswap.org/t/temp-check-activate-v4-protocol-fees/26162), and delivered through the same retryable ticket path, it will encode:\n" +"\n" +"`V4_POOL_MANAGER.setProtocolFeeController(V4_FEE_ADAPTER)`\n" +"\n" +"## Onchain Execution\n" +"\n" +"This Onchain vote covers v2 and v3 fees on Robinhood Chain. v4 fees on Robinhood Chain have been batched in with part 1 of v4 fee activation, as noted above. \n" +"\n" +"All contracts can be found [here](https://github.com/Uniswap/protocol-fees/), and this post will be updated with addresses and explorer links when they have been deployed.\n" +"\n" +"## Next Steps / Timeline\n" +"\n" +"- **Onchain vote begins:** Jul 19, 2026\n" +"- **Onchain ends:** Jul 26, 2026\n"; diff --git a/script/proposal-5/Interfaces.sol b/script/proposal-5/Interfaces.sol new file mode 100644 index 00000000..11704ff3 --- /dev/null +++ b/script/proposal-5/Interfaces.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: AGPl-3.0-only +pragma solidity 0.8.29; + +interface IL1ERC20Gateway { + // Events + event DepositInitiated( + address l1Token, + address indexed _from, + address indexed _to, + uint256 indexed _sequenceNumber, + uint256 _amount + ); + event TxToL2(address indexed _from, address indexed _to, uint256 indexed _seqNum, bytes _data); + event WithdrawRedirected( + address indexed from, + address indexed to, + uint256 indexed exitNum, + bytes newData, + bytes data, + bool madeExternalCall + ); + event WithdrawalFinalized( + address l1Token, + address indexed _from, + address indexed _to, + uint256 indexed _exitNum, + uint256 _amount + ); + event AdminChanged(address previousAdmin, address newAdmin); + event BeaconUpgraded(address indexed beacon); + event Upgraded(address indexed implementation); + + // Functions + function calculateL2TokenAddress(address l1ERC20) external view returns (address); + function cloneableProxyHash() external view returns (bytes32); + function counterpartGateway() external view returns (address); + function encodeWithdrawal(uint256 _exitNum, address _initialDestination) + external + pure + returns (bytes32); + function finalizeInboundTransfer( + address _token, + address _from, + address _to, + uint256 _amount, + bytes memory _data + ) external payable; + function getExternalCall(uint256 _exitNum, address _initialDestination, bytes memory _initialData) + external + view + returns (address target, bytes memory data); + function getOutboundCalldata( + address _token, + address _from, + address _to, + uint256 _amount, + bytes memory _data + ) external view returns (bytes memory outboundCalldata); + function inbox() external view returns (address); + function initialize( + address _l2Counterpart, + address _router, + address _inbox, + bytes32 _cloneableProxyHash, + address _l2BeaconProxyFactory + ) external; + function l2BeaconProxyFactory() external view returns (address); + function outboundTransfer( + address _l1Token, + address _to, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + bytes memory _data + ) external payable returns (bytes memory res); + function outboundTransferCustomRefund( + address _l1Token, + address _refundTo, + address _to, + uint256 _amount, + uint256 _maxGas, + uint256 _gasPriceBid, + bytes memory _data + ) external payable returns (bytes memory res); + function postUpgradeInit() external; + function redirectedExits(bytes32) + external + view + returns (bool isExit, address _newTo, bytes memory _newData); + function router() external view returns (address); + function supportsInterface(bytes4 interfaceId) external view returns (bool); + function transferExitAndCall( + uint256 _exitNum, + address _initialDestination, + address _newDestination, + bytes memory _newData, + bytes memory _data + ) external; + function whitelist() external view returns (address); +} + +interface IArbitrumOrbitResourceFirepit { + function L2_GATEWAY_ROUTER() external pure returns (address); +} diff --git a/script/proposal-5/RobinhoodFees.s.sol b/script/proposal-5/RobinhoodFees.s.sol new file mode 100644 index 00000000..16326928 --- /dev/null +++ b/script/proposal-5/RobinhoodFees.s.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: AGPl-3.0-only +pragma solidity 0.8.29; + +import {Script, console} from "forge-std/Script.sol"; + +import {Recorder} from "govkit/forge/Recorder.sol"; +import {ChainId} from "govkit/constants/ChainId.sol"; +import {Uniswap} from "govkit/types/Uniswap.sol"; +import {InboxEncoder} from "govkit/bridges/InboxEncoder.sol"; +import {GovernanceSeatbelt} from "lib/govkit/src/forge/GovernanceSeatbelt.sol"; +import {Call, LibCall} from "lib/govkit/src/types/Call.sol"; +import {Proposal} from "lib/govkit/src/types/Proposal.sol"; + +import {IUniswapV2Factory} from "govkit/interfaces/IUniswapV2Factory.sol"; +import {IUniswapV3Factory} from "govkit/interfaces/IUniswapV3Factory.sol"; +import {IPoolManager} from "govkit/interfaces/IPoolManager.sol"; +import {ITokenJar} from "govkit/interfaces/ITokenJar.sol"; +import {IReleaser} from "govkit/interfaces/IReleaser.sol"; +import {IV3OpenFeeAdapter} from "govkit/interfaces/IV3OpenFeeAdapter.sol"; + +import "./Constants.sol" as Constants; +import {DESCRIPTION} from "./Description.sol"; + +contract RobinhoodFees is Script { + Recorder internal recorder; + Uniswap internal uniswap; + + function run() external { + uniswap.loadLatest(); + recorder.initialize({scriptName: "DeployFeeInfraRobinhood"}); + + require(keccak256(bytes(DESCRIPTION)) != keccak256(bytes("TODO"))); + + address tokenJar = + recorder.read({chainId: Constants.Robinhood.CHAIN_ID, deploymentName: "TokenJar"}); + address v3OpenFeeAdapter = + recorder.read({chainId: Constants.Robinhood.CHAIN_ID, deploymentName: "V3OpenFeeAdapter"}); + + // --------------------------------------------------------------------------------------------- + // 00: Set Fee Receiver on V2 Factor to on Robinhood + // + Call memory v2SetFeeTo = InboxEncoder.encode({ + inbox: Constants.Ethereum.RH_INBOX, + timelock: uniswap.ethereum.timelock, + remoteCall: Call({ + target: Constants.Robinhood.V2_FACTORY, + value: 0, + data: abi.encodeCall(IUniswapV2Factory.setFeeTo, (tokenJar)) + }) + }); + + // --------------------------------------------------------------------------------------------- + // 01: Transfer Ownership of V3 Factory to V3 Open Fee Adapter on Robinhood + // + Call memory v3SetOwner = InboxEncoder.encode({ + inbox: Constants.Ethereum.RH_INBOX, + timelock: uniswap.ethereum.timelock, + remoteCall: Call({ + target: Constants.Robinhood.V3_FACTORY, + value: 0, + data: abi.encodeCall(IUniswapV3Factory.setOwner, (v3OpenFeeAdapter)) + }) + }); + + Proposal memory robinhoodFeeProposal = + Proposal({description: DESCRIPTION, calls: LibCall.newCalls([v2SetFeeTo, v3SetOwner])}); + + vm.createDir("./out/.seatbelt/", true); + vm.writeFile({ + path: "./out/.seatbelt/RobinhoodFeeProposal.json", + data: GovernanceSeatbelt.toJson({ + proposal: robinhoodFeeProposal, governorBravo: uniswap.ethereum.governorBravo + }) + }); + } +} diff --git a/script/proposal-5/prereq/DeployFeeInfraRobinhood.s.sol b/script/proposal-5/prereq/DeployFeeInfraRobinhood.s.sol new file mode 100644 index 00000000..71e315a9 --- /dev/null +++ b/script/proposal-5/prereq/DeployFeeInfraRobinhood.s.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: AGPl-3.0-only +pragma solidity 0.8.29; + +import {Script, console} from "forge-std/Script.sol"; +import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol"; +import {Recorder} from "govkit/forge/Recorder.sol"; +import {ChainId} from "govkit/constants/ChainId.sol"; +import {Uniswap} from "govkit/types/Uniswap.sol"; +import {InboxEncoder} from "govkit/bridges/InboxEncoder.sol"; + +import {ITokenJar} from "govkit/interfaces/ITokenJar.sol"; +import {IReleaser} from "govkit/interfaces/IReleaser.sol"; +import {IV3OpenFeeAdapter} from "govkit/interfaces/IV3OpenFeeAdapter.sol"; + +import {ArbitrumOrbitDeployer} from "../../deployers/ArbitrumOrbitDeployer.sol"; +import "../Constants.sol" as Constants; + +string constant DEPLOYER_NAME = "ArbitrumOrbitDeployer"; +uint256 constant THRESHOLD = 2000e18; + +contract DeployFeeInfraRobinhood is Script { + Recorder internal recorder; + Uniswap internal uniswap; + ArbitrumOrbitDeployer internal deployer; + + function run() external { + uniswap.loadLatest(); + recorder.initialize({scriptName: "DeployFeeInfraRobinhood"}); + + address timelockAlias = InboxEncoder.arbitrumAlias(uniswap.ethereum.timelock); + + // ----------------------------------------------------------------------------------------- + // Deploy ArbitrumOrbitDeployer + // + // Deploys the fee infra, configures it, and transfers it to the aliased timelock. + // + if (!recorder.exists(Constants.Robinhood.CHAIN_ID, DEPLOYER_NAME)) { + vm.startBroadcast(); + + deployer = new ArbitrumOrbitDeployer({ + _l2GatewayRouter: Constants.Robinhood.L2_GATEWAY_ROUTER, + _resource: Constants.Robinhood.UNI, + _l1Resource: uniswap.ethereum.uni, + _threshold: THRESHOLD, + _owner: timelockAlias, + _v3Factory: Constants.Robinhood.V3_FACTORY + }); + + vm.stopBroadcast(); + + recorder.write({ + chainId: Constants.Robinhood.CHAIN_ID, + deploymentName: DEPLOYER_NAME, + deployment: address(deployer) + }); + } else { + deployer = ArbitrumOrbitDeployer( + recorder.read({chainId: Constants.Robinhood.CHAIN_ID, deploymentName: DEPLOYER_NAME}) + ); + } + + require(address(deployer) != address(0x00), "deployer is null"); + + address tokenJar = address(deployer.TOKEN_JAR()); + address releaser = address(deployer.RELEASER()); + address v3OpenFeeAdapter = address(deployer.V3_OPEN_FEE_ADAPTER()); + + // ----------------------------------------------------------------------------------------- + // Run checks + // + + require(ITokenJar(tokenJar).owner() == timelockAlias); + require(IReleaser(releaser).thresholdSetter() == timelockAlias); + require(IReleaser(releaser).owner() == timelockAlias); + require(IV3OpenFeeAdapter(v3OpenFeeAdapter).owner() == timelockAlias); + require(IV3OpenFeeAdapter(v3OpenFeeAdapter).feeSetter() == timelockAlias); + + // ----------------------------------------------------------------------------------------- + // Write deployments to disk + // + recorder.write({ + chainId: Constants.Robinhood.CHAIN_ID, + deploymentName: DEPLOYER_NAME, + deployment: address(deployer) + }); + recorder.write({ + chainId: Constants.Robinhood.CHAIN_ID, deploymentName: "TokenJar", deployment: tokenJar + }); + recorder.write({ + chainId: Constants.Robinhood.CHAIN_ID, deploymentName: "Releaser", deployment: releaser + }); + recorder.write({ + chainId: Constants.Robinhood.CHAIN_ID, + deploymentName: "V3OpenFeeAdapter", + deployment: v3OpenFeeAdapter + }); + } +} diff --git a/script/proposal-6/Description.sol b/script/proposal-6/Description.sol new file mode 100644 index 00000000..6b7a02ed --- /dev/null +++ b/script/proposal-6/Description.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.29; + +string constant DESCRIPTION = +"## Summary\n" +"\n" +"This proposal continues the protocol fee rollout approved in UNIfication, following proposals [#93](https://vote.uniswapfoundation.org/proposals/93), [#94](https://vote.uniswapfoundation.org/proposals/94), [#95](https://vote.uniswapfoundation.org/proposals/95), and [#96](https://vote.uniswapfoundation.org/proposals/96). It uses the expedited governance process where fee parameter update proposals go directly to a five-day Snapshot followed by an onchain vote.\n" +"\n" +"Protocol fees are now live across all v2 and v3 pools on 11 chains - Ethereum, Arbitrum, Base, Celo, OP Mainnet, Soneium, X Layer, Worldchain, Zora, BNB Chain, and Polygon. Last month, the protocol set a record burning [186,000 UNI in one day](https://x.com/UNIBurnBot/status/2063252718043459633). \n" +"\n" +"Below we introduce a system for v4 protocol fees and propose to activate it on a subset of v4 pools on these same chains. This onchain vote will specifically activate v4 fees on Ethereum, Arbitrum, Base, BNB Chain, Polygon, Optimism, and Robinhood Chain. Please note that because of GovernorBravo's limit of 10 actions per proposal, there will be two separate onchain votes to accommodate all chains.\n" +"The remaining five chains (Celo, Soneium, Worldchain, X Layer, Zora) will be addressed in a subsequent proposal. \n" +"---\n" +"\n" +"## Implementation Details\n" +"\n" +"v4's hook architecture requires a different approach to fee activation than v2 or v3. v2 pools have a single LP fee tier and are charged a static fee. v3 has several LP fee tiers, each charged a static fee. Hooks mean v4 has potentially infinite distinct LP fee tiers, and a pool's fees can change dynamically from one block to the next. To manage this, we propose a V4 Fee Controller system where governance sets rules that let a dedicated contract compute the fee for any pool on demand, rather than setting a fee on each individual pool.\n" +"\n" +"The system splits across two contracts:\n" +"\n" +"* **V4FeePolicy**. Given any pool, it computes the fee from rules defined by governance. This is the contract governance calls to enable and adjust the protocol fee, and it can be swapped out later if the logic for setting fees needs to evolve.\n" +"\n" +"* **V4FeeAdapter**. Enforces governance overrides, so if governance has set a per-pool override, that override wins and the policy is skipped. Otherwise the adapter applies the policy's fee, pushes it to the pool, and collects the proceeds to the TokenJar.\n" +"\n" +"`V4FeePolicy` determines a pool's fee in two steps. First it sorts the pool into a **family**. A pool's family is determined by its characteristics, e.g. whether it has a hook, whether it uses the `PoolManager`'s native swap math, whether it charges dynamic swap fees, et cetera. A pool's family is identified by a flag, which is stored on the hook smart contract. Hook developers can opt their pools into a family via assigning it a specific flag. Governance can also assign a hook to a family directly via a vote. \n" +"\n" +"Once it determines the pool's family, the policy then resolves the fee, applying the rules below, going in order from most specific to least:\n" +"\n" +"1. a per-pair fee, if governance has set one for that token pair in that family\n" +"2. otherwise the family's own fee (defined by governance as a default or curve)\n" +"3. otherwise a global default for anything still unclassified\n" +"\n" +"With this system, governance manages a handful of rules and overrides instead of an unbounded list of pools, any fee is computed deterministically and can be inspected onchain, and the policy itself is replaceable if governance later wants to change how pools are categorized.\n" +"\n" +"This proposal activates fees on three pool families:\n" +"\n" +"* **Static fee pools:** These are pools without hooks. The protocol fee for these pools is set via a curve targeting a proportion of each pool's LP fee. For a description of this curve, please see the appendix below.\n" +"* **CCA Pools**: These are pools launched after a Continuous Clearing Auction. The LBPHook and pools resulting from previous auctions will be opted into the same curve as static pools.\n" +"* **Aggregator hook pools:** These are pools whose hooks integrate external liquidity venues into the v4 routing graph. The protocol fee for the aggregator hook family is a flat fee with overrides for specific pair types. To maintain the option of charging more on this external flow than the v4 PoolManager's hard cap of 10bps, aggregator hooks will multiply their assigned fees by 25, allowing for a cap of 250bps. After the multiplier is applied, the resulting fee for aggregator hooks will be:\n" +" * For all chains other than Base:\n" +" * Family Default: 10bp\n" +" * Select Stable Pairs: 3bp\n" +" * For Base: \n" +" * Family Default: 3bp\n" +" * Select Stable Pairs: 1bp\n" +"**This proposal does not enable the protocol fee for any pools other than those in the Families mentioned above.**\n" +"\n" +"Fees will flow to TokenJar on each chain. UNI burned on L2s and alt-L1s will be bridged back to Ethereum mainnet and sent to `0xdead`.\n" +"\n" +"---\n" +"\n" +"## Onchain Proposal Spec\n" +"\n" +"**Pre-proposal** (to be completed by Uniswap Labs prior to an onchain vote)\n" +"\n" +"* Deploy V4FeeAdapter and V4FeePolicy contracts on the chains where the v2 and v3 protocol fees are currently enabled (Ethereum, Arbitrum, Base, Celo, OP Mainnet, Soneium, X Layer, Worldchain, Zora, BNB Chain, and Polygon).\n" +"* Configure V4FeePolicy contracts with the native math protocol fee curve, CCA Hook and aggregator hook family fee logic described above\n" +"\n" +"\n" +"These contracts can be found [here](https://github.com/Uniswap/protocol-fees/tree/main/src/feeAdapters), and this post will be updated with addresses and explorer links when they have been deployed.\n" +"\n" +"\n" +"**In this proposal** (executed if the vote passes):\n" +"\n" +"* Set the `V4FeeAdapter` as the `ProtocolFeeController` on the PoolManager on each chain\n" +"\n" +"---\n" +"\n" +"## Next Steps / Timeline\n" +"\n" +"**Snapshot:** July 11-16, 2026\n" +"\n" +"**Onchain vote:** July 19-26, 2026\n" +"\n" +"---\n" +"\n" +"## Appendix - Static Fee Curve\n" +"\n" +"The V4 Fee Controller allows fee setting using discrete LP fee tier ranges. Each range has a floor, and the next range sets the ceiling. For each range, governance sets two inputs:\n" +"\n" +"* `alpha` which is the constant. This is the starting fee for that range.\n" +"* `beta` which is the scaling factor. This is how fast the fee grows within that range. The growth always starts from the floor of the range.\n" +"* Inside any range, the fee is: `alpha + beta * (lpFee - floor)`.The output is floored to the nearest 0.01bp increment, consistent with v4's minimum fee resolution.\n" +"\n" +"| *Range (bps)* | *Floor* | *Alpha (bps)* | *Beta (bps)* |\n" +"| :---- | :---- | :---- | :---- |\n" +"| *0 - 0.03* | *0* | *0.01* | *0* |\n" +"| *0.03 - 0.75* | *0.03* | *0.01* | *19/72* |\n" +"| *0.75 - 1* | *0.75* | *0.2* | *0.2* |\n" +"| *1 - 3.75* | *1* | *0.25* | *3/11* |\n" +"| *3.75 - 5* | *3.75* | *1* | *0.2* |\n" +"| *5 - 25* | *5* | *1.25* | *11/80* |\n" +"| *25 - 55* | *25* | *4* | *0.2* |\n" +"| *> 55* | *55* | *10* | *0* |\n" +"\n" +" This results in the following fees at the following points. \n" +"\n" +"| LP Fee (bps) | Protocol Fee (bps) |\n" +"| :---- | :---- |\n" +"| 0.03 | 0.01 |\n" +"| .75 | 0.20 |\n" +"| 1 | 0.25 |\n" +"| 3.75 | 1 |\n" +"| 5 | 1.25 |\n" +"| 25 | 4 |\n" +"| 30 | 5 |\n" +"| 83.34 | 10 |\n" +"| 100 | 10 |\n"; diff --git a/script/proposal-6/Interfaces.sol b/script/proposal-6/Interfaces.sol new file mode 100644 index 00000000..1f3c3652 --- /dev/null +++ b/script/proposal-6/Interfaces.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: AGPl-3.0-only +pragma solidity 0.8.29; + +interface IArbitrumOrbitResourceFirepit { + function L2_GATEWAY_ROUTER() external pure returns (address); +} diff --git a/script/proposal-6/StableStablePairs.sol b/script/proposal-6/StableStablePairs.sol new file mode 100644 index 00000000..ab20240b --- /dev/null +++ b/script/proposal-6/StableStablePairs.sol @@ -0,0 +1,811 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.29; + +import {ChainId} from "lib/govkit/src/constants/ChainId.sol"; +import "../proposal-5/Constants.sol" as Constants; + +struct Pair { + address token0; + address token1; +} + +struct StableStablePairs { + mapping(uint256 => Pair[]) chainPairs; +} + +using LibStableStablePairs for StableStablePairs global; + +library LibStableStablePairs { + function initialize(StableStablePairs storage stableStablePairs) internal { + Pair[] storage ethereumPairs = stableStablePairs.chainPairs[ChainId.Ethereum]; + ethereumPairs.push( + Pair(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0xdC035D45d973E3EC169d2276DDab16f1e407384F, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0x6c3ea9036406852006290770BEdFcAbA0e23A0e8, 0xdC035D45d973E3EC169d2276DDab16f1e407384F) + ); + ethereumPairs.push( + Pair(0x4c9EDD5852cd905f086C759E8383e09bff1E68B3, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0x8292Bb45bf1Ee4d140127049757C2E0fF06317eD, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0x6B175474E89094C44Da98b954EedeAC495271d0F, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0xacA92E438df0B2401fF60dA7E4337B687a2435DA, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0xdD468A1DDc392dcdbEf6db6e34E89AA338F9F186, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 0xC139190F447e929f090Edeb554D95AbB8b18aC1C) + ); + ethereumPairs.push( + Pair(0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0x9D39A5DE30e57443BfF2A8307A4256c8797A3497, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 0xe343167631d89B6Ffc58B88d6b7fB0228795491D) + ); + ethereumPairs.push( + Pair(0x6c3ea9036406852006290770BEdFcAbA0e23A0e8, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 0x4c9EDD5852cd905f086C759E8383e09bff1E68B3) + ); + ethereumPairs.push( + Pair(0x6B175474E89094C44Da98b954EedeAC495271d0F, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0x23238f20b894f29041f48D88eE91131C395Aaa71, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0x085780639CC2cACd35E474e71f4d000e2405d8f6, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E, 0xCAcd6fd266aF91b8AeD52aCCc382b4e165586E29) + ); + ethereumPairs.push( + Pair(0xCAcd6fd266aF91b8AeD52aCCc382b4e165586E29, 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD) + ); + ethereumPairs.push( + Pair(0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0x853d955aCEf822Db058eb8505911ED77F175b99e, 0x4c9EDD5852cd905f086C759E8383e09bff1E68B3) + ); + ethereumPairs.push( + Pair(0x6440f144b7e50D6a8439336510312d2F54beB01D, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0x83F20F44975D03b1b09e64809B757c47f942BEeA, 0x9D39A5DE30e57443BfF2A8307A4256c8797A3497) + ); + ethereumPairs.push( + Pair(0x4c9EDD5852cd905f086C759E8383e09bff1E68B3, 0xC139190F447e929f090Edeb554D95AbB8b18aC1C) + ); + ethereumPairs.push( + Pair(0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E, 0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f) + ); + ethereumPairs.push( + Pair(0x80ac24aA929eaF5013f6436cdA2a7ba190f5Cc0b, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f, 0x9D39A5DE30e57443BfF2A8307A4256c8797A3497) + ); + ethereumPairs.push( + Pair(0x9D39A5DE30e57443BfF2A8307A4256c8797A3497, 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD) + ); + ethereumPairs.push( + Pair(0x865377367054516e17014CcdED1e7d814EDC9ce4, 0x9D39A5DE30e57443BfF2A8307A4256c8797A3497) + ); + ethereumPairs.push( + Pair(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 0xdC035D45d973E3EC169d2276DDab16f1e407384F) + ); + ethereumPairs.push( + Pair(0x0655977FEb2f289A4aB78af67BAB0d17aAb84367, 0x9D39A5DE30e57443BfF2A8307A4256c8797A3497) + ); + ethereumPairs.push( + Pair(0x853d955aCEf822Db058eb8505911ED77F175b99e, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0x57aB1E0003F623289CD798B1824Be09a793e4Bec, 0x0655977FEb2f289A4aB78af67BAB0d17aAb84367) + ); + ethereumPairs.push( + Pair(0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0x356B8d89c1e1239Cbbb9dE4815c39A1474d5BA7D, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0xCAcd6fd266aF91b8AeD52aCCc382b4e165586E29, 0xcf62F905562626CfcDD2261162a51fd02Fc9c5b6) + ); + ethereumPairs.push( + Pair(0xe343167631d89B6Ffc58B88d6b7fB0228795491D, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0x865377367054516e17014CcdED1e7d814EDC9ce4, 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD) + ); + ethereumPairs.push( + Pair(0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E, 0x9D39A5DE30e57443BfF2A8307A4256c8797A3497) + ); + ethereumPairs.push( + Pair(0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E, 0x6c3ea9036406852006290770BEdFcAbA0e23A0e8) + ); + ethereumPairs.push( + Pair(0x865377367054516e17014CcdED1e7d814EDC9ce4, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0xA0d69E286B938e21CBf7E51D71F6A4c8918f482F, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0xFa2B947eEc368f42195f24F36d2aF29f7c24CeC2, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0x73A15FeD60Bf67631dC6cd7Bc5B6e8da8190aCF5, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0x7C135549504245B5eAe64fc0E99Fa5ebabb8e35D, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 0xFa2B947eEc368f42195f24F36d2aF29f7c24CeC2) + ); + ethereumPairs.push( + Pair(0x7C135549504245B5eAe64fc0E99Fa5ebabb8e35D, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0x6c3ea9036406852006290770BEdFcAbA0e23A0e8, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0xCAcd6fd266aF91b8AeD52aCCc382b4e165586E29, 0x4c9EDD5852cd905f086C759E8383e09bff1E68B3) + ); + ethereumPairs.push( + Pair(0x0655977FEb2f289A4aB78af67BAB0d17aAb84367, 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD) + ); + ethereumPairs.push( + Pair(0xCAcd6fd266aF91b8AeD52aCCc382b4e165586E29, 0x085780639CC2cACd35E474e71f4d000e2405d8f6) + ); + ethereumPairs.push( + Pair(0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E, 0x4c9EDD5852cd905f086C759E8383e09bff1E68B3) + ); + ethereumPairs.push( + Pair(0x57aB1E0003F623289CD798B1824Be09a793e4Bec, 0xcf62F905562626CfcDD2261162a51fd02Fc9c5b6) + ); + ethereumPairs.push( + Pair(0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f, 0x4c9EDD5852cd905f086C759E8383e09bff1E68B3) + ); + ethereumPairs.push( + Pair(0x63d74d22E689C715a04F2C13962b1f77F443d35b, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0xCAcd6fd266aF91b8AeD52aCCc382b4e165586E29, 0xb45ad160634c528Cc3D2926d9807104FA3157305) + ); + ethereumPairs.push( + Pair(0xCAcd6fd266aF91b8AeD52aCCc382b4e165586E29, 0x0655977FEb2f289A4aB78af67BAB0d17aAb84367) + ); + ethereumPairs.push( + Pair(0xdD468A1DDc392dcdbEf6db6e34E89AA338F9F186, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0xacA92E438df0B2401fF60dA7E4337B687a2435DA, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0x5f98805A4E8be255a32880FDeC7F6728C6568bA0, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0x63d74d22E689C715a04F2C13962b1f77F443d35b, 0xCAcd6fd266aF91b8AeD52aCCc382b4e165586E29) + ); + ethereumPairs.push( + Pair(0x00000000eFE302BEAA2b3e6e1b18d08D69a9012a, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0x437cc33344a0B27A429f795ff6B469C72698B291, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 0xE556ABa6fe6036275Ec1f87eda296BE72C811BCE) + ); + ethereumPairs.push( + Pair(0xbbAec992fc2d637151dAF40451f160bF85f3C8C1, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 0xaf37c1167910ebC994e266949387d2c7C326b879) + ); + ethereumPairs.push( + Pair(0x2A8e1E676Ec238d8A992307B495b45B3fEAa5e86, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0xB1c2Db5d6cA03FCe73dBd304d320bF76C55Ae1B1, 0xCAcd6fd266aF91b8AeD52aCCc382b4e165586E29) + ); + ethereumPairs.push( + Pair(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 0xB1c2Db5d6cA03FCe73dBd304d320bF76C55Ae1B1) + ); + ethereumPairs.push( + Pair(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 0xaD55aebc9b8c03FC43cd9f62260391c13c23e7c0) + ); + ethereumPairs.push( + Pair(0x056Fd409E1d7A124BD7017459dFEa2F387b6d5Cd, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) + ); + ethereumPairs.push( + Pair(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 0xCAcd6fd266aF91b8AeD52aCCc382b4e165586E29) + ); + ethereumPairs.push( + Pair(0x07041776f5007ACa2A54844F50503a18A72A8b68, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0x96F6eF951840721AdBF46Ac996b59E0235CB985C, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0xC5d6A7B61d18AfA11435a889557b068BB9f29930, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0x4f8e5DE400DE08B164E7421B3EE387f461beCD1A, 0xdAC17F958D2ee523a2206206994597C13D831ec7) + ); + ethereumPairs.push( + Pair(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD) + ); + ethereumPairs.push( + Pair(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, 0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf) + ); + ethereumPairs.push( + Pair(0x18084fbA666a33d37592fA2633fD49a74DD93a88, 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599) + ); + ethereumPairs.push( + Pair(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, 0x8236a87084f8B84306f72007F36F2618A5634494) + ); + ethereumPairs.push( + Pair(0x18084fbA666a33d37592fA2633fD49a74DD93a88, 0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf) + ); + ethereumPairs.push( + Pair(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, 0x73E0C0d45E048D25Fc26Fa3159b0aA04BfA4Db98) + ); + ethereumPairs.push( + Pair(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, 0xB0F70C0bD6FD87dbEb7C10dC692a2a6106817072) + ); + ethereumPairs.push( + Pair(0x657e8C867D8B37dCC18fA4Caead9C45EB088C642, 0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf) + ); + ethereumPairs.push( + Pair(0xB0F70C0bD6FD87dbEb7C10dC692a2a6106817072, 0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf) + ); + ethereumPairs.push( + Pair(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, 0x9BE89D2a4cd102D8Fecc6BF9dA793be995C22541) + ); + ethereumPairs.push( + Pair(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, 0x657e8C867D8B37dCC18fA4Caead9C45EB088C642) + ); + ethereumPairs.push( + Pair(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, 0x7A56E1C57C7475CCf742a1832B028F0456652F97) + ); + ethereumPairs.push( + Pair(0x06ea695B91700071B161A434fED42D1DcbAD9f00, 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599) + ); + ethereumPairs.push( + Pair(0x06ea695B91700071B161A434fED42D1DcbAD9f00, 0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf) + ); + ethereumPairs.push( + Pair(0x8236a87084f8B84306f72007F36F2618A5634494, 0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf) + ); + ethereumPairs.push( + Pair(0x004E9C3EF86bc1ca1f0bB5C7662861Ee93350568, 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599) + ); + + Pair[] storage polygonPairs = stableStablePairs.chainPairs[ChainId.Polygon]; + polygonPairs.push( + Pair(0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359, 0x750e4C4984a9e0f12978eA6742Bc1c5D248f40ed) + ); + polygonPairs.push( + Pair(0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174, 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359) + ); + polygonPairs.push( + Pair(0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063, 0xc2132D05D31c914a87C6611C10748AEb04B58e8F) + ); + polygonPairs.push( + Pair(0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359, 0xc2132D05D31c914a87C6611C10748AEb04B58e8F) + ); + polygonPairs.push( + Pair(0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174, 0xc2132D05D31c914a87C6611C10748AEb04B58e8F) + ); + polygonPairs.push( + Pair(0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063, 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359) + ); + polygonPairs.push( + Pair(0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063, 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174) + ); + polygonPairs.push( + Pair(0x80Eede496655FB9047dd39d9f418d5483ED600df, 0xc2132D05D31c914a87C6611C10748AEb04B58e8F) + ); + polygonPairs.push( + Pair(0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB, 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174) + ); + polygonPairs.push( + Pair(0xC011a7E12a19f7B1f670d46F03B03f3342E82DFB, 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359) + ); + polygonPairs.push( + Pair(0xa3Fa99A148fA48D14Ed51d610c367C61876997F1, 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174) + ); + polygonPairs.push( + Pair(0xa3Fa99A148fA48D14Ed51d610c367C61876997F1, 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359) + ); + polygonPairs.push( + Pair(0x14913815bCFDE78BAeAd2111F463D038Ac9C2949, 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359) + ); + polygonPairs.push( + Pair(0x00000000eFE302BEAA2b3e6e1b18d08D69a9012a, 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359) + ); + polygonPairs.push( + Pair(0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063, 0xa3Fa99A148fA48D14Ed51d610c367C61876997F1) + ); + polygonPairs.push( + Pair(0x45c32fA6DF82ead1e2EF74d17b76547EDdFaFF89, 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174) + ); + polygonPairs.push( + Pair(0x104592a158490a9228070E0A8e5343B499e125D0, 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174) + ); + polygonPairs.push( + Pair(0x9C9e5fD8bbc25984B178FdCE6117Defa39d2db39, 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174) + ); + polygonPairs.push( + Pair(0x2e1AD108fF1D8C782fcBbB89AAd783aC49586756, 0xc2132D05D31c914a87C6611C10748AEb04B58e8F) + ); + polygonPairs.push( + Pair(0x2e1AD108fF1D8C782fcBbB89AAd783aC49586756, 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174) + ); + polygonPairs.push( + Pair(0xa3Fa99A148fA48D14Ed51d610c367C61876997F1, 0xc2132D05D31c914a87C6611C10748AEb04B58e8F) + ); + polygonPairs.push( + Pair(0x9C9e5fD8bbc25984B178FdCE6117Defa39d2db39, 0xc2132D05D31c914a87C6611C10748AEb04B58e8F) + ); + + Pair[] storage bnbChainPairs = stableStablePairs.chainPairs[ChainId.BNBChain]; + bnbChainPairs.push( + Pair(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, 0x55d398326f99059fF775485246999027B3197955) + ); + bnbChainPairs.push( + Pair(0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d, 0x55d398326f99059fF775485246999027B3197955) + ); + bnbChainPairs.push( + Pair(0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d, 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d) + ); + bnbChainPairs.push( + Pair(0xaf44A1E76F56eE12ADBB7ba8acD3CbD474888122, 0x55d398326f99059fF775485246999027B3197955) + ); + bnbChainPairs.push( + Pair(0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56, 0x55d398326f99059fF775485246999027B3197955) + ); + bnbChainPairs.push( + Pair(0xc5f0f7b66764F6ec8C8Dff7BA683102295E16409, 0x55d398326f99059fF775485246999027B3197955) + ); + bnbChainPairs.push( + Pair(0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56, 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d) + ); + bnbChainPairs.push( + Pair(0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3, 0x55d398326f99059fF775485246999027B3197955) + ); + bnbChainPairs.push( + Pair(0xc5f0f7b66764F6ec8C8Dff7BA683102295E16409, 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d) + ); + bnbChainPairs.push( + Pair(0x0Bb150DFa86EA5d7742F07FEfCD8E8edA81D64eF, 0x55d398326f99059fF775485246999027B3197955) + ); + bnbChainPairs.push( + Pair(0x5A110fC00474038f6c02E89C707D638602EA44B5, 0x55d398326f99059fF775485246999027B3197955) + ); + bnbChainPairs.push( + Pair(0xb3b02E4A9Fb2bD28CC2ff97B0aB3F6B3Ec1eE9D2, 0x55d398326f99059fF775485246999027B3197955) + ); + bnbChainPairs.push( + Pair(0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3, 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d) + ); + bnbChainPairs.push( + Pair(0x55d398326f99059fF775485246999027B3197955, 0xeA953eA6634d55dAC6697C436B1e81A679Db5882) + ); + bnbChainPairs.push( + Pair(0x40af3827F39D0EAcBF4A168f8D4ee67c121D11c9, 0x55d398326f99059fF775485246999027B3197955) + ); + bnbChainPairs.push( + Pair(0x8E9d4cEa39299323FE8eda678cAD449718556c4e, 0x55d398326f99059fF775485246999027B3197955) + ); + bnbChainPairs.push( + Pair(0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34, 0x55d398326f99059fF775485246999027B3197955) + ); + bnbChainPairs.push( + Pair(0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56, 0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3) + ); + bnbChainPairs.push( + Pair(0x90C97F71E18723b0Cf0dfa30ee176Ab653E89F40, 0x55d398326f99059fF775485246999027B3197955) + ); + bnbChainPairs.push( + Pair(0xaf44A1E76F56eE12ADBB7ba8acD3CbD474888122, 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d) + ); + bnbChainPairs.push( + Pair(0x55d398326f99059fF775485246999027B3197955, 0xcE24439F2D9C6a2289F741120FE202248B666666) + ); + bnbChainPairs.push( + Pair(0x55d398326f99059fF775485246999027B3197955, 0x61a10E8556BEd032eA176330e7F17D6a12a10000) + ); + bnbChainPairs.push( + Pair(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d, 0xcE24439F2D9C6a2289F741120FE202248B666666) + ); + bnbChainPairs.push( + Pair(0x4268B8F0B87b6Eae5d897996E6b845ddbD99Adf3, 0x55d398326f99059fF775485246999027B3197955) + ); + bnbChainPairs.push( + Pair(0x0555E30da8f98308EdB960aa94C0Db47230d2B9c, 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c) + ); + bnbChainPairs.push( + Pair(0x4aae823a6a0b376De6A78e74eCC5b079d38cBCf7, 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c) + ); + + Pair[] storage celoPairs = stableStablePairs.chainPairs[ChainId.Celo]; + celoPairs.push( + Pair(0x765DE816845861e75A25fCA122bb6898B8B1282a, 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e) + ); + celoPairs.push( + Pair(0x765DE816845861e75A25fCA122bb6898B8B1282a, 0xcebA9300f2b948710d2653dD7B07f33A8B32118C) + ); + celoPairs.push( + Pair(0x765DE816845861e75A25fCA122bb6898B8B1282a, 0x37f750B7cC259A2f741AF45294f6a16572CF5cAd) + ); + celoPairs.push( + Pair(0xcebA9300f2b948710d2653dD7B07f33A8B32118C, 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e) + ); + celoPairs.push( + Pair(0xcebA9300f2b948710d2653dD7B07f33A8B32118C, 0x4F604735c1cF31399C6E711D5962b2B3E0225AD3) + ); + celoPairs.push( + Pair(0x617f3112bf5397D0467D315cC709EF968D9ba546, 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e) + ); + celoPairs.push( + Pair(0x765DE816845861e75A25fCA122bb6898B8B1282a, 0x4F604735c1cF31399C6E711D5962b2B3E0225AD3) + ); + celoPairs.push( + Pair(0x765DE816845861e75A25fCA122bb6898B8B1282a, 0x617f3112bf5397D0467D315cC709EF968D9ba546) + ); + + Pair[] storage soneiumPairs = stableStablePairs.chainPairs[ChainId.Soneium]; + soneiumPairs.push( + Pair(0xbA9986D2381edf1DA03B0B9c1f8b00dc4AacC369, 0x102d758f688a4C1C5a80b116bD945d4455460282) + ); + soneiumPairs.push( + Pair(0xbA9986D2381edf1DA03B0B9c1f8b00dc4AacC369, 0x3A337a6adA9d885b6Ad95ec48F9b75f197b5AE35) + ); + soneiumPairs.push( + Pair(0xbA9986D2381edf1DA03B0B9c1f8b00dc4AacC369, 0x1217BfE6c773EEC6cc4A38b5Dc45B92292B6E189) + ); + soneiumPairs.push( + Pair(0xbA9986D2381edf1DA03B0B9c1f8b00dc4AacC369, 0x3f99231dD03a9F0E7e3421c92B7b90fbe012985a) + ); + soneiumPairs.push( + Pair(0xbA9986D2381edf1DA03B0B9c1f8b00dc4AacC369, 0x139450C2dCeF827C9A2a0Bb1CB5506260940c9fd) + ); + soneiumPairs.push( + Pair(0x102d758f688a4C1C5a80b116bD945d4455460282, 0x3A337a6adA9d885b6Ad95ec48F9b75f197b5AE35) + ); + soneiumPairs.push( + Pair(0x102d758f688a4C1C5a80b116bD945d4455460282, 0x1217BfE6c773EEC6cc4A38b5Dc45B92292B6E189) + ); + soneiumPairs.push( + Pair(0x102d758f688a4C1C5a80b116bD945d4455460282, 0x3f99231dD03a9F0E7e3421c92B7b90fbe012985a) + ); + soneiumPairs.push( + Pair(0x102d758f688a4C1C5a80b116bD945d4455460282, 0x139450C2dCeF827C9A2a0Bb1CB5506260940c9fd) + ); + soneiumPairs.push( + Pair(0x3A337a6adA9d885b6Ad95ec48F9b75f197b5AE35, 0x1217BfE6c773EEC6cc4A38b5Dc45B92292B6E189) + ); + soneiumPairs.push( + Pair(0x3A337a6adA9d885b6Ad95ec48F9b75f197b5AE35, 0x3f99231dD03a9F0E7e3421c92B7b90fbe012985a) + ); + soneiumPairs.push( + Pair(0x3A337a6adA9d885b6Ad95ec48F9b75f197b5AE35, 0x139450C2dCeF827C9A2a0Bb1CB5506260940c9fd) + ); + soneiumPairs.push( + Pair(0x1217BfE6c773EEC6cc4A38b5Dc45B92292B6E189, 0x3f99231dD03a9F0E7e3421c92B7b90fbe012985a) + ); + soneiumPairs.push( + Pair(0x1217BfE6c773EEC6cc4A38b5Dc45B92292B6E189, 0x139450C2dCeF827C9A2a0Bb1CB5506260940c9fd) + ); + soneiumPairs.push( + Pair(0x3f99231dD03a9F0E7e3421c92B7b90fbe012985a, 0x139450C2dCeF827C9A2a0Bb1CB5506260940c9fd) + ); + + Pair[] storage xLayerPairs = stableStablePairs.chainPairs[ChainId.XLayer]; + xLayerPairs.push( + Pair(0x779Ded0c9e1022225f8E0630b35a9b54bE713736, 0x74b7F16337b8972027F6196A17a631aC6dE26d22) + ); + xLayerPairs.push( + Pair(0x779Ded0c9e1022225f8E0630b35a9b54bE713736, 0x4ae46a509F6b1D9056937BA4500cb143933D2dc8) + ); + xLayerPairs.push( + Pair(0x779Ded0c9e1022225f8E0630b35a9b54bE713736, 0x1E4a5963aBFD975d8c9021ce480b42188849D41d) + ); + xLayerPairs.push( + Pair(0x74b7F16337b8972027F6196A17a631aC6dE26d22, 0x1E4a5963aBFD975d8c9021ce480b42188849D41d) + ); + xLayerPairs.push( + Pair(0x74b7F16337b8972027F6196A17a631aC6dE26d22, 0x4ae46a509F6b1D9056937BA4500cb143933D2dc8) + ); + xLayerPairs.push( + Pair(0x1E4a5963aBFD975d8c9021ce480b42188849D41d, 0x4ae46a509F6b1D9056937BA4500cb143933D2dc8) + ); + + Pair[] storage optimismPairs = stableStablePairs.chainPairs[ChainId.Optimism]; + optimismPairs.push( + Pair(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85, 0x94b008aA00579c1307B0EF2c499aD98a8ce58e58) + ); + optimismPairs.push( + Pair(0x7F5c764cBc14f9669B88837ca1490cCa17c31607, 0x94b008aA00579c1307B0EF2c499aD98a8ce58e58) + ); + optimismPairs.push( + Pair(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85, 0x01bFF41798a0BcF287b996046Ca68b395DbC1071) + ); + optimismPairs.push( + Pair(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85, 0x7F5c764cBc14f9669B88837ca1490cCa17c31607) + ); + optimismPairs.push( + Pair(0x94b008aA00579c1307B0EF2c499aD98a8ce58e58, 0x01bFF41798a0BcF287b996046Ca68b395DbC1071) + ); + optimismPairs.push( + Pair(0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1, 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85) + ); + optimismPairs.push( + Pair(0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1, 0x7F5c764cBc14f9669B88837ca1490cCa17c31607) + ); + optimismPairs.push( + Pair(0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1, 0x94b008aA00579c1307B0EF2c499aD98a8ce58e58) + ); + optimismPairs.push( + Pair(0xC52D7F23a2e460248Db6eE192Cb23dD12bDDCbf6, 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85) + ); + optimismPairs.push( + Pair(0xC52D7F23a2e460248Db6eE192Cb23dD12bDDCbf6, 0x7F5c764cBc14f9669B88837ca1490cCa17c31607) + ); + optimismPairs.push( + Pair(0xC52D7F23a2e460248Db6eE192Cb23dD12bDDCbf6, 0x94b008aA00579c1307B0EF2c499aD98a8ce58e58) + ); + optimismPairs.push( + Pair(0x1217BfE6c773EEC6cc4A38b5Dc45B92292B6E189, 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85) + ); + optimismPairs.push( + Pair(0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85, 0x4F604735c1cF31399C6E711D5962b2B3E0225AD3) + ); + optimismPairs.push( + Pair(0x03569CC076654F82679C4BA2124D64774781B01D, 0xc40F949F8a4e094D1b49a23ea9241D289B7b2819) + ); + optimismPairs.push( + Pair(0xc40F949F8a4e094D1b49a23ea9241D289B7b2819, 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85) + ); + optimismPairs.push( + Pair(0xc40F949F8a4e094D1b49a23ea9241D289B7b2819, 0x7F5c764cBc14f9669B88837ca1490cCa17c31607) + ); + optimismPairs.push( + Pair(0x2E3D870790dC77A83DD1d18184Acc7439A53f475, 0x94b008aA00579c1307B0EF2c499aD98a8ce58e58) + ); + optimismPairs.push( + Pair(0x9dAbAE7274D28A45F0B65Bf8ED201A5731492ca0, 0x7F5c764cBc14f9669B88837ca1490cCa17c31607) + ); + optimismPairs.push( + Pair(0xCB8FA9a76b8e203D8C3797bF438d8FB81Ea3326A, 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85) + ); + optimismPairs.push( + Pair(0xEB466342C4d449BC9f53A865D5Cb90586f405215, 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85) + ); + optimismPairs.push( + Pair(0x657e8C867D8B37dCC18fA4Caead9C45EB088C642, 0x68f180fcCe6836688e9084f035309E29Bf0A2095) + ); + optimismPairs.push( + Pair(0x68f180fcCe6836688e9084f035309E29Bf0A2095, 0x6c84a8f1c29108F47a79964b5Fe888D4f4D0dE40) + ); + + Pair[] storage basePairs = stableStablePairs.chainPairs[ChainId.Base]; + basePairs.push( + Pair(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, 0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2) + ); + basePairs.push( + Pair(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, 0x1217BfE6c773EEC6cc4A38b5Dc45B92292B6E189) + ); + basePairs.push( + Pair(0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA, 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) + ); + basePairs.push( + Pair(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, 0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34) + ); + basePairs.push( + Pair(0xCfA3Ef56d303AE4fAabA0592388F19d7C3399FB4, 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) + ); + basePairs.push( + Pair(0x14913815bCFDE78BAeAd2111F463D038Ac9C2949, 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) + ); + basePairs.push( + Pair(0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA, 0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2) + ); + basePairs.push( + Pair(0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb, 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) + ); + basePairs.push( + Pair(0xdD468A1DDc392dcdbEf6db6e34E89AA338F9F186, 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) + ); + basePairs.push( + Pair(0x55380fe7A1910dFf29A47B622057ab4139DA42C5, 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) + ); + basePairs.push( + Pair(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, 0x3a46ed8FCeb6eF1ADA2E4600A522AE7e24D2Ed18) + ); + basePairs.push( + Pair(0x417Ac0e078398C154EdFadD9Ef675d30Be60Af93, 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) + ); + basePairs.push( + Pair(0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb, 0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA) + ); + basePairs.push( + Pair(0xe5020A6d073a794B6E7f05678707dE47986Fb0b6, 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) + ); + basePairs.push( + Pair(0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb, 0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2) + ); + basePairs.push( + Pair(0x6Bb7a212910682DCFdbd5BCBb3e28FB4E8da10Ee, 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) + ); + basePairs.push( + Pair(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, 0x820C137fa70C8691f0e44Dc420a5e53c168921Dc) + ); + basePairs.push( + Pair(0x417Ac0e078398C154EdFadD9Ef675d30Be60Af93, 0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA) + ); + basePairs.push( + Pair(0x660975730059246A68521a3e2FBD4740173100f5, 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) + ); + basePairs.push( + Pair(0x0A1a1A107E45b7Ced86833863f482BC5f4ed82EF, 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) + ); + basePairs.push( + Pair(0xCfA3Ef56d303AE4fAabA0592388F19d7C3399FB4, 0xe5020A6d073a794B6E7f05678707dE47986Fb0b6) + ); + basePairs.push( + Pair(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, 0x04D5ddf5f3a8939889F11E97f8c4BB48317F1938) + ); + basePairs.push( + Pair(0x4621b7A9c75199271F773Ebd9A499dbd165c3191, 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) + ); + basePairs.push( + Pair(0x526728DBc96689597F85ae4cd716d4f7fCcBAE9d, 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) + ); + basePairs.push( + Pair(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, 0x646A737B9B6024e49f5908762B3fF73e65B5160c) + ); + basePairs.push( + Pair(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, 0xEB466342C4d449BC9f53A865D5Cb90586f405215) + ); + basePairs.push( + Pair(0x8210c0634AB8f273806e4b7866E9Db353773c44B, 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) + ); + basePairs.push( + Pair(0x4F604735c1cF31399C6E711D5962b2B3E0225AD3, 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) + ); + basePairs.push( + Pair(0x0555E30da8f98308EdB960aa94C0Db47230d2B9c, 0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf) + ); + basePairs.push( + Pair(0x236aa50979D5f3De3Bd1Eeb40E81137F22ab794b, 0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf) + ); + basePairs.push( + Pair(0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf, 0xecAc9C5F704e954931349Da37F60E39f515c11c1) + ); + basePairs.push( + Pair(0x0555E30da8f98308EdB960aa94C0Db47230d2B9c, 0x236aa50979D5f3De3Bd1Eeb40E81137F22ab794b) + ); + + Pair[] storage arbitrumPairs = stableStablePairs.chainPairs[ChainId.Arbitrum]; + arbitrumPairs.push( + Pair(0xaf88d065e77c8cC2239327C5EDb3A432268e5831, 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9) + ); + arbitrumPairs.push( + Pair(0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8, 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9) + ); + arbitrumPairs.push( + Pair(0xaf88d065e77c8cC2239327C5EDb3A432268e5831, 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8) + ); + arbitrumPairs.push( + Pair(0x0A1a1A107E45b7Ced86833863f482BC5f4ed82EF, 0xaf88d065e77c8cC2239327C5EDb3A432268e5831) + ); + arbitrumPairs.push( + Pair(0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1, 0xaf88d065e77c8cC2239327C5EDb3A432268e5831) + ); + arbitrumPairs.push( + Pair(0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1, 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8) + ); + arbitrumPairs.push( + Pair(0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1, 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9) + ); + arbitrumPairs.push( + Pair(0x498Bf2B1e120FeD3ad3D42EA2165E9b73f99C1e5, 0xaf88d065e77c8cC2239327C5EDb3A432268e5831) + ); + arbitrumPairs.push( + Pair(0x498Bf2B1e120FeD3ad3D42EA2165E9b73f99C1e5, 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8) + ); + arbitrumPairs.push( + Pair(0x498Bf2B1e120FeD3ad3D42EA2165E9b73f99C1e5, 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9) + ); + arbitrumPairs.push( + Pair(0xaf88d065e77c8cC2239327C5EDb3A432268e5831, 0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34) + ); + arbitrumPairs.push( + Pair(0x41CA7586cC1311807B4605fBB748a3B8862b42b5, 0xaf88d065e77c8cC2239327C5EDb3A432268e5831) + ); + arbitrumPairs.push( + Pair(0x0A1a1A107E45b7Ced86833863f482BC5f4ed82EF, 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9) + ); + arbitrumPairs.push( + Pair(0x7dfF72693f6A4149b17e7C6314655f6A9F7c8B33, 0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34) + ); + arbitrumPairs.push( + Pair(0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34, 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9) + ); + arbitrumPairs.push( + Pair(0x211Cc4DD073734dA055fbF44a2b4667d5E5fE5d2, 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9) + ); + arbitrumPairs.push( + Pair(0x7dfF72693f6A4149b17e7C6314655f6A9F7c8B33, 0xaf88d065e77c8cC2239327C5EDb3A432268e5831) + ); + arbitrumPairs.push( + Pair(0x46850aD61C2B7d64d08c9C754F45254596696984, 0xaf88d065e77c8cC2239327C5EDb3A432268e5831) + ); + arbitrumPairs.push( + Pair(0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F, 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8) + ); + arbitrumPairs.push( + Pair(0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F, 0xaf88d065e77c8cC2239327C5EDb3A432268e5831) + ); + arbitrumPairs.push( + Pair(0x0B2b2B2076d95dda7817e785989fE353fe955ef9, 0xaf88d065e77c8cC2239327C5EDb3A432268e5831) + ); + arbitrumPairs.push( + Pair(0x0B2b2B2076d95dda7817e785989fE353fe955ef9, 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9) + ); + arbitrumPairs.push( + Pair(0xaf88d065e77c8cC2239327C5EDb3A432268e5831, 0xEB466342C4d449BC9f53A865D5Cb90586f405215) + ); + arbitrumPairs.push( + Pair(0x4D15a3A2286D883AF0AA1B3f21367843FAc63E07, 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8) + ); + arbitrumPairs.push( + Pair(0x7dfF72693f6A4149b17e7C6314655f6A9F7c8B33, 0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9) + ); + arbitrumPairs.push( + Pair(0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f, 0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf) + ); + arbitrumPairs.push( + Pair(0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f, 0x6c84a8f1c29108F47a79964b5Fe888D4f4D0dE40) + ); + arbitrumPairs.push( + Pair(0x2297aEbD383787A160DD0d9F71508148769342E3, 0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f) + ); + + Pair[] storage robinhoodPairs = stableStablePairs.chainPairs[Constants.Robinhood.CHAIN_ID]; + robinhoodPairs.push( + Pair(0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168, 0x40858070814a57FdF33a613ae84fE0a8b4a874f7) + ); + robinhoodPairs.push( + Pair(0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168, 0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34) + ); + } +} diff --git a/script/proposal-6/V4FeesProposal.s.sol b/script/proposal-6/V4FeesProposal.s.sol new file mode 100644 index 00000000..02b65635 --- /dev/null +++ b/script/proposal-6/V4FeesProposal.s.sol @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.29; + +import {Script, console} from "forge-std/Script.sol"; + +import {Recorder} from "govkit/forge/Recorder.sol"; +import {Uniswap} from "govkit/types/Uniswap.sol"; +import {Proposal} from "govkit/types/Proposal.sol"; +import {ChainId} from "govkit/constants/ChainId.sol"; +import {Call, LibCall} from "govkit/types/Call.sol"; +import {GovernanceSeatbelt} from "govkit/forge/GovernanceSeatbelt.sol"; +import {FxRootEncoder} from "govkit/bridges/FxRootEncoder.sol"; +import {InboxEncoder} from "govkit/bridges/InboxEncoder.sol"; +import {L1CrossDomainMessengerEncoder} from "govkit/bridges/L1CrossDomainMessengerEncoder.sol"; +import {WormholeEncoder} from "govkit/bridges/WormholeEncoder.sol"; +import {IPoolManager} from "govkit/interfaces/IPoolManager.sol"; + +import "../proposal-5/Constants.sol" as Constants; + +import {DESCRIPTION} from "./Description.sol"; + +enum Part { + One, + Two +} + +/// @dev Toggle this to run part 1 or 2. +Part constant V4_FEE_PROPOSAL_PART = Part.One; + +contract V4FeeProposal is Script { + Recorder internal recorder; + Uniswap internal uniswap; + + function run() public { + require(Constants.Robinhood.TOKEN_JAR != address(0x00)); + require(keccak256(bytes(DESCRIPTION)) != keccak256(bytes("TODO"))); + + vm.createDir("./out/.seatbelt/", true); + recorder.initialize("DeployV4FeeInfra"); + uniswap.loadLatest(); + + if (V4_FEE_PROPOSAL_PART == Part.One) { + // --------------------------------------------------------------------------------------------- + // 00: Activate V4 Fees for Ethereum. + // + Call memory activateV4FeesEthereum = Call({ + target: uniswap.ethereum.poolManager, + value: 0, + data: abi.encodeCall( + IPoolManager.setProtocolFeeController, (recorder.read(ChainId.Ethereum, "V4FeeAdapter")) + ) + }); + + // --------------------------------------------------------------------------------------------- + // 01: Activate V4 Fees for Arbitrum. + // + Call memory activateV4FeesArbitrum = InboxEncoder.encode({ + inbox: uniswap.ethereum.bridge.arbitrum, + timelock: uniswap.ethereum.timelock, + remoteCall: Call({ + target: uniswap.arbitrum.poolManager, + value: 0, + data: abi.encodeCall( + IPoolManager.setProtocolFeeController, (recorder.read(ChainId.Arbitrum, "V4FeeAdapter")) + ) + }) + }); + + // --------------------------------------------------------------------------------------------- + // 02: Activate V4 Fees for Base. + // + Call memory activateV4FeesBase = L1CrossDomainMessengerEncoder.encode({ + l1CrossDomainMessenger: uniswap.ethereum.bridge.base, + crossChainAccount: uniswap.base.crossChainAccount, + remoteCall: Call({ + target: uniswap.base.poolManager, + value: 0, + data: abi.encodeCall( + IPoolManager.setProtocolFeeController, (recorder.read(ChainId.Base, "V4FeeAdapter")) + ) + }) + }); + + // --------------------------------------------------------------------------------------------- + // 03: Activate V4 Fees for BNB Chain. + // + Call memory activateV4FeesBNBChain = WormholeEncoder.encode({ + sourceSender: uniswap.ethereum.bridge.bnbChain, + remoteReceiver: uniswap.bnbChain.wormholeReceiver, + chainId: ChainId.BNBChain, + value: 0, + remoteCalls: LibCall.newCalls( + [Call({ + target: uniswap.bnbChain.poolManager, + value: 0, + data: abi.encodeCall( + IPoolManager.setProtocolFeeController, + (recorder.read(ChainId.BNBChain, "V4FeeAdapter")) + ) + })] + ) + }); + + // --------------------------------------------------------------------------------------------- + // 04: Activate V4 Fees for Polygon. + // + Call memory activateV4FeesPolygon = FxRootEncoder.encode({ + fxRoot: uniswap.ethereum.bridge.polygon, + fxReceiver: uniswap.polygon.fxReceiver, + remoteCalls: LibCall.newCalls( + [Call({ + target: uniswap.polygon.poolManager, + value: 0, + data: abi.encodeCall( + IPoolManager.setProtocolFeeController, + (recorder.read(ChainId.Polygon, "V4FeeAdapter")) + ) + })] + ) + }); + + // --------------------------------------------------------------------------------------------- + // 05: Activate V4 Fees for Optimism + // + Call memory activateV4FeesOPMainnet = L1CrossDomainMessengerEncoder.encode({ + l1CrossDomainMessenger: uniswap.ethereum.bridge.optimism, + crossChainAccount: uniswap.optimism.crossChainAccount, + remoteCall: Call({ + target: uniswap.optimism.poolManager, + value: 0, + data: abi.encodeCall( + IPoolManager.setProtocolFeeController, (recorder.read(ChainId.Optimism, "V4FeeAdapter")) + ) + }) + }); + + // --------------------------------------------------------------------------------------------- + // 06: Activate V4 Fees for Robinhood + // + Call memory activateV4FeesRobinhood = InboxEncoder.encode({ + inbox: Constants.Ethereum.RH_INBOX, + timelock: uniswap.ethereum.timelock, + remoteCall: Call({ + target: Constants.Robinhood.POOL_MANAGER, + value: 0, + data: abi.encodeCall( + IPoolManager.setProtocolFeeController, + (recorder.read(Constants.Robinhood.CHAIN_ID, "V4FeeAdapter")) + ) + }) + }); + + Proposal memory v4FeeActivationProposalPartOne = Proposal({ + description: string.concat("# Activate v4 Protocol Fees (Part 1/2) \n\n", DESCRIPTION), + calls: LibCall.newCalls( + [ + activateV4FeesEthereum, + activateV4FeesArbitrum, + activateV4FeesBase, + activateV4FeesBNBChain, + activateV4FeesPolygon, + activateV4FeesOPMainnet, + activateV4FeesRobinhood + ] + ) + }); + + vm.writeFile({ + path: "./out/.seatbelt/V4FeeActivationProposalPartOne.json", + data: GovernanceSeatbelt.toJson({ + proposal: v4FeeActivationProposalPartOne, governorBravo: uniswap.ethereum.governorBravo + }) + }); + } + + if (V4_FEE_PROPOSAL_PART == Part.Two) { + // --------------------------------------------------------------------------------------------- + // 00: Activate V4 Fees for Celo. + // + Call memory activateV4FeesCelo = L1CrossDomainMessengerEncoder.encode({ + l1CrossDomainMessenger: uniswap.ethereum.bridge.celo, + crossChainAccount: uniswap.celo.crossChainAccount, + remoteCall: Call({ + target: uniswap.celo.poolManager, + value: 0, + data: abi.encodeCall( + IPoolManager.setProtocolFeeController, (recorder.read(ChainId.Celo, "V4FeeAdapter")) + ) + }) + }); + + // --------------------------------------------------------------------------------------------- + // 01: Activate V4 Fees for Soneium. + // + Call memory activateV4FeesSoneium = L1CrossDomainMessengerEncoder.encode({ + l1CrossDomainMessenger: uniswap.ethereum.bridge.soneium, + crossChainAccount: uniswap.soneium.crossChainAccount, + remoteCall: Call({ + target: uniswap.soneium.poolManager, + value: 0, + data: abi.encodeCall( + IPoolManager.setProtocolFeeController, (recorder.read(ChainId.Soneium, "V4FeeAdapter")) + ) + }) + }); + + // --------------------------------------------------------------------------------------------- + // 02: Activate V4 Fees for Worldchain. + // + Call memory activateV4FeesWorldchain = L1CrossDomainMessengerEncoder.encode({ + l1CrossDomainMessenger: uniswap.ethereum.bridge.worldChain, + crossChainAccount: uniswap.worldChain.crossChainAccount, + remoteCall: Call({ + target: uniswap.worldChain.poolManager, + value: 0, + data: abi.encodeCall( + IPoolManager.setProtocolFeeController, + (recorder.read(ChainId.WorldChain, "V4FeeAdapter")) + ) + }) + }); + + // --------------------------------------------------------------------------------------------- + // 03: Activate V4 Fees for X Layer + // + Call memory activateV4FeesXLayer = L1CrossDomainMessengerEncoder.encode({ + l1CrossDomainMessenger: uniswap.ethereum.bridge.xLayer, + crossChainAccount: uniswap.xLayer.crossChainAccount, + remoteCall: Call({ + target: uniswap.xLayer.poolManager, + value: 0, + data: abi.encodeCall( + IPoolManager.setProtocolFeeController, (recorder.read(ChainId.XLayer, "V4FeeAdapter")) + ) + }) + }); + + // --------------------------------------------------------------------------------------------- + // 04: Activate V4 Fees for Zora. + // + Call memory activateV4FeesZora = L1CrossDomainMessengerEncoder.encode({ + l1CrossDomainMessenger: uniswap.ethereum.bridge.zora, + crossChainAccount: uniswap.zora.crossChainAccount, + remoteCall: Call({ + target: uniswap.zora.poolManager, + value: 0, + data: abi.encodeCall( + IPoolManager.setProtocolFeeController, (recorder.read(ChainId.Zora, "V4FeeAdapter")) + ) + }) + }); + + Proposal memory v4FeeActivationProposalPartTwo = Proposal({ + description: string.concat("# Activate v4 Protocol Fees (Part 2/2) \n\n", DESCRIPTION), + calls: LibCall.newCalls( + [ + activateV4FeesCelo, + activateV4FeesSoneium, + activateV4FeesWorldchain, + activateV4FeesXLayer, + activateV4FeesZora + ] + ) + }); + + vm.writeFile({ + path: "./out/.seatbelt/V4FeeActivationProposalPartTwo.json", + data: GovernanceSeatbelt.toJson({ + proposal: v4FeeActivationProposalPartTwo, governorBravo: uniswap.ethereum.governorBravo + }) + }); + } + } +} diff --git a/script/proposal-6/prereq/DeployV4FeeInfra.s.sol b/script/proposal-6/prereq/DeployV4FeeInfra.s.sol new file mode 100644 index 00000000..536937f1 --- /dev/null +++ b/script/proposal-6/prereq/DeployV4FeeInfra.s.sol @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: AGPl-3.0-only +pragma solidity 0.8.29; + +import {Script, console} from "forge-std/Script.sol"; +import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol"; +import {Currency} from "v4-core/types/Currency.sol"; + +import {Recorder} from "govkit/forge/Recorder.sol"; +import {Uniswap} from "govkit/types/Uniswap.sol"; +import {ChainId} from "govkit/constants/ChainId.sol"; +import {InboxEncoder} from "govkit/bridges/InboxEncoder.sol"; + +import {V4FeeAdapter} from "../../../src/feeAdapters/V4FeeAdapter.sol"; +import {V4FeePolicy} from "../../../src/feeAdapters/V4FeePolicy.sol"; +import { + FeeBucket, + FlagRule, + PairClassFeeAssignment, + HookFamilyAssignment +} from "../../../src/interfaces/IV4FeePolicy.sol"; +import {StableStablePairs} from "../StableStablePairs.sol"; + +import "../../proposal-5/Constants.sol" as Constants; + +uint256 constant AGG_HOOK_FAMILY = 1 << 11; +uint8 constant AGG_HOOK_ID = 11; + +contract DeployV4FeeInfra is Script { + Recorder internal recorder; + Uniswap internal uniswap; + StableStablePairs internal stableStablePairs; + + function run() external { + uniswap.loadLatest(); + recorder.initialize("DeployV4FeeInfra"); + stableStablePairs.initialize(); + + address scriptRunnerEoa = msg.sender; + uint256 chainId = block.chainid; + + (address poolManager, address tokenJar, address postConfigOwner) = getAddresses(chainId); + + if (chainId == Constants.Robinhood.CHAIN_ID) { + require(Constants.Robinhood.TOKEN_JAR != address(0x00)); + } + + vm.startBroadcast(); + + // ----------------------------------------------------------------------------------------- + // Deploy V4FeeAdapter + // + V4FeeAdapter adapter; + if (!recorder.exists(chainId, "V4FeeAdapter")) { + adapter = new V4FeeAdapter({poolManager: IPoolManager(poolManager), tokenJar: tokenJar}); + + recorder.write({ + chainId: chainId, deploymentName: "V4FeeAdapter", deployment: address(adapter) + }); + } else { + adapter = V4FeeAdapter(recorder.read(chainId, "V4FeeAdapter")); + } + + // ----------------------------------------------------------------------------------------- + // Deploy V4FeePolicy + // + V4FeePolicy policy; + if (!recorder.exists(chainId, "V4FeePolicy")) { + policy = new V4FeePolicy({poolManager: IPoolManager(poolManager)}); + + recorder.write({chainId: chainId, deploymentName: "V4FeePolicy", deployment: address(policy)}); + } else { + policy = V4FeePolicy(recorder.read(chainId, "V4FeePolicy")); + } + + // ----------------------------------------------------------------------------------------- + // Set Policy on Adapter + // + adapter.setPolicy(policy); + + // ----------------------------------------------------------------------------------------- + // Take Fee Setter Authority + // + policy.setFeeSetter(scriptRunnerEoa); + + // ----------------------------------------------------------------------------------------- + // Assign Fee Buckets + // + FeeBucket[] memory feeBuckets = new FeeBucket[](8); + feeBuckets[0] = FeeBucket({lpFeeFloor: 0, alphaPips: 1, betaPips: 0}); + feeBuckets[1] = FeeBucket({lpFeeFloor: 3, alphaPips: 1, betaPips: 263_889}); + feeBuckets[2] = FeeBucket({lpFeeFloor: 75, alphaPips: 20, betaPips: 200_000}); + feeBuckets[3] = FeeBucket({lpFeeFloor: 100, alphaPips: 25, betaPips: 272_728}); + feeBuckets[4] = FeeBucket({lpFeeFloor: 375, alphaPips: 100, betaPips: 200_000}); + feeBuckets[5] = FeeBucket({lpFeeFloor: 500, alphaPips: 125, betaPips: 137_500}); + feeBuckets[6] = FeeBucket({lpFeeFloor: 2500, alphaPips: 400, betaPips: 200_000}); + feeBuckets[7] = FeeBucket({lpFeeFloor: 5500, alphaPips: 1000, betaPips: 0}); + policy.setFeeBuckets(feeBuckets); + + // ----------------------------------------------------------------------------------------- + // Set Flag Rules + // + // | Name | ID | Flags | + // | ---------------- | ---- | --------- | + // | Aggregator Hooks | `11` | `1 << 11` | + // + { + FlagRule[] memory flagRules = new FlagRule[](1); + flagRules[0] = FlagRule({requiredFlags: AGG_HOOK_FAMILY, familyId: AGG_HOOK_ID}); + policy.setFlagRules(flagRules); + } + + // ----------------------------------------------------------------------------------------- + // Set Aggregator Hook Fees + // + { + uint24 defaultFeeValue = (chainId == ChainId.Base ? 300 : 1000) / 25; + uint24 stableStableFeeValue = (chainId == ChainId.Base ? 100 : 300) / 25; + + policy.setFamilyDefault({familyId: AGG_HOOK_ID, feeValue: encodeFee(defaultFeeValue)}); + + uint256 length = stableStablePairs.chainPairs[chainId].length; + + PairClassFeeAssignment[] memory assignments = new PairClassFeeAssignment[](length); + for (uint256 i; i < length; i++) { + address token0 = stableStablePairs.chainPairs[chainId][i].token0; + address token1 = stableStablePairs.chainPairs[chainId][i].token1; + + (token0, token1) = sort(token0, token1); + + assignments[i] = PairClassFeeAssignment({ + currency0: Currency.wrap(token0), + currency1: Currency.wrap(token1), + familyId: AGG_HOOK_ID, + feeValue: encodeFee(stableStableFeeValue) + }); + } + policy.batchSetPairClassFee(assignments); + } + + // ----------------------------------------------------------------------------------------- + // Set LBP & CCA Hooks + // + if (chainId == ChainId.Ethereum) { + HookFamilyAssignment[] memory assignments = new HookFamilyAssignment[](5); + assignments[0] = + HookFamilyAssignment({hook: 0xd53006d1e3110fD319a79AEEc4c527a0d265E080, familyId: 255}); // aztec + // (CCA) + assignments[1] = + HookFamilyAssignment({hook: 0x890681CfF5AD2069F020027f41f5f68F6a292000, familyId: 255}); // octra + // (CCA) + assignments[2] = + HookFamilyAssignment({hook: 0x358Ac5a3FA0d5A80d78013DBe6A4f290438cA000, familyId: 255}); // strato + // (CCA) + assignments[3] = + HookFamilyAssignment({hook: 0xb98766A35cdc28415be0767D4EA41e39fBA3e000, familyId: 255}); // LBP + assignments[4] = + HookFamilyAssignment({hook: 0x49380c4EfaB1b491006aF7FabAB8B3459F0E6000, familyId: 255}); // LBP + policy.batchSetHookFamily(assignments); + } + + if (chainId == ChainId.Base) { + HookFamilyAssignment[] memory assignments = new HookFamilyAssignment[](3); + assignments[0] = + HookFamilyAssignment({hook: 0xeA9346e83952840E69Beb36Df365C4e68DE0E080, familyId: 255}); // flow + // (CCA) + assignments[1] = + HookFamilyAssignment({hook: 0x5bB4bAfafEc57BEd50D864AAA9D1ef992611e000, familyId: 255}); // LBP + assignments[2] = + HookFamilyAssignment({hook: 0x34385dD739FE5464892BF0bA4CC42492804dA000, familyId: 255}); // LBP + policy.batchSetHookFamily(assignments); + } + + if (chainId == Constants.Robinhood.CHAIN_ID) { + HookFamilyAssignment[] memory assignments = new HookFamilyAssignment[](3); + assignments[0] = + HookFamilyAssignment({hook: 0x05d552391067389EE44fec3924157ed33F976000, familyId: 255}); // LBP + assignments[1] = + HookFamilyAssignment({hook: 0xD462a559337859369EF271814851A18F496ba000, familyId: 255}); // new + // hook + assignments[2] = + HookFamilyAssignment({hook: 0x095e38a2135aeBcfFa98A5B6911591937f912000, familyId: 255}); // LBP + policy.batchSetHookFamily(assignments); + } + + if (chainId == ChainId.Arbitrum) { + HookFamilyAssignment[] memory assignments = new HookFamilyAssignment[](2); + assignments[0] = + HookFamilyAssignment({hook: 0x18608AD558dcD233F7854242bbAef73988Bee000, familyId: 255}); // LBP + assignments[1] = + HookFamilyAssignment({hook: 0x8Af0775a70Cc94D71DFc0fE809435e833F2Fe000, familyId: 255}); // LBP + + policy.batchSetHookFamily(assignments); + } + if (chainId == ChainId.XLayer) { + HookFamilyAssignment[] memory assignments = new HookFamilyAssignment[](2); + assignments[0] = + HookFamilyAssignment({hook: 0x95bcb80e3804a085d23778F2956c305d6488e000, familyId: 255}); // LBP + assignments[1] = + HookFamilyAssignment({hook: 0x58DF162fF41e5cB42B8515f75F90C1841938A000, familyId: 255}); // LBP + + policy.batchSetHookFamily(assignments); + } + + // ----------------------------------------------------------------------------------------- + // Transfer Authority + // + policy.setFeeSetter(postConfigOwner); + + policy.transferOwnership(postConfigOwner); + + adapter.setFeeSetter(postConfigOwner); + + adapter.transferOwnership(postConfigOwner); + + vm.stopBroadcast(); + + // ----------------------------------------------------------------------------------------- + // Run Checks + // + console.log("Checking ..."); + + require(address(adapter.POOL_MANAGER()) == poolManager); + require(adapter.TOKEN_JAR() == tokenJar); + require(adapter.feeSetter() == postConfigOwner); + require(address(adapter.policy()) == address(policy)); + + require(address(policy.POOL_MANAGER()) == poolManager); + require(policy.feeSetter() == postConfigOwner); + require(policy.defaultFee() == 0); + require(policy.feeBucketsLength() == feeBuckets.length); + + for (uint256 i; i < feeBuckets.length; i++) { + (uint24 lpFeeFloor, uint24 alphaPips, uint32 betaPips) = policy.feeBucket(i); + require(lpFeeFloor == feeBuckets[i].lpFeeFloor); + require(alphaPips == feeBuckets[i].alphaPips); + require(betaPips == feeBuckets[i].betaPips); + } + + require(policy.flagRulesLength() == 1); + { + (uint256 hookFamily, uint8 hookId) = policy.flagRules(0); + require(hookFamily == AGG_HOOK_FAMILY); + require(hookId == AGG_HOOK_ID); + } + + if (chainId == ChainId.Base) { + require(policy.familyDefaults(AGG_HOOK_ID) == encodeFee(300 / 25)); + } else { + require(policy.familyDefaults(AGG_HOOK_ID) == encodeFee(1000 / 25)); + } + + uint256 len = stableStablePairs.chainPairs[chainId].length; + + for (uint256 i; i < len; i++) { + address token0 = stableStablePairs.chainPairs[chainId][i].token0; + address token1 = stableStablePairs.chainPairs[chainId][i].token1; + + (token0, token1) = sort(token0, token1); + + bytes32 hash = keccak256(abi.encodePacked(token0, token1)); + + if (chainId == ChainId.Base) { + require(policy.pairClassFees(hash, AGG_HOOK_ID) == encodeFee(100 / 25)); + } else { + require(policy.pairClassFees(hash, AGG_HOOK_ID) == encodeFee(300 / 25)); + } + } + } + + /// @dev Resovles the addresses of the pool manager, token jar, and timelock based on chain id. + function getAddresses(uint256 chainId) internal view returns (address, address, address) { + if (chainId == ChainId.Ethereum) { + return (uniswap.ethereum.poolManager, uniswap.ethereum.tokenJar, uniswap.ethereum.timelock); + } + if (chainId == ChainId.Arbitrum) { + return ( + uniswap.arbitrum.poolManager, + uniswap.arbitrum.tokenJar, + InboxEncoder.arbitrumAlias(uniswap.ethereum.timelock) + ); + } + if (chainId == ChainId.Base) { + return (uniswap.base.poolManager, uniswap.base.tokenJar, uniswap.base.crossChainAccount); + } + if (chainId == ChainId.Celo) { + return (uniswap.celo.poolManager, uniswap.celo.tokenJar, uniswap.celo.crossChainAccount); + } + if (chainId == ChainId.Optimism) { + return + ( + uniswap.optimism.poolManager, + uniswap.optimism.tokenJar, + uniswap.optimism.crossChainAccount + ); + } + if (chainId == ChainId.Soneium) { + return + (uniswap.soneium.poolManager, uniswap.soneium.tokenJar, uniswap.soneium.crossChainAccount); + } + if (chainId == ChainId.XLayer) { + return (uniswap.xLayer.poolManager, uniswap.xLayer.tokenJar, uniswap.xLayer.crossChainAccount); + } + if (chainId == ChainId.WorldChain) { + return ( + uniswap.worldChain.poolManager, + uniswap.worldChain.tokenJar, + uniswap.worldChain.crossChainAccount + ); + } + if (chainId == ChainId.Zora) { + return (uniswap.zora.poolManager, uniswap.zora.tokenJar, uniswap.zora.crossChainAccount); + } + if (chainId == ChainId.BNBChain) { + return + (uniswap.bnbChain.poolManager, uniswap.bnbChain.tokenJar, uniswap.bnbChain.wormholeReceiver); + } + if (chainId == ChainId.Polygon) { + return (uniswap.polygon.poolManager, uniswap.polygon.tokenJar, uniswap.polygon.fxReceiver); + } + + if (chainId == Constants.Robinhood.CHAIN_ID) { + return ( + Constants.Robinhood.POOL_MANAGER, + Constants.Robinhood.TOKEN_JAR, + InboxEncoder.arbitrumAlias(uniswap.ethereum.timelock) + ); + } + + revert("invalid chain id"); + } + + /// @dev Handles v4 fee two way encoding + function encodeFee(uint24 fee) internal pure returns (uint24) { + return fee << 12 | fee; + } + + /// @dev sort addresses for pool fee assignment. + function sort(address a, address b) internal pure returns (address, address) { + return uint160(a) >= uint160(b) ? (b, a) : (a, b); + } +} diff --git a/src/feeAdapters/V4FeeAdapter.sol b/src/feeAdapters/V4FeeAdapter.sol index f7b3aa58..68d1ee8c 100644 --- a/src/feeAdapters/V4FeeAdapter.sol +++ b/src/feeAdapters/V4FeeAdapter.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.26; +pragma solidity ^0.8.26; import {Owned} from "solmate/src/auth/Owned.sol"; import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol"; diff --git a/src/feeAdapters/V4FeePolicy.sol b/src/feeAdapters/V4FeePolicy.sol index 58f4e05b..69ea73bf 100644 --- a/src/feeAdapters/V4FeePolicy.sol +++ b/src/feeAdapters/V4FeePolicy.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.26; +pragma solidity ^0.8.26; import {Owned} from "solmate/src/auth/Owned.sol"; import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol"; diff --git a/src/releasers/ArbitrumOrbitResourceFirepit.sol b/src/releasers/ArbitrumOrbitResourceFirepit.sol new file mode 100644 index 00000000..df41a5a1 --- /dev/null +++ b/src/releasers/ArbitrumOrbitResourceFirepit.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.29; + +import {ERC20} from "solmate/src/tokens/ERC20.sol"; +import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; +import {IL2GatewayRouter} from "../interfaces/external/IL2GatewayRouter.sol"; +import {Currency} from "v4-core/types/Currency.sol"; +import {ExchangeReleaser} from "./ExchangeReleaser.sol"; + +/// @title ArbitrumOrbitResourceFirepit +/// @notice A releaser that implements a two-stage burn process for bridged resource tokens on +/// Arbitrum Orbit chains +/// @dev Two-stage burn from Arbitrum Orbit L2's to the underlying resource on L1 +/// **Stage 1: L2 Collection and Bridge Initiation** +/// - User calls `release()` providing resource tokens as payment +/// - ExchangeReleaser transfers resource tokens from user to this smart contract +/// - TokenJar releases accumulated fee assets to the specified recipient +/// - _afterRelease() initiates bridge withdrawal to L1 burn address (0xdead) +/// **Stage 2: L1 Finalization** +/// - L2GatewayRouter burns the L2 tokens held by this contract +/// - Cross-domain message is queued (~7-day challenge period on mainnet) +/// - After challenge period, L1 gateway releases escrowed tokens to 0xdead on L1 +contract ArbitrumOrbitResourceFirepit is ExchangeReleaser { + using SafeTransferLib for ERC20; + + /// @dev The L2 Gateway Router address on the Arbitrum Orbit chain + address public immutable L2_GATEWAY_ROUTER; + + /// @dev The L1 UNI token address (Ethereum mainnet) + /// @dev Required for Arbitrum Orbit gateway calls which use L1 token address + address public immutable L1_RESOURCE; + + /// @dev Final recipient of the bridged resource on L1 (burn address) + /// @dev Note: This is different from RESOURCE_RECIPIENT which is address(this) on L2 + address internal constant L1_RESOURCE_RECIPIENT = address(0xdead); + + /// @notice Creates a new ArbitrumOrbitResourceFirepit instance + /// @param _resource The address of the resource token on Arbitrum Orbit chain (L2 bridged token) + /// @param _l1Resource The address of the resource token on Ethereum (L1 original token) + /// @param _threshold The minimum amount of resource tokens required for exchange + /// @param _tokenJar The address of the TokenJar contract holding accumulated fee assets + /// @dev Sets RESOURCE_RECIPIENT to address(this) to enable the two-stage burn: + /// Stage 1: Collect tokens here (L2) -> Stage 2: Bridge and burn on L1 + constructor( + address _l2GatewayRouter, + address _resource, + address _l1Resource, + uint256 _threshold, + address _tokenJar + ) ExchangeReleaser(_resource, _threshold, _tokenJar, address(this)) { + L2_GATEWAY_ROUTER = _l2GatewayRouter; + L1_RESOURCE = _l1Resource; + } + + /// @notice Hook called after assets are released - initiates stage 2 withdrawal to L1 + function _afterRelease(Currency[] calldata, address) internal override { + // Stage 2: Initiate bridge withdrawal to L1 burn address + // The gateway will: + // 1. Burn the L2 tokens held by this contract + // 2. Queue a cross-domain message for L1 + // 3. After challenge period, release underlying resource tokens to 0xdead on L1 + IL2GatewayRouter(L2_GATEWAY_ROUTER) + .outboundTransfer( + L1_RESOURCE, // L1 token address (required by Arbitrum Orbit chain gateway) + L1_RESOURCE_RECIPIENT, // Destination on L1 (burn address) + threshold, // Amount to withdraw + bytes("") // Empty extra data for standard withdrawal + ); + } +}