From e7a185b9779c760b4e0a95faf33c3587ea4e3637 Mon Sep 17 00:00:00 2001 From: ReflectiveChimp <55021052+ReflectiveChimp@users.noreply.github.com> Date: Thu, 14 Nov 2024 19:55:35 +0000 Subject: [PATCH 1/2] update forge-std to 1.94 (requires latest version of foundry) update BaseTestHarness/WrapperTest/ProdVaultTest to use newer forge-std --- forge/test/ProdVaultTest.t.sol | 6 ++--- forge/test/utils/BaseTestHarness.sol | 38 +++++----------------------- forge/test/wrapper/WrapperTest.t.sol | 6 ++--- package.json | 2 +- yarn.lock | 6 ++--- 5 files changed, 16 insertions(+), 42 deletions(-) diff --git a/forge/test/ProdVaultTest.t.sol b/forge/test/ProdVaultTest.t.sol index 71d77162..cd3e00cc 100644 --- a/forge/test/ProdVaultTest.t.sol +++ b/forge/test/ProdVaultTest.t.sol @@ -112,7 +112,7 @@ contract ProdVaultTest is BaseTestHarness { assertGt(balanceOfPool, balanceOfWant); console.log("Calling panic()"); - FORGE_VM.prank(keeper); + vm.prank(keeper); strategy.panic(); uint256 vaultBalanceAfterPanic = vault.balance(); @@ -129,7 +129,7 @@ contract ProdVaultTest is BaseTestHarness { // Users can't deposit. console.log("Trying to deposit while panicked."); - FORGE_VM.expectRevert("Pausable: paused"); + vm.expectRevert("Pausable: paused"); user.depositAll(vault); // User can still withdraw @@ -196,7 +196,7 @@ contract ProdVaultTest is BaseTestHarness { function _unpauseIfPaused() internal { if (strategy.paused()) { console.log("Unpausing vault."); - FORGE_VM.prank(keeper); + vm.prank(keeper); strategy.unpause(); } } diff --git a/forge/test/utils/BaseTestHarness.sol b/forge/test/utils/BaseTestHarness.sol index da4692b4..20cd2111 100644 --- a/forge/test/utils/BaseTestHarness.sol +++ b/forge/test/utils/BaseTestHarness.sol @@ -1,47 +1,21 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.12; -import {DSTest, console, Vm} from "forge-std/Test.sol"; +import {Test, console, Vm} from "forge-std/Test.sol"; import {IERC20Like} from "../interfaces/IERC20Like.sol"; -contract BaseTestHarness is DSTest { - // Api to modify test vm state. - Vm internal constant FORGE_VM = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); - +contract BaseTestHarness is Test { /* */ /* Forge Hacks */ /* */ function modifyBalance(address token_, address user_, uint256 amount_) internal returns (uint256 slot_) { - IERC20Like erc20 = IERC20Like(token_); - bool found; - for (uint256 i = 0; i < 10; i++) { - // Get before value in case the slot is wrong, so can restore the value. - bytes32 beforeValue = FORGE_VM.load(address(token_), keccak256(abi.encode(user_, slot_))); - - // Modify storage slot. - FORGE_VM.store(address(token_), keccak256(abi.encode(user_, slot_)), bytes32(amount_)); - - uint256 balance = erc20.balanceOf(user_); - - if (balance == amount_) { - found = true; - break; - } - - // Restore value. - FORGE_VM.store(address(token_), keccak256(abi.encode(user_, slot_)), beforeValue); - slot_ += 1; - } - - if (!found) { - assertTrue(false, "Never found storage slot to modify for ERC20 balance hack."); - } + deal(token_, user_, amount_); } function modifyBalanceWithKnownSlot(address token_, address user_, uint256 amount_, uint256 slot) internal { - FORGE_VM.store(address(token_), keccak256(abi.encode(user_, slot)), bytes32(amount_)); + vm.store(address(token_), keccak256(abi.encode(user_, slot)), bytes32(amount_)); } /** @@ -50,8 +24,8 @@ contract BaseTestHarness is DSTest { */ function shift(uint256 seconds_) public { console.log("Shifting forward seconds", seconds_); - FORGE_VM.warp(block.timestamp + seconds_); - FORGE_VM.roll(block.number + getApproximateBlocksFromSeconds(seconds_)); + vm.warp(block.timestamp + seconds_); + vm.roll(block.number + getApproximateBlocksFromSeconds(seconds_)); } /** diff --git a/forge/test/wrapper/WrapperTest.t.sol b/forge/test/wrapper/WrapperTest.t.sol index 4133378e..d4a752b8 100644 --- a/forge/test/wrapper/WrapperTest.t.sol +++ b/forge/test/wrapper/WrapperTest.t.sol @@ -145,7 +145,7 @@ contract WrapperTest is BaseTestHarness { assertGt(balanceOfPool, balanceOfWant); console.log("Calling panic()"); - FORGE_VM.prank(keeper); + vm.prank(keeper); strategy.panic(); uint256 vaultBalanceAfterPanic = wrapper.totalAssets(); @@ -162,7 +162,7 @@ contract WrapperTest is BaseTestHarness { // Users can't deposit. console.log("Trying to deposit while panicked."); - FORGE_VM.expectRevert("Pausable: paused"); + vm.expectRevert("Pausable: paused"); user.depositAll(wrapper); // User can still withdraw @@ -229,7 +229,7 @@ contract WrapperTest is BaseTestHarness { function _unpauseIfPaused() internal { if (strategy.paused()) { console.log("Unpausing vault."); - FORGE_VM.prank(keeper); + vm.prank(keeper); strategy.unpause(); } } diff --git a/package.json b/package.json index 080d1290..821b9fb4 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "eth-multicall": "^1.4.0", "ethereum-waffle": "^3.4.0", "ethers": "^5.5.2", - "forge-std": "https://github.com/foundry-rs/forge-std.git#155d547c449afa8715f538d69454b83944117811", + "forge-std": "https://github.com/foundry-rs/forge-std.git#1eea5bae12ae557d589f9f0f0edae2faa47cb262", "hardhat": "^2.12.2", "hardhat-contract-sizer": "^2.8.0", "hardhat-gas-reporter": "^1.0.9", diff --git a/yarn.lock b/yarn.lock index b52a713e..777d1333 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5207,9 +5207,9 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= -"forge-std@https://github.com/foundry-rs/forge-std.git#155d547c449afa8715f538d69454b83944117811": - version "1.7.4" - resolved "https://github.com/foundry-rs/forge-std.git#155d547c449afa8715f538d69454b83944117811" +"forge-std@https://github.com/foundry-rs/forge-std.git#1eea5bae12ae557d589f9f0f0edae2faa47cb262": + version "1.9.4" + resolved "https://github.com/foundry-rs/forge-std.git#1eea5bae12ae557d589f9f0f0edae2faa47cb262" form-data@^2.2.0: version "2.5.1" From 667eb2cef5bf270943eeec262da0cbc183987777 Mon Sep 17 00:00:00 2001 From: ReflectiveChimp <55021052+ReflectiveChimp@users.noreply.github.com> Date: Thu, 14 Nov 2024 21:07:40 +0000 Subject: [PATCH 2/2] Add multi-hop support to BeefySwapper Add more tests for BeefySwapper Make BeefySwapper implement IBeefySwapper --- contracts/BIFI/infra/BeefySwapper.sol | 213 ++++++--- .../BIFI/interfaces/beefy/IBeefySwapper.sol | 85 +++- forge/test/swapper/BeefySwapper.t.sol | 450 ++++++++++++++++++ forge/test/swapper/Swapper.txt | 234 --------- 4 files changed, 684 insertions(+), 298 deletions(-) create mode 100644 forge/test/swapper/BeefySwapper.t.sol delete mode 100644 forge/test/swapper/Swapper.txt diff --git a/contracts/BIFI/infra/BeefySwapper.sol b/contracts/BIFI/infra/BeefySwapper.sol index 219011a0..f26aadf1 100644 --- a/contracts/BIFI/infra/BeefySwapper.sol +++ b/contracts/BIFI/infra/BeefySwapper.sol @@ -1,18 +1,18 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.19; +pragma solidity 0.8.23; -import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; -import { IERC20MetadataUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; -import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; - -import { IBeefyOracle } from "../interfaces/oracle/IBeefyOracle.sol"; -import { BytesLib } from "../utils/BytesLib.sol"; +import {BytesLib} from "../utils/BytesLib.sol"; +import {IBeefyOracle} from "../interfaces/oracle/IBeefyOracle.sol"; +import {IBeefySwapper} from "../interfaces/beefy/IBeefySwapper.sol"; +import {IERC20MetadataUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; /// @title Beefy Swapper /// @author Beefy, @kexley /// @notice Centralized swapper -contract BeefySwapper is OwnableUpgradeable { +contract BeefySwapper is OwnableUpgradeable, IBeefySwapper { using SafeERC20Upgradeable for IERC20MetadataUpgradeable; using BytesLib for bytes; @@ -35,25 +35,15 @@ contract BeefySwapper is OwnableUpgradeable { /// @param minAmountOut Minimum amount required from the swap error SlippageExceeded(uint256 amountOut, uint256 minAmountOut); - /// @dev Stored data for a swap - /// @param router Target address that will handle the swap - /// @param data Payload of a template swap between the two tokens - /// @param amountIndex Location in the data byte string where the amount should be overwritten - /// @param minIndex Location in the data byte string where the min amount to swap should be - /// overwritten - /// @param minAmountSign Represents the sign of the min amount to be included in the swap, any - /// negative value will encode a negative min amount (required for Balancer) - struct SwapInfo { - address router; - bytes data; - uint256 amountIndex; - uint256 minIndex; - int8 minAmountSign; - } + /// @dev At least minLength tokens should be included in the route + error RouteTooShort(uint256 minLength); - /// @notice Stored swap info for a token + /// @inheritdoc IBeefySwapper mapping(address => mapping(address => SwapInfo)) public swapInfo; + /// @inheritdoc IBeefySwapper + mapping(address => mapping(address => address[])) public swapRoute; + /// @notice Oracle used to calculate the minimum output of a swap IBeefyOracle public oracle; @@ -80,6 +70,12 @@ contract BeefySwapper is OwnableUpgradeable { /// @param swapInfo Struct of stored swap information for the pair of tokens event SetSwapInfo(address indexed fromToken, address indexed toToken, SwapInfo swapInfo); + /// @notice Set new swap route between two tokens + /// @param fromToken Address of the source token + /// @param toToken Address of the destination token + /// @param route Array of tokens to route through + event SetSwapRoute(address indexed fromToken, address indexed toToken, address[] route); + /// @notice Set a new oracle /// @param oracle New oracle address event SetOracle(address oracle); @@ -98,13 +94,7 @@ contract BeefySwapper is OwnableUpgradeable { slippage = _slippage; } - /// @notice Swap between two tokens with slippage calculated using the oracle - /// @dev Caller must have already approved this contract to spend the _fromToken. After the - /// swap the _toToken token is sent directly to the caller - /// @param _fromToken Token to swap from - /// @param _toToken Token to swap to - /// @param _amountIn Amount of _fromToken to use in the swap - /// @return amountOut Amount of _toToken returned to the caller + /// @inheritdoc IBeefySwapper function swap( address _fromToken, address _toToken, @@ -114,14 +104,7 @@ contract BeefySwapper is OwnableUpgradeable { amountOut = _swap(_fromToken, _toToken, _amountIn, minAmountOut); } - /// @notice Swap between two tokens with slippage provided by the caller - /// @dev Caller must have already approved this contract to spend the _fromToken. After the - /// swap the _toToken token is sent directly to the caller - /// @param _fromToken Token to swap from - /// @param _toToken Token to swap to - /// @param _amountIn Amount of _fromToken to use in the swap - /// @param _minAmountOut Minimum amount of _toToken that is acceptable to be returned to caller - /// @return amountOut Amount of _toToken returned to the caller + /// @inheritdoc IBeefySwapper function swap( address _fromToken, address _toToken, @@ -131,23 +114,49 @@ contract BeefySwapper is OwnableUpgradeable { amountOut = _swap(_fromToken, _toToken, _amountIn, _minAmountOut); } - /// @notice Get the amount out from a simulated swap with slippage and non-fresh prices - /// @param _fromToken Token to swap from - /// @param _toToken Token to swap to - /// @param _amountIn Amount of _fromToken to use in the swap - /// @return amountOut Amount of _toTokens returned from the swap + /// @inheritdoc IBeefySwapper + function swap( + address[] calldata _route, + uint256 _amountIn + ) external returns (uint256 amountOut) { + uint256 minAmountOut = _getAmountOut(_route[0], _route[_route.length - 1], _amountIn); + amountOut = _swap(_route, _amountIn, minAmountOut); + } + + /// @inheritdoc IBeefySwapper + function swap( + address[] calldata _route, + uint256 _amountIn, + uint256 _minAmountOut + ) external returns (uint256 amountOut) { + amountOut = _swap(_route, _amountIn, _minAmountOut); + } + + /// @inheritdoc IBeefySwapper function getAmountOut( address _fromToken, address _toToken, uint256 _amountIn ) external view returns (uint256 amountOut) { - (uint256 fromPrice, uint256 toPrice) = - (oracle.getPrice(_fromToken), oracle.getPrice(_toToken)); + (uint256 fromPrice, uint256 toPrice) = + (oracle.getPrice(_fromToken), oracle.getPrice(_toToken)); uint8 decimals0 = IERC20MetadataUpgradeable(_fromToken).decimals(); uint8 decimals1 = IERC20MetadataUpgradeable(_toToken).decimals(); amountOut = _calculateAmountOut(_amountIn, fromPrice, toPrice, decimals0, decimals1); } + /// @inheritdoc IBeefySwapper + function hasSwap( + address _fromToken, + address _toToken + ) external view returns (bool) { + if (_haveSwapData(_fromToken, _toToken)) { + return true; + } + + return swapRoute[_fromToken][_toToken].length > 0; + } + /// @dev Use the oracle to get prices for both _fromToken and _toToken and calculate the /// estimated output reduced by the slippage /// @param _fromToken Token to swap from @@ -168,6 +177,7 @@ contract BeefySwapper is OwnableUpgradeable { /// @dev _fromToken is pulled into this contract from the caller, swap is executed according to /// the stored data, resulting _toTokens are sent to the caller + /// Will attempt direct swap first, if no data is stored for the pair, will attempt to swap via stored route /// @param _fromToken Token to swap from /// @param _toToken Token to swap to /// @param _amountIn Amount of _fromToken to use in the swap @@ -179,12 +189,68 @@ contract BeefySwapper is OwnableUpgradeable { uint256 _amountIn, uint256 _minAmountOut ) private returns (uint256 amountOut) { - IERC20MetadataUpgradeable(_fromToken).safeTransferFrom(msg.sender, address(this), _amountIn); - _executeSwap(_fromToken, _toToken, _amountIn, _minAmountOut); - amountOut = IERC20MetadataUpgradeable(_toToken).balanceOf(address(this)); - if (amountOut < _minAmountOut) revert SlippageExceeded(amountOut, _minAmountOut); - IERC20MetadataUpgradeable(_toToken).safeTransfer(msg.sender, amountOut); - emit Swap(msg.sender, _fromToken, _toToken, _amountIn, amountOut); + if (!_haveSwapData(_fromToken, _toToken)) { + address[] memory route = swapRoute[_fromToken][_toToken]; + if (route.length == 0) { + revert NoSwapData(_fromToken, _toToken); + } + return _swap(route, _amountIn, _minAmountOut); + } + + { + IERC20MetadataUpgradeable(_fromToken).safeTransferFrom(msg.sender, address(this), _amountIn); + amountOut = IERC20MetadataUpgradeable(_fromToken).balanceOf(address(this)); + + _executeSwap(_fromToken, _toToken, amountOut, _minAmountOut); + amountOut = IERC20MetadataUpgradeable(_toToken).balanceOf(address(this)); + + if (amountOut < _minAmountOut) { + revert SlippageExceeded(amountOut, _minAmountOut); + } + + IERC20MetadataUpgradeable(_toToken).safeTransfer(msg.sender, amountOut); + emit Swap(msg.sender, _fromToken, _toToken, _amountIn, amountOut); + } + } + + /// @dev _route[0] is pulled into this contract from the caller, swap is executed according to + /// the stored data, resulting _route[_route.length-1] are sent to the caller + /// @param _route List of tokens to swap via + /// @param _amountIn Amount of _route[0] to use in the swap + /// @param _minAmountOut Minimum amount of _route[_route.length-1] that is acceptable to be returned to caller + /// @return amountOut Amount of _route[_route.length-1] returned to the caller + function _swap( + address[] memory _route, + uint256 _amountIn, + uint256 _minAmountOut + ) private returns (uint256 amountOut) { + uint256 routeLength = _route.length; + if (routeLength < 2) { + revert RouteTooShort(2); + } + uint256 lastIndex = routeLength - 1; + address fromToken = _route[0]; + address toToken = _route[lastIndex]; + + IERC20MetadataUpgradeable(fromToken).safeTransferFrom(msg.sender, address(this), _amountIn); + amountOut = IERC20MetadataUpgradeable(fromToken).balanceOf(address(this)); + + uint256 nextIndex; + address nextToken; + for (uint i; i < lastIndex;) { + nextIndex = i + 1; + nextToken = _route[nextIndex]; + _executeSwap(_route[i], nextToken, amountOut, nextIndex == lastIndex ? _minAmountOut : 0); + amountOut = IERC20MetadataUpgradeable(nextToken).balanceOf(address(this)); + unchecked {++i;} + } + + if (amountOut < _minAmountOut) { + revert SlippageExceeded(amountOut, _minAmountOut); + } + + IERC20MetadataUpgradeable(toToken).safeTransfer(msg.sender, amountOut); + emit Swap(msg.sender, fromToken, toToken, _amountIn, amountOut); } /// @dev Fetch the stored swap info for the route between the two tokens, insert the encoded @@ -201,15 +267,14 @@ contract BeefySwapper is OwnableUpgradeable { ) private { SwapInfo memory swapData = swapInfo[_fromToken][_toToken]; address router = swapData.router; - if (router == address(0)) revert NoSwapData(_fromToken, _toToken); bytes memory data = swapData.data; data = _insertData(data, swapData.amountIndex, abi.encode(_amountIn)); bytes memory minAmountData = swapData.minAmountSign >= 0 ? abi.encode(_minAmountOut) - : abi.encode(-int256(_minAmountOut)); - + : abi.encode(- int256(_minAmountOut)); + data = _insertData(data, swapData.minIndex, minAmountData); IERC20MetadataUpgradeable(_fromToken).forceApprove(router, type(uint256).max); @@ -217,6 +282,14 @@ contract BeefySwapper is OwnableUpgradeable { if (!success) revert SwapFailed(router, data); } + /// @dev Helper function to check if there is swap data defined for a token pair + /// @param _fromToken Token to swap from + /// @param _toToken Token to swap to + /// @return haveData Whether there is swap data defined + function _haveSwapData(address _fromToken, address _toToken) private view returns (bool haveData) { + haveData = swapInfo[_fromToken][_toToken].router != address(0); + } + /// @dev Helper function to insert data to an in-memory bytes string /// @param _data Template swap payload with blank spaces to overwrite /// @param _index Start location in the data byte string where the _newData should overwrite @@ -298,10 +371,38 @@ contract BeefySwapper is OwnableUpgradeable { for (uint i; i < tokenLength;) { swapInfo[_fromTokens[i]][_toTokens[i]] = _swapInfos[i]; emit SetSwapInfo(_fromTokens[i], _toTokens[i], _swapInfos[i]); - unchecked { ++i; } + unchecked {++i;} } } + /// @notice Owner function to set the stored swap route between two tokens + /// @dev Oracle for input and output tokens, and swap data for each token pair must be valid + /// @param _route Array of tokens to route through + function setSwapRoute(address[] calldata _route) external onlyOwner { + uint256 routeLength = _route.length; + if (routeLength < 3) { + revert RouteTooShort(3); + } + + address fromToken = _route[0]; + uint256 lastIndex = routeLength - 1; + address toToken = _route[lastIndex]; + + // check if we have functioning oracle for first and last tokens + _getFreshPrice(fromToken, toToken); + + // check we can swap between each pair of tokens in the route + for (uint i; i < lastIndex;) { + if (!_haveSwapData(_route[i], _route[i + 1])) { + revert NoSwapData(_route[i], _route[i + 1]); + } + unchecked {++i;} + } + + swapRoute[fromToken][toToken] = _route; + emit SetSwapRoute(fromToken, toToken, _route); + } + /// @notice Owner function to set the oracle used to calculate the minimum outputs /// @dev No validation checks /// @param _oracle Address of the new oracle diff --git a/contracts/BIFI/interfaces/beefy/IBeefySwapper.sol b/contracts/BIFI/interfaces/beefy/IBeefySwapper.sol index 0a8980fb..01d4895c 100644 --- a/contracts/BIFI/interfaces/beefy/IBeefySwapper.sol +++ b/contracts/BIFI/interfaces/beefy/IBeefySwapper.sol @@ -3,36 +3,105 @@ pragma solidity ^0.8.0; interface IBeefySwapper { + /// @notice Swap between two tokens with slippage calculated using the oracle + /// @dev Caller must have already approved this contract to spend the _fromToken. + /// After the swap the _toToken token is sent directly to the caller + /// @param _fromToken Token to swap from + /// @param _toToken Token to swap to + /// @param _amountIn Amount of _fromToken to use in the swap + /// @return amountOut Amount of _toToken returned to the caller function swap( - address fromToken, - address toToken, - uint256 amountIn + address _fromToken, + address _toToken, + uint256 _amountIn + ) external returns (uint256 amountOut); + + /// @notice Swap between two tokens with slippage provided by the caller + /// @dev Caller must have already approved this contract to spend the _fromToken. + /// After the swap the _toToken token is sent directly to the caller + /// @param _fromToken Token to swap from + /// @param _toToken Token to swap to + /// @param _amountIn Amount of _fromToken to use in the swap + /// @param _minAmountOut Minimum amount of _toToken that is acceptable to be returned to caller + /// @return amountOut Amount of _toToken returned to the caller + function swap( + address _fromToken, + address _toToken, + uint256 _amountIn, + uint256 _minAmountOut + ) external returns (uint256 amountOut); + + /// @notice Swap between tokens along a route with slippage calculated using the oracle + /// @dev Caller must have already approved this contract to spend the _route[0]. + /// After the swap the _route[_route.length-1] token is sent directly to the caller + /// @param _route List of tokens to swap via + /// @param _amountIn Amount of _route[0] to use in the swap + /// @return amountOut Amount of _route[_route.length-1] returned to the calle + function swap( + address[] calldata _route, + uint256 _amountIn ) external returns (uint256 amountOut); + /// @notice Swap between tokens along a route with slippage provided by the caller + /// @dev Caller must have already approved this contract to spend the _route[0]. After the + /// swap the _route[_route.length-1] token is sent directly to the caller + /// @param _route List of tokens to swap via + /// @param _amountIn Amount of _route[0] to use in the swap + /// @param _minAmountOut Minimum amount of _route[_route.length-1] that is acceptable to be returned to caller + /// @return amountOut Amount of _route[_route.length-1] returned to the caller function swap( - address fromToken, - address toToken, - uint256 amountIn, - uint256 minAmountOut + address[] calldata _route, + uint256 _amountIn, + uint256 _minAmountOut ) external returns (uint256 amountOut); + /// @notice Get the amount out from a simulated swap with slippage and non-fresh prices + /// @param _fromToken Token to swap from + /// @param _toToken Token to swap to + /// @param _amountIn Amount of _fromToken to use in the swap + /// @return amountOut Amount of _toTokens returned from the swap function getAmountOut( address _fromToken, address _toToken, uint256 _amountIn ) external view returns (uint256 amountOut); + /// @notice Get whether a swap can be attempted between two tokens + /// @param _fromToken Token to swap from + /// @param _toToken Token to swap to + /// @return If there is a direct swap, or a multi-hop route between the two tokens + function hasSwap( + address _fromToken, + address _toToken + ) external view returns (bool); + + /// @notice Stored swap info for a token function swapInfo( address _fromToken, address _toToken ) external view returns ( address router, - bytes calldata data, + bytes memory data, uint256 amountIndex, uint256 minIndex, int8 minAmountSign ); + /// @notice Stored multi-hop routes for swapping between two tokens + function swapRoute( + address _fromToken, + address _toToken, + uint256 _index + ) external view returns (address); + + /// @dev Stored data for a swap + /// @param router Target address that will handle the swap + /// @param data Payload of a template swap between the two tokens + /// @param amountIndex Location in the data byte string where the amount should be overwritten + /// @param minIndex Location in the data byte string where the min amount to swap should be + /// overwritten + /// @param minAmountSign Represents the sign of the min amount to be included in the swap, any + /// negative value will encode a negative min amount (required for Balancer) struct SwapInfo { address router; bytes data; diff --git a/forge/test/swapper/BeefySwapper.t.sol b/forge/test/swapper/BeefySwapper.t.sol new file mode 100644 index 00000000..65cd44e9 --- /dev/null +++ b/forge/test/swapper/BeefySwapper.t.sol @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.23; + +import {BeefyOracle} from "../../../contracts/BIFI/infra/BeefyOracle/BeefyOracle.sol"; +import {BeefySwapper} from "../../../contracts/BIFI/infra/BeefySwapper.sol"; +import {IBeefySwapper} from "../../../contracts/BIFI/interfaces/beefy/IBeefySwapper.sol"; +import {IBalancerVault} from "../../../contracts/BIFI/interfaces/beethovenx/IBalancerVault.sol"; +import {ISolidlyRouter} from "../../../contracts/BIFI/interfaces/common/ISolidlyRouter.sol"; +import {IUniswapRouterETH} from "../../../contracts/BIFI/interfaces/common/IUniswapRouterETH.sol"; +import {IUniswapRouterV3WithDeadline} from "../../../contracts/BIFI/interfaces/common/IUniswapRouterV3WithDeadline.sol"; +import {UniswapV3Utils} from "../../../contracts/BIFI/utils/UniswapV3Utils.sol"; +import {IERC20MetadataUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; +import {Test} from "forge-std/Test.sol"; +import {console} from "forge-std/console.sol"; + +// need to do fork test on polygon at a block where there is still USDR liquidity on Pearl +// forge test --match-contract BeefySwapperTest --rpc-url polygon --fork-block-number 45899195 +contract BeefySwapperTest is Test { + address private constant eth = 0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619; + address private constant usdc = 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174; + address private constant matic = 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270; + address private constant usdr = 0x40379a439D4F6795B6fc9aa5687dB461677A2dBa; + address private constant bnb = 0x3BA4c387f786bFEE076A58914F5Bd38d668B42c3; + + address private constant ethFeedAddress = 0xF9680D99D6C9589e2a93a78A04A279e509205945; + address private constant usdcFeedAddress = 0xfE4A8cc5b5B2366C1B58Bea3858e81843581b2F7; + address private constant maticFeedAddress = 0xAB594600376Ec9fD91F8e885dADF0CE036862dE0; + + address private constant usdcUsdrSolidlyPool = 0xD17cb0f162f133e339C0BbFc18c36c357E681D6b; + + bytes32 private constant usdcEthBalancerPool = 0x03cd191f589d12b0582a99808cf19851e468e6b500010000000000000000000a; + + address private constant uniswapV3Router = 0xE592427A0AEce92De3Edee1F18E0157C05861564; + address private constant uniswapV2Router = 0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff; + address private constant solidlyRouter = 0x06374F57991CDc836E5A318569A910FE6456D230; + address private constant balancerRouter = 0xBA12222222228d8Ba445958a75a0704d566BF2C8; + + bytes private constant ethFeed = abi.encode(ethFeedAddress); + bytes private constant usdcFeed = abi.encode(usdcFeedAddress); + bytes private constant maticFeed = abi.encode(maticFeedAddress); + + BeefySwapper private swapper; + BeefyOracle private oracle; + address private alice; + + function setUp() public { + oracle = setUpOracle(); + swapper = setUpSwapper(address(oracle)); + alice = setUpUser("alice", address(swapper)); + } + + function setUpOracle() internal returns (BeefyOracle _oracle) { + _oracle = new BeefyOracle(); + _oracle.initialize(); + + address chainlinkOracle = deployCode("BeefyOracleChainlink.sol"); + _oracle.setOracle(eth, chainlinkOracle, ethFeed); + _oracle.setOracle(usdc, chainlinkOracle, usdcFeed); + _oracle.setOracle(matic, chainlinkOracle, maticFeed); + + address[] memory tokens = new address[](2); + (tokens[0], tokens[1]) = (usdc, usdr); + address[] memory pools = new address[](1); + pools[0] = usdcUsdrSolidlyPool; + uint256[] memory twaps = new uint256[](1); + twaps[0] = 4; + bytes memory usdcUsdrSolidlyFeed = abi.encode(tokens, pools, twaps); + address solidlyOracle = deployCode("BeefyOracleSolidly.sol"); + _oracle.setOracle(usdr, solidlyOracle, usdcUsdrSolidlyFeed); + } + + function setUpSwapper(address _oracle) internal returns (BeefySwapper _swapper) { + _swapper = new BeefySwapper(); + _swapper.initialize(_oracle, 0.99 ether); + } + + function setUpUser(string memory _name, address _swapper) internal returns (address _user) { + _user = makeAddr(_name); + vm.startPrank(_user); + IERC20MetadataUpgradeable(usdc).approve(_swapper, type(uint256).max); + IERC20MetadataUpgradeable(eth).approve(_swapper, type(uint256).max); + IERC20MetadataUpgradeable(matic).approve(_swapper, type(uint256).max); + vm.stopPrank(); + } + + function setSwapInfoUniswapV3(address _asset0, address _asset1, uint24 _fees0_1, address _router) internal { + address[] memory route = new address[](2); + (route[0], route[1]) = (_asset0, _asset1); + uint24[] memory fees = new uint24[](1); + fees[0] = _fees0_1; + + bytes memory data = abi.encodeWithSelector( + IUniswapRouterV3WithDeadline.exactInput.selector, + IUniswapRouterV3WithDeadline.ExactInputParams( + UniswapV3Utils.routeToPath(route, fees), + address(swapper), + type(uint256).max, + 0, + 0 + ) + ); + uint256 amountIndex = 132; + uint256 minIndex = 164; + int8 minSign = 0; + + swapper.setSwapInfo( + route[0], + route[route.length - 1], + IBeefySwapper.SwapInfo(_router, data, amountIndex, minIndex, minSign) + ); + } + + function setSwapInfoUniswapV3(address _asset0, address _asset1, address _asset2, uint24 _fees0_1, uint24 _fees1_2, address _router) internal { + address[] memory route = new address[](3); + (route[0], route[1], route[2]) = (_asset0, _asset1, _asset2); + uint24[] memory fees = new uint24[](2); + (fees[0], fees[1]) = (_fees0_1, _fees1_2); + + bytes memory data = abi.encodeWithSelector( + IUniswapRouterV3WithDeadline.exactInput.selector, + IUniswapRouterV3WithDeadline.ExactInputParams( + UniswapV3Utils.routeToPath(route, fees), + address(swapper), + type(uint256).max, + 0, + 0 + ) + ); + uint256 amountIndex = 132; + uint256 minIndex = 164; + int8 minSign = 0; + + swapper.setSwapInfo( + route[0], + route[route.length - 1], + IBeefySwapper.SwapInfo(_router, data, amountIndex, minIndex, minSign) + ); + } + + function testSetSwapInfoUniswapV3() external { + address tokenIn = usdc; + address tokenOut = eth; + setSwapInfoUniswapV3(tokenIn, tokenOut, 500, uniswapV3Router); + + uint256 amountIn = getInputAmount(tokenIn, alice); + vm.prank(alice); + uint256 received = swapper.swap(tokenIn, tokenOut, amountIn); + + console.log("Received:", received); + assertGt(received, 0, "Should output > 0"); + } + + function testSetSwapInfoUniswapV3Long() external { + address tokenIn = usdc; + address tokenOut = matic; + setSwapInfoUniswapV3(tokenIn, eth, tokenOut, 500, 3000, uniswapV3Router); + + uint256 amountIn = getInputAmount(tokenIn, alice); + vm.prank(alice); + uint256 received = swapper.swap(tokenIn, tokenOut, amountIn); + + console.log("Received:", received); + assertGt(received, 0, "Should output > 0"); + } + + function setSwapInfoUniswapV2(address _asset0, address _asset1, address _router) internal { + address[] memory route = new address[](2); + (route[0], route[1]) = (_asset0, _asset1); + + bytes memory data = abi.encodeWithSelector( + IUniswapRouterETH.swapExactTokensForTokens.selector, + 0, + 0, + route, + address(swapper), + type(uint256).max + ); + uint256 amountIndex = 4; + uint256 minIndex = 36; + int8 minSign = 0; + + swapper.setSwapInfo( + route[0], + route[route.length - 1], + IBeefySwapper.SwapInfo(_router, data, amountIndex, minIndex, minSign) + ); + } + + function testSetSwapInfoUniswapV2() external { + address tokenIn = usdc; + address tokenOut = eth; + setSwapInfoUniswapV2(tokenIn, tokenOut, uniswapV2Router); + + uint256 amountIn = getInputAmount(tokenIn, alice); + vm.prank(alice); + uint256 received = swapper.swap(tokenIn, tokenOut, amountIn); + + console.log("Received:", received); + assertGt(received, 0, "Should output > 0"); + } + + function setSwapInfoSolidly(address _asset0, address _asset1, address _router) internal { + address[] memory route = new address[](2); + (route[0], route[1]) = (_asset0, _asset1); + + ISolidlyRouter.Routes[] memory path = new ISolidlyRouter.Routes[](1); + path[0] = ISolidlyRouter.Routes(route[0], route[1], true); + + bytes memory data = abi.encodeWithSignature( + "swapExactTokensForTokens(uint256,uint256,(address,address,bool)[],address,uint256)", + 0, + 0, + path, + address(swapper), + type(uint256).max + ); + uint256 amountIndex = 4; + uint256 minIndex = 36; + int8 minSign = 0; + + swapper.setSwapInfo( + route[0], + route[route.length - 1], + IBeefySwapper.SwapInfo(_router, data, amountIndex, minIndex, minSign) + ); + } + + function testSetSwapInfoSolidly() external { + address tokenIn = usdc; + address tokenOut = usdr; + setSwapInfoSolidly(tokenIn, tokenOut, solidlyRouter); + + uint256 amountIn = getInputAmount(tokenIn, alice); + vm.prank(alice); + uint256 received = swapper.swap(tokenIn, tokenOut, amountIn); + + console.log("Received:", received); + assertGt(received, 0, "Should output > 0"); + } + + function setSwapInfoBalancer(address _asset0, address _asset1, bytes32 _poolId, address _router) internal { + uint8 swapKind = 0; + IBalancerVault.BatchSwapStep[] memory swapSteps = new IBalancerVault.BatchSwapStep[](1); + swapSteps[0] = IBalancerVault.BatchSwapStep(_poolId, 0, 1, 0, bytes("")); + address[] memory assets = new address[](2); + (assets[0], assets[1]) = (_asset0, _asset1); + IBalancerVault.FundManagement memory funds = IBalancerVault.FundManagement( + address(swapper), false, payable(address(swapper)), false + ); + int256[] memory limits = new int256[](2); + (limits[0], limits[1]) = (type(int256).max, 0); + + bytes memory data = abi.encodeWithSelector( + IBalancerVault.batchSwap.selector, + swapKind, + swapSteps, + assets, + funds, + limits, + type(uint256).max + ); + uint256 amountIndex = 452; + uint256 minIndex = 708; + int8 minSign = - 1; + + swapper.setSwapInfo( + assets[0], + assets[assets.length - 1], + IBeefySwapper.SwapInfo(_router, data, amountIndex, minIndex, minSign) + ); + } + + function testSetSwapInfoBalancer() external { + address tokenIn = usdc; + address tokenOut = eth; + setSwapInfoBalancer(tokenIn, tokenOut, usdcEthBalancerPool, balancerRouter); + + uint256 amountIn = getInputAmount(tokenIn, alice); + vm.prank(alice); + uint256 received = swapper.swap(tokenIn, tokenOut, amountIn); + + console.log("Received:", received); + assertGt(received, 0, "Should output > 0"); + } + + function getInputAmount(address _token, address _user) internal returns (uint256 _amountIn) { + _amountIn = (_token == eth ? 1 : 1000) * 10 ** IERC20MetadataUpgradeable(_token).decimals(); + deal(_token, _user, _amountIn); + } + + function testManualRouteSwapUsdcRouteTooShort() external { + setSwapInfoUniswapV2(usdc, eth, uniswapV2Router); + + address[] memory route = new address[](1); + route[0] = usdc; + + uint256 amountIn = getInputAmount(route[0], alice); + vm.prank(alice); + vm.expectRevert(abi.encodeWithSelector(BeefySwapper.RouteTooShort.selector, 2)); + swapper.swap(route, amountIn); + } + + function testManualRouteSwapUsdcEth() external { + setSwapInfoUniswapV2(usdc, eth, uniswapV2Router); + + address[] memory route = new address[](2); + (route[0], route[1]) = (usdc, eth); + + uint256 amountIn = getInputAmount(route[0], alice); + vm.prank(alice); + uint256 received = swapper.swap(route, amountIn); + + console.log("Received:", received); + assertGt(received, 0, "Should output > 0"); + } + + function testManualRouteSwapEthUsdc() external { + setSwapInfoUniswapV2(eth, usdc, uniswapV2Router); + + address[] memory route = new address[](2); + (route[0], route[1]) = (eth, usdc); + + uint256 amountIn = getInputAmount(route[0], alice); + vm.prank(alice); + uint256 received = swapper.swap(route, amountIn); + + console.log("Received:", received); + assertGt(received, 0, "Should output > 0"); + } + + function testManualRouteSwapUsdcEthMatic() external { + setSwapInfoUniswapV2(usdc, eth, uniswapV2Router); + setSwapInfoUniswapV3(eth, matic, 3000, uniswapV3Router); + + address[] memory route = new address[](3); + (route[0], route[1], route[2]) = (usdc, eth, matic); + + uint256 amountIn = getInputAmount(route[0], alice); + vm.prank(alice); + uint256 received = swapper.swap(route, amountIn); + + console.log("Received:", received); + assertGt(received, 0, "Should output > 0"); + } + + function testManualRouteSwapMaticEthUsdc() external { + setSwapInfoUniswapV3(matic, eth, 3000, uniswapV3Router); + setSwapInfoUniswapV2(eth, usdc, uniswapV2Router); + + address[] memory route = new address[](3); + (route[0], route[1], route[2]) = (matic, eth, usdc); + + uint256 amountIn = getInputAmount(route[0], alice); + vm.prank(alice); + uint256 received = swapper.swap(route, amountIn); + + console.log("Received:", received); + assertGt(received, 0, "Should output > 0"); + } + + function testManualRouteSwapUsdcEthMaticSlippage() external { + setSwapInfoUniswapV2(usdc, eth, uniswapV2Router); + setSwapInfoUniswapV3(eth, matic, 3000, uniswapV3Router); + + address[] memory route = new address[](3); + (route[0], route[1], route[2]) = (usdc, eth, matic); + + uint256 amountIn = getInputAmount(route[0], alice); + uint256 expectedAmountOut = swapper.getAmountOut(route[0], route[2], amountIn); + uint256 minAmountOut = expectedAmountOut * 101 / 100; // 1% more than expected + + vm.prank(alice); + vm.expectPartialRevert(BeefySwapper.SwapFailed.selector); // only check selector, not data + swapper.swap(route, amountIn, minAmountOut); + } + + function testSavedRouteSwapUsdcEthMatic() external { + setSwapInfoUniswapV2(usdc, eth, uniswapV2Router); + setSwapInfoUniswapV3(eth, matic, 3000, uniswapV3Router); + + address[] memory route = new address[](3); + (route[0], route[1], route[2]) = (usdc, eth, matic); + swapper.setSwapRoute(route); + + uint256 amountIn = getInputAmount(route[0], alice); + vm.prank(alice); + uint256 received = swapper.swap(route[0], route[2], amountIn); + + console.log("Received:", received); + assertGt(received, 0, "Should output > 0"); + } + + function testSetSwapRouteTooShort() external { + address[] memory route = new address[](2); + (route[0], route[1]) = (eth, usdc); + + vm.expectRevert(abi.encodeWithSelector(BeefySwapper.RouteTooShort.selector, 3)); + swapper.setSwapRoute(route); + } + + function testSetSwapRouteNoOracle() external { + address[] memory route = new address[](3); + (route[0], route[1], route[2]) = (eth, usdc, bnb); + + // oracle is throwing rather than returning success=false so can't target PriceFailed + //vm.expectRevert(abi.encodeWithSelector(BeefySwapper.PriceFailed.selector, bnb)); + vm.expectRevert(); + swapper.setSwapRoute(route); + } + + function testSetSwapRouteNoSwapData() external { + setSwapInfoUniswapV2(usdc, eth, uniswapV2Router); + //setSwapInfoUniswapV3(eth, matic, 3000, uniswapV3Router); + + address[] memory route = new address[](3); + (route[0], route[1], route[2]) = (usdc, eth, matic); + + vm.expectRevert(abi.encodeWithSelector(BeefySwapper.NoSwapData.selector, eth, matic)); + swapper.setSwapRoute(route); + } + + function testSetSwapRouteTwice() external { + setSwapInfoUniswapV2(usdc, eth, uniswapV2Router); + setSwapInfoUniswapV2(eth, usdc, uniswapV2Router); + setSwapInfoUniswapV3(eth, matic, 3000, uniswapV3Router); + + address[] memory route = new address[](5); + (route[0], route[1], route[2], route[3], route[4]) = (usdc, eth, usdc, eth, matic); + swapper.setSwapRoute(route); + + uint256 amountIn = getInputAmount(route[0], alice); + vm.prank(alice); + uint256 received = swapper.swap(route[0], route[4], amountIn, 0); // fails slippage otherwise + + console.log("Received:", received); + assertGt(received, 0, "Should output > 0"); + + address[] memory routeShorter = new address[](3); + (routeShorter[0], routeShorter[1], routeShorter[2]) = (usdc, eth, matic); + swapper.setSwapRoute(routeShorter); + + uint256 amountInShorter = getInputAmount(routeShorter[0], alice); + vm.prank(alice); + uint256 receivedShorter = swapper.swap(routeShorter[0], routeShorter[2], amountInShorter, 0); + + console.log("Received (Shorter):", receivedShorter); + assertGt(receivedShorter, received, "Should output more than the longer route"); + } +} diff --git a/forge/test/swapper/Swapper.txt b/forge/test/swapper/Swapper.txt deleted file mode 100644 index b6565ac1..00000000 --- a/forge/test/swapper/Swapper.txt +++ /dev/null @@ -1,234 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.19; - -import "forge-std/Test.sol"; - -import { UniswapV3Utils, IUniswapRouterV3WithDeadline } from "../../../contracts/BIFI/utils/UniswapV3Utils.sol"; -import { IUniswapRouterETH } from "../../../contracts/BIFI/interfaces/common/IUniswapRouterETH.sol"; -import { ISolidlyRouter } from "../../../contracts/BIFI/interfaces/common/ISolidlyRouter.sol"; -import { IBalancerVault } from "../../../contracts/BIFI/interfaces/beethovenx/IBalancerVault.sol"; - -// Interfaces -import { IERC20 } from "@openzeppelin-4/contracts/token/ERC20/IERC20.sol"; -import { BeefySwapper } from "../../../contracts/BIFI/infra/BeefySwapper.sol"; -import { BeefyOracle } from "../../../contracts/BIFI/infra/BeefyOracle/BeefyOracle.sol"; -import { BeefyOracleChainlink } from "../../../contracts/BIFI/infra/BeefyOracle/BeefyOracleChainlink.sol"; - -contract Swapper is Test { - - BeefySwapper public swapper; - BeefyOracle public oracle; - - address eth = 0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619; - address usdc = 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174; - address matic = 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270; - address usdr = 0x40379a439D4F6795B6fc9aa5687dB461677A2dBa; - - address ethFeedAddress = 0xF9680D99D6C9589e2a93a78A04A279e509205945; - address usdcFeedAddress = 0xfE4A8cc5b5B2366C1B58Bea3858e81843581b2F7; - address maticFeedAddress = 0xAB594600376Ec9fD91F8e885dADF0CE036862dE0; - - address usdcUsdrSolidlyPool = 0xD17cb0f162f133e339C0BbFc18c36c357E681D6b; - - bytes32 usdcEthBalancerPool = 0x03cd191f589d12b0582a99808cf19851e468e6b500010000000000000000000a; - - address uniswapV3Router = 0xE592427A0AEce92De3Edee1F18E0157C05861564; - address uniswapV2Router = 0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff; - address solidlyRouter = 0x06374F57991CDc836E5A318569A910FE6456D230; - address balancerRouter = 0xBA12222222228d8Ba445958a75a0704d566BF2C8; - - bytes ethFeed = abi.encode(ethFeedAddress); - bytes usdcFeed = abi.encode(usdcFeedAddress); - bytes maticFeed = abi.encode(maticFeedAddress); - - address alice; - - function setUp() public { - oracle = new BeefyOracle(); - oracle.initialize(); - - swapper = new BeefySwapper(); - swapper.initialize(address(oracle), 0.99 ether); - - address chainlinkOracle = deployCode("BeefyOracleChainlink.sol"); - oracle.setOracle(eth, chainlinkOracle, ethFeed); - oracle.setOracle(usdc, chainlinkOracle, usdcFeed); - oracle.setOracle(matic, chainlinkOracle, maticFeed); - - address[] memory tokens = new address[](2); - (tokens[0], tokens[1]) = (usdc, usdr); - address[] memory pools = new address[](1); - pools[0] = usdcUsdrSolidlyPool; - uint256[] memory twaps = new uint256[](1); - twaps[0] = 4; - bytes memory usdcUsdrSolidlyFeed = abi.encode(tokens, pools, twaps); - address solidlyOracle = deployCode("BeefyOracleSolidly.sol"); - oracle.setOracle(usdr, solidlyOracle, usdcUsdrSolidlyFeed); - - alice = makeAddr("alice"); - deal(usdc, alice, 1_000_000 * 10 ** 6); - vm.prank(alice); - IERC20(usdc).approve(address(swapper), type(uint256).max); - } - - function testSetSwapInfo() external { - address[] memory route = new address[](2); - (route[0], route[1]) = (usdc, eth); - uint24[] memory fees = new uint24[](1); - fees[0] = 500; - - bytes memory data = abi.encodeWithSelector( - IUniswapRouterV3WithDeadline.exactInput.selector, - IUniswapRouterV3WithDeadline.ExactInputParams( - UniswapV3Utils.routeToPath(route, fees), - address(swapper), - type(uint256).max, - 0, - 0 - ) - ); - uint256 amountIndex = 132; - uint256 minIndex = 164; - int8 minSign = 0; - - swapper.setSwapInfo( - route[0], - route[route.length - 1], - BeefySwapper.SwapInfo(uniswapV3Router, data, amountIndex, minIndex, minSign) - ); - - vm.prank(alice); - uint256 ethReceived = swapper.swap(usdc, eth, 1000 * 10 ** 6); - console.log("ETH received:", ethReceived); - assertGt(ethReceived, 0, "No ETH received from swap"); - } - - function testSetLongUniswapV3() external { - address[] memory route = new address[](3); - (route[0], route[1], route[2]) = (usdc, eth, matic); - uint24[] memory fees = new uint24[](2); - (fees[0], fees[1]) = (500, 3000); - - bytes memory data = abi.encodeWithSelector( - IUniswapRouterV3WithDeadline.exactInput.selector, - IUniswapRouterV3WithDeadline.ExactInputParams( - UniswapV3Utils.routeToPath(route, fees), - address(swapper), - type(uint256).max, - 0, - 0 - ) - ); - uint256 amountIndex = 132; - uint256 minIndex = 164; - int8 minSign = 0; - - swapper.setSwapInfo( - route[0], - route[route.length - 1], - BeefySwapper.SwapInfo(uniswapV3Router, data, amountIndex, minIndex, minSign) - ); - - vm.prank(alice); - uint256 maticReceived = swapper.swap(usdc, matic, 1000 * 10 ** 6); - console.log("MATIC received:", maticReceived); - assertGt(maticReceived, 0, "No MATIC received from swap"); - } - - function testSetSwapInfoUniswapV2() external { - address[] memory route = new address[](2); - (route[0], route[1]) = (usdc, eth); - - bytes memory data = abi.encodeWithSelector( - IUniswapRouterETH.swapExactTokensForTokens.selector, - 0, - 0, - route, - address(swapper), - type(uint256).max - ); - uint256 amountIndex = 4; - uint256 minIndex = 36; - int8 minSign = 0; - - swapper.setSwapInfo( - route[0], - route[route.length - 1], - BeefySwapper.SwapInfo(uniswapV2Router, data, amountIndex, minIndex, minSign) - ); - - vm.prank(alice); - uint256 ethReceived = swapper.swap(usdc, eth, 1000 * 10 ** 6); - console.log("ETH received:", ethReceived); - assertGt(ethReceived, 0, "No ETH received from swap"); - } - - function testSetSwapInfoSolidly() external { - address[] memory route = new address[](2); - (route[0], route[1]) = (usdc, usdr); - - ISolidlyRouter.Routes[] memory path = new ISolidlyRouter.Routes[](1); - path[0] = ISolidlyRouter.Routes(route[0], route[1], true); - - bytes memory data = abi.encodeWithSignature( - "swapExactTokensForTokens(uint256,uint256,(address,address,bool)[],address,uint256)", - 0, - 0, - path, - address(swapper), - type(uint256).max - ); - uint256 amountIndex = 4; - uint256 minIndex = 36; - int8 minSign = 0; - - swapper.setSwapInfo( - route[0], - route[route.length - 1], - BeefySwapper.SwapInfo(solidlyRouter, data, amountIndex, minIndex, minSign) - ); - - vm.prank(alice); - uint256 usdrReceived = swapper.swap(usdc, usdr, 1000 * 10 ** 6); - console.log("USDR received:", usdrReceived); - assertGt(usdrReceived, 0, "No USDR received from swap"); - } - - function testSetSwapInfoBalancer() external { - uint8 swapKind = 0; - IBalancerVault.BatchSwapStep[] memory swapSteps = new IBalancerVault.BatchSwapStep[](1); - swapSteps[0] = IBalancerVault.BatchSwapStep(usdcEthBalancerPool, 0, 1, 0, bytes("")); - address[] memory assets = new address[](2); - (assets[0], assets[1]) = (usdc, eth); - IBalancerVault.FundManagement memory funds = IBalancerVault.FundManagement( - address(swapper), false, payable(address(swapper)), false - ); - int256[] memory limits = new int256[](2); - (limits[0], limits[1]) = (type(int256).max, 0); - - bytes memory data = abi.encodeWithSelector( - IBalancerVault.batchSwap.selector, - swapKind, - swapSteps, - assets, - funds, - limits, - type(uint256).max - ); - uint256 amountIndex = 452; - uint256 minIndex = 708; - int8 minSign = -1; - - swapper.setSwapInfo( - assets[0], - assets[assets.length - 1], - BeefySwapper.SwapInfo(balancerRouter, data, amountIndex, minIndex, minSign) - ); - - vm.prank(alice); - uint256 ethReceived = swapper.swap(usdc, eth, 1000 * 10 ** 6); - console.log("ETH received:", ethReceived); - assertGt(ethReceived, 0, "No ETH received from swap"); - } -}