diff --git a/contracts/AMMStrategy.sol b/contracts/AMMStrategy.sol deleted file mode 100644 index dcacec09..00000000 --- a/contracts/AMMStrategy.sol +++ /dev/null @@ -1,147 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -import { Ownable } from "./abstracts/Ownable.sol"; -import { Asset } from "./libraries/Asset.sol"; - -import { IWETH } from "./interfaces/IWETH.sol"; -import { IUniswapPermit2 } from "./interfaces/IUniswapPermit2.sol"; -import { IAMMStrategy } from "./interfaces/IAMMStrategy.sol"; -import { IStrategy } from "./interfaces/IStrategy.sol"; - -contract AMMStrategy is IAMMStrategy, Ownable { - using SafeERC20 for IERC20; - - address public immutable weth; - address public immutable permit2; - address public immutable entryPoint; - mapping(address => bool) public ammMapping; - - receive() external payable {} - - constructor(address _owner, address _entryPoint, address _weth, address _permit2, address[] memory _ammAddrs) Ownable(_owner) { - entryPoint = _entryPoint; - weth = _weth; - permit2 = _permit2; - for (uint256 i = 0; i < _ammAddrs.length; ++i) { - ammMapping[_ammAddrs[i]] = true; - emit SetAMM(_ammAddrs[i], true); - } - } - - modifier onlyEntryPoint() { - require(msg.sender == entryPoint, "only entry point"); - _; - } - - /// @inheritdoc IAMMStrategy - function setAMMs(address[] calldata _ammAddrs, bool[] calldata _enables) external override onlyOwner { - for (uint256 i = 0; i < _ammAddrs.length; ++i) { - ammMapping[_ammAddrs[i]] = _enables[i]; - emit SetAMM(_ammAddrs[i], _enables[i]); - } - } - - /// @inheritdoc IAMMStrategy - function approveTokens( - address[] calldata tokens, - address[] calldata spenders, - bool[] calldata usePermit2InSpenders, - uint256 amount - ) external override onlyOwner { - require(spenders.length == usePermit2InSpenders.length, "length of spenders not match"); - for (uint256 i = 0; i < tokens.length; ++i) { - for (uint256 j = 0; j < spenders.length; ++j) { - if (usePermit2InSpenders[j]) { - // The UniversalRouter of Uniswap uses Permit2 to remove the need for token approvals being provided directly to the UniversalRouter. - _permit2Approve(tokens[i], spenders[j], amount); - } else { - IERC20(tokens[i]).safeApprove(spenders[j], amount); - } - } - } - } - - /// @inheritdoc IAMMStrategy - function withdrawLegacyTokensTo(address[] calldata tokens, address receiver) external override onlyOwner { - for (uint256 i = 0; i < tokens.length; ++i) { - uint256 selfBalance = Asset.getBalance(tokens[i], address(this)); - if (selfBalance > 0) { - Asset.transferTo(tokens[i], payable(receiver), selfBalance); - } - } - } - - /// @inheritdoc IStrategy - function executeStrategy(address inputToken, address outputToken, uint256 inputAmount, bytes calldata data) external payable override onlyEntryPoint { - Operation[] memory ops = abi.decode(data, (Operation[])); - require(ops.length > 0, "empty operations"); - require(inputAmount > 0, "empty inputAmount"); - // wrap eth first - if (Asset.isETH(inputToken)) { - IWETH(weth).deposit{ value: inputAmount }(); - } - for (uint256 i = 0; i < ops.length; ++i) { - Operation memory op = ops[i]; - bytes4 selector = _call(op.dest, op.value, op.data); - emit Action(op.dest, op.value, selector); - } - _transferToEntryPoint(outputToken); - } - - /** - * @dev internal function of `executeStrategy`. - * Allow arbitrary call to allowed amms in swap - */ - function _call(address _dest, uint256 _value, bytes memory _data) internal returns (bytes4 selector) { - require(ammMapping[_dest], "invalid op dest"); - - if (_data.length >= 4) { - selector = bytes4(_data); - } - // withdraw needed native eth - if (_value > 0 && address(this).balance < _value) { - IWETH(weth).withdraw(_value - address(this).balance); - } - (bool success, bytes memory result) = _dest.call{ value: _value }(_data); - if (!success) { - assembly { - revert(add(result, 32), mload(result)) - } - } - } - - /** - * @dev internal function of `executeStrategy`. - * Allow the spender to use Permit2 for the token. - */ - function _permit2Approve(address _token, address _spender, uint256 _amount) internal { - if (IERC20(_token).allowance(address(this), permit2) == 0) { - IERC20(_token).safeApprove(permit2, type(uint256).max); - } - uint160 amount = _amount > type(uint160).max ? type(uint160).max : uint160(_amount); - IUniswapPermit2(permit2).approve(_token, _spender, amount, type(uint48).max); - } - - /** - * @dev internal function of `executeStrategy`. - * Transfer output token to entry point - * If outputToken is native ETH and there is WETH remain, unwrap WETH to ETH automatically - */ - function _transferToEntryPoint(address _token) internal { - if (Asset.isETH(_token)) { - // unwrap existing WETH - uint256 wethBalance = IWETH(weth).balanceOf(address(this)); - if (wethBalance > 0) { - IWETH(weth).withdraw(wethBalance); - } - } - uint256 selfBalance = Asset.getBalance(_token, address(this)); - if (selfBalance > 0) { - Asset.transferTo(_token, payable(entryPoint), selfBalance); - } - } -} diff --git a/contracts/UniswapStrategy.sol b/contracts/UniswapStrategy.sol deleted file mode 100644 index 1ed0d836..00000000 --- a/contracts/UniswapStrategy.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -import { Ownable } from "./abstracts/Ownable.sol"; -import { IStrategy } from "./interfaces/IStrategy.sol"; -import { IUniswapRouterV2 } from "./interfaces/IUniswapRouterV2.sol"; - -contract UniswapStrategy is IStrategy, Ownable { - using SafeERC20 for IERC20; - - address public genericSwap; - IUniswapRouterV2 public immutable uniswapV2Router; - - constructor(address _owner, address _genericSwap, address _uniswapV2Router) Ownable(_owner) { - genericSwap = _genericSwap; - uniswapV2Router = IUniswapRouterV2(_uniswapV2Router); - } - - modifier onlyGenericSwap() { - require(msg.sender == genericSwap, "not from GenericSwap contract"); - _; - } - - function approveToken(address token, address spender, uint256 amount) external onlyOwner { - IERC20(token).safeApprove(spender, amount); - } - - function executeStrategy(address inputToken, address outputToken, uint256 inputAmount, bytes calldata data) external payable override onlyGenericSwap { - (address routerAddr, bytes memory makerSpecificData) = abi.decode(data, (address, bytes)); - require(routerAddr == address(uniswapV2Router), "non supported protocol"); - - (uint256 deadline, address[] memory path) = abi.decode(makerSpecificData, (uint256, address[])); - _validateAMMPath(inputToken, outputToken, path); - uint256 receivedAmount = _tradeUniswapV2TokenToToken(inputAmount, deadline, path); - IERC20(outputToken).safeTransfer(genericSwap, receivedAmount); - } - - function _tradeUniswapV2TokenToToken(uint256 _inputAmount, uint256 _deadline, address[] memory _path) internal returns (uint256) { - uint256[] memory amounts = uniswapV2Router.swapExactTokensForTokens(_inputAmount, 0, _path, address(this), _deadline); - return amounts[amounts.length - 1]; - } - - function _validateAMMPath(address _inputToken, address _outputToken, address[] memory _path) internal pure { - require(_path.length >= 2, "path length must be at least two"); - require(_path[0] == _inputToken, "invalid path"); - require(_path[_path.length - 1] == _outputToken, "invalid path"); - } -} diff --git a/contracts/interfaces/IUniswapSwapRouter02.sol b/contracts/interfaces/IUniswapSwapRouter02.sol index 5a2c21b7..336ace70 100644 --- a/contracts/interfaces/IUniswapSwapRouter02.sol +++ b/contracts/interfaces/IUniswapSwapRouter02.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0; -interface ISwapRouter02 { +interface IUniswapSwapRouter02 { function swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to) external payable returns (uint256 amountOut); function swapTokensForExactTokens(uint256 amountOut, uint256 amountInMax, address[] calldata path, address to) external payable returns (uint256 amountIn); diff --git a/contracts/interfaces/IUniswapRouterV2.sol b/contracts/interfaces/IUniswapV2Router.sol similarity index 98% rename from contracts/interfaces/IUniswapRouterV2.sol rename to contracts/interfaces/IUniswapV2Router.sol index 59ef618d..fffc34cd 100644 --- a/contracts/interfaces/IUniswapRouterV2.sol +++ b/contracts/interfaces/IUniswapV2Router.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.0; -interface IUniswapRouterV2 { +interface IUniswapV2Router { function swapExactTokensForTokens( uint256 amountIn, uint256 amountOutMin, diff --git a/contracts/interfaces/IUniswapV3SwapCallback.sol b/contracts/interfaces/IUniswapV3SwapCallback.sol deleted file mode 100644 index 683e5c43..00000000 --- a/contracts/interfaces/IUniswapV3SwapCallback.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity >=0.8.0; - -/// @title Callback for IUniswapV3PoolActions#swap -/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface -interface IUniswapV3SwapCallback { - /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap. - /// @dev In the implementation you must pay the pool tokens owed for the swap. - /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory. - /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped. - /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token0 to the pool. - /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by - /// the end of the swap. If positive, the callback must send that amount of token1 to the pool. - /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call - function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external; -} diff --git a/contracts/interfaces/IUniswapV3SwapRouter.sol b/contracts/interfaces/IUniswapV3SwapRouter.sol index a2140d93..d0ebdaa1 100644 --- a/contracts/interfaces/IUniswapV3SwapRouter.sol +++ b/contracts/interfaces/IUniswapV3SwapRouter.sol @@ -1,11 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity >=0.8.0; -import { IUniswapV3SwapCallback } from "./IUniswapV3SwapCallback.sol"; - /// @title Router token swapping functionality /// @notice Functions for swapping tokens via Uniswap V3 -interface IUniswapV3SwapRouter is IUniswapV3SwapCallback { +interface IUniswapV3SwapRouter { struct ExactInputSingleParams { address tokenIn; address tokenOut; diff --git a/contracts/libraries/UniswapV2.sol b/contracts/libraries/UniswapV2.sol deleted file mode 100644 index 6b396a70..00000000 --- a/contracts/libraries/UniswapV2.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { IUniswapRouterV2 } from "../interfaces/IUniswapRouterV2.sol"; - -library UniswapV2 { - struct SwapExactTokensForTokensParams { - address tokenIn; - uint256 tokenInAmount; - address tokenOut; - uint256 tokenOutAmountMin; - address[] path; - address to; - uint256 deadline; - } - - function swapExactTokensForTokens(address _uniswapV2Router, SwapExactTokensForTokensParams memory _params) internal returns (uint256 amount) { - _validatePath(_params.path, _params.tokenIn, _params.tokenOut); - - uint256[] memory amounts = IUniswapRouterV2(_uniswapV2Router).swapExactTokensForTokens( - _params.tokenInAmount, - _params.tokenOutAmountMin, - _params.path, - _params.to, - _params.deadline - ); - - return amounts[amounts.length - 1]; - } - - function getAmountsOut(address _uniswapV2Router, uint256 _amountIn, address[] memory _path) internal view returns (uint256[] memory amounts) { - return IUniswapRouterV2(_uniswapV2Router).getAmountsOut(_amountIn, _path); - } - - function _validatePath(address[] memory _path, address _tokenIn, address _tokenOut) internal pure { - require(_path.length >= 2, "UniswapV2: Path length must be at least two"); - require(_path[0] == _tokenIn, "UniswapV2: First element of path must match token in"); - require(_path[_path.length - 1] == _tokenOut, "UniswapV2: Last element of path must match token out"); - } -} diff --git a/test/forkMainnet/AMMStrategy.t.sol b/test/forkMainnet/AMMStrategy.t.sol deleted file mode 100644 index 9cab6bc2..00000000 --- a/test/forkMainnet/AMMStrategy.t.sol +++ /dev/null @@ -1,383 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import { Test } from "forge-std/Test.sol"; -import { Tokens } from "test/utils/Tokens.sol"; -import { BalanceUtil } from "test/utils/BalanceUtil.sol"; -import { BalanceSnapshot, Snapshot } from "test/utils/BalanceSnapshot.sol"; - -import { Constant } from "contracts/libraries/Constant.sol"; -import { AMMStrategy } from "contracts/AMMStrategy.sol"; - -import { IUniversalRouter } from "contracts/interfaces/IUniswapUniversalRouter.sol"; -import { UniswapCommands } from "test/utils/UniswapCommands.sol"; -import { IWETH } from "contracts/interfaces/IWETH.sol"; -import { IAMMStrategy } from "contracts/interfaces/IAMMStrategy.sol"; -import { IStrategy } from "contracts/interfaces/IStrategy.sol"; -import { IUniswapRouterV2 } from "contracts//interfaces/IUniswapRouterV2.sol"; -import { IBalancerV2Vault } from "contracts/interfaces/IBalancerV2Vault.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -contract AMMStrategyTest is Test, Tokens, BalanceUtil { - using SafeERC20 for IERC20; - - using BalanceSnapshot for Snapshot; - - address strategyAdmin = makeAddr("strategyAdmin"); - address entryPoint = address(this); - uint256 defaultDeadline = block.timestamp + 1; - address[] tokenList = [USDC_ADDRESS, cUSDC_ADDRESS, WETH_ADDRESS, WBTC_ADDRESS]; - address[] ammList = [UNISWAP_UNIVERSAL_ROUTER_ADDRESS, SUSHISWAP_ADDRESS, BALANCER_V2_ADDRESS, CURVE_USDT_POOL_ADDRESS, CURVE_TRICRYPTO2_POOL_ADDRESS]; - bool[] usePermit2InAMMs = [true, false, false, false, false]; - AMMStrategy ammStrategy; - - receive() external payable {} - - function setUp() public { - ammStrategy = new AMMStrategy(strategyAdmin, entryPoint, WETH_ADDRESS, UNISWAP_PERMIT2_ADDRESS, ammList); - vm.prank(strategyAdmin); - ammStrategy.approveTokens(tokenList, ammList, usePermit2InAMMs, type(uint256).max); - deal(entryPoint, 100 ether); - for (uint256 i = 0; i < tokenList.length; i++) { - setERC20Balance(tokenList[i], entryPoint, 100); - } - vm.label(UNISWAP_UNIVERSAL_ROUTER_ADDRESS, "UniswapUniversalRouter"); - vm.label(SUSHISWAP_ADDRESS, "Sushiswap"); - vm.label(CURVE_USDT_POOL_ADDRESS, "CurveUSDTPool"); - vm.label(BALANCER_V2_ADDRESS, "BalancerV2"); - vm.label(UNISWAP_PERMIT2_ADDRESS, "UniswapPermit2"); - vm.label(WETH_ADDRESS, "WETH"); - vm.label(CURVE_TRICRYPTO2_POOL_ADDRESS, "CurveTriCryptoPool"); - } - - function testAMMStrategyTradeWithMultiAMM() public { - // sushiSwap and curveV1 - address inputToken = USDC_ADDRESS; - address outputToken = DAI_ADDRESS; - uint256 inputAmount = 10 * 1e6; - - IAMMStrategy.Operation[] memory operations = new IAMMStrategy.Operation[](2); - // construct data for sushiSwap (half inputAmount) - address[] memory path = new address[](2); - path[0] = inputToken; - path[1] = outputToken; - - bytes memory payload0 = abi.encodeCall( - IUniswapRouterV2.swapExactTokensForTokens, - (inputAmount / 2, uint256(0), path, address(ammStrategy), defaultDeadline) - ); - operations[0] = IAMMStrategy.Operation(SUSHISWAP_ADDRESS, 0, payload0); - - // construct data for curveV1 (half inputAmount) - // address[] USDT_POOL_COINS = [cDAI_ADDRESS, cUSDC_ADDRESS, USDT_ADDRESS]; - int128 inputTokenIndex = 1; - int128 outputTokenIndex = 0; - // ICurveFi - bytes memory payload1 = abi.encodeWithSignature( - "exchange_underlying(int128,int128,uint256,uint256)", - inputTokenIndex, - outputTokenIndex, - inputAmount - inputAmount / 2, - 0, - defaultDeadline - ); - operations[1] = IAMMStrategy.Operation(CURVE_USDT_POOL_ADDRESS, 0, payload1); - - bytes memory data = abi.encode(operations); - _baseTest(inputToken, outputToken, inputAmount, data); - } - - function testAMMStrategyTradeSushiswap() public { - address inputToken = USDC_ADDRESS; - address outputToken = DAI_ADDRESS; - uint256 inputAmount = 10 * 1e6; - address[] memory path = new address[](2); - path[0] = inputToken; - path[1] = outputToken; - - IAMMStrategy.Operation[] memory operations = new IAMMStrategy.Operation[](1); - bytes memory payload0 = abi.encodeCall( - IUniswapRouterV2.swapExactTokensForTokens, - (inputAmount, uint256(0), path, address(ammStrategy), defaultDeadline) - ); - operations[0] = IAMMStrategy.Operation(SUSHISWAP_ADDRESS, 0, payload0); - - bytes memory data = abi.encode(operations); - _baseTest(inputToken, outputToken, inputAmount, data); - } - - function testAMMStrategyTradeSushiswapWithETHAsInput() public { - address inputToken = Constant.ETH_ADDRESS; - address outputToken = DAI_ADDRESS; - uint256 inputAmount = 1 ether; - address[] memory path = new address[](2); - path[0] = WETH_ADDRESS; // use weth when swap - path[1] = outputToken; - // ETH -> WETH -> DAI - IAMMStrategy.Operation[] memory operations = new IAMMStrategy.Operation[](1); - bytes memory payload1 = abi.encodeCall( - IUniswapRouterV2.swapExactTokensForTokens, - (inputAmount, uint256(0), path, address(ammStrategy), defaultDeadline) - ); - operations[0] = IAMMStrategy.Operation(SUSHISWAP_ADDRESS, 0, payload1); - - bytes memory data = abi.encode(operations); - _baseTest(inputToken, outputToken, inputAmount, data); - } - - function testAMMStrategyTradeSushiswapWithETHAsOutput() public { - address inputToken = USDC_ADDRESS; - address outputToken = Constant.ETH_ADDRESS; - uint256 inputAmount = 10 * 1e6; - address[] memory path = new address[](2); - path[0] = inputToken; - path[1] = WETH_ADDRESS; // use weth when swap - // USDC -> WETH - // if outputToken is native ETH, strategy contract will unwrap WETH to ETH automatically - IAMMStrategy.Operation[] memory operations = new IAMMStrategy.Operation[](1); - - bytes memory payload0 = abi.encodeCall( - IUniswapRouterV2.swapExactTokensForTokens, - (inputAmount, uint256(0), path, address(ammStrategy), defaultDeadline) - ); - operations[0] = IAMMStrategy.Operation(SUSHISWAP_ADDRESS, 0, payload0); - - bytes memory data = abi.encode(operations); - _baseTest(inputToken, outputToken, inputAmount, data); - } - - function testAMMStrategyTradeUniswapV2() public { - address inputToken = USDC_ADDRESS; - address outputToken = DAI_ADDRESS; - uint256 inputAmount = 10 * 1e6; - address[] memory path = new address[](2); - path[0] = inputToken; - path[1] = outputToken; - - bytes memory commands = abi.encodePacked(bytes1(uint8(UniswapCommands.V2_SWAP_EXACT_IN))); - bytes[] memory inputs = new bytes[](1); - inputs[0] = abi.encode(address(ammStrategy), inputAmount, 0, path, true); - - IAMMStrategy.Operation[] memory operations = new IAMMStrategy.Operation[](1); - bytes memory payload0 = abi.encodeCall(IUniversalRouter.execute, (commands, inputs, defaultDeadline)); - operations[0] = IAMMStrategy.Operation(UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 0, payload0); - - bytes memory data = abi.encode(operations); - _baseTest(inputToken, outputToken, inputAmount, data); - } - - function testAMMStrategyTradeUniswapV3WithMultiHop() public { - uint16 DEFAULT_FEE_FACTOR = 500; - address inputToken = USDC_ADDRESS; - address outputToken = DAI_ADDRESS; - uint256 inputAmount = 10 * 1e6; - - address[] memory path = new address[](3); - path[0] = inputToken; - path[1] = address(weth); - path[2] = outputToken; - - uint24[] memory fees = new uint24[](2); - fees[0] = DEFAULT_FEE_FACTOR; - fees[1] = DEFAULT_FEE_FACTOR; - - address[] memory routerAddrList = new address[](1); - routerAddrList[0] = UNISWAP_UNIVERSAL_ROUTER_ADDRESS; - - bytes memory encodePath; - for (uint256 i = 0; i < fees.length; i++) { - encodePath = abi.encodePacked(encodePath, path[i], fees[i]); - } - encodePath = abi.encodePacked(encodePath, path[path.length - 1]); - - bytes memory commands = abi.encodePacked(bytes1(uint8(UniswapCommands.V3_SWAP_EXACT_IN))); - bytes[] memory inputs = new bytes[](1); - inputs[0] = abi.encode(address(ammStrategy), inputAmount, 0, encodePath, true); - - IAMMStrategy.Operation[] memory operations = new IAMMStrategy.Operation[](1); - bytes memory payload0 = abi.encodeCall(IUniversalRouter.execute, (commands, inputs, defaultDeadline)); - operations[0] = IAMMStrategy.Operation(UNISWAP_UNIVERSAL_ROUTER_ADDRESS, 0, payload0); - - bytes memory data = abi.encode(operations); - _baseTest(inputToken, outputToken, inputAmount, data); - } - - function testTradeBalancerV2MultiHop() public { - bytes32 BALANCER_WETH_DAI_POOL = 0x0b09dea16768f0799065c475be02919503cb2a3500020000000000000000001a; - bytes32 BALANCER_WETH_USDC_POOL = 0x96646936b91d6b9d7d0c47c496afbf3d6ec7b6f8000200000000000000000019; - address inputToken = USDC_ADDRESS; - address outputToken = DAI_ADDRESS; - uint256 inputAmount = 10 * 1e6; - - address[] memory path = new address[](3); - path[0] = inputToken; - path[1] = address(weth); - path[2] = outputToken; - - IBalancerV2Vault.BatchSwapStep[] memory swapSteps = new IBalancerV2Vault.BatchSwapStep[](2); - swapSteps[0] = IBalancerV2Vault.BatchSwapStep( - BALANCER_WETH_USDC_POOL, // poolId - 0, // assetInIndex - 1, // assetOutIndex - inputAmount, // amount - new bytes(0) // userData - ); - swapSteps[1] = IBalancerV2Vault.BatchSwapStep( - BALANCER_WETH_DAI_POOL, // poolId - 1, // assetInIndex - 2, // assetOutIndex - 0, // amount - new bytes(0) // userData - ); - - int256[] memory limits = _buildBalancerV2Limits(path, int256(inputAmount), 0); - - IAMMStrategy.Operation[] memory operations = new IAMMStrategy.Operation[](1); - - bytes memory payload0 = abi.encodeCall( - IBalancerV2Vault.batchSwap, - ( - IBalancerV2Vault.SwapKind.GIVEN_IN, - swapSteps, - path, - // Balancer supports internal balance which keeps user balance in their contract to skip actual token transfer for efficiency. - // AMM user should receive tokens right away after swap, so we need to turn off internal balance flag here. - IBalancerV2Vault.FundManagement({ - sender: address(ammStrategy), - fromInternalBalance: false, - recipient: payable(address(ammStrategy)), - toInternalBalance: false - }), - limits, - defaultDeadline - ) - ); - operations[0] = IAMMStrategy.Operation(BALANCER_V2_ADDRESS, 0, payload0); - - bytes memory data = abi.encode(operations); - _baseTest(inputToken, outputToken, inputAmount, data); - } - - function testTradeCurveV1WithUnderlyingCoin() public { - address inputToken = USDC_ADDRESS; - address outputToken = DAI_ADDRESS; - uint256 inputAmount = 10 * 1e6; - // address[] USDT_POOL_COINS = [cDAI_ADDRESS, cUSDC_ADDRESS, USDT_ADDRESS]; - int128 inputTokenIndex = 1; - int128 outputTokenIndex = 0; - - IAMMStrategy.Operation[] memory operations = new IAMMStrategy.Operation[](1); - // ICurveFi - bytes memory payload0 = abi.encodeWithSignature( - "exchange_underlying(int128,int128,uint256,uint256)", - inputTokenIndex, - outputTokenIndex, - inputAmount, - 0, - defaultDeadline - ); - operations[0] = IAMMStrategy.Operation(CURVE_USDT_POOL_ADDRESS, 0, payload0); - - bytes memory data = abi.encode(operations); - _baseTest(inputToken, outputToken, inputAmount, data); - } - - function testTradeCurveV1() public { - address inputToken = cUSDC_ADDRESS; - address outputToken = cDAI_ADDRESS; - uint256 inputAmount = 10 * 1e6; - // address[] USDT_POOL_COINS = [cDAI_ADDRESS, cUSDC_ADDRESS, USDT_ADDRESS]; - int128 inputTokenIndex = 1; - int128 outputTokenIndex = 0; - - IAMMStrategy.Operation[] memory operations = new IAMMStrategy.Operation[](1); - // ICurveFi - bytes memory payload0 = abi.encodeWithSignature( - "exchange(int128,int128,uint256,uint256)", - inputTokenIndex, - outputTokenIndex, - inputAmount, - 0, - defaultDeadline - ); - operations[0] = IAMMStrategy.Operation(CURVE_USDT_POOL_ADDRESS, 0, payload0); - - bytes memory data = abi.encode(operations); - _baseTest(inputToken, outputToken, inputAmount, data); - } - - function testTradeCurveV2WithETH() public { - address inputToken = Constant.ETH_ADDRESS; - address outputToken = WBTC_ADDRESS; - uint256 inputAmount = 1 ether; - // address[] TRICRYPTO2POOL_COINS = [USDT_ADDRESS, WBTC_ADDRESS, WETH_ADDRESS]; - int128 inputTokenIndex = 2; // WETH - int128 outputTokenIndex = 1; // WBTC - IAMMStrategy.Operation[] memory operations = new IAMMStrategy.Operation[](1); - // ICurveFiV2 - bytes memory payload0 = abi.encodeWithSignature( - "exchange(uint256,uint256,uint256,uint256,bool)", - uint256(uint128(inputTokenIndex)), - uint256(uint128(outputTokenIndex)), - inputAmount, - 0, - true // use_eth = true - ); - operations[0] = IAMMStrategy.Operation(CURVE_TRICRYPTO2_POOL_ADDRESS, inputAmount, payload0); - - bytes memory data = abi.encode(operations); - _baseTest(inputToken, outputToken, inputAmount, data); - } - - function testTradeCurveV2WithWETH() public { - address inputToken = WETH_ADDRESS; - address outputToken = WBTC_ADDRESS; - uint256 inputAmount = 10 * 1e18; - // address[] TRICRYPTO2POOL_COINS = [USDT_ADDRESS, WBTC_ADDRESS, WETH_ADDRESS]; - int128 inputTokenIndex = 2; // WETH - int128 outputTokenIndex = 1; // WBTC - IAMMStrategy.Operation[] memory operations = new IAMMStrategy.Operation[](1); - // ICurveFiV2 - bytes memory payload0 = abi.encodeWithSignature( - "exchange(uint256,uint256,uint256,uint256,bool)", - uint256(uint128(inputTokenIndex)), - uint256(uint128(outputTokenIndex)), - inputAmount, - 0, - false // use_eth = false - ); - operations[0] = IAMMStrategy.Operation(CURVE_TRICRYPTO2_POOL_ADDRESS, 0, payload0); - - bytes memory data = abi.encode(operations); - _baseTest(inputToken, outputToken, inputAmount, data); - } - - function _baseTest(address inputToken, address outputToken, uint256 inputAmount, bytes memory data) internal { - Snapshot memory inputTokenBalance = BalanceSnapshot.take(entryPoint, inputToken); - Snapshot memory outputTokenBalance = BalanceSnapshot.take(entryPoint, outputToken); - - if (inputToken == Constant.ETH_ADDRESS) { - IStrategy(ammStrategy).executeStrategy{ value: inputAmount }(inputToken, outputToken, inputAmount, data); - } else { - IERC20(inputToken).safeTransfer(address(ammStrategy), inputAmount); - IStrategy(ammStrategy).executeStrategy(inputToken, outputToken, inputAmount, data); - } - - inputTokenBalance.assertChange(-int256(inputAmount)); - outputTokenBalance.assertChangeGt(0); - } - - function _buildBalancerV2Limits(address[] memory _path, int256 inputAmount, int256 _minOutputAmount) internal pure returns (int256[] memory) { - int256[] memory limits = new int256[](_path.length); - // amount swapped in to balancer will denoted with positive sign - limits[0] = inputAmount; - for (uint256 i = 1; i < _path.length - 1; ++i) { - // we only care final maker asset out amount - limits[i] = type(int256).max; - } - // amount swapped out from balancer will denoted with negative sign - limits[_path.length - 1] = -_minOutputAmount; - return limits; - } -} diff --git a/test/forkMainnet/GenericSwap.t.sol b/test/forkMainnet/GenericSwap.t.sol index c356f9b2..af32c268 100644 --- a/test/forkMainnet/GenericSwap.t.sol +++ b/test/forkMainnet/GenericSwap.t.sol @@ -12,11 +12,14 @@ import { MockStrategy } from "test/mocks/MockStrategy.sol"; import { GenericSwap } from "contracts/GenericSwap.sol"; import { AllowanceTarget } from "contracts/AllowanceTarget.sol"; import { TokenCollector } from "contracts/abstracts/TokenCollector.sol"; -import { UniswapStrategy } from "contracts/UniswapStrategy.sol"; +import { SmartOrderStrategy } from "contracts/SmartOrderStrategy.sol"; import { Constant } from "contracts/libraries/Constant.sol"; +import { UniswapV3 } from "contracts/libraries/UniswapV3.sol"; import { GenericSwapData, getGSDataHash } from "contracts/libraries/GenericSwapData.sol"; import { IGenericSwap } from "contracts/interfaces/IGenericSwap.sol"; -import { IUniswapRouterV2 } from "contracts/interfaces/IUniswapRouterV2.sol"; +import { ISmartOrderStrategy } from "contracts/interfaces/ISmartOrderStrategy.sol"; +import { IUniswapV3Quoter } from "contracts/interfaces/IUniswapV3Quoter.sol"; +import { IUniswapSwapRouter02 } from "contracts/interfaces/IUniswapSwapRouter02.sol"; contract GenericSwapTest is Test, Tokens, BalanceUtil, Permit2Helper, SigHelper { using BalanceSnapshot for Snapshot; @@ -37,8 +40,13 @@ contract GenericSwapTest is Test, Tokens, BalanceUtil, Permit2Helper, SigHelper uint256 takerPrivateKey = uint256(1); address taker = vm.addr(takerPrivateKey); uint256 defaultExpiry = block.timestamp + 1; + address defaultInputToken = USDT_ADDRESS; + uint256 defaultInputAmount = 10 * 1e6; + address defaultOutputToken = DAI_ADDRESS; + address[] defaultPath = [defaultInputToken, defaultOutputToken]; + uint24[] defaultV3Fees = [3000]; bytes defaultTakerPermit; - UniswapStrategy uniswapStrategy; + SmartOrderStrategy smartStrategy; GenericSwap genericSwap; GenericSwapData defaultGSData; MockStrategy mockStrategy; @@ -53,16 +61,43 @@ contract GenericSwapTest is Test, Tokens, BalanceUtil, Permit2Helper, SigHelper allowanceTarget = new AllowanceTarget(allowanceTargetOwner, trusted); genericSwap = new GenericSwap(UNISWAP_PERMIT2_ADDRESS, address(allowanceTarget)); - uniswapStrategy = new UniswapStrategy(strategyAdmin, address(genericSwap), UNISWAP_V2_ADDRESS); + smartStrategy = new SmartOrderStrategy(strategyAdmin, address(genericSwap), WETH_ADDRESS); mockStrategy = new MockStrategy(); vm.prank(strategyAdmin); - uniswapStrategy.approveToken(USDT_ADDRESS, UNISWAP_V2_ADDRESS, type(uint256).max); - - address[] memory defaultPath = new address[](2); - defaultPath[0] = USDT_ADDRESS; - defaultPath[1] = DAI_ADDRESS; - bytes memory makerSpecificData = abi.encode(defaultExpiry, defaultPath); - bytes memory swapData = abi.encode(UNISWAP_V2_ADDRESS, makerSpecificData); + address[] memory tokenList = new address[](1); + tokenList[0] = USDT_ADDRESS; + address[] memory ammList = new address[](1); + ammList[0] = UNISWAP_SWAP_ROUTER_02_ADDRESS; + smartStrategy.approveTokens(tokenList, ammList); + + IUniswapV3Quoter v3Quoter = IUniswapV3Quoter(UNISWAP_V3_QUOTER_ADDRESS); + bytes memory encodedPath = UniswapV3.encodePath(defaultPath, defaultV3Fees); + uint256 expectedOut = v3Quoter.quoteExactInput(encodedPath, defaultInputAmount); + uint256 minOutputAmount = (expectedOut * 95) / 100; // default 5% slippage tolerance + bytes memory routerPayload = abi.encodeCall( + IUniswapSwapRouter02.exactInputSingle, + ( + IUniswapSwapRouter02.ExactInputSingleParams({ + tokenIn: defaultInputToken, + tokenOut: defaultOutputToken, + fee: defaultV3Fees[0], + recipient: address(smartStrategy), + amountIn: defaultInputAmount, + amountOutMinimum: minOutputAmount, + sqrtPriceLimitX96: 0 + }) + ) + ); + ISmartOrderStrategy.Operation[] memory operations = new ISmartOrderStrategy.Operation[](1); + operations[0] = ISmartOrderStrategy.Operation({ + dest: UNISWAP_SWAP_ROUTER_02_ADDRESS, + inputToken: defaultInputToken, + inputRatio: 0, // zero ratio indicate no replacement + dataOffset: 0, + value: 0, + data: routerPayload + }); + bytes memory swapData = abi.encode(operations); deal(taker, 100 ether); setTokenBalanceAndApprove(taker, UNISWAP_PERMIT2_ADDRESS, tokens, 100000); @@ -70,12 +105,12 @@ contract GenericSwapTest is Test, Tokens, BalanceUtil, Permit2Helper, SigHelper setTokenBalanceAndApprove(address(mockStrategy), UNISWAP_PERMIT2_ADDRESS, tokens, 100000); defaultGSData = GenericSwapData({ - maker: payable(address(uniswapStrategy)), - takerToken: USDT_ADDRESS, - takerTokenAmount: 10 * 1e6, - makerToken: DAI_ADDRESS, - makerTokenAmount: 0, // to be filled later - minMakerTokenAmount: 0, // to be filled later + maker: payable(address(smartStrategy)), + takerToken: defaultInputToken, + takerTokenAmount: defaultInputAmount, + makerToken: defaultOutputToken, + makerTokenAmount: expectedOut, + minMakerTokenAmount: minOutputAmount, expiry: defaultExpiry, salt: 5678, recipient: payable(taker), @@ -83,13 +118,6 @@ contract GenericSwapTest is Test, Tokens, BalanceUtil, Permit2Helper, SigHelper }); defaultTakerPermit = getTokenlonPermit2Data(taker, takerPrivateKey, defaultGSData.takerToken, address(genericSwap)); - - IUniswapRouterV2 router = IUniswapRouterV2(UNISWAP_V2_ADDRESS); - uint256[] memory amounts = router.getAmountsOut(defaultGSData.takerTokenAmount, defaultPath); - uint256 expectedOut = amounts[amounts.length - 1]; - // update defaultGSData - defaultGSData.makerTokenAmount = expectedOut; - defaultGSData.minMakerTokenAmount = (expectedOut * 95) / 100; // default 5% slippage tolerance } function testGenericSwapWithUniswap() public { diff --git a/test/forkMainnet/LimitOrderSwap/CoordinatedTaker.t.sol b/test/forkMainnet/LimitOrderSwap/CoordinatedTaker.t.sol index c626901d..078005e0 100644 --- a/test/forkMainnet/LimitOrderSwap/CoordinatedTaker.t.sol +++ b/test/forkMainnet/LimitOrderSwap/CoordinatedTaker.t.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -import { IUniswapRouterV2 } from "contracts/interfaces/IUniswapRouterV2.sol"; import { ILimitOrderSwap } from "contracts/interfaces/ILimitOrderSwap.sol"; import { ICoordinatedTaker } from "contracts/interfaces/ICoordinatedTaker.sol"; import { IWETH } from "contracts/interfaces/IWETH.sol"; @@ -25,7 +24,7 @@ contract CoordinatedTakerTest is LimitOrderSwapTest { address user = vm.addr(userPrivateKey); address[] tokenList = [USDC_ADDRESS, USDT_ADDRESS, DAI_ADDRESS, WETH_ADDRESS, WBTC_ADDRESS]; - address[] ammList = [UNISWAP_V2_ADDRESS, SUSHISWAP_ADDRESS, BALANCER_V2_ADDRESS, CURVE_USDT_POOL_ADDRESS]; + address[] ammList = [UNISWAP_SWAP_ROUTER_02_ADDRESS, SUSHISWAP_ADDRESS]; uint256 crdPrivateKey = uint256(2); address coordinator = vm.addr(crdPrivateKey); diff --git a/test/forkMainnet/LimitOrderSwap/Fill.t.sol b/test/forkMainnet/LimitOrderSwap/Fill.t.sol index 001f2e96..88f62088 100644 --- a/test/forkMainnet/LimitOrderSwap/Fill.t.sol +++ b/test/forkMainnet/LimitOrderSwap/Fill.t.sol @@ -4,11 +4,11 @@ pragma solidity 0.8.17; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { IUniswapRouterV2 } from "contracts/interfaces/IUniswapRouterV2.sol"; import { ILimitOrderSwap } from "contracts/interfaces/ILimitOrderSwap.sol"; import { Constant } from "contracts/libraries/Constant.sol"; import { LimitOrder, getLimitOrderHash } from "contracts/libraries/LimitOrder.sol"; import { BalanceSnapshot, Snapshot } from "test/utils/BalanceSnapshot.sol"; +import { UniswapV2Library } from "test/utils/UniswapV2Library.sol"; import { LimitOrderSwapTest } from "test/forkMainnet/LimitOrderSwap/Setup.t.sol"; import { MockStrategy } from "test/mocks/MockStrategy.sol"; @@ -74,8 +74,7 @@ contract FillTest is LimitOrderSwapTest { uint256 fee = (order.makerTokenAmount * defaultFeeFactor) / Constant.BPS_MAX; { // update order takerTokenAmount by AMM quote - IUniswapRouterV2 router = IUniswapRouterV2(UNISWAP_V2_ADDRESS); - uint256[] memory amounts = router.getAmountsOut(defaultOrder.makerTokenAmount - fee, defaultAMMPath); + uint256[] memory amounts = UniswapV2Library.getAmountsOut(defaultOrder.makerTokenAmount - fee, defaultAMMPath); order.takerTokenAmount = amounts[amounts.length - 1]; } @@ -83,8 +82,8 @@ contract FillTest is LimitOrderSwapTest { bytes memory extraAction; { - bytes memory makerSpecificData = abi.encode(defaultExpiry, defaultAMMPath); - bytes memory strategyData = abi.encode(UNISWAP_V2_ADDRESS, makerSpecificData); + bytes memory makerSpecificData = abi.encode(defaultAMMPath); + bytes memory strategyData = abi.encode(UNISWAP_SWAP_ROUTER_02_ADDRESS, makerSpecificData); extraAction = abi.encode(address(mockLimitOrderTaker), strategyData); } diff --git a/test/forkMainnet/LimitOrderSwap/Setup.t.sol b/test/forkMainnet/LimitOrderSwap/Setup.t.sol index 8ec59032..116f2a1e 100644 --- a/test/forkMainnet/LimitOrderSwap/Setup.t.sol +++ b/test/forkMainnet/LimitOrderSwap/Setup.t.sol @@ -62,7 +62,7 @@ contract LimitOrderSwapTest is Test, Tokens, BalanceUtil, Permit2Helper, SigHelp allowanceTarget = new AllowanceTarget(allowanceTargetOwner, trusted); limitOrderSwap = new LimitOrderSwap(limitOrderOwner, UNISWAP_PERMIT2_ADDRESS, address(allowanceTarget), IWETH(WETH_ADDRESS), feeCollector); - mockLimitOrderTaker = new MockLimitOrderTaker(walletOwner, UNISWAP_V2_ADDRESS); + mockLimitOrderTaker = new MockLimitOrderTaker(walletOwner, UNISWAP_SWAP_ROUTER_02_ADDRESS); deal(maker, 100 ether); setTokenBalanceAndApprove(maker, UNISWAP_PERMIT2_ADDRESS, tokens, 100000); @@ -76,7 +76,7 @@ contract LimitOrderSwapTest is Test, Tokens, BalanceUtil, Permit2Helper, SigHelp tokenList[0] = DAI_ADDRESS; tokenList[1] = USDT_ADDRESS; vm.startPrank(walletOwner); - mockLimitOrderTaker.setAllowance(tokenList, UNISWAP_V2_ADDRESS); + mockLimitOrderTaker.setAllowance(tokenList, UNISWAP_SWAP_ROUTER_02_ADDRESS); vm.stopPrank(); defaultOrder = LimitOrder({ diff --git a/test/forkMainnet/SmartOrderStrategy/AMMs.t.sol b/test/forkMainnet/SmartOrderStrategy/AMMs.t.sol index 56a7a793..db956d0d 100644 --- a/test/forkMainnet/SmartOrderStrategy/AMMs.t.sol +++ b/test/forkMainnet/SmartOrderStrategy/AMMs.t.sol @@ -5,11 +5,12 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { SmartOrderStrategyTest } from "./Setup.t.sol"; -import { IUniswapRouterV2 } from "contracts/interfaces/IUniswapRouterV2.sol"; import { ICurveFiV2 } from "contracts/interfaces/ICurveFiV2.sol"; import { ISmartOrderStrategy } from "contracts/interfaces/ISmartOrderStrategy.sol"; +import { IUniswapSwapRouter02 } from "contracts/interfaces/IUniswapSwapRouter02.sol"; import { Constant } from "contracts/libraries/Constant.sol"; import { BalanceSnapshot, Snapshot } from "test/utils/BalanceSnapshot.sol"; +import { UniswapV2Library } from "test/utils/UniswapV2Library.sol"; contract AMMsTest is SmartOrderStrategyTest { using SafeERC20 for IERC20; @@ -17,16 +18,15 @@ contract AMMsTest is SmartOrderStrategyTest { function testUniswapV2WithoutAmountReplace() public { bytes memory uniswapData = abi.encodeWithSelector( - IUniswapRouterV2.swapExactTokensForTokens.selector, + IUniswapSwapRouter02.swapExactTokensForTokens.selector, defaultInputAmount, 0, // minOutputAmount defaultUniV2Path, - address(smartOrderStrategy), - defaultExpiry + address(smartOrderStrategy) ); ISmartOrderStrategy.Operation[] memory operations = new ISmartOrderStrategy.Operation[](1); operations[0] = ISmartOrderStrategy.Operation({ - dest: UNISWAP_V2_ADDRESS, + dest: UNISWAP_SWAP_ROUTER_02_ADDRESS, inputToken: defaultInputToken, inputRatio: 0, // zero ratio indicate no replacement dataOffset: 0, @@ -36,8 +36,7 @@ contract AMMsTest is SmartOrderStrategyTest { bytes memory data = abi.encode(operations); // get the exact quote from uniswap - IUniswapRouterV2 router = IUniswapRouterV2(UNISWAP_V2_ADDRESS); - uint256[] memory amounts = router.getAmountsOut(defaultInputAmount, defaultUniV2Path); + uint256[] memory amounts = UniswapV2Library.getAmountsOut(defaultInputAmount, defaultUniV2Path); uint256 expectedOut = amounts[amounts.length - 1]; vm.startPrank(genericSwap, genericSwap); @@ -53,16 +52,15 @@ contract AMMsTest is SmartOrderStrategyTest { function testUniswapV2WithAmountReplace() public { bytes memory uniswapData = abi.encodeWithSelector( - IUniswapRouterV2.swapExactTokensForTokens.selector, + IUniswapSwapRouter02.swapExactTokensForTokens.selector, defaultInputAmount, 0, defaultUniV2Path, - address(smartOrderStrategy), - defaultExpiry + address(smartOrderStrategy) ); ISmartOrderStrategy.Operation[] memory operations = new ISmartOrderStrategy.Operation[](1); operations[0] = ISmartOrderStrategy.Operation({ - dest: UNISWAP_V2_ADDRESS, + dest: UNISWAP_SWAP_ROUTER_02_ADDRESS, inputToken: defaultInputToken, inputRatio: defaultInputRatio, dataOffset: uint128(4 + 32), // add 32 bytes of length prefix @@ -73,8 +71,7 @@ contract AMMsTest is SmartOrderStrategyTest { // get the exact quote from uniswap uint256 inputAmountAfterRatio = (defaultInputAmount * defaultInputRatio) / Constant.BPS_MAX; - IUniswapRouterV2 router = IUniswapRouterV2(UNISWAP_V2_ADDRESS); - uint256[] memory amounts = router.getAmountsOut(inputAmountAfterRatio, defaultUniV2Path); + uint256[] memory amounts = UniswapV2Library.getAmountsOut(inputAmountAfterRatio, defaultUniV2Path); uint256 expectedOut = amounts[amounts.length - 1]; vm.startPrank(genericSwap, genericSwap); @@ -90,16 +87,15 @@ contract AMMsTest is SmartOrderStrategyTest { function testUniswapV2WithMaxAmountReplace() public { bytes memory uniswapData = abi.encodeWithSelector( - IUniswapRouterV2.swapExactTokensForTokens.selector, + IUniswapSwapRouter02.swapExactTokensForTokens.selector, defaultInputAmount, 0, defaultUniV2Path, - address(smartOrderStrategy), - defaultExpiry + address(smartOrderStrategy) ); ISmartOrderStrategy.Operation[] memory operations = new ISmartOrderStrategy.Operation[](1); operations[0] = ISmartOrderStrategy.Operation({ - dest: UNISWAP_V2_ADDRESS, + dest: UNISWAP_SWAP_ROUTER_02_ADDRESS, inputToken: defaultInputToken, inputRatio: Constant.BPS_MAX, // BPS_MAX indicate the input amount will be replaced by the actual balance dataOffset: uint128(4 + 32), // add 32 bytes of length prefix @@ -112,8 +108,7 @@ contract AMMsTest is SmartOrderStrategyTest { uint256 actualInputAmount = 5678; // get the exact quote from uniswap - IUniswapRouterV2 router = IUniswapRouterV2(UNISWAP_V2_ADDRESS); - uint256[] memory amounts = router.getAmountsOut(actualInputAmount, defaultUniV2Path); + uint256[] memory amounts = UniswapV2Library.getAmountsOut(actualInputAmount, defaultUniV2Path); uint256 expectedOut = amounts[amounts.length - 1]; vm.startPrank(genericSwap, genericSwap); @@ -130,16 +125,15 @@ contract AMMsTest is SmartOrderStrategyTest { function testUniswapV2WithWETHUnwrap() public { bytes memory uniswapData = abi.encodeWithSelector( - IUniswapRouterV2.swapExactTokensForTokens.selector, + IUniswapSwapRouter02.swapExactTokensForTokens.selector, defaultInputAmount, 0, // minOutputAmount defaultUniV2Path, - address(smartOrderStrategy), - defaultExpiry + address(smartOrderStrategy) ); ISmartOrderStrategy.Operation[] memory operations = new ISmartOrderStrategy.Operation[](2); operations[0] = ISmartOrderStrategy.Operation({ - dest: UNISWAP_V2_ADDRESS, + dest: UNISWAP_SWAP_ROUTER_02_ADDRESS, inputToken: defaultInputToken, inputRatio: 0, // zero ratio indicate no replacement dataOffset: 0, @@ -149,8 +143,7 @@ contract AMMsTest is SmartOrderStrategyTest { bytes memory data = abi.encode(operations); // get the exact quote from uniswap - IUniswapRouterV2 router = IUniswapRouterV2(UNISWAP_V2_ADDRESS); - uint256[] memory amounts = router.getAmountsOut(defaultInputAmount, defaultUniV2Path); + uint256[] memory amounts = UniswapV2Library.getAmountsOut(defaultInputAmount, defaultUniV2Path); uint256 expectedOut = amounts[amounts.length - 1]; // set output token as ETH @@ -172,17 +165,15 @@ contract AMMsTest is SmartOrderStrategyTest { // Curve : WETH -> USDT // get the exact quote from uniswap - IUniswapRouterV2 router = IUniswapRouterV2(UNISWAP_V2_ADDRESS); - uint256[] memory amounts = router.getAmountsOut(defaultInputAmount, defaultUniV2Path); + uint256[] memory amounts = UniswapV2Library.getAmountsOut(defaultInputAmount, defaultUniV2Path); uint256 uniOut = amounts[amounts.length - 1]; bytes memory uniswapData = abi.encodeWithSelector( - IUniswapRouterV2.swapExactTokensForTokens.selector, + IUniswapSwapRouter02.swapExactTokensForTokens.selector, defaultInputAmount, 0, // minOutputAmount defaultUniV2Path, - address(smartOrderStrategy), - defaultExpiry + address(smartOrderStrategy) ); // exhange function selector : 0x5b41b908 @@ -192,7 +183,7 @@ contract AMMsTest is SmartOrderStrategyTest { ISmartOrderStrategy.Operation[] memory operations = new ISmartOrderStrategy.Operation[](2); operations[0] = ISmartOrderStrategy.Operation({ - dest: UNISWAP_V2_ADDRESS, + dest: UNISWAP_SWAP_ROUTER_02_ADDRESS, inputToken: USDC_ADDRESS, inputRatio: 0, // zero ratio indicate no replacement dataOffset: 0, diff --git a/test/forkMainnet/SmartOrderStrategy/Setup.t.sol b/test/forkMainnet/SmartOrderStrategy/Setup.t.sol index e6448182..2f55b813 100644 --- a/test/forkMainnet/SmartOrderStrategy/Setup.t.sol +++ b/test/forkMainnet/SmartOrderStrategy/Setup.t.sol @@ -16,8 +16,9 @@ contract SmartOrderStrategyTest is Test, Tokens, BalanceUtil { uint256 defaultExpiry = block.timestamp + 100; bytes defaultOpsData; address[] defaultUniV2Path = [USDC_ADDRESS, WETH_ADDRESS]; - address[] tokenList = [USDT_ADDRESS, USDC_ADDRESS, cUSDC_ADDRESS, WETH_ADDRESS, WBTC_ADDRESS]; - address[] ammList = [UNISWAP_V2_ADDRESS, SUSHISWAP_ADDRESS, BALANCER_V2_ADDRESS, CURVE_USDT_POOL_ADDRESS, CURVE_TRICRYPTO2_POOL_ADDRESS]; + address[] tokenList = [USDT_ADDRESS, USDC_ADDRESS, WETH_ADDRESS, WBTC_ADDRESS]; + address[] ammList = [UNISWAP_SWAP_ROUTER_02_ADDRESS, SUSHISWAP_ADDRESS, CURVE_TRICRYPTO2_POOL_ADDRESS]; + SmartOrderStrategy smartOrderStrategy; function setUp() public virtual { diff --git a/test/forkMainnet/UniAgent/SwapRouter02.t.sol b/test/forkMainnet/UniAgent/SwapRouter02.t.sol index 764dbd4f..9c2369c1 100644 --- a/test/forkMainnet/UniAgent/SwapRouter02.t.sol +++ b/test/forkMainnet/UniAgent/SwapRouter02.t.sol @@ -1,18 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -import { ISwapRouter02 } from "contracts/interfaces/IUniswapSwapRouter02.sol"; -import { IUniswapRouterV2 } from "contracts/interfaces/IUniswapRouterV2.sol"; +import { IUniswapSwapRouter02 } from "contracts/interfaces/IUniswapSwapRouter02.sol"; import { IUniswapV3Quoter } from "contracts/interfaces/IUniswapV3Quoter.sol"; import { IUniAgent } from "contracts/interfaces/IUniAgent.sol"; import { UniswapV3 } from "contracts/libraries/UniswapV3.sol"; import { BalanceSnapshot, Snapshot } from "test/utils/BalanceSnapshot.sol"; +import { UniswapV2Library } from "test/utils/UniswapV2Library.sol"; import { UniAgentTest } from "test/forkMainnet/UniAgent/Setup.t.sol"; contract SwapRouter02Test is UniAgentTest { using BalanceSnapshot for Snapshot; - IUniswapRouterV2 v2Router = IUniswapRouterV2(UNISWAP_V2_ADDRESS); IUniswapV3Quoter v3Quoter = IUniswapV3Quoter(UNISWAP_V3_QUOTER_ADDRESS); uint256 defaultOutputAmount; uint24 defaultFee = 3000; @@ -27,10 +26,10 @@ contract SwapRouter02Test is UniAgentTest { Snapshot memory userInputToken = BalanceSnapshot.take({ owner: user, token: defaultInputToken }); Snapshot memory recvOutputToken = BalanceSnapshot.take({ owner: recipient, token: defaultOutputToken }); - uint256[] memory amounts = v2Router.getAmountsOut(defaultInputAmount, defaultPath); + uint256[] memory amounts = UniswapV2Library.getAmountsOut(defaultInputAmount, defaultPath); uint256 outputAmount = amounts[amounts.length - 1]; uint256 minOutputAmount = (defaultOutputAmount * 95) / 100; // default 5% slippage tolerance - bytes memory payload = abi.encodeCall(ISwapRouter02.swapExactTokensForTokens, (defaultInputAmount, minOutputAmount, defaultPath, recipient)); + bytes memory payload = abi.encodeCall(IUniswapSwapRouter02.swapExactTokensForTokens, (defaultInputAmount, minOutputAmount, defaultPath, recipient)); vm.prank(user); uniAgent.swap(IUniAgent.RouterType.SwapRouter02, defaultInputToken, defaultInputAmount, payload, defaultUserPermit); @@ -49,9 +48,9 @@ contract SwapRouter02Test is UniAgentTest { defaultOutputAmount = v3Quoter.quoteExactInput(encodedPath, defaultInputAmount); uint256 minOutputAmount = (defaultOutputAmount * 95) / 100; // default 5% slippage tolerance bytes memory payload = abi.encodeCall( - ISwapRouter02.exactInputSingle, + IUniswapSwapRouter02.exactInputSingle, ( - ISwapRouter02.ExactInputSingleParams({ + IUniswapSwapRouter02.ExactInputSingleParams({ tokenIn: defaultInputToken, tokenOut: defaultOutputToken, fee: defaultFee, diff --git a/test/forkMainnet/UniAgent/Universal.t.sol b/test/forkMainnet/UniAgent/Universal.t.sol index 7d2a91d7..53c8be1e 100644 --- a/test/forkMainnet/UniAgent/Universal.t.sol +++ b/test/forkMainnet/UniAgent/Universal.t.sol @@ -1,36 +1,24 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -import { IUniswapRouterV2 } from "contracts/interfaces/IUniswapRouterV2.sol"; import { IUniswapV3Quoter } from "contracts/interfaces/IUniswapV3Quoter.sol"; import { IUniversalRouter } from "contracts/interfaces/IUniswapUniversalRouter.sol"; import { IUniAgent } from "contracts/interfaces/IUniAgent.sol"; import { UniswapV3 } from "contracts/libraries/UniswapV3.sol"; import { UniswapCommands } from "test/utils/UniswapCommands.sol"; +import { UniswapV2Library } from "test/utils/UniswapV2Library.sol"; import { BalanceSnapshot, Snapshot } from "test/utils/BalanceSnapshot.sol"; import { UniAgentTest } from "test/forkMainnet/UniAgent/Setup.t.sol"; contract UniversalTest is UniAgentTest { using BalanceSnapshot for Snapshot; - IUniswapRouterV2 v2Router; IUniswapV3Quoter v3Quoter; - uint256 defaultOutputAmount; - bytes defaultRouterPayload; function setUp() public override { super.setUp(); - v2Router = IUniswapRouterV2(UNISWAP_V2_ADDRESS); v3Quoter = IUniswapV3Quoter(UNISWAP_V3_QUOTER_ADDRESS); - uint256[] memory amounts = v2Router.getAmountsOut(defaultInputAmount, defaultPath); - defaultOutputAmount = amounts[amounts.length - 1]; - uint256 minOutputAmount = (defaultOutputAmount * 95) / 100; // default 5% slippage tolerance - - bytes memory cmds = abi.encodePacked(bytes1(uint8(UniswapCommands.V2_SWAP_EXACT_IN))); - bytes[] memory inputs = new bytes[](1); - inputs[0] = abi.encode(recipient, defaultInputAmount, minOutputAmount, defaultPath, false); - defaultRouterPayload = abi.encodeCall(IUniversalRouter.execute, (cmds, inputs, defaultExpiry)); } function testURV2SwapExactIn() public { @@ -38,12 +26,21 @@ contract UniversalTest is UniAgentTest { Snapshot memory userInputToken = BalanceSnapshot.take({ owner: user, token: defaultInputToken }); Snapshot memory recvOutputToken = BalanceSnapshot.take({ owner: recipient, token: defaultOutputToken }); + uint256[] memory amounts = UniswapV2Library.getAmountsOut(defaultInputAmount, defaultPath); + uint256 outputAmount = amounts[amounts.length - 1]; + uint256 minOutputAmount = (outputAmount * 95) / 100; // default 5% slippage tolerance + + bytes memory cmds = abi.encodePacked(bytes1(uint8(UniswapCommands.V2_SWAP_EXACT_IN))); + bytes[] memory inputs = new bytes[](1); + inputs[0] = abi.encode(recipient, defaultInputAmount, minOutputAmount, defaultPath, false); + bytes memory payload = abi.encodeCall(IUniversalRouter.execute, (cmds, inputs, defaultExpiry)); + vm.prank(user); - uniAgent.swap(IUniAgent.RouterType.UniversalRouter, defaultInputToken, defaultInputAmount, defaultRouterPayload, defaultUserPermit); + uniAgent.swap(IUniAgent.RouterType.UniversalRouter, defaultInputToken, defaultInputAmount, payload, defaultUserPermit); userInputToken.assertChange(-int256(defaultInputAmount)); // recipient should receive exact amount of quote from Uniswap - recvOutputToken.assertChange(int256(defaultOutputAmount)); + recvOutputToken.assertChange(int256(outputAmount)); } function testURV3SwapExactIn() public { @@ -72,8 +69,13 @@ contract UniversalTest is UniAgentTest { function testURHandleRouterError() public { vm.warp(defaultExpiry + 1); + bytes memory cmds = abi.encodePacked(bytes1(uint8(UniswapCommands.V2_SWAP_EXACT_IN))); + bytes[] memory inputs = new bytes[](1); + inputs[0] = abi.encode(recipient, defaultInputAmount, 0, defaultPath, false); + bytes memory payload = abi.encodeCall(IUniversalRouter.execute, (cmds, inputs, defaultExpiry)); + vm.expectRevert(IUniversalRouter.TransactionDeadlinePassed.selector); vm.prank(user); - uniAgent.swap(IUniAgent.RouterType.UniversalRouter, defaultInputToken, defaultInputAmount, defaultRouterPayload, defaultUserPermit); + uniAgent.swap(IUniAgent.RouterType.UniversalRouter, defaultInputToken, defaultInputAmount, payload, defaultUserPermit); } } diff --git a/test/forkMainnet/UniAgent/V2.t.sol b/test/forkMainnet/UniAgent/V2.t.sol index ae997ff0..a56eddc0 100644 --- a/test/forkMainnet/UniAgent/V2.t.sol +++ b/test/forkMainnet/UniAgent/V2.t.sol @@ -1,29 +1,28 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.17; -import { IUniswapRouterV2 } from "contracts//interfaces/IUniswapRouterV2.sol"; import { IUniAgent } from "contracts/interfaces/IUniAgent.sol"; +import { IUniswapV2Router } from "contracts/interfaces/IUniswapV2Router.sol"; import { Constant } from "contracts/libraries/Constant.sol"; import { BalanceSnapshot, Snapshot } from "test/utils/BalanceSnapshot.sol"; +import { UniswapV2Library } from "test/utils/UniswapV2Library.sol"; import { UniAgentTest } from "test/forkMainnet/UniAgent/Setup.t.sol"; contract V2Test is UniAgentTest { using BalanceSnapshot for Snapshot; - IUniswapRouterV2 v2Router; uint256 defaultOutputAmount; bytes defaultRouterPayload; function setUp() public override { super.setUp(); - v2Router = IUniswapRouterV2(UNISWAP_V2_ADDRESS); - uint256[] memory amounts = v2Router.getAmountsOut(defaultInputAmount, defaultPath); + uint256[] memory amounts = UniswapV2Library.getAmountsOut(defaultInputAmount, defaultPath); defaultOutputAmount = amounts[amounts.length - 1]; uint256 minOutputAmount = (defaultOutputAmount * 95) / 100; // default 5% slippage tolerance defaultRouterPayload = abi.encodeCall( - IUniswapRouterV2.swapExactTokensForTokens, + IUniswapV2Router.swapExactTokensForTokens, (defaultInputAmount, minOutputAmount, defaultPath, recipient, defaultExpiry) ); } @@ -52,10 +51,10 @@ contract V2Test is UniAgentTest { Snapshot memory userInputToken = BalanceSnapshot.take({ owner: user, token: inputToken }); Snapshot memory recvOutputToken = BalanceSnapshot.take({ owner: recipient, token: path[1] }); - uint256[] memory amounts = v2Router.getAmountsOut(defaultInputAmount, path); + uint256[] memory amounts = UniswapV2Library.getAmountsOut(defaultInputAmount, path); uint256 outputAmount = amounts[amounts.length - 1]; - bytes memory payload = abi.encodeCall(IUniswapRouterV2.swapExactETHForTokens, (outputAmount, path, recipient, defaultExpiry)); + bytes memory payload = abi.encodeCall(IUniswapV2Router.swapExactETHForTokens, (outputAmount, path, recipient, defaultExpiry)); vm.prank(user); uniAgent.swap{ value: defaultInputAmount }(IUniAgent.RouterType.V2Router, inputToken, defaultInputAmount, payload, defaultUserPermit); @@ -76,10 +75,10 @@ contract V2Test is UniAgentTest { Snapshot memory userInputToken = BalanceSnapshot.take({ owner: user, token: path[0] }); Snapshot memory recvOutputToken = BalanceSnapshot.take({ owner: recipient, token: outputToken }); - uint256[] memory amounts = v2Router.getAmountsOut(defaultInputAmount, path); + uint256[] memory amounts = UniswapV2Library.getAmountsOut(defaultInputAmount, path); uint256 outputAmount = amounts[amounts.length - 1]; - bytes memory payload = abi.encodeCall(IUniswapRouterV2.swapExactTokensForETH, (defaultInputAmount, outputAmount, path, recipient, defaultExpiry)); + bytes memory payload = abi.encodeCall(IUniswapV2Router.swapExactTokensForETH, (defaultInputAmount, outputAmount, path, recipient, defaultExpiry)); vm.prank(user); uniAgent.swap(IUniAgent.RouterType.V2Router, path[0], defaultInputAmount, payload, defaultUserPermit); @@ -99,10 +98,10 @@ contract V2Test is UniAgentTest { path[0] = inputToken; path[1] = defaultOutputToken; - uint256[] memory amounts = v2Router.getAmountsOut(defaultInputAmount, path); + uint256[] memory amounts = UniswapV2Library.getAmountsOut(defaultInputAmount, path); uint256 outputAmount = amounts[amounts.length - 1]; - bytes memory payload = abi.encodeCall(IUniswapRouterV2.swapExactTokensForTokens, (defaultInputAmount, outputAmount, path, recipient, defaultExpiry)); + bytes memory payload = abi.encodeCall(IUniswapV2Router.swapExactTokensForTokens, (defaultInputAmount, outputAmount, path, recipient, defaultExpiry)); bytes memory userPermit = getTokenlonPermit2Data(user, userPrivateKey, inputToken, address(uniAgent)); vm.prank(user); @@ -130,10 +129,10 @@ contract V2Test is UniAgentTest { path[0] = inputToken; path[1] = defaultOutputToken; - uint256[] memory amounts = v2Router.getAmountsOut(defaultInputAmount, path); + uint256[] memory amounts = UniswapV2Library.getAmountsOut(defaultInputAmount, path); uint256 outputAmount = amounts[amounts.length - 1]; - bytes memory payload = abi.encodeCall(IUniswapRouterV2.swapExactTokensForTokens, (defaultInputAmount, outputAmount, path, recipient, defaultExpiry)); + bytes memory payload = abi.encodeCall(IUniswapV2Router.swapExactTokensForTokens, (defaultInputAmount, outputAmount, path, recipient, defaultExpiry)); bytes memory userPermit = getTokenlonPermit2Data(user, userPrivateKey, inputToken, address(uniAgent)); // should still succeed even re-approve the token diff --git a/test/mocks/MockLimitOrderTaker.sol b/test/mocks/MockLimitOrderTaker.sol index d49e5ecb..4cf2487f 100644 --- a/test/mocks/MockLimitOrderTaker.sol +++ b/test/mocks/MockLimitOrderTaker.sol @@ -6,30 +6,29 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { Ownable } from "contracts/abstracts/Ownable.sol"; import { IStrategy } from "contracts/interfaces/IStrategy.sol"; -import { IUniswapRouterV2 } from "contracts/interfaces/IUniswapRouterV2.sol"; +import { IUniswapSwapRouter02 } from "contracts/interfaces/IUniswapSwapRouter02.sol"; import { MockERC1271Wallet } from "./MockERC1271Wallet.sol"; contract MockLimitOrderTaker is IStrategy, MockERC1271Wallet { using SafeERC20 for IERC20; - IUniswapRouterV2 public immutable uniswapV2Router; + IUniswapSwapRouter02 public immutable uniswapRouter02; - constructor(address _operator, address _uniswapV2Router) MockERC1271Wallet(_operator) { - uniswapV2Router = IUniswapRouterV2(_uniswapV2Router); + constructor(address _operator, address _uniswapRouter02) MockERC1271Wallet(_operator) { + uniswapRouter02 = IUniswapSwapRouter02(_uniswapRouter02); } function executeStrategy(address inputToken, address outputToken, uint256 inputAmount, bytes calldata data) external payable override { (address routerAddr, bytes memory makerSpecificData) = abi.decode(data, (address, bytes)); - require(routerAddr == address(uniswapV2Router), "non supported protocol"); + require(routerAddr == address(uniswapRouter02), "non supported protocol"); - (uint256 deadline, address[] memory path) = abi.decode(makerSpecificData, (uint256, address[])); + address[] memory path = abi.decode(makerSpecificData, (address[])); _validateAMMPath(inputToken, outputToken, path); - _tradeUniswapV2TokenToToken(inputAmount, deadline, path); + _tradeUniswapV2TokenToToken(inputAmount, path); } - function _tradeUniswapV2TokenToToken(uint256 _inputAmount, uint256 _deadline, address[] memory _path) internal returns (uint256) { - uint256[] memory amounts = uniswapV2Router.swapExactTokensForTokens(_inputAmount, 0, _path, address(this), _deadline); - return amounts[amounts.length - 1]; + function _tradeUniswapV2TokenToToken(uint256 _inputAmount, address[] memory _path) internal returns (uint256) { + return uniswapRouter02.swapExactTokensForTokens(_inputAmount, 0, _path, address(this)); } function _validateAMMPath(address _inputToken, address _outputToken, address[] memory _path) internal pure { diff --git a/test/utils/Addresses.sol b/test/utils/Addresses.sol index c1b959cd..0583940c 100644 --- a/test/utils/Addresses.sol +++ b/test/utils/Addresses.sol @@ -14,33 +14,13 @@ contract Addresses is Test { address DAI_ADDRESS = abi.decode(vm.parseJson(file, "$.DAI_ADDRESS"), (address)); address LON_ADDRESS = abi.decode(vm.parseJson(file, "$.LON_ADDRESS"), (address)); address WBTC_ADDRESS = abi.decode(vm.parseJson(file, "$.WBTC_ADDRESS"), (address)); - address ANKRETH_ADDRESS = abi.decode(vm.parseJson(file, "$.ANKRETH_ADDRESS"), (address)); - address UNISWAP_V2_ADDRESS = abi.decode(vm.parseJson(file, "$.UNISWAP_V2_ADDRESS"), (address)); + address CURVE_TRICRYPTO2_POOL_ADDRESS = abi.decode(vm.parseJson(file, "$.CURVE_TRICRYPTO2_POOL_ADDRESS"), (address)); address SUSHISWAP_ADDRESS = abi.decode(vm.parseJson(file, "$.SUSHISWAP_ADDRESS"), (address)); - address UNISWAP_V3_ADDRESS = abi.decode(vm.parseJson(file, "$.UNISWAP_V3_ADDRESS"), (address)); address UNISWAP_V3_QUOTER_ADDRESS = abi.decode(vm.parseJson(file, "$.UNISWAP_V3_QUOTER_ADDRESS"), (address)); address UNISWAP_PERMIT2_ADDRESS = abi.decode(vm.parseJson(file, "$.UNISWAP_PERMIT2_ADDRESS"), (address)); + address UNISWAP_SWAP_ROUTER_02_ADDRESS = abi.decode(vm.parseJson(file, "$.UNISWAP_SWAP_ROUTER_02_ADDRESS"), (address)); address UNISWAP_UNIVERSAL_ROUTER_ADDRESS = abi.decode(vm.parseJson(file, "$.UNISWAP_UNIVERSAL_ROUTER_ADDRESS"), (address)); - address CURVE_USDT_POOL_ADDRESS = abi.decode(vm.parseJson(file, "$.CURVE_USDT_POOL_ADDRESS"), (address)); - address CURVE_COMPOUND_POOL_ADDRESS = abi.decode(vm.parseJson(file, "$.CURVE_COMPOUND_POOL_ADDRESS"), (address)); - address CURVE_Y_POOL_ADDRESS = abi.decode(vm.parseJson(file, "$.CURVE_Y_POOL_ADDRESS"), (address)); - address CURVE_3_POOL_ADDRESS = abi.decode(vm.parseJson(file, "$.CURVE_3_POOL_ADDRESS"), (address)); - address CURVE_TRICRYPTO2_POOL_ADDRESS = abi.decode(vm.parseJson(file, "$.CURVE_TRICRYPTO2_POOL_ADDRESS"), (address)); - address CURVE_ANKRETH_POOL_ADDRESS = abi.decode(vm.parseJson(file, "$.CURVE_ANKRETH_POOL_ADDRESS"), (address)); - address BALANCER_V2_ADDRESS = abi.decode(vm.parseJson(file, "$.BALANCER_V2_ADDRESS"), (address)); - - // Curve coins - address cDAI_ADDRESS = abi.decode(vm.parseJson(file, "$.cDAI_ADDRESS"), (address)); - address cUSDC_ADDRESS = abi.decode(vm.parseJson(file, "$.cUSDC_ADDRESS"), (address)); - address yDAI_ADDRESS = abi.decode(vm.parseJson(file, "$.yDAI_ADDRESS"), (address)); - address yUSDC_ADDRESS = abi.decode(vm.parseJson(file, "$.yUSDC_ADDRESS"), (address)); - address yUSDT_ADDRESS = abi.decode(vm.parseJson(file, "$.yUSDT_ADDRESS"), (address)); - address yTUSD_ADDRESS = abi.decode(vm.parseJson(file, "$.yTUSD_ADDRESS"), (address)); - - address ARBITRUM_L1_GATEWAY_ROUTER_ADDR = abi.decode(vm.parseJson(file, "$.ARBITRUM_L1_GATEWAY_ROUTER_ADDR"), (address)); - address ARBITRUM_L1_BRIDGE_ADDR = abi.decode(vm.parseJson(file, "$.ARBITRUM_L1_BRIDGE_ADDR"), (address)); - address OPTIMISM_L1_STANDARD_BRIDGE_ADDR = abi.decode(vm.parseJson(file, "$.OPTIMISM_L1_STANDARD_BRIDGE_ADDR"), (address)); function getChainId() internal view returns (uint256 chainId) { assembly { diff --git a/test/utils/Tokens.sol b/test/utils/Tokens.sol index 394184bc..3384d8a0 100644 --- a/test/utils/Tokens.sol +++ b/test/utils/Tokens.sol @@ -27,7 +27,6 @@ contract Tokens is Addresses { dai = new MockERC20("DAI", "DAI", 18); wbtc = new MockERC20("WBTC", "WBTC", 18); lon = new MockERC20("LON", "LON", 18); - ankreth = new MockERC20("ANKRETH", "ANKRETH", 18); } else { // forked mainnet, load ERC20s using constant address weth = IERC20(WETH_ADDRESS); @@ -36,10 +35,9 @@ contract Tokens is Addresses { dai = IERC20(DAI_ADDRESS); wbtc = IERC20(WBTC_ADDRESS); lon = IERC20(LON_ADDRESS); - ankreth = IERC20(ANKRETH_ADDRESS); } - tokens = [weth, usdt, usdc, dai, wbtc, lon, ankreth]; + tokens = [weth, usdt, usdc, dai, wbtc, lon]; vm.label(address(weth), "WETH"); vm.label(address(usdt), "USDT"); diff --git a/test/utils/UniswapV2Library.sol b/test/utils/UniswapV2Library.sol new file mode 100644 index 00000000..e546616c --- /dev/null +++ b/test/utils/UniswapV2Library.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +interface IUniswapV2Pair { + event Approval(address indexed owner, address indexed spender, uint256 value); + event Transfer(address indexed from, address indexed to, uint256 value); + + function name() external pure returns (string memory); + + function symbol() external pure returns (string memory); + + function decimals() external pure returns (uint8); + + function totalSupply() external view returns (uint256); + + function balanceOf(address owner) external view returns (uint256); + + function allowance(address owner, address spender) external view returns (uint256); + + function approve(address spender, uint256 value) external returns (bool); + + function transfer(address to, uint256 value) external returns (bool); + + function transferFrom(address from, address to, uint256 value) external returns (bool); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + + function PERMIT_TYPEHASH() external pure returns (bytes32); + + function nonces(address owner) external view returns (uint256); + + function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external; + + event Mint(address indexed sender, uint256 amount0, uint256 amount1); + event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to); + event Swap(address indexed sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, address indexed to); + event Sync(uint112 reserve0, uint112 reserve1); + + function MINIMUM_LIQUIDITY() external pure returns (uint256); + + function factory() external view returns (address); + + function token0() external view returns (address); + + function token1() external view returns (address); + + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + + function price0CumulativeLast() external view returns (uint256); + + function price1CumulativeLast() external view returns (uint256); + + function kLast() external view returns (uint256); + + function mint(address to) external returns (uint256 liquidity); + + function burn(address to) external returns (uint256 amount0, uint256 amount1); + + function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external; + + function skim(address to) external; + + function sync() external; + + function initialize(address, address) external; +} + +library UniswapV2Library { + address constant factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f; + + // returns sorted token addresses, used to handle return values from pairs sorted in this order + function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { + require(tokenA != tokenB, "UniswapV2Library: IDENTICAL_ADDRESSES"); + (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + require(token0 != address(0), "UniswapV2Library: ZERO_ADDRESS"); + } + + // calculates the CREATE2 address for a pair without making any external calls + function pairFor(address tokenA, address tokenB) internal pure returns (address pair) { + (address token0, address token1) = sortTokens(tokenA, tokenB); + pair = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + hex"ff", + factory, + keccak256(abi.encodePacked(token0, token1)), + hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f" // init code hash + ) + ) + ) + ) + ); + } + + // fetches and sorts the reserves for a pair + function getReserves(address tokenA, address tokenB) internal view returns (uint256 reserveA, uint256 reserveB) { + (address token0, ) = sortTokens(tokenA, tokenB); + (uint256 reserve0, uint256 reserve1, ) = IUniswapV2Pair(pairFor(tokenA, tokenB)).getReserves(); + (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); + } + + // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset + function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) internal pure returns (uint256 amountOut) { + require(amountIn > 0, "UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT"); + require(reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY"); + uint256 amountInWithFee = amountIn * 997; + uint256 numerator = amountInWithFee * reserveOut; + uint256 denominator = (reserveIn * 1000) + amountInWithFee; + amountOut = numerator / denominator; + } + + // performs chained getAmountOut calculations on any number of pairs + function getAmountsOut(uint256 amountIn, address[] memory path) internal view returns (uint256[] memory amounts) { + require(path.length >= 2, "UniswapV2Library: INVALID_PATH"); + amounts = new uint256[](path.length); + amounts[0] = amountIn; + for (uint256 i; i < path.length - 1; ++i) { + (uint256 reserveIn, uint256 reserveOut) = getReserves(path[i], path[i + 1]); + amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut); + } + } +} diff --git a/test/utils/config/arbitrumMainnet.json b/test/utils/config/arbitrumMainnet.json index f8bf22a3..c4908852 100644 --- a/test/utils/config/arbitrumMainnet.json +++ b/test/utils/config/arbitrumMainnet.json @@ -7,31 +7,11 @@ "DAI_ADDRESS": "0x0000000000000000000000000000000000000000", "LON_ADDRESS": "0x0000000000000000000000000000000000000000", "WBTC_ADDRESS": "0x0000000000000000000000000000000000000000", - "ANKRETH_ADDRESS": "0x0000000000000000000000000000000000000000", - "UNISWAP_V2_ADDRESS": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", + "CURVE_TRICRYPTO2_POOL_ADDRESS": "0x0000000000000000000000000000000000000000", "SUSHISWAP_ADDRESS": "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506", - "UNISWAP_V3_ADDRESS": "0xE592427A0AEce92De3Edee1F18E0157C05861564", "UNISWAP_V3_QUOTER_ADDRESS": "0x0000000000000000000000000000000000000000", "UNISWAP_PERMIT2_ADDRESS": "0x000000000022d473030f116ddee9f6b43ac78ba3", "UNISWAP_SWAP_ROUTER_02_ADDRESS": "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", - "UNISWAP_UNIVERSAL_ROUTER_ADDRESS": "0x4648a43B2C14Da09FdF82B161150d3F634f40491", - "CURVE_USDT_POOL_ADDRESS": "0x0000000000000000000000000000000000000000", - "CURVE_COMPOUND_POOL_ADDRESS": "0x0000000000000000000000000000000000000000", - "CURVE_Y_POOL_ADDRESS": "0x0000000000000000000000000000000000000000", - "CURVE_3_POOL_ADDRESS": "0x0000000000000000000000000000000000000000", - "CURVE_TRICRYPTO2_POOL_ADDRESS": "0x0000000000000000000000000000000000000000", - "CURVE_ANKRETH_POOL_ADDRESS": "0x0000000000000000000000000000000000000000", - "BALANCER_V2_ADDRESS": "0x0000000000000000000000000000000000000000", - - "cDAI_ADDRESS": "0x0000000000000000000000000000000000000000", - "cUSDC_ADDRESS": "0x0000000000000000000000000000000000000000", - "yDAI_ADDRESS": "0x0000000000000000000000000000000000000000", - "yUSDC_ADDRESS": "0x0000000000000000000000000000000000000000", - "yUSDT_ADDRESS": "0x0000000000000000000000000000000000000000", - "yTUSD_ADDRESS": "0x0000000000000000000000000000000000000000", - - "ARBITRUM_L1_GATEWAY_ROUTER_ADDR": "0x0000000000000000000000000000000000000000", - "ARBITRUM_L1_BRIDGE_ADDR": "0x0000000000000000000000000000000000000000", - "OPTIMISM_L1_STANDARD_BRIDGE_ADDR": "0x0000000000000000000000000000000000000000" + "UNISWAP_UNIVERSAL_ROUTER_ADDRESS": "0x4C60051384bd2d3C01bfc845Cf5F4b44bcbE9de5" } diff --git a/test/utils/config/goerli.json b/test/utils/config/goerli.json index d56af036..30f49ed3 100644 --- a/test/utils/config/goerli.json +++ b/test/utils/config/goerli.json @@ -7,31 +7,11 @@ "DAI_ADDRESS": "0x0000000000000000000000000000000000000000", "LON_ADDRESS": "0x6dA0e6ABd44175f50C563cd8b860DD988A7C3433", "WBTC_ADDRESS": "0x0000000000000000000000000000000000000000", - "ANKRETH_ADDRESS": "0x0000000000000000000000000000000000000000", - "UNISWAP_V2_ADDRESS": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", + "CURVE_TRICRYPTO2_POOL_ADDRESS": "0x0000000000000000000000000000000000000000", "SUSHISWAP_ADDRESS": "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506", - "UNISWAP_V3_ADDRESS": "0xE592427A0AEce92De3Edee1F18E0157C05861564", "UNISWAP_V3_QUOTER_ADDRESS": "0x0000000000000000000000000000000000000000", "UNISWAP_PERMIT2_ADDRESS": "0x000000000022d473030f116ddee9f6b43ac78ba3", "UNISWAP_SWAP_ROUTER_02_ADDRESS": "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", - "UNISWAP_UNIVERSAL_ROUTER_ADDRESS": "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD", - "CURVE_USDT_POOL_ADDRESS": "0x0000000000000000000000000000000000000000", - "CURVE_COMPOUND_POOL_ADDRESS": "0x0000000000000000000000000000000000000000", - "CURVE_Y_POOL_ADDRESS": "0x0000000000000000000000000000000000000000", - "CURVE_3_POOL_ADDRESS": "0x0000000000000000000000000000000000000000", - "CURVE_TRICRYPTO2_POOL_ADDRESS": "0x0000000000000000000000000000000000000000", - "CURVE_ANKRETH_POOL_ADDRESS": "0x0000000000000000000000000000000000000000", - "BALANCER_V2_ADDRESS": "0x0000000000000000000000000000000000000000", - - "cDAI_ADDRESS": "0x0000000000000000000000000000000000000000", - "cUSDC_ADDRESS": "0x0000000000000000000000000000000000000000", - "yDAI_ADDRESS": "0x0000000000000000000000000000000000000000", - "yUSDC_ADDRESS": "0x0000000000000000000000000000000000000000", - "yUSDT_ADDRESS": "0x0000000000000000000000000000000000000000", - "yTUSD_ADDRESS": "0x0000000000000000000000000000000000000000", - - "ARBITRUM_L1_GATEWAY_ROUTER_ADDR": "0x4c7708168395aEa569453Fc36862D2ffcDaC588c", - "ARBITRUM_L1_BRIDGE_ADDR": "0x0000000000000000000000000000000000000000", - "OPTIMISM_L1_STANDARD_BRIDGE_ADDR": "0x636Af16bf2f682dD3109e60102b8E1A089FedAa8" + "UNISWAP_UNIVERSAL_ROUTER_ADDRESS": "0x4648a43B2C14Da09FdF82B161150d3F634f40491" } diff --git a/test/utils/config/local.json b/test/utils/config/local.json index 3f5f478f..dd01cfb0 100644 --- a/test/utils/config/local.json +++ b/test/utils/config/local.json @@ -7,31 +7,11 @@ "DAI_ADDRESS": "0x000000000000000000000000000000000000000", "LON_ADDRESS": "0x000000000000000000000000000000000000000", "WBTC_ADDRESS": "0x0000000000000000000000000000000000000000", - "ANKRETH_ADDRESS": "0x000000000000000000000000000000000000000", - "UNISWAP_V2_ADDRESS": "0x000000000000000000000000000000000000000", + "CURVE_TRICRYPTO2_POOL_ADDRESS": "0x000000000000000000000000000000000000000", "SUSHISWAP_ADDRESS": "0x000000000000000000000000000000000000000", - "UNISWAP_V3_ADDRESS": "0x000000000000000000000000000000000000000", "UNISWAP_V3_QUOTER_ADDRESS": "0x000000000000000000000000000000000000000", "UNISWAP_PERMIT2_ADDRESS": "0x000000000000000000000000000000000000000", "UNISWAP_SWAP_ROUTER_02_ADDRESS": "0x000000000000000000000000000000000000000", - "UNISWAP_UNIVERSAL_ROUTER_ADDRESS": "0x000000000000000000000000000000000000000", - "CURVE_USDT_POOL_ADDRESS": "0x000000000000000000000000000000000000000", - "CURVE_COMPOUND_POOL_ADDRESS": "0x000000000000000000000000000000000000000", - "CURVE_Y_POOL_ADDRESS": "0x000000000000000000000000000000000000000", - "CURVE_3_POOL_ADDRESS": "0x000000000000000000000000000000000000000", - "CURVE_TRICRYPTO2_POOL_ADDRESS": "0x000000000000000000000000000000000000000", - "CURVE_ANKRETH_POOL_ADDRESS": "0x000000000000000000000000000000000000000", - "BALANCER_V2_ADDRESS": "0x000000000000000000000000000000000000000", - - "cDAI_ADDRESS": "0x000000000000000000000000000000000000000", - "cUSDC_ADDRESS": "0x000000000000000000000000000000000000000", - "yDAI_ADDRESS": "0x000000000000000000000000000000000000000", - "yUSDC_ADDRESS": "0x000000000000000000000000000000000000000", - "yUSDT_ADDRESS": "0x000000000000000000000000000000000000000", - "yTUSD_ADDRESS": "0x000000000000000000000000000000000000000", - - "ARBITRUM_L1_GATEWAY_ROUTER_ADDR": "0x000000000000000000000000000000000000000", - "ARBITRUM_L1_BRIDGE_ADDR": "0x000000000000000000000000000000000000000", - "OPTIMISM_L1_STANDARD_BRIDGE_ADDR": "0x000000000000000000000000000000000000000" + "UNISWAP_UNIVERSAL_ROUTER_ADDRESS": "0x000000000000000000000000000000000000000" } diff --git a/test/utils/config/mainnet.json b/test/utils/config/mainnet.json index e23e12a7..6755532a 100644 --- a/test/utils/config/mainnet.json +++ b/test/utils/config/mainnet.json @@ -7,31 +7,11 @@ "DAI_ADDRESS": "0x6B175474E89094C44Da98b954EedeAC495271d0F", "LON_ADDRESS": "0x0000000000095413afC295d19EDeb1Ad7B71c952", "WBTC_ADDRESS": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", - "ANKRETH_ADDRESS": "0xE95A203B1a91a908F9B9CE46459d101078c2c3cb", - "UNISWAP_V2_ADDRESS": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", + "CURVE_TRICRYPTO2_POOL_ADDRESS": "0x80466c64868E1ab14a1Ddf27A676C3fcBE638Fe5", "SUSHISWAP_ADDRESS": "0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F", - "UNISWAP_V3_ADDRESS": "0xE592427A0AEce92De3Edee1F18E0157C05861564", "UNISWAP_V3_QUOTER_ADDRESS": "0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6", "UNISWAP_PERMIT2_ADDRESS": "0x000000000022d473030f116ddee9f6b43ac78ba3", "UNISWAP_SWAP_ROUTER_02_ADDRESS": "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", - "UNISWAP_UNIVERSAL_ROUTER_ADDRESS": "0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD", - "CURVE_USDT_POOL_ADDRESS": "0x52EA46506B9CC5Ef470C5bf89f17Dc28bB35D85C", - "CURVE_COMPOUND_POOL_ADDRESS": "0xA2B47E3D5c44877cca798226B7B8118F9BFb7A56", - "CURVE_Y_POOL_ADDRESS": "0x45F783CCE6B7FF23B2ab2D70e416cdb7D6055f51", - "CURVE_3_POOL_ADDRESS": "0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7", - "CURVE_TRICRYPTO2_POOL_ADDRESS": "0x80466c64868E1ab14a1Ddf27A676C3fcBE638Fe5", - "CURVE_ANKRETH_POOL_ADDRESS": "0xA96A65c051bF88B4095Ee1f2451C2A9d43F53Ae2", - "BALANCER_V2_ADDRESS": "0xBA12222222228d8Ba445958a75a0704d566BF2C8", - - "cDAI_ADDRESS": "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643", - "cUSDC_ADDRESS": "0x39AA39c021dfbaE8faC545936693aC917d5E7563", - "yDAI_ADDRESS": "0x16de59092dAE5CcF4A1E6439D611fd0653f0Bd01", - "yUSDC_ADDRESS": "0xd6aD7a6750A7593E092a9B218d66C0A814a3436e", - "yUSDT_ADDRESS": "0x83f798e925BcD4017Eb265844FDDAbb448f1707D", - "yTUSD_ADDRESS": "0x73a052500105205d34Daf004eAb301916DA8190f", - - "ARBITRUM_L1_GATEWAY_ROUTER_ADDR": "0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef", - "ARBITRUM_L1_BRIDGE_ADDR": "0x8315177aB297bA92A06054cE80a67Ed4DBd7ed3a", - "OPTIMISM_L1_STANDARD_BRIDGE_ADDR": "0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1" + "UNISWAP_UNIVERSAL_ROUTER_ADDRESS": "0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B" } diff --git a/test/utils/config/polygon.json b/test/utils/config/polygon.json new file mode 100644 index 00000000..ab2936b7 --- /dev/null +++ b/test/utils/config/polygon.json @@ -0,0 +1,17 @@ +{ + "WETH_ADDRESS": "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", + "USDT_ADDRESS": "0xc2132D05D31c914a87C6611C10748AEb04B58e8F", + "USDC_ADDRESS": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", + "CRV_ADDRESS": "0x172370d5Cd63279eFa6d502DAB29171933a610AF", + "TUSD_ADDRESS": "0x2e1AD108fF1D8C782fcBbB89AAd783aC49586756", + "DAI_ADDRESS": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", + "LON_ADDRESS": "0x6f7C932e7684666C9fd1d44527765433e01fF61d", + "WBTC_ADDRESS": "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6", + + "CURVE_TRICRYPTO2_POOL_ADDRESS": "0x000000000000000000000000000000000000000", + "SUSHISWAP_ADDRESS": "0x000000000000000000000000000000000000000", + "UNISWAP_V3_QUOTER_ADDRESS": "0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6", + "UNISWAP_PERMIT2_ADDRESS": "0x000000000022d473030f116ddee9f6b43ac78ba3", + "UNISWAP_SWAP_ROUTER_02_ADDRESS": "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", + "UNISWAP_UNIVERSAL_ROUTER_ADDRESS": "0x4C60051384bd2d3C01bfc845Cf5F4b44bcbE9de5" +}