├── .github └── workflows │ └── ci-jobs.yml ├── .gitignore ├── .gitmodules ├── README.md ├── docs └── deployment.md └── src ├── 0x ├── ITransform.sol └── TransformController.sol ├── aave ├── AaveEthController.sol ├── AaveV2Controller.sol ├── AaveV3Controller.sol ├── IPoolV3.sol └── IProtocolDataProvider.sol ├── aura ├── IBooster.sol ├── IRewards.sol ├── IStashToken.sol └── RewardPoolController.sol ├── balancer ├── BalancerController.sol ├── BalancerLPStakingController.sol └── IVault.sol ├── compound ├── CompoundController.sol └── ICToken.sol ├── convex ├── ConvexBoosterController.sol ├── ConvexRewardPoolController.sol └── IRewardPool.sol ├── core ├── BaseController.sol ├── ControllerFacade.sol ├── IController.sol └── IControllerFacade.sol ├── curve ├── CurveCryptoSwapController.sol ├── CurveLPStakingController.sol ├── CurveMinterController.sol ├── CurveZapCryptoSwapController.sol ├── IStableSwapPool.sol ├── StableSwap2PoolController.sol ├── StableSwap2PoolEthController.sol └── StableSwap3PoolController.sol ├── erc4626 ├── ERC4626Controller.sol └── IERC4626.sol ├── gmx ├── RewardRouterController.sol └── RewardRouterV2Controller.sol ├── plutus └── PLVGLPController.sol ├── rage ├── DepositPeripheryController.sol └── WithdrawPeripheryController.sol ├── tests ├── 0xTransform.t.sol ├── AuraRewardPoolController.t.sol ├── Balancer.t.sol ├── BalancerLPStaking.t.sol ├── BaseController.t.sol ├── ConvexController.t.sol ├── DNGMXVaultController.t.sol ├── GLPController.t.sol ├── PLVGLPController.t.sol ├── StableSwap2PoolController.t.sol └── utils │ └── Base.t.sol ├── uniswap ├── ISwapRouterV3.sol ├── IUniV2Factory.sol ├── UniV2Controller.sol └── UniV3Controller.sol ├── utils ├── Errors.sol └── Ownable.sol ├── weth └── WETHController.sol └── yearn └── YearnController.sol /.github/workflows/ci-jobs.yml: -------------------------------------------------------------------------------- 1 | name: Compile and Test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | check: 11 | name: Foundry project 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | submodules: recursive 17 | 18 | - name: Install Foundry 19 | uses: onbjerg/foundry-toolchain@v1.0.1 20 | with: 21 | version: nightly 22 | 23 | - name: Run tests 24 | run: forge test --no-match-contract Arbi --fork-url https://eth.llamarpc.com 25 | 26 | - name: Run arbitrum tests 27 | run: forge test --mc Arbi --fork-url https://arb1.arbitrum.io/rpc 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cache/ 2 | out/ 3 | remappings.txt 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/solidity-bytes-utils"] 2 | path = lib/solidity-bytes-utils 3 | url = https://github.com/GNSPS/solidity-bytes-utils 4 | [submodule "lib/forge-std"] 5 | path = lib/forge-std 6 | url = https://github.com/foundry-rs/forge-std 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Controller 2 | 3 | Interaction Controller For Sentiment 4 | 5 | ## Instructions 6 | 7 | ### Requirements 8 | 9 | 1. Rust 10 | 2. Foundry - https://github.com/gakonst/foundry 11 | 12 | --- 13 | 14 | ### Cloning Repo 15 | 16 | ```bash 17 | git clone --recurse-submodules git@github.com:sentimentxyz/controller.git 18 | ``` 19 | 20 | --- 21 | 22 | ### Building Contracts 23 | 24 | ```bash 25 | forge build 26 | ``` 27 | 28 | --- 29 | 30 | ### Running tests 31 | 32 | #### Unit/Functional tests 33 | 34 | ```bash 35 | forge test --fork-url https://rpc.ankr.com/eth -vvv 36 | ``` 37 | 38 | --- 39 | 40 | #### Configuring mappings 41 | 42 | ```bash 43 | forge config > remappings.txt 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/deployment.md: -------------------------------------------------------------------------------- 1 | ### Arbitrum Deployment Addresses 2 | 3 | --- 4 | ControllerFacade: [0xe7128748A9d70fb48af14F8e6907C5aA8415F065](https://arbiscan.io/address/0xe7128748A9d70fb48af14F8e6907C5aA8415F065)\ 5 | BaseController: [0x947e74f019A47EcccB8d81e802EC92D1364Ed278](https://arbiscan.io/address/0x947e74f019A47EcccB8d81e802EC92D1364Ed278)\ 6 | 0x/TransformController: [0xFac8859851677074a3F029c44a3cB88ED94e825f](https://arbiscan.io/address/0xFac8859851677074a3F029c44a3cB88ED94e825f)\ 7 | aave/AaveV3Controller: [0xC44f3ae3950efb7735C179714D133BA62bE7BDec](https://arbiscan.io/address/0xC44f3ae3950efb7735C179714D133BA62bE7BDec)\ 8 | aave/AaveEthController: [0xc04b6ecA0400001d112f23714B6b4136381DC16B](https://arbiscan.io/address/0xc04b6ecA0400001d112f23714B6b4136381DC16B)\ 9 | balancer/BalancerController: [0xC243f6e368ac321Ccce360E8BAaa7525e4b1BDD9](https://arbiscan.io/address/0xC243f6e368ac321Ccce360E8BAaa7525e4b1BDD9)\ 10 | balancer/BalancerLPStakingController: [0x2aD380c9c480cffEd54C24F8E9eeC3a701d0fFE0](https://arbiscan.io/address/0x2aD380c9c480cffEd54C24F8E9eeC3a701d0fFE0)\ 11 | convex/BoosterController: [0x30aedA69AB89402193933C82Df418b419BB645f2](https://arbiscan.io/address/0x30aedA69AB89402193933C82Df418b419BB645f2)\ 12 | convex/RewardController: [0x9b4c4B2EAde35CE2Ab0bC3Fb97Ac81E3Dba3B5B2](https://arbiscan.io/address/0x9b4c4B2EAde35CE2Ab0bC3Fb97Ac81E3Dba3B5B2)\ 13 | curve/CurveCryptoSwapController: [0x3Ec0e77eAECE115e8c253614028815beAE7B6ff4](https://arbiscan.io/address/0x3Ec0e77eAECE115e8c253614028815beAE7B6ff4)\ 14 | curve/CurveLPStakingController: [0xa20528fB3545c269691dbEbAa971eE42852BEb71](https://arbiscan.io/address/0xa20528fB3545c269691dbEbAa971eE42852BEb71)\ 15 | curve/CurveMinterController: [0x05F685b113f9b79F5044b030b8280BD93F1e9E23](https://arbiscan.io/address/0x05F685b113f9b79F5044b030b8280BD93F1e9E23)\ 16 | curve/CurveMinterController(for Balancer): [0xD2664D856b455733B49d5F33702e4F4d8cDfAe75](https://arbiscan.io/address/0xD2664D856b455733B49d5F33702e4F4d8cDfAe75)\ 17 | curve/StableSwap2PoolController: [0xfCC9Ea8bBbBd2dCB558931B189027e42EeF7e9D8](https://arbiscan.io/address/0xfCC9Ea8bBbBd2dCB558931B189027e42EeF7e9D8)\ 18 | gmx/RewardRouterController: [0x48b7CB0489F0aeD4E0D48f34b38851cF54c5b9AF](https://arbiscan.io/address/0x48b7CB0489F0aeD4E0D48f34b38851cF54c5b9AF)\ 19 | gmx/RewardRouterV2Controller: [0x3e1C3E175eba948eb74a68C2f946678A863550CE](https://arbiscan.io/address/0x3e1C3E175eba948eb74a68C2f946678A863550CE)\ 20 | uniswap/UniV2Controller: [0x6Df0b4F7E3056d52Fff8D9d3B5E45b4E29ee7Bfd](https://arbiscan.io/address/0x6Df0b4F7E3056d52Fff8D9d3B5E45b4E29ee7Bfd)\ 21 | uniswap/UniV3Controller: [0x4f0da4c622C07D126c87fD3fA9B93Bf5721f9edB](https://arbiscan.io/address/0x4f0da4c622C07D126c87fD3fA9B93Bf5721f9edB)\ 22 | weth/WethController: [0x697cfba173537f9C59FeE745Bbd8A4A28F5381cc](https://arbiscan.io/address/0x697cfba173537f9C59FeE745Bbd8A4A28F5381cc)\ 23 | aura/RewardPoolController: [0x2AB248C6A51261ef86BB99fa0C210B097349d628](https://arbiscan.io/address/0x2AB248C6A51261ef86BB99fa0C210B097349d628)\ 24 | 25 | --- -------------------------------------------------------------------------------- /src/0x/ITransform.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface ITransformERC20Feature { 5 | struct Transformation { 6 | uint32 deploymentNonce; 7 | bytes data; 8 | } 9 | 10 | function transformERC20( 11 | address inputToken, 12 | address outputToken, 13 | uint256 inputTokenAmount, 14 | uint256 minOutputTokenAmount, 15 | Transformation[] calldata transformations 16 | ) external payable returns (uint256 outputTokenAmount); 17 | } 18 | -------------------------------------------------------------------------------- /src/0x/TransformController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | import {ITransformERC20Feature} from "./ITransform.sol"; 6 | 7 | /** 8 | * @title 0x V4 Controller 9 | * @notice 0x v4 controller for transformERC20 10 | */ 11 | contract TransformController is IController { 12 | /* -------------------------------------------------------------------------- */ 13 | /* CONSTANT VARIABLES */ 14 | /* -------------------------------------------------------------------------- */ 15 | 16 | /// @notice transformERC20(address, address, uint256, uint256, (uint32,bytes)[]) 17 | bytes4 constant TRANSFORMERC20 = 0x415565b0; 18 | 19 | /// @notice ETH address 20 | address constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; 21 | 22 | /* -------------------------------------------------------------------------- */ 23 | /* EXTERNAL FUNCTIONS */ 24 | /* -------------------------------------------------------------------------- */ 25 | 26 | /// @inheritdoc IController 27 | function canCall(address, bool, bytes calldata data) 28 | external 29 | pure 30 | returns (bool, address[] memory tokensIn, address[] memory tokensOut) 31 | { 32 | bytes4 sig = bytes4(data); 33 | 34 | if (sig != TRANSFORMERC20) { 35 | return (false, new address[](0), new address[](0)); 36 | } 37 | 38 | (address tokenOut, address tokenIn) = 39 | abi.decode(data[4:], (address, address)); 40 | 41 | if (tokenIn == ETH) { 42 | tokensOut = new address[](1); 43 | tokensOut[0] = tokenOut; 44 | return (true, new address[](0), tokensOut); 45 | } 46 | 47 | if (tokenOut == ETH) { 48 | tokensIn = new address[](1); 49 | tokensIn[0] = tokenIn; 50 | return (true, tokensIn, new address[](0)); 51 | } 52 | 53 | tokensIn = new address[](1); 54 | tokensOut = new address[](1); 55 | 56 | tokensIn[0] = tokenIn; 57 | tokensOut[0] = tokenOut; 58 | 59 | return (true, tokensIn, tokensOut); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/aave/AaveEthController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | 6 | /** 7 | @title Aave Eth Controller 8 | @notice Controller for aave Weth interaction 9 | arbi:0xC09e69E79106861dF5d289dA88349f10e2dc6b5C 10 | */ 11 | contract AaveEthController is IController { 12 | 13 | /* -------------------------------------------------------------------------- */ 14 | /* CONSTANT VARIABLES */ 15 | /* -------------------------------------------------------------------------- */ 16 | 17 | /// @notice depositETH(address,address,uint16) function signature 18 | bytes4 public constant DEPOSIT = 0x474cf53d; 19 | 20 | /// @notice withdrawETH(address,uint256,address) function signature 21 | bytes4 public constant WITHDRAW = 0x80500d20; 22 | 23 | /* -------------------------------------------------------------------------- */ 24 | /* STATE VARIABLES */ 25 | /* -------------------------------------------------------------------------- */ 26 | 27 | /// @notice List of tokens 28 | /// @dev Will always have one token aave WETH 29 | address[] public tokens; 30 | 31 | /* -------------------------------------------------------------------------- */ 32 | /* CONSTRUCTOR */ 33 | /* -------------------------------------------------------------------------- */ 34 | 35 | /** 36 | @notice Contract constructor 37 | @param _aWeth address of aave WETH 38 | */ 39 | constructor(address _aWeth) { 40 | tokens.push(_aWeth); 41 | } 42 | 43 | /* -------------------------------------------------------------------------- */ 44 | /* PUBLIC FUNCTIONS */ 45 | /* -------------------------------------------------------------------------- */ 46 | 47 | /// @inheritdoc IController 48 | function canCall(address, bool, bytes calldata data) 49 | external 50 | view 51 | returns (bool, address[] memory, address[] memory) 52 | { 53 | bytes4 sig = bytes4(data); 54 | if (sig == DEPOSIT) return (true, tokens, new address[](0)); 55 | if (sig == WITHDRAW) return (true, new address[](0), tokens); 56 | return (false, new address[](0), new address[](0)); 57 | } 58 | } -------------------------------------------------------------------------------- /src/aave/AaveV2Controller.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | import {IProtocolDataProvider} from "./IProtocolDataProvider.sol"; 6 | 7 | /** 8 | @title Aave V2 controller 9 | @notice Controller for aave v2 interaction 10 | eth:0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9 11 | */ 12 | contract AaveV2Controller is IController { 13 | 14 | /* -------------------------------------------------------------------------- */ 15 | /* CONSTANT VARIABLES */ 16 | /* -------------------------------------------------------------------------- */ 17 | 18 | /// @notice deposit(address,uint256,address,uint16) function signature 19 | bytes4 public constant DEPOSIT = 0xe8eda9df; 20 | 21 | /// @notice withdraw(address,uint256,address) function signature 22 | bytes4 public constant WITHDRAW = 0x69328dec; 23 | 24 | /* -------------------------------------------------------------------------- */ 25 | /* STATE_VARIABLES */ 26 | /* -------------------------------------------------------------------------- */ 27 | 28 | /** 29 | @notice IProtocolDataProvider 30 | @dev https://docs.aave.com/developers/v/2.0/the-core-protocol/protocol-data-provider/iprotocoldataprovider 31 | */ 32 | IProtocolDataProvider public immutable dataProvider; 33 | 34 | /* -------------------------------------------------------------------------- */ 35 | /* CONSTRUCTOR */ 36 | /* -------------------------------------------------------------------------- */ 37 | 38 | /** 39 | @notice Contract constructor 40 | @param _dataProvider address of aave v2 data provider 41 | */ 42 | constructor( 43 | IProtocolDataProvider _dataProvider 44 | ) 45 | { 46 | dataProvider = _dataProvider; 47 | } 48 | 49 | /* -------------------------------------------------------------------------- */ 50 | /* PUBLIC FUNCTIONS */ 51 | /* -------------------------------------------------------------------------- */ 52 | 53 | /// @inheritdoc IController 54 | function canCall(address, bool, bytes calldata data) 55 | external 56 | view 57 | returns (bool, address[] memory, address[] memory) 58 | { 59 | bytes4 sig = bytes4(data); 60 | if (sig == DEPOSIT) { 61 | address asset = abi.decode( 62 | data[4:], 63 | (address) 64 | ); 65 | address[] memory tokensIn = new address[](1); 66 | address[] memory tokensOut = new address[](1); 67 | (tokensIn[0],,) = dataProvider.getReserveTokensAddresses(asset); 68 | tokensOut[0] = asset; 69 | return ( 70 | true, 71 | tokensIn, 72 | tokensOut 73 | ); 74 | } 75 | if (sig == WITHDRAW) { 76 | address asset = abi.decode( 77 | data[4:], 78 | (address) 79 | ); 80 | address[] memory tokensIn = new address[](1); 81 | address[] memory tokensOut = new address[](1); 82 | tokensIn[0] = asset; 83 | (tokensOut[0],,) = dataProvider.getReserveTokensAddresses(asset); 84 | return (true, tokensIn, tokensOut); 85 | } 86 | return (false, new address[](0), new address[](0)); 87 | } 88 | } -------------------------------------------------------------------------------- /src/aave/AaveV3Controller.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | import {IPoolV3} from "./IPoolV3.sol"; 6 | 7 | /** 8 | @title Aave V3 controller 9 | @notice Controller for aave v3 interaction 10 | arbi:0x794a61358D6845594F94dc1DB02A252b5b4814aD 11 | */ 12 | contract AaveV3Controller is IController { 13 | 14 | /* -------------------------------------------------------------------------- */ 15 | /* CONSTANT VARIABLES */ 16 | /* -------------------------------------------------------------------------- */ 17 | 18 | /// @notice supply(address,uint256,address,uint16) function signature 19 | bytes4 public constant SUPPLY = 0x617ba037; 20 | 21 | /// @notice withdraw(address,uint256,address) function signature 22 | bytes4 public constant WITHDRAW = 0x69328dec; 23 | 24 | /* -------------------------------------------------------------------------- */ 25 | /* PUBLIC FUNCTIONS */ 26 | /* -------------------------------------------------------------------------- */ 27 | 28 | /// @inheritdoc IController 29 | function canCall(address target, bool, bytes calldata data) 30 | external 31 | view 32 | returns (bool, address[] memory, address[] memory) 33 | { 34 | bytes4 sig = bytes4(data); 35 | if (sig == SUPPLY) { 36 | address asset = abi.decode( 37 | data[4:], 38 | (address) 39 | ); 40 | address[] memory tokensIn = new address[](1); 41 | address[] memory tokensOut = new address[](1); 42 | tokensIn[0] = IPoolV3(target).getReserveData(asset).aTokenAddress; 43 | tokensOut[0] = asset; 44 | return ( 45 | true, 46 | tokensIn, 47 | tokensOut 48 | ); 49 | } 50 | if (sig == WITHDRAW) { 51 | address asset = abi.decode( 52 | data[4:], 53 | (address) 54 | ); 55 | address[] memory tokensIn = new address[](1); 56 | address[] memory tokensOut = new address[](1); 57 | tokensIn[0] = asset; 58 | tokensOut[0] = IPoolV3(target).getReserveData(asset).aTokenAddress; 59 | return (true, tokensIn, tokensOut); 60 | } 61 | return (false, new address[](0), new address[](0)); 62 | } 63 | } -------------------------------------------------------------------------------- /src/aave/IPoolV3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface IPoolV3 { 5 | struct ReserveConfigurationMap { 6 | uint256 data; 7 | } 8 | 9 | struct ReserveData { 10 | ReserveConfigurationMap configuration; 11 | uint128 liquidityIndex; 12 | uint128 currentLiquidityRate; 13 | uint128 variableBorrowIndex; 14 | uint128 currentVariableBorrowRate; 15 | uint128 currentStableBorrowRate; 16 | uint40 lastUpdateTimestamp; 17 | uint16 id; 18 | address aTokenAddress; 19 | address stableDebtTokenAddress; 20 | address variableDebtTokenAddress; 21 | address interestRateStrategyAddress; 22 | uint128 accruedToTreasury; 23 | uint128 unbacked; 24 | uint128 isolationModeTotalDebt; 25 | } 26 | 27 | function getReserveData(address asset) 28 | external 29 | view 30 | returns (ReserveData memory); 31 | } -------------------------------------------------------------------------------- /src/aave/IProtocolDataProvider.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface IProtocolDataProvider { 5 | function getReserveTokensAddresses(address asset) 6 | external 7 | view 8 | returns ( 9 | address aTokenAddress, 10 | address stableDebtTokenAddress, 11 | address variableDebtTokenAddress 12 | ); 13 | } -------------------------------------------------------------------------------- /src/aura/IBooster.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface IBooster { 5 | function minter() external view returns (address); 6 | } 7 | -------------------------------------------------------------------------------- /src/aura/IRewards.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface IRewards { 5 | function operator() external view returns (address); 6 | function stake(address, uint256) external; 7 | function stakeFor(address, uint256) external; 8 | function withdraw(address, uint256) external; 9 | function exit(address) external; 10 | function getReward(address) external; 11 | function queueNewRewards(uint256) external; 12 | function notifyRewardAmount(uint256) external; 13 | function addExtraReward(address) external; 14 | function extraRewardsLength() external view returns (uint256); 15 | function stakingToken() external view returns (address); 16 | function rewardToken() external view returns (address); 17 | function earned(address account) external view returns (uint256); 18 | function extraRewards(uint256 index) external view returns (address); 19 | } 20 | -------------------------------------------------------------------------------- /src/aura/IStashToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface IStashToken { 5 | function baseToken() external view returns (address); 6 | } 7 | -------------------------------------------------------------------------------- /src/aura/RewardPoolController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | import {IERC4626} from "../erc4626/IERC4626.sol"; 6 | import {IRewards} from "./IRewards.sol"; 7 | import {IBooster} from "./IBooster.sol"; 8 | import {IStashToken} from "./IStashToken.sol"; 9 | 10 | /** 11 | * @title Aura reward pool controller 12 | */ 13 | contract RewardPoolController is IController { 14 | /* -------------------------------------------------------------------------- */ 15 | /* CONSTANT VARIABLES */ 16 | /* -------------------------------------------------------------------------- */ 17 | 18 | /// @notice deposit(uint256,address) 19 | bytes4 constant DEPOSIT = 0x6e553f65; 20 | 21 | /// @notice mint(uint256,address) 22 | bytes4 constant MINT = 0x94bf804d; 23 | 24 | /// @notice redeem(uint256,address,address) 25 | bytes4 constant REDEEM = 0xba087652; 26 | 27 | /// @notice withdraw(uint256,address,address) 28 | bytes4 constant WITHDRAW = 0xb460af94; 29 | 30 | /// @notice getReward() 31 | bytes4 constant GET_REWARD = 0x3d18b912; 32 | 33 | address public immutable AURA = 0x1509706a6c66CA549ff0cB464de88231DDBe213B; 34 | 35 | /* -------------------------------------------------------------------------- */ 36 | /* PUBLIC FUNCTIONS */ 37 | /* -------------------------------------------------------------------------- */ 38 | 39 | /// @inheritdoc IController 40 | function canCall(address target, bool, bytes calldata data) 41 | external 42 | view 43 | returns (bool, address[] memory, address[] memory) 44 | { 45 | bytes4 sig = bytes4(data); 46 | 47 | if (sig == DEPOSIT || sig == MINT) { 48 | return canCallDepositAndMint(target); 49 | } 50 | 51 | if (sig == REDEEM || sig == WITHDRAW) { 52 | return canCallWithdrawAndRedeem(target); 53 | } 54 | 55 | if (sig == GET_REWARD) { 56 | return canCallGetReward(target); 57 | } 58 | 59 | return (false, new address[](0), new address[](0)); 60 | } 61 | 62 | function canCallDepositAndMint(address target) internal view returns (bool, address[] memory, address[] memory) { 63 | address[] memory tokensIn = new address[](1); 64 | address[] memory tokensOut = new address[](1); 65 | tokensIn[0] = target; 66 | tokensOut[0] = IERC4626(target).asset(); 67 | return (true, tokensIn, tokensOut); 68 | } 69 | 70 | function canCallWithdrawAndRedeem(address target) 71 | internal 72 | view 73 | returns (bool, address[] memory, address[] memory) 74 | { 75 | address[] memory tokensIn = new address[](1); 76 | address[] memory tokensOut = new address[](1); 77 | tokensIn[0] = IERC4626(target).asset(); 78 | tokensOut[0] = target; 79 | return (true, tokensIn, tokensOut); 80 | } 81 | 82 | function canCallGetReward(address target) internal view returns (bool, address[] memory, address[] memory) { 83 | uint256 rewardLength = IRewards(target).extraRewardsLength(); 84 | address[] memory tokensIn = new address[](rewardLength + 2); 85 | for (uint256 i = 0; i < rewardLength; i++) { 86 | tokensIn[i] = IStashToken(IRewards(IRewards(target).extraRewards(i)).rewardToken()).baseToken(); 87 | } 88 | tokensIn[rewardLength] = IRewards(target).rewardToken(); 89 | tokensIn[rewardLength + 1] = AURA; 90 | return (true, tokensIn, new address[](0)); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/balancer/BalancerController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | import {IVault, IAsset} from "./IVault.sol"; 6 | 7 | /** 8 | @title Balancer V2 Controller 9 | @notice Balance v2 controller for join/exit/swap/batchSwap (multiHop) 10 | */ 11 | contract BalancerController is IController { 12 | 13 | /* -------------------------------------------------------------------------- */ 14 | /* CONSTANT VARIABLES */ 15 | /* -------------------------------------------------------------------------- */ 16 | 17 | /// @notice joinPool(bytes32,address,address,(address[],uint256[],bytes,bool)) 18 | bytes4 constant JOIN = 0xb95cac28; 19 | 20 | /// @notice exitPool(bytes32,address,address,(address[],uint256[],bytes,bool)) 21 | bytes4 constant EXIT = 0x8bdb3913; 22 | 23 | /// @notice swap((bytes32,uint8,address,address,uint256,bytes),(address,bool,address,bool),uint256,uint256) 24 | bytes4 constant SWAP = 0x52bbbe29; 25 | bytes4 constant BATCH_SWAP = 0x945bcec9; 26 | 27 | /* -------------------------------------------------------------------------- */ 28 | /* EXTERNAL FUNCTIONS */ 29 | /* -------------------------------------------------------------------------- */ 30 | 31 | /// @inheritdoc IController 32 | function canCall(address target, bool useEth, bytes calldata data) 33 | external 34 | view 35 | returns (bool, address[] memory, address[] memory) 36 | { 37 | bytes4 sig = bytes4(data); 38 | 39 | if (sig == JOIN) 40 | return canJoin(target, useEth, data[4:]); 41 | if (sig == EXIT) 42 | return canExit(target, useEth, data[4:]); 43 | if (sig == SWAP) 44 | return canSwap(target, useEth, data[4:]); 45 | if (sig == BATCH_SWAP) 46 | return canBatchSwap(target, useEth, data[4:]); 47 | return (false, new address[](0), new address[](0)); 48 | } 49 | 50 | /* -------------------------------------------------------------------------- */ 51 | /* INTERNAL FUNCTIONS */ 52 | /* -------------------------------------------------------------------------- */ 53 | 54 | function canJoin(address target, bool, bytes calldata data) 55 | internal 56 | view 57 | returns (bool, address[] memory, address[] memory) 58 | { 59 | ( 60 | bytes32 poolId, 61 | , 62 | , 63 | IVault.JoinPoolRequest memory request 64 | ) = abi.decode(data, ( 65 | bytes32, address, address, IVault.JoinPoolRequest 66 | ) 67 | ); 68 | address[] memory tokensIn = new address[](1); 69 | address[] memory tokensOut = new address[](request.assets.length); 70 | 71 | uint i; uint j; 72 | while(i < request.assets.length) { 73 | if ( 74 | request.maxAmountsIn[i] > 0 && 75 | address(request.assets[i]) != address(0) 76 | ) 77 | tokensOut[j++] = address(request.assets[i]); 78 | unchecked { ++i; } 79 | } 80 | assembly { mstore(tokensOut, j) } 81 | 82 | (tokensIn[0],) = IVault(target).getPool(poolId); 83 | 84 | return ( 85 | true, 86 | tokensIn, 87 | tokensOut 88 | ); 89 | } 90 | 91 | function canExit(address target, bool, bytes calldata data) 92 | internal 93 | view 94 | returns (bool, address[] memory, address[] memory) 95 | { 96 | ( 97 | bytes32 poolId, 98 | , 99 | , 100 | IVault.ExitPoolRequest memory request 101 | ) = abi.decode(data, ( 102 | bytes32, address, address, IVault.ExitPoolRequest 103 | ) 104 | ); 105 | address[] memory tokensOut = new address[](1); 106 | address[] memory tokensIn = new address[](request.assets.length); 107 | 108 | uint i; uint j; 109 | while(i < request.assets.length) { 110 | if (address(request.assets[i]) != address(0)) 111 | tokensIn[j++] = address(request.assets[i]); 112 | unchecked { ++i; } 113 | } 114 | assembly { mstore(tokensIn, j) } 115 | 116 | (tokensOut[0],) = IVault(target).getPool(poolId); 117 | 118 | return ( 119 | true, 120 | tokensIn, 121 | tokensOut 122 | ); 123 | } 124 | 125 | function canSwap(address, bool, bytes calldata data) 126 | internal 127 | pure 128 | returns (bool, address[] memory, address[] memory) 129 | { 130 | ( 131 | IVault.SingleSwap memory swap, 132 | , 133 | , 134 | ) = abi.decode(data, ( 135 | IVault.SingleSwap, IVault.FundManagement, uint256, uint256 136 | ) 137 | ); 138 | 139 | address[] memory tokensIn; 140 | address[] memory tokensOut; 141 | 142 | if (address(swap.assetIn) == address(0)) { 143 | tokensIn = new address[](1); 144 | tokensIn[0] = address(swap.assetOut); 145 | return ( 146 | true, 147 | tokensIn, 148 | new address[](0) 149 | ); 150 | } 151 | 152 | if (address(swap.assetOut) == address(0)) { 153 | tokensOut = new address[](1); 154 | tokensOut[0] = address(swap.assetIn); 155 | return ( 156 | true, 157 | new address[](0), 158 | tokensOut 159 | ); 160 | } 161 | 162 | tokensIn = new address[](1); 163 | tokensOut = new address[](1); 164 | tokensOut[0] = address(swap.assetIn); 165 | tokensIn[0] = address(swap.assetOut); 166 | 167 | return ( 168 | true, 169 | tokensIn, 170 | tokensOut 171 | ); 172 | } 173 | 174 | function canBatchSwap(address, bool, bytes calldata data) 175 | internal 176 | pure 177 | returns (bool, address[] memory, address[] memory) 178 | { 179 | ( 180 | IVault.SwapKind kind, 181 | IVault.BatchSwapStep[] memory swaps, 182 | IAsset[] memory assets, 183 | , 184 | , 185 | ) = abi.decode(data, ( 186 | IVault.SwapKind, 187 | IVault.BatchSwapStep[], 188 | IAsset[], 189 | IVault.FundManagement, 190 | uint256[], 191 | uint256 192 | ) 193 | ); 194 | 195 | uint tokenInIndex; 196 | uint tokenOutIndex; 197 | 198 | if (kind == IVault.SwapKind.GIVEN_IN) { 199 | if (!isMultiHopSwapGivenIn(swaps)) 200 | return (false, new address[](0), new address[](0)); 201 | tokenInIndex = swaps[swaps.length - 1].assetOutIndex; 202 | tokenOutIndex = swaps[0].assetInIndex; 203 | } else { 204 | if (!isMultiHopSwapGivenOut(swaps)) 205 | return (false, new address[](0), new address[](0)); 206 | tokenOutIndex = swaps[swaps.length - 1].assetInIndex; 207 | tokenInIndex = swaps[0].assetOutIndex; 208 | } 209 | 210 | address[] memory tokensIn; 211 | address[] memory tokensOut; 212 | 213 | if (address(assets[tokenOutIndex]) == address(0)) { 214 | tokensIn = new address[](1); 215 | tokensIn[0] = address(assets[tokenInIndex]); 216 | return ( 217 | true, 218 | tokensIn, 219 | new address[](0) 220 | ); 221 | } 222 | 223 | if (address(assets[tokenInIndex]) == address(0)) { 224 | tokensOut = new address[](1); 225 | tokensOut[0] = address(assets[tokenOutIndex]); 226 | return ( 227 | true, 228 | new address[](0), 229 | tokensOut 230 | ); 231 | } 232 | 233 | tokensIn = new address[](1); 234 | tokensOut = new address[](1); 235 | tokensOut[0] = address(assets[tokenOutIndex]); 236 | tokensIn[0] = address(assets[tokenInIndex]); 237 | 238 | return ( 239 | true, 240 | tokensIn, 241 | tokensOut 242 | ); 243 | } 244 | 245 | function isMultiHopSwapGivenIn(IVault.BatchSwapStep[] memory swaps) 246 | internal 247 | pure 248 | returns (bool) 249 | { 250 | uint steps = swaps.length; 251 | for (uint i; i < steps - 1; i++) { 252 | if ( 253 | swaps[i].assetOutIndex != swaps[i+1].assetInIndex || 254 | swaps[i+1].amount > 0 255 | ) 256 | return false; 257 | } 258 | return true; 259 | } 260 | 261 | function isMultiHopSwapGivenOut(IVault.BatchSwapStep[] memory swaps) 262 | internal 263 | pure 264 | returns (bool) 265 | { 266 | uint steps = swaps.length; 267 | for (uint i; i < steps - 1; i++) { 268 | if ( 269 | swaps[i].assetInIndex != swaps[i+1].assetOutIndex || 270 | swaps[i+1].amount > 0 271 | ) 272 | return false; 273 | } 274 | return true; 275 | } 276 | } -------------------------------------------------------------------------------- /src/balancer/BalancerLPStakingController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | 6 | interface IChildGauge { 7 | function lp_token() external view returns (address); 8 | function reward_tokens(uint256) external view returns (address); 9 | } 10 | 11 | /** 12 | @title Curve LP staking controller 13 | @notice Interaction controller for staking curve LP controllers 14 | */ 15 | contract BalancerLPStakingController is IController { 16 | 17 | /* -------------------------------------------------------------------------- */ 18 | /* STATE VARIABLES */ 19 | /* -------------------------------------------------------------------------- */ 20 | 21 | /// @notice deposit(uint256) 22 | bytes4 constant DEPOSIT = 0xb6b55f25; 23 | 24 | /// @notice deposit(uint256,address,bool) 25 | bytes4 constant DEPOSITCLAIM = 0x83df6747; 26 | 27 | /// @notice withdraw(uint256) 28 | bytes4 constant WITHDRAW = 0x2e1a7d4d; 29 | 30 | /// @notice withdraw(uint256,bool) 31 | bytes4 constant WITHDRAWCLAIM = 0x38d07436; 32 | 33 | /// @notice claim_rewards() 34 | bytes4 constant CLAIM = 0xe6f1daf2; 35 | 36 | /// @notice reward count 37 | uint256 constant rewardsCount = 8; 38 | 39 | /* -------------------------------------------------------------------------- */ 40 | /* EXTERNAL FUNCTIONS */ 41 | /* -------------------------------------------------------------------------- */ 42 | 43 | /// @inheritdoc IController 44 | function canCall(address target, bool, bytes calldata data) 45 | external 46 | view 47 | returns (bool, address[] memory, address[] memory) 48 | { 49 | bytes4 sig = bytes4(data); 50 | 51 | if (sig == DEPOSIT) return canDeposit(target); 52 | if (sig == DEPOSITCLAIM) return canDepositAndClaim(target, data); 53 | if (sig == WITHDRAW) return canWithdraw(target); 54 | if (sig == WITHDRAWCLAIM) return canWithdrawAndClaim(target, data); 55 | if (sig == CLAIM) return canClaim(target); 56 | 57 | return (false, new address[](0), new address[](0)); 58 | } 59 | 60 | function canDeposit(address target) 61 | internal 62 | view 63 | returns (bool, address[] memory, address[] memory) 64 | { 65 | address[] memory tokensIn = new address[](1); 66 | address[] memory tokensOut = new address[](1); 67 | tokensIn[0] = target; 68 | tokensOut[0] = IChildGauge(target).lp_token(); 69 | return (true, tokensIn, tokensOut); 70 | } 71 | 72 | function canDepositAndClaim(address target, bytes calldata data) 73 | internal 74 | view 75 | returns (bool, address[] memory, address[] memory) 76 | { 77 | (,,bool claim) = abi.decode( 78 | data[4:], (uint256, address, bool) 79 | ); 80 | 81 | if (!claim) return canDeposit(target); 82 | 83 | address[] memory tokensIn = new address[](rewardsCount + 1); 84 | address reward; uint i; 85 | for (; i 0) { 43 | for(uint i; i < rewardsLen; ++i) { 44 | tokensIn[i] = IRewardPool(rewardPool).rewards(i).reward_token; 45 | } 46 | } 47 | 48 | tokensIn[rewardsLen] = ICurveGauge(IRewardPool(rewardPool).curveGauge()).lp_token(); 49 | 50 | address[] memory tokensOut = new address[](1); 51 | tokensOut[0] = rewardPool; 52 | 53 | return (true, tokensIn, tokensOut); 54 | } 55 | 56 | function canGetReward(address rewardPool) 57 | internal 58 | view 59 | returns (bool, address[] memory, address[] memory) 60 | { 61 | uint len = IRewardPool(rewardPool).rewardLength(); 62 | address[] memory tokensIn = new address[](len); 63 | 64 | for(uint i; i < len; ++i) { 65 | tokensIn[i] = IRewardPool(rewardPool).rewards(i).reward_token; 66 | } 67 | 68 | return (true, tokensIn, new address[](0)); 69 | } 70 | } -------------------------------------------------------------------------------- /src/convex/IRewardPool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface IRewardPool { 5 | function curveGauge() external view returns (address); 6 | function rewardLength() external view returns (uint256); 7 | function rewards(uint index) external view returns (RewardType memory); 8 | 9 | struct RewardType { 10 | address reward_token; 11 | uint128 reward_integral; 12 | uint128 reward_remaining; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/core/BaseController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | 6 | /** 7 | @title Base Controller 8 | @notice Base controller with no interactions 9 | */ 10 | contract BaseController is IController { 11 | 12 | /// @inheritdoc IController 13 | function canCall(address, bool, bytes calldata) 14 | external 15 | pure 16 | returns (bool, address[] memory, address[] memory) 17 | { 18 | return (false, new address[](0), new address[](0)); 19 | } 20 | } -------------------------------------------------------------------------------- /src/core/ControllerFacade.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Ownable} from "../utils/Ownable.sol"; 5 | import {IController} from "./IController.sol"; 6 | import {IControllerFacade} from "./IControllerFacade.sol"; 7 | 8 | /** 9 | @title Controller Facade 10 | @notice This contract acts as a single interface for the client to determine 11 | if a given interactions is acceptable 12 | */ 13 | contract ControllerFacade is Ownable, IControllerFacade { 14 | 15 | /* -------------------------------------------------------------------------- */ 16 | /* STATE VARIABLES */ 17 | /* -------------------------------------------------------------------------- */ 18 | 19 | /// Mapping that returns if a given token is supported by the protocol 20 | mapping(address => bool) public isTokenAllowed; 21 | 22 | /// Mapping of external interaction with respective controller 23 | mapping(address => IController) public controllerFor; 24 | 25 | /* -------------------------------------------------------------------------- */ 26 | /* EVENTS */ 27 | /* -------------------------------------------------------------------------- */ 28 | 29 | event UpdateController(address indexed target, address indexed controller); 30 | 31 | /* -------------------------------------------------------------------------- */ 32 | /* CONSTRUCTOR */ 33 | /* -------------------------------------------------------------------------- */ 34 | 35 | /** 36 | @notice Contract Constructor 37 | */ 38 | constructor() Ownable(msg.sender) {} 39 | 40 | /* -------------------------------------------------------------------------- */ 41 | /* PUBLIC FUNCTIONS */ 42 | /* -------------------------------------------------------------------------- */ 43 | 44 | /// @inheritdoc IControllerFacade 45 | function canCall( 46 | address target, 47 | bool useEth, 48 | bytes calldata data 49 | ) 50 | external 51 | view 52 | returns (bool isValid, address[] memory tokensIn, address[] memory tokensOut) 53 | { 54 | (isValid, tokensIn, tokensOut) = controllerFor[target].canCall(target, useEth, data); 55 | if (isValid) isValid = validateTokensIn(tokensIn); 56 | } 57 | 58 | /* -------------------------------------------------------------------------- */ 59 | /* INTERNAL FUNCTIONS */ 60 | /* -------------------------------------------------------------------------- */ 61 | 62 | function validateTokensIn(address[] memory tokensIn) 63 | internal 64 | view 65 | returns (bool) 66 | { 67 | for (uint i; i < tokensIn.length; i++) 68 | if (!isTokenAllowed[tokensIn[i]]) return false; 69 | return true; 70 | } 71 | 72 | 73 | /* -------------------------------------------------------------------------- */ 74 | /* ADMIN FUNCTIONS */ 75 | /* -------------------------------------------------------------------------- */ 76 | 77 | function updateController(address target, IController controller) 78 | external 79 | adminOnly 80 | { 81 | controllerFor[target] = controller; 82 | emit UpdateController(target, address(controller)); 83 | } 84 | 85 | function toggleTokenAllowance(address token) external adminOnly { 86 | isTokenAllowed[token] = !isTokenAllowed[token]; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/core/IController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface IController { 5 | 6 | /** 7 | @notice General function that evaluates whether the target contract can 8 | be interacted with using the specified calldata 9 | @param target Address of external protocol/interaction 10 | @param useEth Specifies if Eth is being sent to the target 11 | @param data Calldata of the call made to target 12 | @return canCall Specifies if the interaction is accepted 13 | @return tokensIn List of tokens that the account will receive after the 14 | interactions 15 | @return tokensOut List of tokens that will be removed from the account 16 | after the interaction 17 | */ 18 | function canCall( 19 | address target, 20 | bool useEth, 21 | bytes calldata data 22 | ) external view returns (bool, address[] memory, address[] memory); 23 | } 24 | -------------------------------------------------------------------------------- /src/core/IControllerFacade.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "./IController.sol"; 5 | 6 | interface IControllerFacade { 7 | function isTokenAllowed(address token) external view returns (bool); 8 | function controllerFor(address target) external view returns (IController); 9 | 10 | /** 11 | @notice General function that evaluates whether the target contract can 12 | be interacted with using the specified calldata 13 | @param target Address of external protocol/interaction 14 | @param useEth Specifies if Eth is being sent to the target 15 | @param data Calldata of the call made to target 16 | @return canCall Specifies if the interaction is accepted 17 | @return tokensIn List of tokens that the account will receive after the 18 | interactions 19 | @return tokensOut List of tokens that will be removed from the account 20 | after the interaction 21 | */ 22 | function canCall( 23 | address target, 24 | bool useEth, 25 | bytes calldata data 26 | ) external view returns (bool, address[] memory, address[] memory); 27 | } 28 | -------------------------------------------------------------------------------- /src/curve/CurveCryptoSwapController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | import {IStableSwapPool} from "./IStableSwapPool.sol"; 6 | 7 | /** 8 | @title Curve Crypto Swap Controller 9 | @notice Controller for curve crypto swap interaction 10 | arbi:0x960ea3e3C7FB317332d990873d354E18d7645590 11 | */ 12 | contract CurveCryptoSwapController is IController { 13 | 14 | /* -------------------------------------------------------------------------- */ 15 | /* CONSTANT VARIABLES */ 16 | /* -------------------------------------------------------------------------- */ 17 | 18 | /// @notice exchange(uint256,uint256,uint256,uint256,bool) function signature 19 | bytes4 public constant EXCHANGE = 0x394747c5; 20 | 21 | /// @notice add_liquidity(uint256[3],uint256) function signature 22 | bytes4 public constant ADD_LIQUIDITY = 0x4515cef3; 23 | 24 | /// @notice remove_liquidity(uint256,uint256[3]) function signature 25 | bytes4 public constant REMOVE_LIQUIDITY = 0xecb586a5; 26 | 27 | /// @notice remove_liquidity_one_coin(uint256,uint256,uint256) function signature 28 | bytes4 public constant REMOVE_LIQUIDITY_ONE_COIN = 0xf1dc3cc9; 29 | 30 | /* -------------------------------------------------------------------------- */ 31 | /* PUBLIC FUNCTIONS */ 32 | /* -------------------------------------------------------------------------- */ 33 | 34 | /// @inheritdoc IController 35 | function canCall(address target, bool useEth, bytes calldata data) 36 | external 37 | view 38 | returns (bool, address[] memory, address[] memory) 39 | { 40 | bytes4 sig = bytes4(data); 41 | 42 | if (sig == ADD_LIQUIDITY) return canAddLiquidity(target, data); 43 | if (sig == REMOVE_LIQUIDITY_ONE_COIN) 44 | return canRemoveLiquidityOneCoin(target, data); 45 | if (sig == REMOVE_LIQUIDITY) return canRemoveLiquidity(target); 46 | if (sig == EXCHANGE) return canExchange(target, useEth, data); 47 | 48 | return (false, new address[](0), new address[](0)); 49 | } 50 | 51 | /* -------------------------------------------------------------------------- */ 52 | /* INTERNAL FUNCTIONS */ 53 | /* -------------------------------------------------------------------------- */ 54 | 55 | /** 56 | @notice Evaluates whether protocol can add liquidity to the target contract 57 | @param target External protocol address 58 | @param data calldata of the interaction with the target address 59 | @return canCall Specifies if the interaction is accepted 60 | @return tokensIn List of tokens that the account will receive after the 61 | interactions 62 | @return tokensOut List of tokens that will be removed from the account 63 | after the interaction 64 | */ 65 | function canAddLiquidity(address target, bytes calldata data) 66 | internal 67 | view 68 | returns (bool, address[] memory, address[] memory) 69 | { 70 | address[] memory tokensIn = new address[](1); 71 | tokensIn[0] = IStableSwapPool(target).token(); 72 | 73 | uint i; uint j; 74 | (uint[3] memory amounts) = abi.decode(data[4:], (uint[3])); 75 | address[] memory tokensOut = new address[](3); 76 | while(i < 3) { 77 | if(amounts[i] > 0) 78 | tokensOut[j++] = IStableSwapPool(target).coins(i); 79 | unchecked { ++i; } 80 | } 81 | assembly { mstore(tokensOut, j) } 82 | 83 | return (true, tokensIn, tokensOut); 84 | } 85 | 86 | /** 87 | @notice Evaluates whether protocol can remove liquidity from the target contract 88 | @param target External protocol address 89 | @param data calldata of the interaction with the target address 90 | @return canCall Specifies if the interaction is accepted 91 | @return tokensIn List of tokens that the account will receive after the 92 | interactions 93 | @return tokensOut List of tokens that will be removed from the account 94 | after the interaction 95 | */ 96 | function canRemoveLiquidityOneCoin(address target, bytes calldata data) 97 | internal 98 | view 99 | returns (bool, address[] memory, address[] memory) 100 | { 101 | (,uint256 i, uint256 min_amount) = abi.decode( 102 | data[4:], 103 | (uint256, uint256, uint256) 104 | ); 105 | 106 | if (min_amount == 0) 107 | return (false, new address[](0), new address[](0)); 108 | 109 | address[] memory tokensIn = new address[](1); 110 | address[] memory tokensOut = new address[](1); 111 | 112 | tokensIn[0] = IStableSwapPool(target).coins(i); 113 | tokensOut[0] = IStableSwapPool(target).token(); 114 | 115 | return (true, tokensIn, tokensOut); 116 | } 117 | 118 | /** 119 | @notice Evaluates whether protocol can remove liquidity from the target contract 120 | @param target External protocol address 121 | @return canCall Specifies if the interaction is accepted 122 | @return tokensIn List of tokens that the account will receive after the 123 | interactions 124 | @return tokensOut List of tokens that will be removed from the account 125 | after the interaction 126 | */ 127 | function canRemoveLiquidity(address target) 128 | internal 129 | view 130 | returns (bool, address[] memory, address[] memory) 131 | { 132 | address[] memory tokensOut = new address[](1); 133 | tokensOut[0] = IStableSwapPool(target).token(); 134 | 135 | address[] memory tokensIn = new address[](3); 136 | tokensIn[0] = IStableSwapPool(target).coins(0); 137 | tokensIn[1] = IStableSwapPool(target).coins(1); 138 | tokensIn[2] = IStableSwapPool(target).coins(2); 139 | 140 | return (true, tokensIn, tokensOut); 141 | } 142 | 143 | /** 144 | @notice Evaluates whether protocol can perform a swap using the target contract 145 | @param target External protocol address 146 | @param data calldata of the interaction with the target address 147 | @return canCall Specifies if the interaction is accepted 148 | @return tokensIn List of tokens that the account will receive after the 149 | interactions 150 | @return tokensOut List of tokens that will be removed from the account 151 | after the interaction 152 | */ 153 | function canExchange(address target, bool useEth, bytes calldata data) 154 | internal 155 | view 156 | returns (bool, address[] memory, address[] memory) 157 | { 158 | (uint256 i, uint256 j) = abi.decode( 159 | data[4:], 160 | (uint256, uint256) 161 | ); 162 | 163 | address[] memory tokensIn = new address[](1); 164 | tokensIn[0] = IStableSwapPool(target).coins(j); 165 | 166 | if (useEth) 167 | return ( 168 | true, 169 | tokensIn, 170 | new address[](0) 171 | ); 172 | 173 | address[] memory tokensOut = new address[](1); 174 | tokensOut[0] = IStableSwapPool(target).coins(i); 175 | 176 | return ( 177 | true, 178 | tokensIn, 179 | tokensOut 180 | ); 181 | } 182 | } -------------------------------------------------------------------------------- /src/curve/CurveLPStakingController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.15; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | 6 | interface IChildGauge { 7 | function lp_token() external view returns (address); 8 | function reward_count() external view returns (uint256); 9 | function reward_tokens(uint256) external view returns (address); 10 | } 11 | 12 | /** 13 | @title Curve LP staking controller 14 | @notice Interaction controller for staking curve LP controllers 15 | */ 16 | contract CurveLPStakingController is IController { 17 | 18 | /* -------------------------------------------------------------------------- */ 19 | /* STATE VARIABLES */ 20 | /* -------------------------------------------------------------------------- */ 21 | 22 | /// @notice deposit(uint256) 23 | bytes4 constant DEPOSIT = 0xb6b55f25; 24 | 25 | /// @notice deposit(uint256,address,bool) 26 | bytes4 constant DEPOSITCLAIM = 0x83df6747; 27 | 28 | /// @notice withdraw(uint256) 29 | bytes4 constant WITHDRAW = 0x2e1a7d4d; 30 | 31 | /// @notice withdraw(uint256,address,bool) 32 | bytes4 constant WITHDRAWCLAIM = 0x00ebf5dd; 33 | 34 | /// @notice claim_rewards() 35 | bytes4 constant CLAIM = 0xe6f1daf2; 36 | 37 | /* -------------------------------------------------------------------------- */ 38 | /* EXTERNAL FUNCTIONS */ 39 | /* -------------------------------------------------------------------------- */ 40 | 41 | /// @inheritdoc IController 42 | function canCall(address target, bool, bytes calldata data) 43 | external 44 | view 45 | returns (bool, address[] memory, address[] memory) 46 | { 47 | bytes4 sig = bytes4(data); 48 | 49 | if (sig == DEPOSIT) return canDeposit(target); 50 | if (sig == DEPOSITCLAIM) return canDepositAndClaim(target, data); 51 | if (sig == WITHDRAW) return canWithdraw(target); 52 | if (sig == WITHDRAWCLAIM) return canWithdrawAndClaim(target, data); 53 | if (sig == CLAIM) return canClaim(target); 54 | 55 | return (false, new address[](0), new address[](0)); 56 | } 57 | 58 | function canDeposit(address target) 59 | internal 60 | view 61 | returns (bool, address[] memory, address[] memory) 62 | { 63 | address[] memory tokensIn = new address[](1); 64 | address[] memory tokensOut = new address[](1); 65 | tokensIn[0] = target; 66 | tokensOut[0] = IChildGauge(target).lp_token(); 67 | return (true, tokensIn, tokensOut); 68 | } 69 | 70 | function canDepositAndClaim(address target, bytes calldata data) 71 | internal 72 | view 73 | returns (bool, address[] memory, address[] memory) 74 | { 75 | (,,bool claim) = abi.decode( 76 | data[4:], (uint256, address, bool) 77 | ); 78 | if (!claim) return canDeposit(target); 79 | 80 | uint count = IChildGauge(target).reward_count(); 81 | 82 | address[] memory tokensIn = new address[](count + 1); 83 | 84 | for (uint i; i 0) 76 | tokensOut[j++] = IStableSwapPool(target).coins(i); 77 | unchecked { ++i; } 78 | } 79 | assembly { mstore(tokensOut, j) } 80 | 81 | return (true, tokensIn, tokensOut); 82 | } 83 | 84 | /** 85 | @notice Evaluates whether protocol can remove liquidity from the target contract 86 | @param target External protocol address 87 | @param data calldata of the interaction with the target address 88 | @return canCall Specifies if the interaction is accepted 89 | @return tokensIn List of tokens that the account will receive after the 90 | interactions 91 | @return tokensOut List of tokens that will be removed from the account 92 | after the interaction 93 | */ 94 | function canRemoveLiquidityOneCoin(address target, bytes calldata data) 95 | internal 96 | view 97 | returns (bool, address[] memory, address[] memory) 98 | { 99 | (,uint256 i, uint256 min_amount) = abi.decode( 100 | data[4:], 101 | (uint256, uint256, uint256) 102 | ); 103 | 104 | if (min_amount == 0) 105 | return (false, new address[](0), new address[](0)); 106 | 107 | address[] memory tokensOut = new address[](1); 108 | tokensOut[0] = IStableSwapPool(target).token(); 109 | 110 | // if eth is being removed 111 | if (i == 2) return (true, new address[](0), tokensOut); 112 | 113 | address[] memory tokensIn = new address[](1); 114 | tokensIn[0] = IStableSwapPool(target).coins(i); 115 | 116 | return (true, tokensIn, tokensOut); 117 | } 118 | 119 | /** 120 | @notice Evaluates whether protocol can remove liquidity from the target contract 121 | @param target External protocol address 122 | @return canCall Specifies if the interaction is accepted 123 | @return tokensIn List of tokens that the account will receive after the 124 | interactions 125 | @return tokensOut List of tokens that will be removed from the account 126 | after the interaction 127 | */ 128 | function canRemoveLiquidity(address target) 129 | internal 130 | view 131 | returns (bool, address[] memory, address[] memory) 132 | { 133 | address[] memory tokensOut = new address[](1); 134 | tokensOut[0] = IStableSwapPool(target).token(); 135 | 136 | address[] memory tokensIn = new address[](3); 137 | tokensIn[0] = IStableSwapPool(target).coins(0); 138 | tokensIn[1] = IStableSwapPool(target).coins(1); 139 | tokensIn[2] = IStableSwapPool(target).coins(2); 140 | 141 | return (true, tokensIn, tokensOut); 142 | } 143 | } -------------------------------------------------------------------------------- /src/curve/IStableSwapPool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface IStableSwapPool { 5 | function coins(uint256 i) external view returns (address); 6 | function token() external view returns (address); 7 | function lp_token() external view returns (address); 8 | } -------------------------------------------------------------------------------- /src/curve/StableSwap2PoolController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | import {IStableSwapPool} from "./IStableSwapPool.sol"; 6 | 7 | /** 8 | @title Curve stable Swap Controller 9 | @notice Controller for curve stable swap 2 pool interaction 10 | arbi:0x7f90122BF0700F9E7e1F688fe926940E8839F353 11 | */ 12 | contract StableSwap2PoolController is IController { 13 | 14 | /* -------------------------------------------------------------------------- */ 15 | /* CONSTANT VARIABLES */ 16 | /* -------------------------------------------------------------------------- */ 17 | 18 | /// @notice exchange(int128,int128,uint256,uint256) function signature 19 | bytes4 public constant EXCHANGE = 0x3df02124; 20 | 21 | /// @notice add_liquidity(uint256[2],uint256) function signature 22 | bytes4 public constant ADD_LIQUIDITY = 0x0b4c7e4d; 23 | 24 | /// @notice remove_liquidity(uint256,uint256[2]) function signature 25 | bytes4 public constant REMOVE_LIQUIDITY = 0x5b36389c; 26 | 27 | /// @notice remove_liquidity_one_coin(uint256,int128,uint256) function signature 28 | bytes4 public constant REMOVE_LIQUIDITY_ONE_COIN = 0x1a4d01d2; 29 | 30 | /* -------------------------------------------------------------------------- */ 31 | /* PUBLIC FUNCTIONS */ 32 | /* -------------------------------------------------------------------------- */ 33 | 34 | /// @inheritdoc IController 35 | function canCall(address target, bool, bytes calldata data) 36 | external 37 | view 38 | returns (bool, address[] memory, address[] memory) 39 | { 40 | bytes4 sig = bytes4(data); 41 | 42 | if (sig == ADD_LIQUIDITY) return canAddLiquidity(target, data); 43 | if (sig == REMOVE_LIQUIDITY_ONE_COIN) 44 | return canRemoveLiquidityOneCoin(target, data); 45 | if (sig == REMOVE_LIQUIDITY) return canRemoveLiquidity(target); 46 | if (sig == EXCHANGE) return canExchange(target, data); 47 | 48 | return (false, new address[](0), new address[](0)); 49 | } 50 | 51 | /* -------------------------------------------------------------------------- */ 52 | /* INTERNAL FUNCTIONS */ 53 | /* -------------------------------------------------------------------------- */ 54 | 55 | /** 56 | @notice Evaluates whether protocol can add liquidity to the target contract 57 | @param target External protocol address 58 | @param data calldata of the interaction with the target address 59 | @return canCall Specifies if the interaction is accepted 60 | @return tokensIn List of tokens that the account will receive after the 61 | interactions 62 | @return tokensOut List of tokens that will be removed from the account 63 | after the interaction 64 | */ 65 | function canAddLiquidity(address target, bytes calldata data) 66 | internal 67 | view 68 | returns (bool, address[] memory, address[] memory) 69 | { 70 | address[] memory tokensIn = new address[](1); 71 | tokensIn[0] = target; 72 | 73 | uint i; uint j; 74 | (uint[2] memory amounts) = abi.decode(data[4:], (uint[2])); 75 | address[] memory tokensOut = new address[](2); 76 | while(i < 2) { 77 | if(amounts[i] > 0) 78 | tokensOut[j++] = IStableSwapPool(target).coins(i); 79 | unchecked { ++i; } 80 | } 81 | assembly { mstore(tokensOut, j) } 82 | 83 | return (true, tokensIn, tokensOut); 84 | } 85 | 86 | 87 | /** 88 | @notice Evaluates whether protocol can remove liquidity from the target contract 89 | @param target External protocol address 90 | @param data calldata of the interaction with the target address 91 | @return canCall Specifies if the interaction is accepted 92 | @return tokensIn List of tokens that the account will receive after the 93 | interactions 94 | @return tokensOut List of tokens that will be removed from the account 95 | after the interaction 96 | */ 97 | function canRemoveLiquidityOneCoin(address target, bytes calldata data) 98 | internal 99 | view 100 | returns (bool, address[] memory, address[] memory) 101 | { 102 | (,int128 i, uint256 min_amount) = abi.decode( 103 | data[4:], 104 | (uint256, int128, uint256) 105 | ); 106 | 107 | if (min_amount == 0) 108 | return (false, new address[](0), new address[](0)); 109 | 110 | address[] memory tokensIn = new address[](1); 111 | address[] memory tokensOut = new address[](1); 112 | 113 | tokensIn[0] = IStableSwapPool(target).coins(uint128(i)); 114 | tokensOut[0] = target; 115 | 116 | return (true, tokensIn, tokensOut); 117 | } 118 | 119 | /** 120 | @notice Evaluates whether protocol can remove liquidity from the target contract 121 | @param target External protocol address 122 | @return canCall Specifies if the interaction is accepted 123 | @return tokensIn List of tokens that the account will receive after the 124 | interactions 125 | @return tokensOut List of tokens that will be removed from the account 126 | after the interaction 127 | */ 128 | function canRemoveLiquidity(address target) 129 | internal 130 | view 131 | returns (bool, address[] memory, address[] memory) 132 | { 133 | address[] memory tokensOut = new address[](1); 134 | tokensOut[0] = target; 135 | 136 | address[] memory tokensIn = new address[](2); 137 | tokensIn[0] = IStableSwapPool(target).coins(0); 138 | tokensIn[1] = IStableSwapPool(target).coins(1); 139 | 140 | return (true, tokensIn, tokensOut); 141 | } 142 | 143 | /** 144 | @notice Evaluates whether protocol can perform a swap using the target contract 145 | @param target External protocol address 146 | @param data calldata of the interaction with the target address 147 | @return canCall Specifies if the interaction is accepted 148 | @return tokensIn List of tokens that the account will receive after the 149 | interactions 150 | @return tokensOut List of tokens that will be removed from the account 151 | after the interaction 152 | */ 153 | function canExchange(address target, bytes calldata data) 154 | internal 155 | view 156 | returns (bool, address[] memory, address[] memory) 157 | { 158 | (int128 i, int128 j,,) = abi.decode( 159 | data[4:], 160 | (int128, int128, uint256, uint256) 161 | ); 162 | 163 | address[] memory tokensIn = new address[](1); 164 | address[] memory tokensOut = new address[](1); 165 | tokensIn[0] = IStableSwapPool(target).coins(uint128(j)); 166 | tokensOut[0] = IStableSwapPool(target).coins(uint128(i)); 167 | 168 | return ( 169 | true, 170 | tokensIn, 171 | tokensOut 172 | ); 173 | } 174 | } -------------------------------------------------------------------------------- /src/curve/StableSwap2PoolEthController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | import {IStableSwapPool} from "./IStableSwapPool.sol"; 6 | 7 | /** 8 | @title Curve stable Swap Controller 9 | @notice Controller for curve stable swap 10 | arbi:0x6eB2dc694eB516B16Dc9FBc678C60052BbdD7d80 11 | */ 12 | contract StableSwap2PoolEthController is IController { 13 | 14 | /* -------------------------------------------------------------------------- */ 15 | /* CONSTANT VARIABLES */ 16 | /* -------------------------------------------------------------------------- */ 17 | 18 | address immutable ETH; 19 | 20 | /// @notice exchange(int128,int128,uint256,uint256) function signature 21 | bytes4 public constant EXCHANGE = 0x3df02124; 22 | 23 | /// @notice add_liquidity(uint256[2],uint256) function signature 24 | bytes4 public constant ADD_LIQUIDITY = 0x0b4c7e4d; 25 | 26 | /// @notice remove_liquidity(uint256,uint256[2]) function signature 27 | bytes4 public constant REMOVE_LIQUIDITY = 0x5b36389c; 28 | 29 | /// @notice remove_liquidity_one_coin(uint256,int128,uint256) function signature 30 | bytes4 public constant REMOVE_LIQUIDITY_ONE_COIN = 0x1a4d01d2; 31 | 32 | constructor(address _ETH) { 33 | ETH = _ETH; 34 | } 35 | 36 | /* -------------------------------------------------------------------------- */ 37 | /* PUBLIC FUNCTIONS */ 38 | /* -------------------------------------------------------------------------- */ 39 | 40 | /// @inheritdoc IController 41 | function canCall(address target, bool useEth, bytes calldata data) 42 | external 43 | view 44 | returns (bool, address[] memory, address[] memory) 45 | { 46 | bytes4 sig = bytes4(data); 47 | 48 | if (sig == ADD_LIQUIDITY) return canAddLiquidity(target, data); 49 | if (sig == REMOVE_LIQUIDITY_ONE_COIN) 50 | return canRemoveLiquidityOneCoin(target, data); 51 | if (sig == REMOVE_LIQUIDITY) return canRemoveLiquidity(target); 52 | if (sig == EXCHANGE) return canExchange(target, useEth, data); 53 | 54 | return (false, new address[](0), new address[](0)); 55 | } 56 | 57 | /* -------------------------------------------------------------------------- */ 58 | /* INTERNAL FUNCTIONS */ 59 | /* -------------------------------------------------------------------------- */ 60 | 61 | function canAddLiquidity(address target, bytes calldata data) 62 | internal 63 | view 64 | returns (bool, address[] memory tokensIn, address[] memory tokensOut) 65 | { 66 | (uint[2] memory amounts) = abi.decode(data[4:], (uint[2])); 67 | 68 | tokensIn = new address[](1); 69 | tokensIn[0] = IStableSwapPool(target).lp_token(); 70 | 71 | address coin; 72 | for(uint i; i<2; i++) { 73 | if (amounts[i] > 0) { 74 | coin = IStableSwapPool(target).coins(i); 75 | if (coin != ETH) { 76 | tokensOut = new address[](1); 77 | tokensOut[0] = coin; 78 | return (true, tokensIn, tokensOut); 79 | } 80 | } 81 | } 82 | 83 | return (true, tokensIn, tokensOut); 84 | } 85 | 86 | function canRemoveLiquidityOneCoin(address target, bytes calldata data) 87 | internal 88 | view 89 | returns (bool, address[] memory, address[] memory tokensOut) 90 | { 91 | (,int128 i, uint256 min_amount) = abi.decode( 92 | data[4:], 93 | (uint256, int128, uint256) 94 | ); 95 | 96 | if (min_amount == 0) 97 | return (false, new address[](0), new address[](0)); 98 | 99 | tokensOut = new address[](1); 100 | tokensOut[0] = IStableSwapPool(target).lp_token(); 101 | 102 | address coin = IStableSwapPool(target).coins(uint128(i)); 103 | if (ETH != coin) { 104 | address[] memory tokensIn = new address[](1); 105 | tokensIn[0] = coin; 106 | return (true, tokensIn, tokensOut); 107 | } 108 | 109 | return (true, new address[](0), tokensOut); 110 | } 111 | 112 | function canRemoveLiquidity(address target) 113 | internal 114 | view 115 | returns (bool, address[] memory tokensIn, address[] memory tokensOut) 116 | { 117 | tokensIn = new address[](1); 118 | tokensOut = new address[](1); 119 | 120 | tokensOut[0] = IStableSwapPool(target).lp_token(); 121 | 122 | address coin; 123 | for(uint i; i<2; i++) { 124 | coin = IStableSwapPool(target).coins(i); 125 | if (coin != ETH) { 126 | tokensIn[0] = coin; 127 | return (true, tokensIn, tokensOut); 128 | } 129 | } 130 | 131 | return (false, tokensIn, tokensOut); 132 | } 133 | 134 | function canExchange(address target, bool useEth, bytes calldata data) 135 | internal 136 | view 137 | returns (bool, address[] memory tokensIn, address[] memory tokensOut) 138 | { 139 | (int128 i, int128 j,,) = abi.decode( 140 | data[4:], 141 | (int128, int128, uint256, uint256) 142 | ); 143 | 144 | if (useEth) { 145 | tokensIn = new address[](1); 146 | tokensIn[0] = IStableSwapPool(target).coins(uint128(j)); 147 | return (true, tokensIn, new address[](0)); 148 | } 149 | 150 | address coinIn = IStableSwapPool(target).coins(uint128(j)); 151 | if (coinIn == ETH) { 152 | tokensOut = new address[](1); 153 | tokensOut[0] = IStableSwapPool(target).coins(uint128(i)); 154 | return (true, new address[](0), tokensOut); 155 | } 156 | 157 | tokensIn = new address[](1); 158 | tokensOut = new address[](1); 159 | tokensIn[0] = IStableSwapPool(target).coins(uint128(j)); 160 | tokensOut[0] = IStableSwapPool(target).coins(uint128(i)); 161 | 162 | return ( 163 | true, 164 | tokensIn, 165 | tokensOut 166 | ); 167 | } 168 | } -------------------------------------------------------------------------------- /src/curve/StableSwap3PoolController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | import {IStableSwapPool} from "./IStableSwapPool.sol"; 6 | 7 | /** 8 | @title Curve stable Swap Controller 9 | @notice Controller for curve stable swap 3 pool interaction 10 | arbi:0x30dF229cefa463e991e29D42DB0bae2e122B2AC7 11 | */ 12 | contract StableSwap3PoolController is IController { 13 | 14 | /* -------------------------------------------------------------------------- */ 15 | /* CONSTANT VARIABLES */ 16 | /* -------------------------------------------------------------------------- */ 17 | 18 | /// @notice exchange(int128,int128,uint256,uint256) function signature 19 | bytes4 public constant EXCHANGE = 0x3df02124; 20 | 21 | /// @notice add_liquidity(uint256[3],uint256) function signature 22 | bytes4 public constant ADD_LIQUIDITY = 0x4515cef3; 23 | 24 | /// @notice remove_liquidity(uint256,uint256[3]) function signature 25 | bytes4 public constant REMOVE_LIQUIDITY = 0xecb586a5; 26 | 27 | /// @notice remove_liquidity_one_coin(uint256,int128,uint256) function signature 28 | bytes4 public constant REMOVE_LIQUIDITY_ONE_COIN = 0x1a4d01d2; 29 | 30 | /* -------------------------------------------------------------------------- */ 31 | /* PUBLIC FUNCTIONS */ 32 | /* -------------------------------------------------------------------------- */ 33 | 34 | /// @inheritdoc IController 35 | function canCall(address target, bool, bytes calldata data) 36 | external 37 | view 38 | returns (bool, address[] memory, address[] memory) 39 | { 40 | bytes4 sig = bytes4(data); 41 | 42 | if (sig == ADD_LIQUIDITY) return canAddLiquidity(target, data); 43 | if (sig == REMOVE_LIQUIDITY_ONE_COIN) 44 | return canRemoveLiquidityOneCoin(target, data); 45 | if (sig == REMOVE_LIQUIDITY) return canRemoveLiquidity(target); 46 | if (sig == EXCHANGE) return canExchange(target, data); 47 | 48 | return (false, new address[](0), new address[](0)); 49 | } 50 | 51 | /* -------------------------------------------------------------------------- */ 52 | /* INTERNAL FUNCTIONS */ 53 | /* -------------------------------------------------------------------------- */ 54 | 55 | /** 56 | @notice Evaluates whether protocol can add liquidity to the target contract 57 | @param target External protocol address 58 | @param data calldata of the interaction with the target address 59 | @return canCall Specifies if the interaction is accepted 60 | @return tokensIn List of tokens that the account will receive after the 61 | interactions 62 | @return tokensOut List of tokens that will be removed from the account 63 | after the interaction 64 | */ 65 | function canAddLiquidity(address target, bytes calldata data) 66 | internal 67 | view 68 | returns (bool, address[] memory, address[] memory) 69 | { 70 | address[] memory tokensIn = new address[](1); 71 | tokensIn[0] = target; 72 | 73 | uint i; uint j; 74 | (uint[3] memory amounts) = abi.decode(data[4:], (uint[3])); 75 | address[] memory tokensOut = new address[](3); 76 | while(i < 3) { 77 | if(amounts[i] > 0) 78 | tokensOut[j++] = IStableSwapPool(target).coins(i); 79 | unchecked { ++i; } 80 | } 81 | assembly { mstore(tokensOut, j) } 82 | 83 | return (true, tokensIn, tokensOut); 84 | } 85 | 86 | 87 | /** 88 | @notice Evaluates whether protocol can remove liquidity from the target contract 89 | @param target External protocol address 90 | @param data calldata of the interaction with the target address 91 | @return canCall Specifies if the interaction is accepted 92 | @return tokensIn List of tokens that the account will receive after the 93 | interactions 94 | @return tokensOut List of tokens that will be removed from the account 95 | after the interaction 96 | */ 97 | function canRemoveLiquidityOneCoin(address target, bytes calldata data) 98 | internal 99 | view 100 | returns (bool, address[] memory, address[] memory) 101 | { 102 | (,int128 i, uint256 min_amount) = abi.decode( 103 | data[4:], 104 | (uint256, int128, uint256) 105 | ); 106 | 107 | if (min_amount == 0) 108 | return (false, new address[](0), new address[](0)); 109 | 110 | address[] memory tokensIn = new address[](1); 111 | address[] memory tokensOut = new address[](1); 112 | 113 | tokensIn[0] = IStableSwapPool(target).coins(uint128(i)); 114 | tokensOut[0] = target; 115 | 116 | return (true, tokensIn, tokensOut); 117 | } 118 | 119 | /** 120 | @notice Evaluates whether protocol can remove liquidity from the target contract 121 | @param target External protocol address 122 | @return canCall Specifies if the interaction is accepted 123 | @return tokensIn List of tokens that the account will receive after the 124 | interactions 125 | @return tokensOut List of tokens that will be removed from the account 126 | after the interaction 127 | */ 128 | function canRemoveLiquidity(address target) 129 | internal 130 | view 131 | returns (bool, address[] memory, address[] memory) 132 | { 133 | address[] memory tokensOut = new address[](1); 134 | tokensOut[0] = target; 135 | 136 | address[] memory tokensIn = new address[](3); 137 | tokensIn[0] = IStableSwapPool(target).coins(0); 138 | tokensIn[1] = IStableSwapPool(target).coins(1); 139 | tokensIn[2] = IStableSwapPool(target).coins(2); 140 | 141 | return (true, tokensIn, tokensOut); 142 | } 143 | 144 | /** 145 | @notice Evaluates whether protocol can perform a swap using the target contract 146 | @param target External protocol address 147 | @param data calldata of the interaction with the target address 148 | @return canCall Specifies if the interaction is accepted 149 | @return tokensIn List of tokens that the account will receive after the 150 | interactions 151 | @return tokensOut List of tokens that will be removed from the account 152 | after the interaction 153 | */ 154 | function canExchange(address target, bytes calldata data) 155 | internal 156 | view 157 | returns (bool, address[] memory, address[] memory) 158 | { 159 | (int128 i, int128 j,,) = abi.decode( 160 | data[4:], 161 | (int128, int128, uint256, uint256) 162 | ); 163 | 164 | address[] memory tokensIn = new address[](1); 165 | address[] memory tokensOut = new address[](1); 166 | tokensIn[0] = IStableSwapPool(target).coins(uint128(j)); 167 | tokensOut[0] = IStableSwapPool(target).coins(uint128(i)); 168 | 169 | return ( 170 | true, 171 | tokensIn, 172 | tokensOut 173 | ); 174 | } 175 | } -------------------------------------------------------------------------------- /src/erc4626/ERC4626Controller.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | import {IERC4626} from "./IERC4626.sol"; 6 | 7 | /** 8 | @title ERC4626 vault controller 9 | */ 10 | contract ERC4626Controller is IController { 11 | 12 | /* -------------------------------------------------------------------------- */ 13 | /* CONSTANT VARIABLES */ 14 | /* -------------------------------------------------------------------------- */ 15 | 16 | /// @notice deposit(uint256,address) 17 | bytes4 constant DEPOSIT = 0x6e553f65; 18 | 19 | /// @notice mint(uint256,address) 20 | bytes4 constant MINT = 0x94bf804d; 21 | 22 | /// @notice redeem(uint256,address,address) 23 | bytes4 constant REDEEM = 0xba087652; 24 | 25 | /// @notice withdraw(uint256,address,address) 26 | bytes4 constant WITHDRAW = 0xb460af94; 27 | 28 | /* -------------------------------------------------------------------------- */ 29 | /* PUBLIC FUNCTIONS */ 30 | /* -------------------------------------------------------------------------- */ 31 | 32 | /// @inheritdoc IController 33 | function canCall(address target, bool, bytes calldata data) 34 | external 35 | view 36 | returns (bool, address[] memory, address[] memory) 37 | { 38 | bytes4 sig = bytes4(data); 39 | 40 | if (sig == DEPOSIT || sig == MINT) { 41 | address[] memory tokensIn = new address[](1); 42 | address[] memory tokensOut = new address[](1); 43 | tokensIn[0] = target; 44 | tokensOut[0] = IERC4626(target).asset(); 45 | return (true, tokensIn, tokensOut); 46 | } 47 | 48 | if (sig == REDEEM || sig == WITHDRAW) { 49 | address[] memory tokensIn = new address[](1); 50 | address[] memory tokensOut = new address[](1); 51 | tokensIn[0] = IERC4626(target).asset(); 52 | tokensOut[0] = target; 53 | return (true, tokensIn, tokensOut); 54 | } 55 | 56 | return (false, new address[](0), new address[](0)); 57 | } 58 | } -------------------------------------------------------------------------------- /src/erc4626/IERC4626.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface IERC4626 { 5 | function previewRedeem(uint256 shares) external view returns (uint256 assets); 6 | function asset() external view returns (address asset); 7 | } -------------------------------------------------------------------------------- /src/gmx/RewardRouterController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | 6 | /** 7 | * @title Reward Router Controller for claiming and compounding rewards 8 | * @dev arbi:0xA906F338CB21815cBc4Bc87ace9e68c87eF8d8F1 9 | */ 10 | contract RewardRouterController is IController { 11 | /* -------------------------------------------------------------------------- */ 12 | /* STORAGE VARIABLES */ 13 | /* -------------------------------------------------------------------------- */ 14 | 15 | /// @notice compound() 16 | bytes4 constant compound = 0xf69e2046; 17 | 18 | /// @notice claimFees() 19 | bytes4 constant claimFees = 0xd294f093; 20 | 21 | /// @notice WETH 22 | address[] WETH; 23 | 24 | /* -------------------------------------------------------------------------- */ 25 | /* CONSTRUCTOR */ 26 | /* -------------------------------------------------------------------------- */ 27 | 28 | constructor(address _WETH) { 29 | WETH.push(_WETH); 30 | } 31 | 32 | /* -------------------------------------------------------------------------- */ 33 | /* EXTERNAL FUNCTIONS */ 34 | /* -------------------------------------------------------------------------- */ 35 | 36 | function canCall(address, bool, bytes calldata data) 37 | external 38 | view 39 | returns (bool, address[] memory, address[] memory) 40 | { 41 | bytes4 sig = bytes4(data); 42 | 43 | if (sig == compound) return canCallCompound(); 44 | if (sig == claimFees) return canCallClaimFees(); 45 | 46 | return (false, new address[](0), new address[](0)); 47 | } 48 | 49 | /* -------------------------------------------------------------------------- */ 50 | /* INTERNAL FUNCTIONS */ 51 | /* -------------------------------------------------------------------------- */ 52 | 53 | function canCallClaimFees() internal view returns (bool, address[] memory, address[] memory) { 54 | return (true, WETH, new address[](0)); 55 | } 56 | 57 | function canCallCompound() internal pure returns (bool, address[] memory, address[] memory) { 58 | return (true, new address[](0), new address[](0)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/gmx/RewardRouterV2Controller.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | 6 | /** 7 | @title Reward Router V2 Controller for minting and redeeming GLP 8 | @dev arbi:0xB95DB5B167D75e6d04227CfFFA61069348d271F5 9 | */ 10 | contract RewardRouterV2Controller is IController { 11 | 12 | /* -------------------------------------------------------------------------- */ 13 | /* STORAGE VARIABLES */ 14 | /* -------------------------------------------------------------------------- */ 15 | 16 | /// @notice mintAndStakeGlpETH(uint256 _minUsdg,uint256 _minGlp) 17 | bytes4 constant mintAndStakeGlpETH = 0x53a8aa03; 18 | 19 | /// @notice mintAndStakeGlp(address _token,uint256 _amount,uint256 _minUsdg,uint256 _minGlp) 20 | bytes4 constant mintAndStakeGlp = 0x364e2311; 21 | 22 | /// @notice unstakeAndRedeemGlp(address _tokenOut,uint256 _glpAmount,uint256 _minOut,address _receiver) 23 | bytes4 constant unstakeAndRedeemGlp = 0x0f3aa554; 24 | 25 | /// @notice unstakeAndRedeemGlpETH(uint256,uint256,address) 26 | bytes4 constant unstakeAndRedeemGlpETH = 0xabb5e5e2; 27 | 28 | /// @notice Staked GLP: 0x5402B5F40310bDED796c7D0F3FF6683f5C0cFfdf 29 | address[] sGLP; 30 | 31 | /* -------------------------------------------------------------------------- */ 32 | /* CONSTRUCTOR */ 33 | /* -------------------------------------------------------------------------- */ 34 | 35 | constructor(address _SGLP) { 36 | sGLP.push(_SGLP); 37 | } 38 | 39 | /* -------------------------------------------------------------------------- */ 40 | /* EXTERNAL FUNCTIONS */ 41 | /* -------------------------------------------------------------------------- */ 42 | 43 | function canCall(address, bool useEth, bytes calldata data) 44 | external 45 | view 46 | returns (bool, address[] memory, address[] memory) 47 | { 48 | bytes4 sig = bytes4(data); 49 | 50 | if (sig == mintAndStakeGlp) return canCallMint(data[4:]); 51 | if (sig == mintAndStakeGlpETH) return canCallMintEth(useEth); 52 | if (sig == unstakeAndRedeemGlp) return canCallRedeem(data[4:]); 53 | if (sig == unstakeAndRedeemGlpETH) return canCallRedeemEth(); 54 | 55 | return (false, new address[](0), new address[](0)); 56 | } 57 | 58 | /* -------------------------------------------------------------------------- */ 59 | /* INTERNAL FUNCTIONS */ 60 | /* -------------------------------------------------------------------------- */ 61 | 62 | function canCallMint(bytes calldata data) 63 | internal 64 | view 65 | returns (bool, address[] memory, address[] memory) 66 | { 67 | address[] memory tokensOut = new address[](1); 68 | (tokensOut[0],,,) = abi.decode(data, (address, uint256, uint256, uint256)); 69 | 70 | return (true, sGLP, tokensOut); 71 | } 72 | 73 | function canCallMintEth(bool useEth) 74 | internal 75 | view 76 | returns (bool, address[] memory, address[] memory) 77 | { 78 | if (!useEth) return (false, new address[](0), new address[](0)); 79 | return (true, sGLP, new address[](0)); 80 | } 81 | 82 | function canCallRedeem(bytes calldata data) 83 | internal 84 | view 85 | returns (bool, address[] memory, address[] memory) 86 | { 87 | address[] memory tokensIn = new address[](1); 88 | (tokensIn[0],,,) = abi.decode(data, (address, uint256, uint256, uint256)); 89 | 90 | return (true, tokensIn, sGLP); 91 | } 92 | 93 | function canCallRedeemEth() 94 | internal 95 | view 96 | returns (bool, address[] memory, address[] memory) 97 | { 98 | return (true, new address[](0), sGLP); 99 | } 100 | } -------------------------------------------------------------------------------- /src/plutus/PLVGLPController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | 6 | /** 7 | @title Plutus GLP Vault Controller 8 | @notice Controller for Interacting with plutus glp vault 9 | */ 10 | contract PLVGLPController is IController { 11 | 12 | /* -------------------------------------------------------------------------- */ 13 | /* CONSTANT VARIABLES */ 14 | /* -------------------------------------------------------------------------- */ 15 | 16 | /// @notice deposit(uint256) function signature 17 | bytes4 constant DEPOSIT = 0xb6b55f25; 18 | 19 | /// @notice depositAll() function signature 20 | bytes4 constant DEPOSIT_ALL = 0xde5f6268; 21 | 22 | /// @notice redeem(uint256) function signature 23 | bytes4 constant REDEEM = 0xdb006a75; 24 | 25 | /// @notice redeemAll() function signature 26 | bytes4 constant REDEEM_ALL = 0x2f4350c2; 27 | 28 | /// @notice Staked GLP: 0x5402B5F40310bDED796c7D0F3FF6683f5C0cFfdf 29 | address[] sGLP; 30 | 31 | /// @notice PLVGLP: 0x5326E71Ff593Ecc2CF7AcaE5Fe57582D6e74CFF1 32 | address[] PLVGLP; 33 | 34 | /* -------------------------------------------------------------------------- */ 35 | /* CONSTRUCTOR */ 36 | /* -------------------------------------------------------------------------- */ 37 | 38 | constructor(address _SGLP, address _PLVGLP) { 39 | sGLP.push(_SGLP); 40 | PLVGLP.push(_PLVGLP); 41 | } 42 | 43 | /* -------------------------------------------------------------------------- */ 44 | /* PUBLIC FUNCTIONS */ 45 | /* -------------------------------------------------------------------------- */ 46 | 47 | /// @inheritdoc IController 48 | function canCall(address, bool, bytes calldata data) 49 | external 50 | view 51 | returns (bool, address[] memory, address[] memory) 52 | { 53 | bytes4 sig = bytes4(data); 54 | 55 | if (sig == DEPOSIT || sig == DEPOSIT_ALL) { 56 | return (true, PLVGLP, sGLP); 57 | } 58 | if (sig == REDEEM || sig == REDEEM_ALL) { 59 | return (true, sGLP, PLVGLP); 60 | } 61 | 62 | return (false, new address[](0), new address[](0)); 63 | } 64 | } -------------------------------------------------------------------------------- /src/rage/DepositPeripheryController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | 6 | /** 7 | @title Rage Trade delta netural gmx vault controller 8 | */ 9 | contract DepositPeripheryController is IController { 10 | /* -------------------------------------------------------------------------- */ 11 | /* CONSTANT VARIABLES */ 12 | /* -------------------------------------------------------------------------- */ 13 | 14 | /// @notice depositToken(address,address,uint256) 15 | bytes4 constant DEPOSIT = 0xfb0f97a8; 16 | 17 | /// @notice rage trade delta netural jr vault 18 | address[] public vault; 19 | 20 | /* -------------------------------------------------------------------------- */ 21 | /* CONSTRUCTOR */ 22 | /* -------------------------------------------------------------------------- */ 23 | 24 | /** 25 | @param _vault rage trade delta netural jr vault 26 | */ 27 | constructor(address _vault) { 28 | vault.push(_vault); 29 | } 30 | 31 | /* -------------------------------------------------------------------------- */ 32 | /* PUBLIC FUNCTIONS */ 33 | /* -------------------------------------------------------------------------- */ 34 | 35 | /// @inheritdoc IController 36 | function canCall( 37 | address, 38 | bool, 39 | bytes calldata data 40 | ) 41 | external 42 | view 43 | returns ( 44 | bool, 45 | address[] memory, 46 | address[] memory 47 | ) 48 | { 49 | if (bytes4(data) == DEPOSIT) { 50 | (address token, , ) = abi.decode( 51 | data[4:], 52 | (address, address, uint256) 53 | ); 54 | 55 | address[] memory tokensOut = new address[](1); 56 | tokensOut[0] = token; 57 | 58 | return (true, vault, tokensOut); 59 | } 60 | 61 | return (false, new address[](0), new address[](0)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/rage/WithdrawPeripheryController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | 6 | /** 7 | @title Rage Trade delta netural gmx vault controller 8 | */ 9 | contract WithdrawPeripheryController is IController { 10 | /* -------------------------------------------------------------------------- */ 11 | /* CONSTANT VARIABLES */ 12 | /* -------------------------------------------------------------------------- */ 13 | 14 | /// @notice redeemToken(address,address,uint256) 15 | bytes4 constant REDEEM = 0x0d71bdc3; 16 | 17 | /// @notice withdrawToken(address,address,uint256) 18 | bytes4 constant WITHDRAW = 0x01e33667; 19 | 20 | /// @notice rage trade delta netural jr vault 21 | address[] public vault; 22 | 23 | /* -------------------------------------------------------------------------- */ 24 | /* CONSTRUCTOR */ 25 | /* -------------------------------------------------------------------------- */ 26 | 27 | /** 28 | @param _vault rage trade delta netural jr vault 29 | */ 30 | constructor(address _vault) { 31 | vault.push(_vault); 32 | } 33 | 34 | /* -------------------------------------------------------------------------- */ 35 | /* PUBLIC FUNCTIONS */ 36 | /* -------------------------------------------------------------------------- */ 37 | 38 | /// @inheritdoc IController 39 | function canCall( 40 | address, 41 | bool, 42 | bytes calldata data 43 | ) 44 | external 45 | view 46 | returns ( 47 | bool, 48 | address[] memory, 49 | address[] memory 50 | ) 51 | { 52 | bytes4 sig = bytes4(data); 53 | 54 | if (sig == REDEEM || sig == WITHDRAW) { 55 | (address token, , ) = abi.decode( 56 | data[4:], 57 | (address, address, uint256) 58 | ); 59 | 60 | address[] memory tokensIn = new address[](1); 61 | tokensIn[0] = token; 62 | 63 | return (true, tokensIn, vault); 64 | } 65 | 66 | return (false, new address[](0), new address[](0)); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/tests/0xTransform.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {TestBase} from "./utils/Base.t.sol"; 5 | import {ITransformERC20Feature} from "../0x/ITransform.sol"; 6 | import {TransformController} from "../0x/TransformController.sol"; 7 | 8 | contract TestTransformController is TestBase { 9 | TransformController transformController; 10 | 11 | address target = makeAddr("target"); 12 | 13 | address constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; 14 | 15 | function setUp() public override { 16 | super.setUp(); 17 | transformController = new TransformController(); 18 | controllerFacade.updateController(target, transformController); 19 | } 20 | 21 | function testCanTranformERC20(address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOut) public { 22 | vm.assume(tokenIn != ETH && tokenOut != ETH); 23 | // Setup 24 | controllerFacade.toggleTokenAllowance(tokenIn); 25 | 26 | ITransformERC20Feature.Transformation[] memory transformations = new ITransformERC20Feature.Transformation[](1); 27 | 28 | bytes memory data = abi.encodeWithSelector( 29 | ITransformERC20Feature.transformERC20.selector, tokenOut, tokenIn, amountIn, amountOut, transformations 30 | ); 31 | 32 | // Test 33 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) = 34 | controllerFacade.canCall(target, true, data); 35 | 36 | // Assert 37 | assertTrue(canCall); 38 | if (tokenIn != ETH) { 39 | assertEq(tokensIn[0], tokenIn); 40 | } else { 41 | assertEq(tokensIn.length, 0); 42 | } 43 | if (tokenOut != ETH) { 44 | assertEq(tokensOut[0], tokenOut); 45 | } else { 46 | assertEq(tokensOut.length, 0); 47 | } 48 | } 49 | 50 | function testCanTranformETHtoERC20(address tokenIn, uint256 amountIn, uint256 amountOut) public { 51 | vm.assume(tokenIn != ETH); 52 | // Setup 53 | controllerFacade.toggleTokenAllowance(tokenIn); 54 | 55 | ITransformERC20Feature.Transformation[] memory transformations = new ITransformERC20Feature.Transformation[](1); 56 | 57 | bytes memory data = abi.encodeWithSelector( 58 | ITransformERC20Feature.transformERC20.selector, ETH, tokenIn, amountIn, amountOut, transformations 59 | ); 60 | 61 | // Test 62 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) = 63 | controllerFacade.canCall(target, true, data); 64 | 65 | // Assert 66 | assertTrue(canCall); 67 | assertEq(tokensIn[0], tokenIn); 68 | assertEq(tokensOut.length, 0); 69 | } 70 | 71 | function testCanTranformERC20ToETH(address tokenOut, uint256 amountIn, uint256 amountOut) public { 72 | vm.assume(tokenOut != ETH); 73 | ITransformERC20Feature.Transformation[] memory transformations = new ITransformERC20Feature.Transformation[](1); 74 | 75 | bytes memory data = abi.encodeWithSelector( 76 | ITransformERC20Feature.transformERC20.selector, tokenOut, ETH, amountIn, amountOut, transformations 77 | ); 78 | 79 | // Test 80 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) = 81 | controllerFacade.canCall(target, true, data); 82 | 83 | // Assert 84 | assertTrue(canCall); 85 | assertEq(tokensOut[0], tokenOut); 86 | assertEq(tokensIn.length, 0); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/tests/AuraRewardPoolController.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {TestBase} from "./utils/Base.t.sol"; 5 | import {RewardPoolController} from "../aura/RewardPoolController.sol"; 6 | import {IERC4626} from "../erc4626/IERC4626.sol"; 7 | import {IRewards} from "../aura/IRewards.sol"; 8 | 9 | contract TestRewardPoolControllerArbi is TestBase { 10 | RewardPoolController rewardPoolController; 11 | 12 | address target = 0x49e998899FF11598182918098588E8b90d7f60D3; 13 | address BAL = 0x040d1EdC9569d4Bab2D15287Dc5A4F10F56a56B8; 14 | address ARB = 0x912CE59144191C1204E64559FE8253a0e49E6548; 15 | address AURA = 0x1509706a6c66CA549ff0cB464de88231DDBe213B; 16 | 17 | function setUp() public override { 18 | super.setUp(); 19 | rewardPoolController = new RewardPoolController(); 20 | controllerFacade.updateController(target, rewardPoolController); 21 | } 22 | 23 | function testCanDeposit(uint256 assets, address sender) public { 24 | // Setup 25 | controllerFacade.toggleTokenAllowance(target); 26 | 27 | bytes memory data = abi.encodeWithSelector(0x6e553f65, assets, sender); 28 | 29 | // Test 30 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) = 31 | controllerFacade.canCall(target, true, data); 32 | 33 | // Assert 34 | assertTrue(canCall); 35 | assertEq(tokensIn[0], target); 36 | assertEq(tokensOut[0], IERC4626(target).asset()); 37 | } 38 | 39 | function testCanMint(uint256 shares, address sender) public { 40 | // Setup 41 | controllerFacade.toggleTokenAllowance(target); 42 | 43 | bytes memory data = abi.encodeWithSelector(0x94bf804d, shares, sender); 44 | 45 | // Test 46 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) = 47 | controllerFacade.canCall(target, true, data); 48 | 49 | // Assert 50 | assertTrue(canCall); 51 | assertEq(tokensIn[0], target); 52 | assertEq(tokensOut[0], IERC4626(target).asset()); 53 | } 54 | 55 | function testCanRedeem(uint256 shares, address sender) public { 56 | // Setup 57 | controllerFacade.toggleTokenAllowance(IERC4626(target).asset()); 58 | 59 | bytes memory data = abi.encodeWithSelector(0xba087652, shares, sender, sender); 60 | 61 | // Test 62 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) = 63 | controllerFacade.canCall(target, true, data); 64 | 65 | // Assert 66 | assertTrue(canCall); 67 | assertEq(tokensIn.length, 1); 68 | assertEq(tokensOut.length, 1); 69 | assertEq(tokensIn[0], IERC4626(target).asset()); 70 | assertEq(tokensOut[0], target); 71 | } 72 | 73 | function testCanWithdraw(uint256 assets, address sender) public { 74 | // Setup 75 | controllerFacade.toggleTokenAllowance(IERC4626(target).asset()); 76 | 77 | bytes memory data = abi.encodeWithSelector(0xb460af94, assets, sender, sender); 78 | 79 | // Test 80 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) = 81 | controllerFacade.canCall(target, true, data); 82 | 83 | // Assert 84 | assertTrue(canCall); 85 | assertEq(tokensIn.length, 1); 86 | assertEq(tokensOut.length, 1); 87 | assertEq(tokensIn[0], IERC4626(target).asset()); 88 | assertEq(tokensOut[0], target); 89 | } 90 | 91 | function testCanGetRewards() public { 92 | // Setup 93 | controllerFacade.toggleTokenAllowance(ARB); 94 | controllerFacade.toggleTokenAllowance(BAL); 95 | controllerFacade.toggleTokenAllowance(AURA); 96 | 97 | bytes memory data = abi.encodeWithSelector(0x3d18b912); 98 | 99 | // Test 100 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) = 101 | controllerFacade.canCall(target, true, data); 102 | 103 | // Assert 104 | assertTrue(canCall); 105 | assertEq(tokensOut.length, 0); 106 | assertEq(tokensIn[0], ARB); 107 | assertEq(tokensIn[1], BAL); 108 | assertEq(tokensIn[2], AURA); 109 | assertEq(tokensIn.length, 3); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/tests/Balancer.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {TestBase} from "./utils/Base.t.sol"; 5 | import {IVault, IAsset} from "../balancer/IVault.sol"; 6 | import {BalancerController} from "../balancer/BalancerController.sol"; 7 | 8 | contract TestBalancer is TestBase { 9 | 10 | BalancerController balancerController; 11 | 12 | address constant vault = 0xBA12222222228d8Ba445958a75a0704d566BF2C8; 13 | 14 | function setUp() override public { 15 | super.setUp(); 16 | balancerController = new BalancerController(); 17 | controllerFacade.updateController(vault, balancerController); 18 | } 19 | 20 | function testCanJoin() public { 21 | // Setup 22 | controllerFacade.toggleTokenAllowance(0xCfCA23cA9CA720B6E98E3Eb9B6aa0fFC4a5C08B9); 23 | 24 | bytes32 poolId = 0xcfca23ca9ca720b6e98e3eb9b6aa0ffc4a5c08b9000200000000000000000274; 25 | address sender = 0xABBb9Eb2512904123f9d372f26e2390a190d8550; 26 | address receiver = 0xABBb9Eb2512904123f9d372f26e2390a190d8550; 27 | 28 | IAsset[] memory assets = new IAsset[](2); 29 | assets[0] = IAsset(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); 30 | assets[1] = IAsset(0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF); 31 | 32 | uint256[] memory maxAmountsIn = new uint256[](2); 33 | maxAmountsIn[0] = 0; 34 | maxAmountsIn[1] = 5283061898487873009179; 35 | 36 | bytes memory userData = "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000013d76dd2f55e7a75f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000141f6f514c5100000"; 37 | 38 | bool internalBalance = false; 39 | 40 | IVault.JoinPoolRequest memory request = IVault.JoinPoolRequest( 41 | assets, 42 | maxAmountsIn, 43 | userData, 44 | internalBalance 45 | ); 46 | 47 | bytes memory data = abi.encodeWithSelector(0xb95cac28, 48 | poolId, 49 | sender, 50 | receiver, 51 | request 52 | ); 53 | 54 | // Test 55 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 56 | = controllerFacade.canCall(vault, true, data); 57 | 58 | (address token,) = IVault(vault).getPool(poolId); 59 | 60 | // Assert 61 | assertTrue(canCall); 62 | assertEq(tokensIn[0], token); 63 | assertEq(tokensOut[0], address(assets[1])); 64 | } 65 | 66 | function testCannotJoin() public { 67 | // Setup 68 | bytes32 poolId = 0xcfca23ca9ca720b6e98e3eb9b6aa0ffc4a5c08b9000200000000000000000274; 69 | address sender = 0xABBb9Eb2512904123f9d372f26e2390a190d8550; 70 | address receiver = 0xABBb9Eb2512904123f9d372f26e2390a190d8550; 71 | 72 | IAsset[] memory assets = new IAsset[](2); 73 | assets[0] = IAsset(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); 74 | assets[1] = IAsset(0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF); 75 | 76 | uint256[] memory maxAmountsIn = new uint256[](2); 77 | maxAmountsIn[0] = 0; 78 | maxAmountsIn[1] = 5283061898487873009179; 79 | 80 | bytes memory userData = "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000013d76dd2f55e7a75f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000141f6f514c5100000"; 81 | 82 | bool internalBalance = false; 83 | 84 | IVault.JoinPoolRequest memory request = IVault.JoinPoolRequest( 85 | assets, 86 | maxAmountsIn, 87 | userData, 88 | internalBalance 89 | ); 90 | 91 | bytes memory data = abi.encodeWithSelector(0xb95cac28, 92 | poolId, 93 | sender, 94 | receiver, 95 | request 96 | ); 97 | 98 | // Test 99 | (bool canCall,,) 100 | = controllerFacade.canCall(vault, true, data); 101 | 102 | // Assert 103 | assertTrue(!canCall); 104 | } 105 | 106 | function testCanExit() public { 107 | // Setup 108 | controllerFacade.toggleTokenAllowance(0xCfCA23cA9CA720B6E98E3Eb9B6aa0fFC4a5C08B9); 109 | controllerFacade.toggleTokenAllowance(0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF); 110 | bytes32 poolId = 0xcfca23ca9ca720b6e98e3eb9b6aa0ffc4a5c08b9000200000000000000000274; 111 | address sender = 0xABBb9Eb2512904123f9d372f26e2390a190d8550; 112 | address receiver = 0xABBb9Eb2512904123f9d372f26e2390a190d8550; 113 | 114 | IAsset[] memory assets = new IAsset[](2); 115 | assets[0] = IAsset(0x0000000000000000000000000000000000000000); 116 | assets[1] = IAsset(0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF); 117 | 118 | uint256[] memory maxAmountsIn = new uint256[](3); 119 | maxAmountsIn[0] = 13432569841622014589; 120 | maxAmountsIn[1] = 0; 121 | 122 | bytes memory userData = "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000013d76dd2f55e7a75f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000141f6f514c5100000"; 123 | 124 | bool internalBalance = false; 125 | 126 | IVault.ExitPoolRequest memory request = IVault.ExitPoolRequest( 127 | assets, 128 | maxAmountsIn, 129 | userData, 130 | internalBalance 131 | ); 132 | 133 | bytes memory data = abi.encodeWithSelector(0x8bdb3913, 134 | poolId, 135 | sender, 136 | receiver, 137 | request 138 | ); 139 | 140 | // Test 141 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 142 | = controllerFacade.canCall(vault, true, data); 143 | 144 | (address token,) = IVault(vault).getPool(poolId); 145 | 146 | // Assert 147 | assertTrue(canCall); 148 | assertEq(tokensOut[0], token); 149 | assertEq(tokensIn.length, 1); 150 | } 151 | 152 | function testCanSwap() public { 153 | // Setup 154 | controllerFacade.toggleTokenAllowance(0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF); 155 | 156 | IVault.SingleSwap memory swap = IVault.SingleSwap( 157 | "0", 158 | 0, 159 | IAsset(0xCfCA23cA9CA720B6E98E3Eb9B6aa0fFC4a5C08B9), 160 | IAsset(0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF), 161 | 0, 162 | "0" 163 | ); 164 | 165 | IVault.FundManagement memory funds = IVault.FundManagement( 166 | address(0), 167 | false, 168 | payable(address(0)), 169 | false 170 | ); 171 | 172 | bytes memory data = abi.encodeWithSelector(0x52bbbe29, 173 | swap, 174 | funds, 175 | 0, 176 | 0 177 | ); 178 | 179 | // Test 180 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 181 | = controllerFacade.canCall(vault, true, data); 182 | 183 | // Assert 184 | assertTrue(canCall); 185 | assertEq(tokensOut[0], 0xCfCA23cA9CA720B6E98E3Eb9B6aa0fFC4a5C08B9); 186 | assertEq(tokensIn[0], 0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF); 187 | assertEq(tokensIn.length, 1); 188 | assertEq(tokensOut.length, 1); 189 | } 190 | 191 | function testCanBatchSwapGivenIn() public { 192 | // Setup 193 | controllerFacade.toggleTokenAllowance(0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF); 194 | 195 | int256[] memory limits = new int256[](3); 196 | 197 | IAsset[] memory assets = new IAsset[](3); 198 | assets[0] = IAsset(0xBA12222222228d8Ba445958a75a0704d566BF2C8); 199 | assets[1] = IAsset(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); 200 | assets[2] = IAsset(0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF); 201 | 202 | IVault.BatchSwapStep memory swap1 = IVault.BatchSwapStep( 203 | "0", 204 | 0, 205 | 1, 206 | 10, 207 | "0" 208 | ); 209 | 210 | IVault.BatchSwapStep memory swap2 = IVault.BatchSwapStep( 211 | "0", 212 | 1, 213 | 2, 214 | 0, 215 | "0" 216 | ); 217 | 218 | IVault.FundManagement memory funds = IVault.FundManagement( 219 | address(0), 220 | false, 221 | payable(address(0)), 222 | false 223 | ); 224 | 225 | IVault.BatchSwapStep[] memory swaps = new IVault.BatchSwapStep[](2); 226 | swaps[0] = swap1; 227 | swaps[1] = swap2; 228 | 229 | bytes memory data = abi.encodeWithSelector(0x945bcec9, 230 | IVault.SwapKind.GIVEN_IN, 231 | swaps, 232 | assets, 233 | funds, 234 | limits, 235 | 0 236 | ); 237 | 238 | // Test 239 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 240 | = controllerFacade.canCall(vault, true, data); 241 | 242 | // Assert 243 | assertTrue(canCall); 244 | assertEq(tokensOut[0], 0xBA12222222228d8Ba445958a75a0704d566BF2C8); 245 | assertEq(tokensIn[0], 0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF); 246 | assertEq(tokensIn.length, 1); 247 | assertEq(tokensOut.length, 1); 248 | } 249 | 250 | function testCanBatchSwapGivenOut() public { 251 | // Setup 252 | controllerFacade.toggleTokenAllowance(0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF); 253 | 254 | int256[] memory limits = new int256[](3); 255 | 256 | IAsset[] memory assets = new IAsset[](3); 257 | assets[0] = IAsset(0xBA12222222228d8Ba445958a75a0704d566BF2C8); 258 | assets[1] = IAsset(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); 259 | assets[2] = IAsset(0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF); 260 | 261 | IVault.BatchSwapStep memory swap1 = IVault.BatchSwapStep( 262 | "0", 263 | 1, 264 | 2, 265 | 10, 266 | "0" 267 | ); 268 | 269 | IVault.BatchSwapStep memory swap2 = IVault.BatchSwapStep( 270 | "0", 271 | 0, 272 | 1, 273 | 0, 274 | "0" 275 | ); 276 | 277 | IVault.FundManagement memory funds = IVault.FundManagement( 278 | address(0), 279 | false, 280 | payable(address(0)), 281 | false 282 | ); 283 | 284 | IVault.BatchSwapStep[] memory swaps = new IVault.BatchSwapStep[](2); 285 | swaps[0] = swap1; 286 | swaps[1] = swap2; 287 | 288 | bytes memory data = abi.encodeWithSelector(0x945bcec9, 289 | IVault.SwapKind.GIVEN_OUT, 290 | swaps, 291 | assets, 292 | funds, 293 | limits, 294 | 0 295 | ); 296 | 297 | // Test 298 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 299 | = controllerFacade.canCall(vault, true, data); 300 | 301 | // Assert 302 | assertTrue(canCall); 303 | assertEq(tokensOut[0], 0xBA12222222228d8Ba445958a75a0704d566BF2C8); 304 | assertEq(tokensIn[0], 0xC0c293ce456fF0ED870ADd98a0828Dd4d2903DBF); 305 | assertEq(tokensIn.length, 1); 306 | assertEq(tokensOut.length, 1); 307 | } 308 | } -------------------------------------------------------------------------------- /src/tests/BalancerLPStaking.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {TestBase} from "./utils/Base.t.sol"; 5 | import {BalancerLPStakingController} from "../balancer/BalancerLPStakingController.sol"; 6 | 7 | contract TestBalancerStakingArbi is TestBase { 8 | 9 | BalancerLPStakingController balancerController; 10 | 11 | address constant gauge = 0x5b6776cD9c51768Fc915caD7a7e8F5c4a6331131; 12 | address constant lp = 0xFB5e6d0c1DfeD2BA000fBC040Ab8DF3615AC329c; 13 | address constant r1 = 0x040d1EdC9569d4Bab2D15287Dc5A4F10F56a56B8; 14 | address constant r2 = 0x13Ad51ed4F1B7e9Dc168d8a00cB3f4dDD85EfA60; 15 | 16 | function setUp() override public { 17 | super.setUp(); 18 | balancerController = new BalancerLPStakingController(); 19 | controllerFacade.updateController(gauge, balancerController); 20 | } 21 | 22 | function testCanDeposit() public { 23 | // Setup 24 | controllerFacade.toggleTokenAllowance(gauge); 25 | 26 | bytes memory data = abi.encodeWithSelector(0xb6b55f25, 27 | 0 28 | ); 29 | 30 | // Test 31 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 32 | = controllerFacade.canCall(gauge, true, data); 33 | 34 | // Assert 35 | assertTrue(canCall); 36 | assertEq(tokensIn[0], gauge); 37 | assertEq(tokensOut[0], lp); 38 | } 39 | 40 | function testCannotDeposit() public { 41 | bytes memory data = abi.encodeWithSelector(0xb6b55f25, 42 | 0 43 | ); 44 | 45 | // Test 46 | (bool canCall,,) 47 | = controllerFacade.canCall(gauge, true, data); 48 | 49 | // Assert 50 | assertTrue(!canCall); 51 | } 52 | 53 | function testCanDepositAndClaim(bool claim) public { 54 | // Setup 55 | controllerFacade.toggleTokenAllowance(gauge); 56 | controllerFacade.toggleTokenAllowance(r1); 57 | controllerFacade.toggleTokenAllowance(r2); 58 | 59 | bytes memory data = abi.encodeWithSelector(0x83df6747, 60 | 0, 61 | address(0), 62 | claim 63 | ); 64 | 65 | // Test 66 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 67 | = controllerFacade.canCall(gauge, true, data); 68 | 69 | // Assert 70 | assertTrue(canCall); 71 | assertEq(tokensOut[0], lp); 72 | if (claim) { 73 | assertEq(tokensIn[0], r1); 74 | assertEq(tokensIn[1], r2); 75 | assertEq(tokensIn[2], gauge); 76 | assertEq(tokensIn.length, 3); 77 | } else { 78 | assertEq(tokensIn[0], gauge); 79 | assertEq(tokensIn.length, 1); 80 | } 81 | } 82 | 83 | function testCannotDepositAndClaim() public { 84 | bytes memory data = abi.encodeWithSelector(0x83df6747, 85 | 0, 86 | address(0), 87 | true 88 | ); 89 | 90 | // Test 91 | (bool canCall,,) 92 | = controllerFacade.canCall(gauge, true, data); 93 | 94 | // Assert 95 | assertTrue(!canCall); 96 | } 97 | 98 | function testCanWithdrawAndClaim(bool claim) public { 99 | // Setup 100 | controllerFacade.toggleTokenAllowance(lp); 101 | controllerFacade.toggleTokenAllowance(r1); 102 | controllerFacade.toggleTokenAllowance(r2); 103 | 104 | bytes memory data = abi.encodeWithSelector(0x38d07436, 105 | 0, 106 | claim 107 | ); 108 | 109 | // Test 110 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 111 | = controllerFacade.canCall(gauge, true, data); 112 | 113 | // Assert 114 | assertTrue(canCall); 115 | assertEq(tokensOut[0], gauge); 116 | 117 | if (claim) { 118 | assertEq(tokensIn[0], r1); 119 | assertEq(tokensIn[1], r2); 120 | assertEq(tokensIn[2], lp); 121 | assertEq(tokensIn.length, 3); 122 | } else { 123 | assertEq(tokensIn[0], lp); 124 | assertEq(tokensIn.length, 1); 125 | } 126 | } 127 | 128 | function testCannotWithdrawAndClaim() public { 129 | bytes memory data = abi.encodeWithSelector(0x38d07436, 130 | 0, 131 | true 132 | ); 133 | 134 | // Test 135 | (bool canCall,,) 136 | = controllerFacade.canCall(gauge, true, data); 137 | 138 | // Assert 139 | assertTrue(!canCall); 140 | } 141 | 142 | function testCanWithdraw() public { 143 | // Setup 144 | controllerFacade.toggleTokenAllowance(lp); 145 | 146 | bytes memory data = abi.encodeWithSelector(0x2e1a7d4d, 147 | 0 148 | ); 149 | 150 | // Test 151 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 152 | = controllerFacade.canCall(gauge, true, data); 153 | 154 | // Assert 155 | assertTrue(canCall); 156 | assertEq(tokensIn[0], lp); 157 | assertEq(tokensOut[0], gauge); 158 | } 159 | 160 | function testCannotWithdraw() public { 161 | bytes memory data = abi.encodeWithSelector(0x2e1a7d4d, 162 | 0 163 | ); 164 | 165 | // Test 166 | (bool canCall,,) 167 | = controllerFacade.canCall(gauge, true, data); 168 | 169 | // Assert 170 | assertTrue(!canCall); 171 | } 172 | 173 | function testCanClaim() public { 174 | // Setup 175 | controllerFacade.toggleTokenAllowance(r1); 176 | controllerFacade.toggleTokenAllowance(r2); 177 | 178 | bytes memory data = abi.encodeWithSelector(0xe6f1daf2); 179 | 180 | // Test 181 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 182 | = controllerFacade.canCall(gauge, true, data); 183 | 184 | // Assert 185 | assertTrue(canCall); 186 | assertEq(tokensIn[0], r1); 187 | assertEq(tokensIn[1], r2); 188 | assertEq(tokensIn.length, 2); 189 | assertEq(tokensOut.length, 0); 190 | } 191 | 192 | function testCannotClaim() public { 193 | bytes memory data = abi.encodeWithSelector(0xe6f1daf2); 194 | 195 | // Test 196 | (bool canCall,,) 197 | = controllerFacade.canCall(gauge, true, data); 198 | 199 | // Assert 200 | assertTrue(!canCall); 201 | } 202 | 203 | function testCannotCall() public { 204 | bytes memory data = abi.encodeWithSelector(0xe6f1aaf2); 205 | 206 | // Test 207 | (bool canCall,,) 208 | = controllerFacade.canCall(gauge, true, data); 209 | 210 | // Assert 211 | assertTrue(!canCall); 212 | } 213 | } -------------------------------------------------------------------------------- /src/tests/BaseController.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import "forge-std/console2.sol"; 5 | import {TestBase} from "./utils/Base.t.sol"; 6 | import {BaseController} from "../core/BaseController.sol"; 7 | 8 | contract TestBaseControllerArbi is TestBase { 9 | 10 | BaseController baseController; 11 | 12 | address target = makeAddr("target"); 13 | 14 | function setUp() override public { 15 | super.setUp(); 16 | baseController = new BaseController(); 17 | controllerFacade.updateController(target, baseController); 18 | } 19 | 20 | function testCannotCall(bytes calldata data) public { 21 | 22 | (bool canCall,,) 23 | = controllerFacade.canCall(target, false, data); 24 | 25 | assertFalse(canCall); 26 | } 27 | } -------------------------------------------------------------------------------- /src/tests/ConvexController.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import "forge-std/console2.sol"; 5 | import {TestBase} from "./utils/Base.t.sol"; 6 | import {ConvexBoosterController} from "../convex/ConvexBoosterController.sol"; 7 | import {ConvexRewardPoolController} from "../convex/ConvexRewardPoolController.sol"; 8 | 9 | interface IBooster { 10 | function addPool(address _lptoken, address _gauge, address _factory) external returns (bool); 11 | function poolInfo(uint256) external view returns (address, address, address, bool, address); 12 | } 13 | 14 | contract TestConvexControllerArbi is TestBase { 15 | 16 | // Convex 17 | address constant CVX = 0xb952A807345991BD529FDded05009F5e80Fe8F45; 18 | address constant BOOSTER = 0xF403C135812408BFbE8713b5A23a04b3D48AAE31; 19 | address constant DEPLOYER = 0x947B7742C403f20e5FaCcDAc5E092C943E7D0277; 20 | address constant TRICRYPTO_REWARD_POOL = 0x90927a78ad13C0Ec9ACf546cE0C16248A7E7a86D; 21 | 22 | // Curve 23 | address constant CRV = 0x11cDb42B0EB46D95f990BeDD4695A6e3fA034978; 24 | address constant TRICRYPTO_LP = 0x8e0B8c8BB9db49a46697F3a5Bb8A308e744821D2; 25 | address constant TRICRYPTO_GAUGE = 0x555766f3da968ecBefa690Ffd49A2Ac02f47aa5f; 26 | address constant CURVE_POOL_FACTORY = 0xabC000d88f23Bb45525E447528DBF656A9D55bf5; 27 | 28 | // Controller 29 | ConvexBoosterController convexBoosterController; 30 | ConvexRewardPoolController convexRewardPoolController; 31 | 32 | function setUp() override public { 33 | convexBoosterController = new ConvexBoosterController(BOOSTER); 34 | convexRewardPoolController = new ConvexRewardPoolController(); 35 | } 36 | 37 | function testDeposit() public { 38 | bytes memory data = abi.encodeWithSignature("deposit(uint256,uint256)", 3, 0); 39 | 40 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 41 | = convexBoosterController.canCall(BOOSTER, false, data); 42 | 43 | assertTrue(canCall); 44 | assertEq(tokensIn[0], TRICRYPTO_REWARD_POOL); 45 | assertEq(tokensIn[1], CRV); 46 | assertEq(tokensIn[2], CVX); 47 | assertEq(tokensOut[0], TRICRYPTO_LP); 48 | } 49 | 50 | function testWithdraw() public { 51 | bytes memory data = abi.encodeWithSignature("withdraw(uint256,bool)", 0, false); 52 | 53 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 54 | = convexRewardPoolController.canCall(TRICRYPTO_REWARD_POOL, false, data); 55 | 56 | assertTrue(canCall); 57 | assertEq(tokensIn[0], TRICRYPTO_LP); 58 | assertEq(tokensOut[0], TRICRYPTO_REWARD_POOL); 59 | } 60 | 61 | function testWithdrawAndClaim() public { 62 | bytes memory data = abi.encodeWithSignature("withdraw(uint256,bool)", 0, true); 63 | 64 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 65 | = convexRewardPoolController.canCall(TRICRYPTO_REWARD_POOL, false, data); 66 | 67 | assertTrue(canCall); 68 | assertEq(tokensIn[0], CRV); 69 | assertEq(tokensIn[1], CVX); 70 | assertEq(tokensIn[2], TRICRYPTO_LP); 71 | assertEq(tokensOut[0], TRICRYPTO_REWARD_POOL); 72 | } 73 | 74 | function testClaim() public { 75 | bytes memory data = abi.encodeWithSignature("getReward(address)", address(0)); 76 | 77 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 78 | = convexRewardPoolController.canCall(TRICRYPTO_REWARD_POOL, false, data); 79 | 80 | assertTrue(canCall); 81 | assertEq(tokensIn[0], CRV); 82 | assertEq(tokensIn[1], CVX); 83 | assertEq(tokensOut.length, 0); 84 | } 85 | } -------------------------------------------------------------------------------- /src/tests/DNGMXVaultController.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {TestBase} from "./utils/Base.t.sol"; 5 | import {DepositPeripheryController} from "../rage/DepositPeripheryController.sol"; 6 | import {WithdrawPeripheryController} from "../rage/WithdrawPeripheryController.sol"; 7 | 8 | interface DNGMXVault { 9 | function withdrawToken(address token, address receiver, uint256 sGlpAmount) external returns (uint256 amountOut); 10 | 11 | function redeemToken(address token, address receiver, uint256 sharesAmount) external returns (uint256 amountOut); 12 | 13 | function depositToken(address token, address receiver, uint256 tokenAmount) 14 | external 15 | returns (uint256 sharesReceived); 16 | 17 | function deposit(address token, address receiver, uint256 tokenAmount) external returns (uint256 sharesReceived); 18 | } 19 | 20 | contract TestDNGMXVaultController is TestBase { 21 | DepositPeripheryController depositController; 22 | WithdrawPeripheryController withdrawController; 23 | 24 | address depositPeriphery = makeAddr("depositPeriphery"); 25 | address withdrawPeriphery = makeAddr("withdrawPeriphery"); 26 | address vault = makeAddr("vault"); 27 | 28 | function setUp() public override { 29 | super.setUp(); 30 | depositController = new DepositPeripheryController(vault); 31 | withdrawController = new WithdrawPeripheryController(vault); 32 | controllerFacade.updateController(depositPeriphery, depositController); 33 | controllerFacade.updateController( 34 | withdrawPeriphery, 35 | withdrawController 36 | ); 37 | } 38 | 39 | function testDeposit( 40 | address token, 41 | address receiver, 42 | uint64 amt 43 | ) public { 44 | // Setup 45 | controllerFacade.toggleTokenAllowance(vault); 46 | 47 | bytes memory data = abi.encodeWithSelector(DNGMXVault.depositToken.selector, token, receiver, amt); 48 | 49 | // Test 50 | ( 51 | bool canCall, 52 | address[] memory tokensIn, 53 | address[] memory tokensOut 54 | ) = controllerFacade.canCall(depositPeriphery, false, data); 55 | 56 | // Assert 57 | assertTrue(canCall); 58 | assertEq(tokensIn[0], vault); 59 | assertEq(tokensOut[0], token); 60 | } 61 | 62 | function testCannotWithdraw( 63 | address token, 64 | address receiver, 65 | uint64 amt 66 | ) public { 67 | // Setup 68 | bytes memory data = abi.encodeWithSelector(DNGMXVault.withdrawToken.selector, token, receiver, amt); 69 | 70 | // Test 71 | (bool canCall, , ) = controllerFacade.canCall( 72 | withdrawPeriphery, 73 | false, 74 | data 75 | ); 76 | 77 | // Assert 78 | assertTrue(!canCall); 79 | } 80 | 81 | function testWithdraw( 82 | address token, 83 | address receiver, 84 | uint64 amt 85 | ) public { 86 | // Setup 87 | controllerFacade.toggleTokenAllowance(token); 88 | 89 | bytes memory data = abi.encodeWithSelector(DNGMXVault.withdrawToken.selector, token, receiver, amt); 90 | 91 | // Test 92 | ( 93 | bool canCall, 94 | address[] memory tokensIn, 95 | address[] memory tokensOut 96 | ) = controllerFacade.canCall(withdrawPeriphery, false, data); 97 | 98 | // Assert 99 | assertTrue(canCall); 100 | assertEq(tokensIn[0], token); 101 | assertEq(tokensOut[0], vault); 102 | } 103 | 104 | function testRedeem( 105 | address token, 106 | address receiver, 107 | uint64 amt 108 | ) public { 109 | // Setup 110 | controllerFacade.toggleTokenAllowance(token); 111 | 112 | bytes memory data = abi.encodeWithSelector(DNGMXVault.redeemToken.selector, token, receiver, amt); 113 | 114 | // Test 115 | ( 116 | bool canCall, 117 | address[] memory tokensIn, 118 | address[] memory tokensOut 119 | ) = controllerFacade.canCall(withdrawPeriphery, false, data); 120 | 121 | // Assert 122 | assertTrue(canCall); 123 | assertEq(tokensIn[0], token); 124 | assertEq(tokensOut[0], vault); 125 | } 126 | 127 | function testCannotRedeem( 128 | address token, 129 | address receiver, 130 | uint64 amt 131 | ) public { 132 | bytes memory data = abi.encodeWithSelector( 133 | DNGMXVault.redeemToken.selector, 134 | token, 135 | receiver, 136 | amt 137 | ); 138 | 139 | // Test 140 | (bool canCall, , ) = controllerFacade.canCall( 141 | withdrawPeriphery, 142 | false, 143 | data 144 | ); 145 | 146 | // Assert 147 | assertTrue(!canCall); 148 | } 149 | 150 | function testCanNotCallDeposit( 151 | address token, 152 | address receiver, 153 | uint64 amt 154 | ) public { 155 | // Setup 156 | controllerFacade.toggleTokenAllowance(token); 157 | 158 | bytes memory data = abi.encodeWithSelector( 159 | DNGMXVault.depositToken.selector, 160 | token, 161 | receiver, 162 | amt 163 | ); 164 | 165 | // Test 166 | (bool canCall, , ) = controllerFacade.canCall( 167 | withdrawPeriphery, 168 | false, 169 | data 170 | ); 171 | 172 | // Assert 173 | assertTrue(!canCall); 174 | } 175 | 176 | function testCanNotCallWithdraw( 177 | address token, 178 | address receiver, 179 | uint64 amt 180 | ) public { 181 | // Setup 182 | controllerFacade.toggleTokenAllowance(token); 183 | 184 | bytes memory data = abi.encodeWithSelector( 185 | DNGMXVault.withdrawToken.selector, 186 | token, 187 | receiver, 188 | amt 189 | ); 190 | 191 | // Test 192 | (bool canCall, , ) = controllerFacade.canCall( 193 | depositPeriphery, 194 | false, 195 | data 196 | ); 197 | 198 | // Assert 199 | assertTrue(!canCall); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/tests/GLPController.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import "forge-std/console2.sol"; 5 | import {TestBase} from "./utils/Base.t.sol"; 6 | import {RewardRouterV2Controller} from "../gmx/RewardRouterV2Controller.sol"; 7 | import {RewardRouterController} from "../gmx/RewardRouterController.sol"; 8 | 9 | interface IRewardRouter { 10 | function mintAndStakeGlp(address _token, uint256 _amount, uint256 _minUsdg, uint256 _minGlp) 11 | external 12 | returns (uint256); 13 | 14 | function mintAndStakeGlpETH(uint256 _minUsdg, uint256 _minGlp) external payable returns (uint256); 15 | 16 | function unstakeAndRedeemGlp(address _tokenOut, uint256 _glpAmount, uint256 _minOut, address _receiver) 17 | external 18 | returns (uint256); 19 | 20 | function unstakeAndRedeemGlpETH(uint256 _glpAmount, uint256 _minOut, address payable _receiver) 21 | external 22 | returns (uint256); 23 | 24 | function claimFees() external; 25 | 26 | function compound() external; 27 | } 28 | 29 | contract TestGLPControllerArbi is TestBase { 30 | RewardRouterV2Controller rewardRouterV2Controller; 31 | RewardRouterController rewardRouterController; 32 | 33 | // GMX 34 | address constant SGLP = 0x5402B5F40310bDED796c7D0F3FF6683f5C0cFfdf; 35 | address constant rewardRouterV2 = 0xB95DB5B167D75e6d04227CfFFA61069348d271F5; 36 | address constant rewardRouter = 0xA906F338CB21815cBc4Bc87ace9e68c87eF8d8F1; 37 | address WETH = makeAddr("WETH"); 38 | 39 | function setUp() public override { 40 | super.setUp(); 41 | rewardRouterV2Controller = new RewardRouterV2Controller(SGLP); 42 | rewardRouterController = new RewardRouterController(WETH); 43 | controllerFacade.updateController(rewardRouterV2, rewardRouterV2Controller); 44 | controllerFacade.updateController(rewardRouter, rewardRouterController); 45 | } 46 | 47 | function testCanMintAndStake(address token, uint64 amt, uint64 minUSDG, uint64 minGLP) public { 48 | controllerFacade.toggleTokenAllowance(SGLP); 49 | bytes memory data = abi.encodeWithSelector(IRewardRouter.mintAndStakeGlp.selector, token, amt, minUSDG, minGLP); 50 | 51 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) = 52 | controllerFacade.canCall(rewardRouterV2, false, data); 53 | 54 | assertTrue(canCall); 55 | assertEq(tokensIn[0], SGLP); 56 | assertEq(tokensOut[0], token); 57 | } 58 | 59 | function testCanMintAndStakeEth(uint64 minUSDG, uint64 minGLP) public { 60 | controllerFacade.toggleTokenAllowance(SGLP); 61 | bytes memory data = abi.encodeWithSelector(IRewardRouter.mintAndStakeGlpETH.selector, minUSDG, minGLP); 62 | 63 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) = 64 | controllerFacade.canCall(rewardRouterV2, true, data); 65 | 66 | assertTrue(canCall); 67 | assertEq(tokensIn[0], SGLP); 68 | assertEq(tokensOut.length, 0); 69 | } 70 | 71 | function testCanUnstakeAndRedeemGLPEth(uint256 _glpAmount, uint256 _minOut, address payable _receiver) public { 72 | bytes memory data = 73 | abi.encodeWithSelector(IRewardRouter.unstakeAndRedeemGlpETH.selector, _glpAmount, _minOut, _receiver); 74 | 75 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) = 76 | controllerFacade.canCall(rewardRouterV2, false, data); 77 | 78 | assertTrue(canCall); 79 | assertEq(tokensOut[0], SGLP); 80 | assertEq(tokensIn.length, 0); 81 | } 82 | 83 | function testCanUnstakeAndRedeemGLP(address _token, uint256 _glpAmount, uint256 _minOut, address payable _receiver) 84 | public 85 | { 86 | controllerFacade.toggleTokenAllowance(_token); 87 | bytes memory data = 88 | abi.encodeWithSelector(IRewardRouter.unstakeAndRedeemGlp.selector, _token, _glpAmount, _minOut, _receiver); 89 | 90 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) = 91 | controllerFacade.canCall(rewardRouterV2, false, data); 92 | 93 | assertTrue(canCall); 94 | assertEq(tokensOut[0], SGLP); 95 | assertEq(tokensIn[0], _token); 96 | } 97 | 98 | function testCanCompound() public { 99 | controllerFacade.toggleTokenAllowance(WETH); 100 | bytes memory data = abi.encodeWithSelector(IRewardRouter.compound.selector); 101 | 102 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) = 103 | controllerFacade.canCall(rewardRouter, false, data); 104 | 105 | assertTrue(canCall); 106 | assertEq(tokensOut.length, 0); 107 | assertEq(tokensIn.length, 0); 108 | } 109 | 110 | function testCanClaimFees() public { 111 | controllerFacade.toggleTokenAllowance(WETH); 112 | 113 | bytes memory data = abi.encodeWithSelector(IRewardRouter.claimFees.selector); 114 | 115 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) = 116 | controllerFacade.canCall(rewardRouter, false, data); 117 | 118 | assertTrue(canCall); 119 | assertEq(tokensOut.length, 0); 120 | assertEq(tokensIn[0], WETH); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/tests/PLVGLPController.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {TestBase} from "./utils/Base.t.sol"; 5 | import {PLVGLPController} from "../plutus/PLVGLPController.sol"; 6 | 7 | contract TestPLVGLPControllerArbi is TestBase { 8 | 9 | PLVGLPController plvGLPController; 10 | 11 | address PLVGLP = makeAddr("PLVGLP"); 12 | address SGLP = makeAddr("SGLP"); 13 | address target = makeAddr("target"); 14 | 15 | function setUp() override public { 16 | super.setUp(); 17 | plvGLPController = new PLVGLPController(SGLP, PLVGLP); 18 | controllerFacade.updateController(target, plvGLPController); 19 | } 20 | 21 | function testCanDeposit() public { 22 | // Setup 23 | controllerFacade.toggleTokenAllowance(PLVGLP); 24 | 25 | bytes memory data = abi.encodeWithSelector(0xb6b55f25, 26 | 0 27 | ); 28 | 29 | // Test 30 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 31 | = controllerFacade.canCall(target, true, data); 32 | 33 | // Assert 34 | assertTrue(canCall); 35 | assertEq(tokensIn[0], PLVGLP); 36 | assertEq(tokensOut[0], SGLP); 37 | } 38 | 39 | function testCanDepositAll() public { 40 | // Setup 41 | controllerFacade.toggleTokenAllowance(PLVGLP); 42 | 43 | bytes memory data = abi.encodeWithSelector(0xde5f6268); 44 | 45 | // Test 46 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 47 | = controllerFacade.canCall(target, true, data); 48 | 49 | // Assert 50 | assertTrue(canCall); 51 | assertEq(tokensIn[0], PLVGLP); 52 | assertEq(tokensOut[0], SGLP); 53 | } 54 | 55 | function testCannotDeposit() public { 56 | bytes memory data = abi.encodeWithSelector(0xb6b55f25, 57 | 0 58 | ); 59 | 60 | // Test 61 | (bool canCall,,) 62 | = controllerFacade.canCall(target, true, data); 63 | 64 | // Assert 65 | assertTrue(!canCall); 66 | } 67 | 68 | function testCannotDepositAll() public { 69 | bytes memory data = abi.encodeWithSelector(0xde5f6268); 70 | 71 | // Test 72 | (bool canCall,,) 73 | = controllerFacade.canCall(target, true, data); 74 | 75 | // Assert 76 | assertTrue(!canCall); 77 | } 78 | 79 | function testCanRedeem() public { 80 | // Setup 81 | controllerFacade.toggleTokenAllowance(SGLP); 82 | 83 | bytes memory data = abi.encodeWithSelector(0xdb006a75, 84 | 0 85 | ); 86 | 87 | // Test 88 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 89 | = controllerFacade.canCall(target, true, data); 90 | 91 | // Assert 92 | assertTrue(canCall); 93 | assertEq(tokensIn[0], SGLP); 94 | assertEq(tokensOut[0], PLVGLP); 95 | } 96 | 97 | function testCanRedeemAll() public { 98 | // Setup 99 | controllerFacade.toggleTokenAllowance(SGLP); 100 | 101 | bytes memory data = abi.encodeWithSelector(0x2f4350c2); 102 | 103 | // Test 104 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 105 | = controllerFacade.canCall(target, true, data); 106 | 107 | // Assert 108 | assertTrue(canCall); 109 | assertEq(tokensIn[0], SGLP); 110 | assertEq(tokensOut[0], PLVGLP); 111 | } 112 | 113 | function testCannotRedeem() public { 114 | bytes memory data = abi.encodeWithSelector(0xdb006a75, 115 | 0 116 | ); 117 | 118 | // Test 119 | (bool canCall,,) 120 | = controllerFacade.canCall(target, true, data); 121 | 122 | // Assert 123 | assertTrue(!canCall); 124 | } 125 | 126 | function testCannotRedeemAll() public { 127 | bytes memory data = abi.encodeWithSelector(0x2f4350c2); 128 | 129 | // Test 130 | (bool canCall,,) 131 | = controllerFacade.canCall(target, true, data); 132 | 133 | // Assert 134 | assertTrue(!canCall); 135 | } 136 | } -------------------------------------------------------------------------------- /src/tests/StableSwap2PoolController.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {TestBase} from "./utils/Base.t.sol"; 5 | import {StableSwap2PoolEthController} from "../curve/StableSwap2PoolEthController.sol"; 6 | import {IStableSwapPool} from "../curve/IStableSwapPool.sol"; 7 | 8 | contract TestStableSwap2PoolEthControllerArbi is TestBase { 9 | 10 | StableSwap2PoolEthController curveController; 11 | 12 | address constant pool = 0x6eB2dc694eB516B16Dc9FBc678C60052BbdD7d80; 13 | address constant lp = 0xDbcD16e622c95AcB2650b38eC799f76BFC557a0b; 14 | address constant WSTETH = 0x5979D7b546E38E414F7E9822514be443A4800529; 15 | 16 | function setUp() override public { 17 | super.setUp(); 18 | curveController = new StableSwap2PoolEthController(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); 19 | controllerFacade.updateController(pool, curveController); 20 | } 21 | 22 | function testCanAddLiquidityAllTokens() public { 23 | // Setup 24 | controllerFacade.toggleTokenAllowance(lp); 25 | 26 | uint256[2] memory amounts; 27 | amounts[0] = 123; 28 | amounts[1] = 123; 29 | 30 | bytes memory data = abi.encodeWithSelector(0x0b4c7e4d, 31 | amounts, 32 | 123 33 | ); 34 | 35 | // Test 36 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 37 | = controllerFacade.canCall(pool, true, data); 38 | 39 | // Assert 40 | assertTrue(canCall); 41 | assertEq(tokensIn[0], lp); 42 | assertEq(tokensOut[0], WSTETH); 43 | } 44 | 45 | function testCanAddLiquidityToken() public { 46 | // Setup 47 | controllerFacade.toggleTokenAllowance(lp); 48 | 49 | uint256[2] memory amounts; 50 | amounts[1] = 123; 51 | 52 | bytes memory data = abi.encodeWithSelector(0x0b4c7e4d, 53 | amounts, 54 | 123 55 | ); 56 | 57 | // Test 58 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 59 | = controllerFacade.canCall(pool, true, data); 60 | 61 | // Assert 62 | assertTrue(canCall); 63 | assertEq(tokensIn[0], lp); 64 | assertEq(tokensOut[0], WSTETH); 65 | assertEq(tokensOut.length, 1); 66 | } 67 | 68 | function testCanAddLiquidityETH() public { 69 | // Setup 70 | controllerFacade.toggleTokenAllowance(lp); 71 | 72 | uint256[2] memory amounts; 73 | amounts[0] = 123; 74 | 75 | bytes memory data = abi.encodeWithSelector(0x0b4c7e4d, 76 | amounts, 77 | 123 78 | ); 79 | 80 | // Test 81 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 82 | = controllerFacade.canCall(pool, true, data); 83 | 84 | // Assert 85 | assertTrue(canCall); 86 | assertEq(tokensIn[0], lp); 87 | assertEq(tokensOut.length, 0); 88 | } 89 | 90 | function testCanRemoveLiquidityETH() public { 91 | 92 | bytes memory data = abi.encodeWithSelector(0x1a4d01d2, 93 | 123, 94 | 0, 95 | 123 96 | ); 97 | 98 | // Test 99 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 100 | = controllerFacade.canCall(pool, true, data); 101 | 102 | // Assert 103 | assertTrue(canCall); 104 | assertEq(tokensOut[0], lp); 105 | assertEq(tokensIn.length, 0); 106 | } 107 | 108 | function testCanRemoveLiquidityToken() public { 109 | // Setup 110 | controllerFacade.toggleTokenAllowance(WSTETH); 111 | 112 | bytes memory data = abi.encodeWithSelector(0x1a4d01d2, 113 | 123, 114 | 1, 115 | 123 116 | ); 117 | 118 | // Test 119 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 120 | = controllerFacade.canCall(pool, true, data); 121 | 122 | // Assert 123 | assertTrue(canCall); 124 | assertEq(tokensOut[0], lp); 125 | assertEq(tokensIn.length, 1); 126 | assertEq(tokensIn[0], WSTETH); 127 | } 128 | 129 | function testCanRemoveLiquidity() public { 130 | // Setup 131 | controllerFacade.toggleTokenAllowance(WSTETH); 132 | 133 | uint256[2] memory amounts; 134 | amounts[0] = 123; 135 | amounts[1] = 123; 136 | 137 | bytes memory data = abi.encodeWithSelector(0x5b36389c, 138 | 123, 139 | amounts 140 | ); 141 | 142 | // Test 143 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 144 | = controllerFacade.canCall(pool, true, data); 145 | 146 | // Assert 147 | assertTrue(canCall); 148 | assertEq(tokensOut[0], lp); 149 | assertEq(tokensIn.length, 1); 150 | assertEq(tokensIn[0], WSTETH); 151 | } 152 | 153 | function testCanExchangeETHtoWSTETH() public { 154 | // Setup 155 | controllerFacade.toggleTokenAllowance(WSTETH); 156 | 157 | bytes memory data = abi.encodeWithSelector(0x3df02124, 158 | 0, 159 | 1, 160 | 1, 161 | 1 162 | ); 163 | 164 | // Test 165 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 166 | = controllerFacade.canCall(pool, true, data); 167 | 168 | // Assert 169 | assertTrue(canCall); 170 | assertEq(tokensOut.length, 0); 171 | assertEq(tokensIn[0], WSTETH); 172 | } 173 | 174 | function testCanExchangeWSTETHtoETH() public { 175 | // Setup 176 | controllerFacade.toggleTokenAllowance(WSTETH); 177 | 178 | bytes memory data = abi.encodeWithSelector(0x3df02124, 179 | 1, 180 | 0, 181 | 1, 182 | 1 183 | ); 184 | 185 | // Test 186 | (bool canCall, address[] memory tokensIn, address[] memory tokensOut) 187 | = controllerFacade.canCall(pool, false, data); 188 | 189 | // Assert 190 | assertTrue(canCall); 191 | assertEq(tokensIn.length, 0); 192 | assertEq(tokensOut[0], WSTETH); 193 | } 194 | } -------------------------------------------------------------------------------- /src/tests/utils/Base.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {ControllerFacade} from "../../core/ControllerFacade.sol"; 6 | 7 | contract TestBase is Test { 8 | 9 | ControllerFacade controllerFacade; 10 | 11 | function setUp() virtual public { 12 | controllerFacade = new ControllerFacade(); 13 | } 14 | } -------------------------------------------------------------------------------- /src/uniswap/ISwapRouterV3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface ISwapRouterV3 { 5 | struct ExactOutputSingleParams { 6 | address tokenIn; 7 | address tokenOut; 8 | uint24 fee; 9 | address recipient; 10 | uint256 amountOut; 11 | uint256 amountInMaximum; 12 | uint160 sqrtPriceLimitX96; 13 | } 14 | 15 | struct ExactInputSingleParams { 16 | address tokenIn; 17 | address tokenOut; 18 | uint24 fee; 19 | address recipient; 20 | uint256 amountIn; 21 | uint256 amountOutMinimum; 22 | uint160 sqrtPriceLimitX96; 23 | } 24 | 25 | struct ExactInputParams { 26 | bytes path; 27 | address recipient; 28 | uint256 amountIn; 29 | uint256 amountOutMinimum; 30 | } 31 | 32 | struct ExactOutputParams { 33 | bytes path; 34 | address recipient; 35 | uint256 amountOut; 36 | uint256 amountInMaximum; 37 | } 38 | } -------------------------------------------------------------------------------- /src/uniswap/IUniV2Factory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface IUniV2Factory { 5 | function getPair(address tokenA, address tokenB) 6 | external 7 | view 8 | returns (address pair); 9 | } -------------------------------------------------------------------------------- /src/uniswap/UniV2Controller.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | import {IUniV2Factory} from "./IUniV2Factory.sol"; 6 | 7 | /** 8 | @title Uniswap V2 Controller 9 | @notice Controller for uniswap v2 interaction 10 | eth:0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D 11 | */ 12 | contract UniV2Controller is IController { 13 | 14 | /* -------------------------------------------------------------------------- */ 15 | /* CONSTANT VARIABLES */ 16 | /* -------------------------------------------------------------------------- */ 17 | 18 | /// @notice swapExactTokensForTokens(uint256,uint256,address[],address,uint256) function signature 19 | bytes4 constant SWAP_EXACT_TOKENS_FOR_TOKENS = 0x38ed1739; 20 | 21 | /// @notice swapTokensForExactTokens(uint256,uint256,address[],address,uint256) function signature 22 | bytes4 constant SWAP_TOKENS_FOR_EXACT_TOKENS = 0x8803dbee; 23 | 24 | /// @notice swapExactETHForTokens(uint256,address[],address,uint256) function signature 25 | bytes4 constant SWAP_EXACT_ETH_FOR_TOKENS = 0x7ff36ab5; 26 | 27 | /// @notice swapTokensForExactETH(uint256,uint256,address[],address,uint256) function signature 28 | bytes4 constant SWAP_TOKENS_FOR_EXACT_ETH = 0x4a25d94a; 29 | 30 | /// @notice swapExactTokensForETH(uint256,uint256,address[],address,uint256) function signature 31 | bytes4 constant SWAP_EXACT_TOKENS_FOR_ETH = 0x18cbafe5; 32 | 33 | /// @notice swapETHForExactTokens(uint256,address[],address,uint256) function signature 34 | bytes4 constant SWAP_ETH_FOR_EXACT_TOKENS = 0xfb3bdb41; 35 | 36 | /// @notice addLiquidity(address,address,uint256,uint256,uint256,uint256,address,uint256) function signature 37 | bytes4 constant ADD_LIQUIDITY = 0xe8e33700; 38 | 39 | /// @notice removeLiquidity(address,address,uint256,uint256,uint256,address,uint256) function signature 40 | bytes4 constant REMOVE_LIQUIDITY = 0xbaa2abde; 41 | 42 | /// @notice addLiquidityETH(address,uint256,uint256,uint256,address,uint256) function signature 43 | bytes4 constant ADD_LIQUIDITY_ETH = 0xf305d719; 44 | 45 | /// @notice removeLiquidityETH(address,uint256,uint256,uint256,address,uint256) function signature 46 | bytes4 constant REMOVE_LIQUIDITY_ETH = 0x02751cec; 47 | 48 | /* -------------------------------------------------------------------------- */ 49 | /* PUBLIC FUNCTIONS */ 50 | /* -------------------------------------------------------------------------- */ 51 | 52 | /// @notice WETH address 53 | address public immutable WETH; 54 | 55 | /// @notice Uniswap v2 factory 56 | IUniV2Factory public immutable UNIV2_FACTORY; 57 | 58 | /* -------------------------------------------------------------------------- */ 59 | /* CONSTRUCTOR */ 60 | /* -------------------------------------------------------------------------- */ 61 | 62 | /** 63 | @notice Contract constructor 64 | @param _WETH WETH address 65 | @param _uniV2Factory Uniswap V2 Factory address 66 | */ 67 | constructor( 68 | address _WETH, 69 | IUniV2Factory _uniV2Factory 70 | ) { 71 | WETH = _WETH; 72 | UNIV2_FACTORY = _uniV2Factory; 73 | } 74 | 75 | /* -------------------------------------------------------------------------- */ 76 | /* PUBLIC FUNCTIONS */ 77 | /* -------------------------------------------------------------------------- */ 78 | 79 | /// @inheritdoc IController 80 | function canCall(address, bool, bytes calldata data) 81 | external 82 | view 83 | returns (bool, address[] memory, address[] memory) 84 | { 85 | bytes4 sig = bytes4(data); 86 | 87 | // Swap Functions 88 | if (sig == SWAP_EXACT_TOKENS_FOR_TOKENS || sig == SWAP_TOKENS_FOR_EXACT_TOKENS) 89 | return swapErc20ForErc20(data[4:]); // ERC20 -> ERC20 90 | if (sig == SWAP_EXACT_ETH_FOR_TOKENS || sig == SWAP_ETH_FOR_EXACT_TOKENS) 91 | return swapEthForErc20(data[4:]); // ETH -> ERC20 92 | if (sig == SWAP_TOKENS_FOR_EXACT_ETH || sig == SWAP_EXACT_TOKENS_FOR_ETH) 93 | return swapErc20ForEth(data[4:]); // ERC20 -> ETH 94 | 95 | // LP Functions 96 | if (sig == ADD_LIQUIDITY) return addLiquidity(data[4:]); 97 | if (sig == REMOVE_LIQUIDITY) return removeLiquidity(data[4:]); 98 | if (sig == ADD_LIQUIDITY_ETH) return addLiquidityEth(data[4:]); 99 | if (sig == REMOVE_LIQUIDITY_ETH) return removeLiquidityEth(data[4:]); 100 | 101 | return(false, new address[](0), new address[](0)); 102 | } 103 | 104 | /* -------------------------------------------------------------------------- */ 105 | /* INTERNAL FUNCTIONS */ 106 | /* -------------------------------------------------------------------------- */ 107 | 108 | /** 109 | @notice Evaluates whether liquidity can be added 110 | @param data calldata for adding liquidity 111 | @return canCall Specifies if the interaction is accepted 112 | @return tokensIn List of tokens that the account will receive after the 113 | interactions 114 | @return tokensOut List of tokens that will be removed from the account 115 | after the interaction 116 | */ 117 | function addLiquidity(bytes calldata data) 118 | internal 119 | view 120 | returns (bool, address[] memory, address[] memory) 121 | { 122 | (address tokenA, address tokenB) = abi.decode(data, (address, address)); 123 | 124 | address[] memory tokensOut = new address[](2); 125 | tokensOut[0] = tokenA; 126 | tokensOut[1] = tokenB; 127 | 128 | address[] memory tokensIn = new address[](1); 129 | tokensIn[0] = UNIV2_FACTORY.getPair(tokenA, tokenB); 130 | 131 | return(true, tokensIn, tokensOut); 132 | } 133 | 134 | /** 135 | @notice Evaluates whether liquidity can be added 136 | @param data calldata for adding liquidity 137 | @return canCall Specifies if the interaction is accepted 138 | @return tokensIn List of tokens that the account will receive after the 139 | interactions 140 | @return tokensOut List of tokens that will be removed from the account 141 | after the interaction 142 | */ 143 | function addLiquidityEth(bytes calldata data) 144 | internal 145 | view 146 | returns (bool, address[] memory, address[] memory) 147 | { 148 | address token = abi.decode(data, (address)); 149 | 150 | address[] memory tokensOut = new address[](1); 151 | tokensOut[0] = token; 152 | 153 | address[] memory tokensIn = new address[](1); 154 | tokensIn[0] = UNIV2_FACTORY.getPair(token, WETH); 155 | 156 | return(true, tokensIn, tokensOut); 157 | } 158 | 159 | /** 160 | @notice Evaluates whether liquidity can be removed 161 | @param data calldata for removing liquidity 162 | @return canCall Specifies if the interaction is accepted 163 | @return tokensIn List of tokens that the account will receive after the 164 | interactions 165 | @return tokensOut List of tokens that will be removed from the account 166 | after the interaction 167 | */ 168 | function removeLiquidity(bytes calldata data) 169 | internal 170 | view 171 | returns (bool, address[] memory, address[] memory) 172 | { 173 | (address tokenA, address tokenB) = abi.decode(data, (address, address)); 174 | 175 | address[] memory tokensIn = new address[](2); 176 | tokensIn[0] = tokenA; 177 | tokensIn[1] = tokenB; 178 | 179 | address[] memory tokensOut = new address[](1); 180 | tokensOut[0] = UNIV2_FACTORY.getPair(tokenA, tokenB); 181 | 182 | return(true, tokensIn, tokensOut); 183 | } 184 | 185 | /** 186 | @notice Evaluates whether liquidity can be removed 187 | @param data calldata for removing liquidity 188 | @return canCall Specifies if the interaction is accepted 189 | @return tokensIn List of tokens that the account will receive after the 190 | interactions 191 | @return tokensOut List of tokens that will be removed from the account 192 | after the interaction 193 | */ 194 | function removeLiquidityEth(bytes calldata data) 195 | internal 196 | view 197 | returns (bool, address[] memory, address[] memory) 198 | { 199 | (address token) = abi.decode(data, (address)); 200 | 201 | address[] memory tokensIn = new address[](1); 202 | tokensIn[0] = token; 203 | 204 | address[] memory tokensOut = new address[](1); 205 | tokensOut[0] = UNIV2_FACTORY.getPair(token, WETH); 206 | 207 | return(true, tokensIn, tokensOut); 208 | } 209 | 210 | /** 211 | @notice Evaluates whether swap can be performed 212 | @param data calldata for swapping tokens 213 | @return canCall Specifies if the interaction is accepted 214 | @return tokensIn List of tokens that the account will receive after the 215 | interactions 216 | @return tokensOut List of tokens that will be removed from the account 217 | after the interaction 218 | */ 219 | function swapErc20ForErc20(bytes calldata data) 220 | internal 221 | pure 222 | returns (bool, address[] memory, address[] memory) 223 | { 224 | (,, address[] memory path,,) 225 | = abi.decode(data, (uint, uint, address[], address, uint)); 226 | 227 | address[] memory tokensOut = new address[](1); 228 | tokensOut[0] = path[0]; 229 | 230 | address[] memory tokensIn = new address[](1); 231 | tokensIn[0] = path[path.length - 1]; 232 | 233 | return( 234 | true, 235 | tokensIn, 236 | tokensOut 237 | ); 238 | } 239 | 240 | /** 241 | @notice Evaluates whether swap can be performed 242 | @param data calldata for swapping tokens 243 | @return canCall Specifies if the interaction is accepted 244 | @return tokensIn List of tokens that the account will receive after the 245 | interactions 246 | @return tokensOut List of tokens that will be removed from the account 247 | after the interaction 248 | */ 249 | function swapEthForErc20(bytes calldata data) 250 | internal 251 | pure 252 | returns (bool, address[] memory, address[] memory) 253 | { 254 | (, address[] memory path,,) 255 | = abi.decode(data, (uint, address[], address, uint)); 256 | 257 | address[] memory tokensIn = new address[](1); 258 | tokensIn[0] = path[path.length - 1]; 259 | 260 | return ( 261 | true, 262 | tokensIn, 263 | new address[](0) 264 | ); 265 | } 266 | 267 | /** 268 | @notice Evaluates whether swap can be performed 269 | @param data calldata for swapping tokens 270 | @return canCall Specifies if the interaction is accepted 271 | @return tokensIn List of tokens that the account will receive after the 272 | interactions 273 | @return tokensOut List of tokens that will be removed from the account 274 | after the interaction 275 | */ 276 | function swapErc20ForEth(bytes calldata data) 277 | internal 278 | pure 279 | returns (bool, address[] memory, address[] memory) 280 | { 281 | (,, address[] memory path) 282 | = abi.decode(data, (uint, uint, address[])); 283 | 284 | address[] memory tokensOut = new address[](1); 285 | tokensOut[0] = path[0]; 286 | 287 | return (true, new address[](0), tokensOut); 288 | } 289 | } -------------------------------------------------------------------------------- /src/uniswap/UniV3Controller.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {ISwapRouterV3} from "./ISwapRouterV3.sol"; 5 | import {IController} from "../core/IController.sol"; 6 | import {BytesLib} from "solidity-bytes-utils/BytesLib.sol"; 7 | import {IControllerFacade} from "../core/IControllerFacade.sol"; 8 | 9 | /** 10 | @title Uniswap V3 Controller 11 | @notice Controller for uniswap v3 interaction 12 | arbi:0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45 13 | */ 14 | contract UniV3Controller is IController { 15 | using BytesLib for bytes; 16 | 17 | /* -------------------------------------------------------------------------- */ 18 | /* CONSTANT VARIABLES */ 19 | /* -------------------------------------------------------------------------- */ 20 | 21 | /// @notice size of address stored in bytes 22 | uint256 private constant ADDR_SIZE = 20; 23 | 24 | /// @notice multicall(bytes[]) function signature 25 | bytes4 constant MULTICALL = 0xac9650d8; 26 | 27 | /// @notice refundETH() function signature 28 | bytes4 constant REFUND_ETH = 0x12210e8a; 29 | 30 | /// @notice unwrapWETH9(uint256,address) function signature 31 | bytes4 constant UNWRAP_ETH = 0x49404b7c; 32 | 33 | /// @notice exactInputSingle((address,address,uint24,address,uint256,uint256,uint160)) function signature 34 | bytes4 constant EXACT_INPUT_SINGLE = 0x04e45aaf; 35 | 36 | /// @notice exactOutputSingle((address,address,uint24,address,uint256,uint256,uint160)) function signature 37 | bytes4 constant EXACT_OUTPUT_SINGLE = 0x5023b4df; 38 | 39 | /// @notice exactInput((bytes,address,uint256,uint256)) function signature 40 | bytes4 constant EXACT_INPUT = 0xb858183f; 41 | 42 | /// @notice exactOutput((bytes,address,uint256,uint256)) function signature 43 | bytes4 constant EXACT_OUTPUT = 0x09b81346; 44 | 45 | /* -------------------------------------------------------------------------- */ 46 | /* STATE_VARIABLES */ 47 | /* -------------------------------------------------------------------------- */ 48 | 49 | /// @notice IControllerFacade 50 | IControllerFacade public immutable controllerFacade; 51 | 52 | /* -------------------------------------------------------------------------- */ 53 | /* CONSTRUCTOR */ 54 | /* -------------------------------------------------------------------------- */ 55 | 56 | /** 57 | @notice Contract constructor 58 | @param _controllerFacade Controller Facade 59 | */ 60 | constructor(IControllerFacade _controllerFacade) { 61 | controllerFacade = _controllerFacade; 62 | } 63 | 64 | /* -------------------------------------------------------------------------- */ 65 | /* PUBLIC FUNCTIONS */ 66 | /* -------------------------------------------------------------------------- */ 67 | 68 | /// @inheritdoc IController 69 | function canCall(address, bool useEth, bytes calldata data) 70 | external 71 | view 72 | returns (bool, address[] memory, address[] memory) 73 | { 74 | bytes4 sig = bytes4(data); // Slice function selector 75 | 76 | if (sig == MULTICALL) return parseMultiCall(data[4:], useEth); 77 | if (sig == EXACT_OUTPUT_SINGLE) 78 | return parseExactOutputSingle(data[4:]); 79 | if (sig == EXACT_INPUT_SINGLE) 80 | return parseExactInputSingle(data[4:], useEth); 81 | if (sig == EXACT_OUTPUT) 82 | return parseExactOutput(data[4:]); 83 | if (sig == EXACT_INPUT) 84 | return parseExactInput(data[4:], useEth); 85 | return (false, new address[](0), new address[](0)); 86 | } 87 | 88 | /* -------------------------------------------------------------------------- */ 89 | /* INTERNAL FUNCTIONS */ 90 | /* -------------------------------------------------------------------------- */ 91 | 92 | /** 93 | @notice Evaluates whether Multi Call can be performed 94 | @param data calldata for performing multi call 95 | @return canCall Specifies if the interaction is accepted 96 | @return tokensIn List of tokens that the account will receive after the 97 | interactions 98 | @return tokensOut List of tokens that will be removed from the account 99 | after the interaction 100 | */ 101 | function parseMultiCall(bytes calldata data, bool useEth) 102 | internal 103 | view 104 | returns (bool, address[] memory, address[] memory) 105 | { 106 | // Decompose function calls 107 | bytes[] memory calls = abi.decode(data, (bytes[])); 108 | 109 | // Multicalls with > 2 calls are treated as malformed data 110 | if (calls.length > 2) 111 | return (false, new address[](0), new address[](0)); 112 | 113 | bytes4 sig = bytes4(calls[0]); 114 | 115 | // Handle case when first function call is exactOutputSingle 116 | if (sig == EXACT_OUTPUT_SINGLE) 117 | return parseExactOutputSingleMulticall(calls, useEth); 118 | 119 | // Handle case when first function call is exactInputSingle 120 | if (sig == EXACT_INPUT_SINGLE) 121 | return parseExactInputSingleMulticall(calls); 122 | 123 | // Handle case when first function call is exactInput 124 | if (sig == EXACT_INPUT) 125 | return parseExactInputMulticall(calls); 126 | 127 | // Handle case when first function call is exactOutput 128 | if (sig == EXACT_OUTPUT) 129 | return parseExactOutputMulticall(calls, useEth); 130 | 131 | return (false, new address[](0), new address[](0)); 132 | } 133 | 134 | function parseExactOutputSingle(bytes calldata data) 135 | internal 136 | view 137 | returns (bool, address[] memory, address[] memory) 138 | { 139 | // Decode Params 140 | ISwapRouterV3.ExactOutputSingleParams memory params = abi.decode( 141 | data, 142 | (ISwapRouterV3.ExactOutputSingleParams) 143 | ); 144 | 145 | address[] memory tokensIn = new address[](1); 146 | address[] memory tokensOut = new address[](1); 147 | 148 | tokensIn[0] = params.tokenOut; 149 | tokensOut[0] = params.tokenIn; 150 | 151 | return ( 152 | controllerFacade.isTokenAllowed(tokensIn[0]), 153 | tokensIn, 154 | tokensOut 155 | ); 156 | } 157 | 158 | function parseExactOutput(bytes calldata data) 159 | internal 160 | view 161 | returns (bool, address[] memory, address[] memory) 162 | { 163 | // Decode Params 164 | ISwapRouterV3.ExactOutputParams memory params = abi.decode( 165 | data, 166 | (ISwapRouterV3.ExactOutputParams) 167 | ); 168 | 169 | address[] memory tokensIn = new address[](1); 170 | address[] memory tokensOut = new address[](1); 171 | 172 | tokensOut[0] = params.path.toAddress(params.path.length - ADDR_SIZE); 173 | tokensIn[0] = params.path.toAddress(0); 174 | 175 | return ( 176 | controllerFacade.isTokenAllowed(tokensIn[0]), 177 | tokensIn, 178 | tokensOut 179 | ); 180 | } 181 | 182 | function parseExactInputSingle(bytes calldata data, bool useEth) 183 | internal 184 | view 185 | returns (bool, address[] memory, address[] memory) 186 | { 187 | // Decode swap params 188 | ISwapRouterV3.ExactInputSingleParams memory params = abi.decode( 189 | data, 190 | (ISwapRouterV3.ExactInputSingleParams) 191 | ); 192 | 193 | address[] memory tokensIn = new address[](1); 194 | tokensIn[0] = params.tokenOut; 195 | 196 | // If swapping ETH <-> ERC20 197 | if (useEth) { 198 | return ( 199 | controllerFacade.isTokenAllowed(tokensIn[0]), 200 | tokensIn, 201 | new address[](0) 202 | ); 203 | } 204 | 205 | address[] memory tokensOut = new address[](1); 206 | tokensOut[0] = params.tokenIn; 207 | 208 | return ( 209 | controllerFacade.isTokenAllowed(tokensIn[0]), 210 | tokensIn, 211 | tokensOut 212 | ); 213 | } 214 | 215 | function parseExactInput(bytes calldata data, bool useEth) 216 | internal 217 | view 218 | returns (bool, address[] memory, address[] memory) 219 | { 220 | // Decode swap params 221 | ISwapRouterV3.ExactInputParams memory params = abi.decode( 222 | data, 223 | (ISwapRouterV3.ExactInputParams) 224 | ); 225 | 226 | address[] memory tokensIn = new address[](1); 227 | tokensIn[0] = params.path.toAddress(params.path.length - ADDR_SIZE); 228 | 229 | // If swapping ETH <-> ERC20 230 | if (useEth) { 231 | return ( 232 | controllerFacade.isTokenAllowed(tokensIn[0]), 233 | tokensIn, 234 | new address[](0) 235 | ); 236 | } 237 | 238 | address[] memory tokensOut = new address[](1); 239 | tokensOut[0] = params.path.toAddress(0); 240 | 241 | return ( 242 | controllerFacade.isTokenAllowed(tokensIn[0]), 243 | tokensIn, 244 | tokensOut 245 | ); 246 | } 247 | 248 | function parseExactOutputSingleMulticall( 249 | bytes[] memory multiData, 250 | bool useEth 251 | ) 252 | internal 253 | view 254 | returns (bool, address[] memory, address[] memory) 255 | { 256 | // remove sig from data and decode params 257 | ISwapRouterV3.ExactOutputSingleParams memory params = abi.decode( 258 | multiData[0].slice(4, multiData[0].length - 4), 259 | (ISwapRouterV3.ExactOutputSingleParams) 260 | ); 261 | 262 | // Swapping Eth <-> ERC20 263 | if (useEth && bytes4(multiData[1]) == REFUND_ETH) { 264 | address[] memory tokensIn = new address[](1); 265 | tokensIn[0] = params.tokenOut; 266 | return ( 267 | controllerFacade.isTokenAllowed(tokensIn[0]), 268 | tokensIn, 269 | new address[](0) 270 | ); 271 | } 272 | 273 | // Swapping ERC20 <-> ETH 274 | if (bytes4(multiData[1]) == UNWRAP_ETH) { 275 | address[] memory tokensOut = new address[](1); 276 | tokensOut[0] = params.tokenIn; 277 | return (true, new address[](0), tokensOut); 278 | } 279 | 280 | return (false, new address[](0), new address[](0)); 281 | } 282 | 283 | function parseExactOutputMulticall( 284 | bytes[] memory multiData, 285 | bool useEth 286 | ) 287 | internal 288 | view 289 | returns (bool, address[] memory, address[] memory) 290 | { 291 | // remove sig from data and decode params 292 | ISwapRouterV3.ExactOutputParams memory params = abi.decode( 293 | multiData[0].slice(4, multiData[0].length - 4), 294 | (ISwapRouterV3.ExactOutputParams) 295 | ); 296 | 297 | // Swapping Eth <-> ERC20 298 | if (useEth && bytes4(multiData[1]) == REFUND_ETH) { 299 | address[] memory tokensIn = new address[](1); 300 | tokensIn[0] = params.path.toAddress(0); 301 | return ( 302 | controllerFacade.isTokenAllowed(tokensIn[0]), 303 | tokensIn, 304 | new address[](0) 305 | ); 306 | } 307 | 308 | // Swapping ERC20 <-> ETH 309 | if (bytes4(multiData[1]) == UNWRAP_ETH) { 310 | address[] memory tokensOut = new address[](1); 311 | tokensOut[0] = params.path.toAddress(params.path.length - ADDR_SIZE); 312 | return (true, new address[](0), tokensOut); 313 | } 314 | 315 | return (false, new address[](0), new address[](0)); 316 | } 317 | 318 | function parseExactInputSingleMulticall( 319 | bytes[] memory multiData 320 | ) 321 | internal 322 | pure 323 | returns (bool, address[] memory, address[] memory) 324 | { 325 | // Swap ERC20 <-> ETH 326 | if (bytes4(multiData[1]) == UNWRAP_ETH) { 327 | ISwapRouterV3.ExactInputSingleParams memory params = abi.decode( 328 | multiData[0].slice(4, multiData[0].length - 4), 329 | (ISwapRouterV3.ExactInputSingleParams) 330 | ); 331 | 332 | address[] memory tokensOut = new address[](1); 333 | tokensOut[0] = params.tokenIn; 334 | return (true, new address[](0), tokensOut); 335 | } 336 | return (false, new address[](0), new address[](0)); 337 | } 338 | 339 | function parseExactInputMulticall( 340 | bytes[] memory multiData 341 | ) 342 | internal 343 | pure 344 | returns (bool, address[] memory, address[] memory) 345 | { 346 | // Swap ERC20 <-> ETH 347 | if (bytes4(multiData[1]) == UNWRAP_ETH) { 348 | ISwapRouterV3.ExactInputParams memory params = abi.decode( 349 | multiData[0].slice(4, multiData[0].length - 4), 350 | (ISwapRouterV3.ExactInputParams) 351 | ); 352 | 353 | address[] memory tokensOut = new address[](1); 354 | tokensOut[0] = params.path.toAddress(0); 355 | return (true, new address[](0), tokensOut); 356 | } 357 | return (false, new address[](0), new address[](0)); 358 | } 359 | } -------------------------------------------------------------------------------- /src/utils/Errors.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface Errors { 5 | error AdminOnly(); 6 | error ZeroAddress(); 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/Ownable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Errors} from "./Errors.sol"; 5 | 6 | abstract contract Ownable { 7 | address public admin; 8 | 9 | event OwnershipTransferred(address indexed previousAdmin, address indexed newAdmin); 10 | 11 | constructor(address _admin) { 12 | if (_admin == address(0)) revert Errors.ZeroAddress(); 13 | admin = _admin; 14 | } 15 | 16 | modifier adminOnly() { 17 | if (admin != msg.sender) revert Errors.AdminOnly(); 18 | _; 19 | } 20 | 21 | function transferOwnership(address newAdmin) external virtual adminOnly { 22 | if (newAdmin == address(0)) revert Errors.ZeroAddress(); 23 | emit OwnershipTransferred(admin, newAdmin); 24 | admin = newAdmin; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/weth/WETHController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | 6 | /** 7 | @title WETH Controller 8 | @notice Controller for Interacting with Wrapped Ether contract 9 | arbi:0x82aF49447D8a07e3bd95BD0d56f35241523fBab1 10 | */ 11 | contract WETHController is IController { 12 | 13 | /* -------------------------------------------------------------------------- */ 14 | /* CONSTANT VARIABLES */ 15 | /* -------------------------------------------------------------------------- */ 16 | 17 | /// @notice deposit() function signature 18 | bytes4 constant DEPOSIT = 0xd0e30db0; 19 | 20 | /// @notice withdraw(uint256) function signature 21 | bytes4 constant WITHDRAW = 0x2e1a7d4d; 22 | 23 | /* -------------------------------------------------------------------------- */ 24 | /* STATE VARIABLES */ 25 | /* -------------------------------------------------------------------------- */ 26 | 27 | /// @notice List of tokens 28 | /// @dev Will always have one token WETH 29 | address[] public weth; 30 | 31 | /* -------------------------------------------------------------------------- */ 32 | /* CONSTRUCTOR */ 33 | /* -------------------------------------------------------------------------- */ 34 | 35 | /** 36 | @notice Contract constructor 37 | @param wEth address of WETH 38 | */ 39 | constructor(address wEth) { 40 | weth.push(wEth); 41 | } 42 | 43 | /* -------------------------------------------------------------------------- */ 44 | /* PUBLIC FUNCTIONS */ 45 | /* -------------------------------------------------------------------------- */ 46 | 47 | /// @inheritdoc IController 48 | function canCall( 49 | address, 50 | bool, 51 | bytes calldata data 52 | ) external view returns (bool, address[] memory, address[] memory) 53 | { 54 | bytes4 sig = bytes4(data); 55 | if(sig == DEPOSIT) return (true, weth, new address[](0)); 56 | if(sig == WITHDRAW) return (true, new address[](0), weth); 57 | return (false, new address[](0), new address[](0)); 58 | } 59 | } -------------------------------------------------------------------------------- /src/yearn/YearnController.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IController} from "../core/IController.sol"; 5 | 6 | interface IYVault { 7 | function token() external view returns (address); 8 | } 9 | 10 | /** 11 | @title Yearn Vault Controller 12 | @notice Controller for Interacting with yearn vaults 13 | arbi:0x239e14A19DFF93a17339DCC444f74406C17f8E67 14 | */ 15 | contract YearnVaultController is IController { 16 | 17 | /* -------------------------------------------------------------------------- */ 18 | /* CONSTANT VARIABLES */ 19 | /* -------------------------------------------------------------------------- */ 20 | 21 | /// @notice deposit(uint256) function signature 22 | bytes4 constant DEPOSIT = 0xb6b55f25; 23 | 24 | /// @notice withdraw(uint256) function signature 25 | bytes4 constant WITHDRAW = 0x2e1a7d4d; 26 | 27 | /* -------------------------------------------------------------------------- */ 28 | /* PUBLIC FUNCTIONS */ 29 | /* -------------------------------------------------------------------------- */ 30 | 31 | /// @inheritdoc IController 32 | function canCall(address target, bool, bytes calldata data) 33 | external 34 | view 35 | returns (bool, address[] memory, address[] memory) 36 | { 37 | bytes4 sig = bytes4(data); 38 | 39 | if (sig == DEPOSIT) { 40 | address[] memory tokensIn = new address[](1); 41 | address[] memory tokensOut = new address[](1); 42 | 43 | tokensIn[0] = target; 44 | tokensOut[0] = IYVault(target).token(); 45 | 46 | return (true, tokensIn, tokensOut); 47 | } 48 | if (sig == WITHDRAW) { 49 | address[] memory tokensIn = new address[](1); 50 | address[] memory tokensOut = new address[](1); 51 | 52 | tokensOut[0] = target; 53 | tokensIn[0] = IYVault(target).token(); 54 | 55 | return (true, tokensIn, tokensOut); 56 | } 57 | 58 | return (false, new address[](0), new address[](0)); 59 | } 60 | } --------------------------------------------------------------------------------