From 729ba7199fd902c9ec5d41454fedbda67f05f3dc Mon Sep 17 00:00:00 2001 From: Dima Lekhovitsky Date: Thu, 26 Oct 2023 16:39:39 +0300 Subject: [PATCH] test: adapters unit tests (#70) --- .github/workflows/pr.yml | 7 +- contracts/adapters/AbstractAdapter.sol | 36 +- .../aave/AaveV2_LendingPoolAdapter.sol | 44 +- .../aave/AaveV2_WrappedATokenAdapter.sol | 82 +- .../balancer/BalancerV2VaultAdapter.sol | 134 +- .../compound/CompoundV2_CErc20Adapter.sol | 42 +- .../compound/CompoundV2_CEtherAdapter.sol | 54 +- .../compound/CompoundV2_CTokenAdapter.sol | 42 +- .../convex/ConvexV1_BaseRewardPool.sol | 78 +- .../adapters/convex/ConvexV1_Booster.sol | 34 +- contracts/adapters/curve/CurveV1_2.sol | 13 +- contracts/adapters/curve/CurveV1_3.sol | 13 +- contracts/adapters/curve/CurveV1_4.sol | 13 +- contracts/adapters/curve/CurveV1_Base.sol | 218 ++-- contracts/adapters/curve/CurveV1_stETH.sol | 24 +- contracts/adapters/erc4626/ERC4626Adapter.sol | 71 +- contracts/adapters/lido/LidoV1.sol | 37 +- contracts/adapters/lido/WstETHV1.sol | 52 +- contracts/adapters/uniswap/UniswapV2.sol | 70 +- contracts/adapters/uniswap/UniswapV3.sol | 102 +- contracts/adapters/yearn/YearnV2.sol | 68 +- .../compound/CompoundV2_CEtherGateway.sol | 29 +- contracts/helpers/lido/LidoV1_WETHGateway.sol | 15 +- ...anagerMock.sol => CreditManagerV3Mock.sol} | 26 +- contracts/test/mocks/integrations/BPTMock.sol | 42 - .../test/mocks/integrations/BPTStableMock.sol | 44 - .../mocks/integrations/BalancerVaultMock.sol | 441 ------- .../integrations/ConvexBaseRewardPoolMock.sol | 238 ---- .../mocks/integrations/ConvexBoosterMock.sol | 190 --- .../ConvexExtraRewardPoolMock.sol | 125 -- .../ConvexTokenRewardContractMock.sol | 63 - .../integrations/CurveV1MetapoolMock.sol | 358 ------ .../test/mocks/integrations/CurveV1Mock.sol | 269 ---- .../integrations/CurveV1Mock_2Assets.sol | 94 -- .../integrations/CurveV1Mock_3Assets.sol | 94 -- .../integrations/CurveV1Mock_4Assets.sol | 94 -- .../mocks/integrations/CurveV1StETHMock.sol | 229 ---- .../test/mocks/integrations/LidoMock.sol | 61 - .../test/mocks/integrations/UniswapV2Mock.sol | 303 ----- .../test/mocks/integrations/UniswapV3Mock.sol | 162 --- .../test/mocks/integrations/WstETHV1Mock.sol | 56 - .../test/mocks/integrations/YearnV2Mock.sol | 92 -- .../mocks/integrations/balancer/VaultMock.sol | 33 + .../convex/BaseRewardPoolMock.sol | 33 + .../mocks/integrations/convex/BoosterMock.sol | 21 + .../convex/ExtraRewardWrapperMock.sol | 12 + .../mocks/integrations/convex/RewardsMock.sol | 12 + .../mocks/integrations/curve/PoolMock.sol | 30 + .../unit/adapters/AbstractAdapter.unit.t.sol | 171 --- .../unit/adapters/AbstractAdapterHarness.sol | 48 - .../test/unit/adapters/AdapterTestHelper.sol | 85 -- .../unit/adapters/AdapterUnitTestHelper.sol | 134 ++ .../unit/adapters/aave/AaveTestHelper.sol | 171 --- .../aave/AaveV2_LendingPoolAdapter.unit.t.sol | 331 ++--- .../aave/AaveV2_WrappedATokenAdapter.t.sol | 209 --- .../AaveV2_WrappedATokenAdapter.unit.t.sol | 220 ++++ .../balancer/BalancerV2VaultAdapter.t.sol | 1139 ----------------- .../BalancerV2VaultAdapter.unit.t.sol | 490 +++++++ .../adapters/compound/CompoundTestHelper.sol | 127 -- .../compound/CompoundV2_CErc20Adapter.t.sol | 39 - .../CompoundV2_CErc20Adapter.unit.t.sol | 179 +++ .../compound/CompoundV2_CEtherAdapter.t.sol | 34 - .../CompoundV2_CEtherAdapter.unit.t.sol | 184 +++ .../compound/CompoundV2_CTokenAdapter.t.sol | 315 ----- .../adapters/convex/ConvexAdapterHelper.sol | 328 ----- .../ConvexV1BaseRewardPoolAdapter.unit.t.sol | 263 ++++ .../convex/ConvexV1BoosterAdapter.harness.sol | 14 + .../convex/ConvexV1BoosterAdapter.unit.t.sol | 129 ++ .../convex/ConvexV1_BaseRewardPool.t.sol | 382 ------ .../adapters/convex/ConvexV1_Booster.t.sol | 163 --- .../curve/CurveV1Adapter2Assets.unit.t.sol | 101 ++ .../curve/CurveV1Adapter3Assets.unit.t.sol | 107 ++ .../curve/CurveV1Adapter4Assets.unit.t.sol | 112 ++ .../curve/CurveV1AdapterBase.harness.sol | 74 ++ .../curve/CurveV1AdapterBase.unit.t.sol | 444 +++++++ .../CurveV1AdapterBaseMetapoolTest.t.sol | 112 -- .../curve/CurveV1AdapterBaseTest.t.sol | 616 --------- .../curve/CurveV1AdapterCryptoTest.t.sol | 362 ------ .../adapters/curve/CurveV1AdapterHelper.sol | 696 ---------- .../curve/CurveV1Adapter_2AssetsTest.t.sol | 199 --- .../curve/CurveV1Adapter_3AssetsTest.t.sol | 209 --- .../curve/CurveV1Adapter_4AssetsTest.t.sol | 230 ---- .../adapters/curve/CurveV1StETHTest.t.sol | 318 ----- .../erc4626/ERC4626Adapter.unit.t.sol | 171 +++ .../unit/adapters/lido/LidoV1Adapter.t.sol | 176 --- .../adapters/lido/LidoV1Adapter.unit.t.sol | 102 ++ .../unit/adapters/lido/WstETHV1Adapter.t.sol | 273 ---- .../adapters/lido/WstETHV1Adapter.unit.t.sol | 129 ++ .../uniswap/UniswapV2Adapter.harness.sol | 18 + .../adapters/uniswap/UniswapV2Adapter.t.sol | 315 ----- .../uniswap/UniswapV2Adapter.unit.t.sol | 221 ++++ .../uniswap/UniswapV3Adapter.harness.sol | 14 + .../adapters/uniswap/UniswapV3Adapter.t.sol | 543 -------- .../uniswap/UniswapV3Adapter.unit.t.sol | 376 ++++++ .../unit/adapters/yearn/YearnV2Adapter.t.sol | 384 ------ .../adapters/yearn/YearnV2Adapter.unit.t.sol | 187 +++ .../CompoundV2_CEtherGateway.unit.t.sol | 158 +++ .../lido/LidoV1_WETHGateway.unit.t.sol | 88 ++ foundry.toml | 2 +- 99 files changed, 4876 insertions(+), 11416 deletions(-) rename contracts/test/mocks/credit/{CreditManagerMock.sol => CreditManagerV3Mock.sol} (51%) delete mode 100644 contracts/test/mocks/integrations/BPTMock.sol delete mode 100644 contracts/test/mocks/integrations/BPTStableMock.sol delete mode 100644 contracts/test/mocks/integrations/BalancerVaultMock.sol delete mode 100644 contracts/test/mocks/integrations/ConvexBaseRewardPoolMock.sol delete mode 100644 contracts/test/mocks/integrations/ConvexBoosterMock.sol delete mode 100644 contracts/test/mocks/integrations/ConvexExtraRewardPoolMock.sol delete mode 100644 contracts/test/mocks/integrations/ConvexTokenRewardContractMock.sol delete mode 100644 contracts/test/mocks/integrations/CurveV1MetapoolMock.sol delete mode 100644 contracts/test/mocks/integrations/CurveV1Mock.sol delete mode 100644 contracts/test/mocks/integrations/CurveV1Mock_2Assets.sol delete mode 100644 contracts/test/mocks/integrations/CurveV1Mock_3Assets.sol delete mode 100644 contracts/test/mocks/integrations/CurveV1Mock_4Assets.sol delete mode 100644 contracts/test/mocks/integrations/CurveV1StETHMock.sol delete mode 100644 contracts/test/mocks/integrations/LidoMock.sol delete mode 100644 contracts/test/mocks/integrations/UniswapV2Mock.sol delete mode 100644 contracts/test/mocks/integrations/UniswapV3Mock.sol delete mode 100644 contracts/test/mocks/integrations/WstETHV1Mock.sol delete mode 100644 contracts/test/mocks/integrations/YearnV2Mock.sol create mode 100644 contracts/test/mocks/integrations/balancer/VaultMock.sol create mode 100644 contracts/test/mocks/integrations/convex/BaseRewardPoolMock.sol create mode 100644 contracts/test/mocks/integrations/convex/BoosterMock.sol create mode 100644 contracts/test/mocks/integrations/convex/ExtraRewardWrapperMock.sol create mode 100644 contracts/test/mocks/integrations/convex/RewardsMock.sol create mode 100644 contracts/test/mocks/integrations/curve/PoolMock.sol delete mode 100644 contracts/test/unit/adapters/AbstractAdapter.unit.t.sol delete mode 100644 contracts/test/unit/adapters/AbstractAdapterHarness.sol delete mode 100644 contracts/test/unit/adapters/AdapterTestHelper.sol create mode 100644 contracts/test/unit/adapters/AdapterUnitTestHelper.sol delete mode 100644 contracts/test/unit/adapters/aave/AaveTestHelper.sol delete mode 100644 contracts/test/unit/adapters/aave/AaveV2_WrappedATokenAdapter.t.sol create mode 100644 contracts/test/unit/adapters/aave/AaveV2_WrappedATokenAdapter.unit.t.sol delete mode 100644 contracts/test/unit/adapters/balancer/BalancerV2VaultAdapter.t.sol create mode 100644 contracts/test/unit/adapters/balancer/BalancerV2VaultAdapter.unit.t.sol delete mode 100644 contracts/test/unit/adapters/compound/CompoundTestHelper.sol delete mode 100644 contracts/test/unit/adapters/compound/CompoundV2_CErc20Adapter.t.sol create mode 100644 contracts/test/unit/adapters/compound/CompoundV2_CErc20Adapter.unit.t.sol delete mode 100644 contracts/test/unit/adapters/compound/CompoundV2_CEtherAdapter.t.sol create mode 100644 contracts/test/unit/adapters/compound/CompoundV2_CEtherAdapter.unit.t.sol delete mode 100644 contracts/test/unit/adapters/compound/CompoundV2_CTokenAdapter.t.sol delete mode 100644 contracts/test/unit/adapters/convex/ConvexAdapterHelper.sol create mode 100644 contracts/test/unit/adapters/convex/ConvexV1BaseRewardPoolAdapter.unit.t.sol create mode 100644 contracts/test/unit/adapters/convex/ConvexV1BoosterAdapter.harness.sol create mode 100644 contracts/test/unit/adapters/convex/ConvexV1BoosterAdapter.unit.t.sol delete mode 100644 contracts/test/unit/adapters/convex/ConvexV1_BaseRewardPool.t.sol delete mode 100644 contracts/test/unit/adapters/convex/ConvexV1_Booster.t.sol create mode 100644 contracts/test/unit/adapters/curve/CurveV1Adapter2Assets.unit.t.sol create mode 100644 contracts/test/unit/adapters/curve/CurveV1Adapter3Assets.unit.t.sol create mode 100644 contracts/test/unit/adapters/curve/CurveV1Adapter4Assets.unit.t.sol create mode 100644 contracts/test/unit/adapters/curve/CurveV1AdapterBase.harness.sol create mode 100644 contracts/test/unit/adapters/curve/CurveV1AdapterBase.unit.t.sol delete mode 100644 contracts/test/unit/adapters/curve/CurveV1AdapterBaseMetapoolTest.t.sol delete mode 100644 contracts/test/unit/adapters/curve/CurveV1AdapterBaseTest.t.sol delete mode 100644 contracts/test/unit/adapters/curve/CurveV1AdapterCryptoTest.t.sol delete mode 100644 contracts/test/unit/adapters/curve/CurveV1AdapterHelper.sol delete mode 100644 contracts/test/unit/adapters/curve/CurveV1Adapter_2AssetsTest.t.sol delete mode 100644 contracts/test/unit/adapters/curve/CurveV1Adapter_3AssetsTest.t.sol delete mode 100644 contracts/test/unit/adapters/curve/CurveV1Adapter_4AssetsTest.t.sol delete mode 100644 contracts/test/unit/adapters/curve/CurveV1StETHTest.t.sol create mode 100644 contracts/test/unit/adapters/erc4626/ERC4626Adapter.unit.t.sol delete mode 100644 contracts/test/unit/adapters/lido/LidoV1Adapter.t.sol create mode 100644 contracts/test/unit/adapters/lido/LidoV1Adapter.unit.t.sol delete mode 100644 contracts/test/unit/adapters/lido/WstETHV1Adapter.t.sol create mode 100644 contracts/test/unit/adapters/lido/WstETHV1Adapter.unit.t.sol create mode 100644 contracts/test/unit/adapters/uniswap/UniswapV2Adapter.harness.sol delete mode 100644 contracts/test/unit/adapters/uniswap/UniswapV2Adapter.t.sol create mode 100644 contracts/test/unit/adapters/uniswap/UniswapV2Adapter.unit.t.sol create mode 100644 contracts/test/unit/adapters/uniswap/UniswapV3Adapter.harness.sol delete mode 100644 contracts/test/unit/adapters/uniswap/UniswapV3Adapter.t.sol create mode 100644 contracts/test/unit/adapters/uniswap/UniswapV3Adapter.unit.t.sol delete mode 100644 contracts/test/unit/adapters/yearn/YearnV2Adapter.t.sol create mode 100644 contracts/test/unit/adapters/yearn/YearnV2Adapter.unit.t.sol create mode 100644 contracts/test/unit/helpers/compound/CompoundV2_CEtherGateway.unit.t.sol create mode 100644 contracts/test/unit/helpers/lido/LidoV1_WETHGateway.unit.t.sol diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 94696ae2..65560a87 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -33,11 +33,14 @@ jobs: with: version: nightly + - name: Run forge install + run: forge install + - name: Run forge unit tests - run: forge test --no-match-test _live_ + run: forge test --match-test test_U - name: Run forge integration tests - run: forge test --match-test _live_ --fork-url ${{ secrets.LIVE_TESTS_FORK }} --chain-id 1337 + run: forge test --match-test _live_ --fork-url ${{ secrets.MAINNET_TESTS_FORK }} --chain-id 1337 - name: Perform checks run: | diff --git a/contracts/adapters/AbstractAdapter.sol b/contracts/adapters/AbstractAdapter.sol index 085d1e77..c8510d50 100644 --- a/contracts/adapters/AbstractAdapter.sol +++ b/contracts/adapters/AbstractAdapter.sol @@ -24,12 +24,12 @@ abstract contract AbstractAdapter is IAdapter, ACLTrait { /// @param _creditManager Credit manager to connect the adapter to /// @param _targetContract Address of the adapted contract constructor(address _creditManager, address _targetContract) - ACLTrait(ICreditManagerV3(_creditManager).addressProvider()) // U:[AA-1A] - nonZeroAddress(_targetContract) // U:[AA-1A] + ACLTrait(ICreditManagerV3(_creditManager).addressProvider()) + nonZeroAddress(_targetContract) { - creditManager = _creditManager; // U:[AA-1B] - addressProvider = ICreditManagerV3(_creditManager).addressProvider(); // U:[AA-1B] - targetContract = _targetContract; // U:[AA-1B] + creditManager = _creditManager; + addressProvider = ICreditManagerV3(_creditManager).addressProvider(); + targetContract = _targetContract; } /// @dev Ensures that caller of the function is credit facade connected to the credit manager @@ -42,18 +42,18 @@ abstract contract AbstractAdapter is IAdapter, ACLTrait { /// @dev Ensures that caller is credit facade connected to the credit manager function _revertIfCallerNotCreditFacade() internal view { if (msg.sender != ICreditManagerV3(creditManager).creditFacade()) { - revert CallerNotCreditFacadeException(); // U:[AA-2] + revert CallerNotCreditFacadeException(); } } /// @dev Ensures that active credit account is set and returns its address function _creditAccount() internal view returns (address) { - return ICreditManagerV3(creditManager).getActiveCreditAccountOrRevert(); // U:[AA-3] + return ICreditManagerV3(creditManager).getActiveCreditAccountOrRevert(); } /// @dev Ensures that token is registered as collateral in the credit manager and returns its mask function _getMaskOrRevert(address token) internal view returns (uint256 tokenMask) { - tokenMask = ICreditManagerV3(creditManager).getTokenMaskOrRevert(token); // U:[AA-4] + tokenMask = ICreditManagerV3(creditManager).getTokenMaskOrRevert(token); } /// @dev Approves target contract to spend given token from the active credit account @@ -61,7 +61,7 @@ abstract contract AbstractAdapter is IAdapter, ACLTrait { /// @param token Token to approve /// @param amount Amount to approve function _approveToken(address token, uint256 amount) internal { - ICreditManagerV3(creditManager).approveCreditAccount(token, amount); // U:[AA-5] + ICreditManagerV3(creditManager).approveCreditAccount(token, amount); } /// @dev Executes an external call from the active credit account to the target contract @@ -69,7 +69,7 @@ abstract contract AbstractAdapter is IAdapter, ACLTrait { /// @param callData Data to call the target contract with /// @return result Call result function _execute(bytes memory callData) internal returns (bytes memory result) { - return ICreditManagerV3(creditManager).execute(callData); // U:[AA-6] + return ICreditManagerV3(creditManager).execute(callData); } /// @dev Executes a swap operation without input token approval @@ -86,10 +86,10 @@ abstract contract AbstractAdapter is IAdapter, ACLTrait { internal returns (uint256 tokensToEnable, uint256 tokensToDisable, bytes memory result) { - tokensToEnable = _getMaskOrRevert(tokenOut); // U:[AA-7] + tokensToEnable = _getMaskOrRevert(tokenOut); uint256 tokenInMask = _getMaskOrRevert(tokenIn); - if (disableTokenIn) tokensToDisable = tokenInMask; // U:[AA-7] - result = _execute(callData); // U:[AA-7] + if (disableTokenIn) tokensToDisable = tokenInMask; + result = _execute(callData); } /// @dev Executes a swap operation with maximum input token approval, and revokes approval after the call @@ -107,10 +107,10 @@ abstract contract AbstractAdapter is IAdapter, ACLTrait { internal returns (uint256 tokensToEnable, uint256 tokensToDisable, bytes memory result) { - tokensToEnable = _getMaskOrRevert(tokenOut); // U:[AA-8] - if (disableTokenIn) tokensToDisable = _getMaskOrRevert(tokenIn); // U:[AA-8] - _approveToken(tokenIn, type(uint256).max); // U:[AA-8] - result = _execute(callData); // U:[AA-8] - _approveToken(tokenIn, 1); // U:[AA-8] + tokensToEnable = _getMaskOrRevert(tokenOut); + if (disableTokenIn) tokensToDisable = _getMaskOrRevert(tokenIn); + _approveToken(tokenIn, type(uint256).max); + result = _execute(callData); + _approveToken(tokenIn, 1); } } diff --git a/contracts/adapters/aave/AaveV2_LendingPoolAdapter.sol b/contracts/adapters/aave/AaveV2_LendingPoolAdapter.sol index 80ac2bc7..3d086d76 100644 --- a/contracts/adapters/aave/AaveV2_LendingPoolAdapter.sol +++ b/contracts/adapters/aave/AaveV2_LendingPoolAdapter.sol @@ -20,7 +20,9 @@ contract AaveV2_LendingPoolAdapter is AbstractAdapter, IAaveV2_LendingPoolAdapte /// @notice Constructor /// @param _creditManager Credit manager address /// @param _lendingPool Lending pool address - constructor(address _creditManager, address _lendingPool) AbstractAdapter(_creditManager, _lendingPool) {} + constructor(address _creditManager, address _lendingPool) + AbstractAdapter(_creditManager, _lendingPool) // U:[AAVE2-1] + {} /// @dev Returns aToken address for given underlying token function _aToken(address underlying) internal view returns (address) { @@ -39,11 +41,11 @@ contract AaveV2_LendingPoolAdapter is AbstractAdapter, IAaveV2_LendingPoolAdapte function deposit(address asset, uint256 amount, address, uint16) external override - creditFacadeOnly // F: [AAV2LP-1] + creditFacadeOnly // U:[AAVE2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); - (tokensToEnable, tokensToDisable) = _deposit(creditAccount, asset, amount, false); // F: [AAV2LP-3] + address creditAccount = _creditAccount(); // U:[AAVE2-3] + (tokensToEnable, tokensToDisable) = _deposit(creditAccount, asset, amount, false); // U:[AAVE2-3] } /// @notice Deposit all underlying tokens into Aave in exchange for aTokens, disables underlying @@ -51,18 +53,18 @@ contract AaveV2_LendingPoolAdapter is AbstractAdapter, IAaveV2_LendingPoolAdapte function depositAll(address asset) external override - creditFacadeOnly // F: [AAV2LP-1] + creditFacadeOnly // U:[AAVE2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[AAVE2-4] - uint256 amount = IERC20(asset).balanceOf(creditAccount); + uint256 amount = IERC20(asset).balanceOf(creditAccount); // U:[AAVE2-4] if (amount <= 1) return (0, 0); unchecked { - --amount; + --amount; // U:[AAVE2-4] } - (tokensToEnable, tokensToDisable) = _deposit(creditAccount, asset, amount, true); // F: [AAV2LP-4] + (tokensToEnable, tokensToDisable) = _deposit(creditAccount, asset, amount, true); // U:[AAVE2-4] } /// @dev Internal implementation of `deposit` and `depositAll` functions @@ -79,7 +81,7 @@ contract AaveV2_LendingPoolAdapter is AbstractAdapter, IAaveV2_LendingPoolAdapte _aToken(asset), abi.encodeCall(ILendingPool.deposit, (asset, amount, creditAccount, 0)), disableTokenIn - ); // F: [AAV2LP-2] + ); // U:[AAVE2-3,4] } // ----------- // @@ -94,14 +96,14 @@ contract AaveV2_LendingPoolAdapter is AbstractAdapter, IAaveV2_LendingPoolAdapte function withdraw(address asset, uint256 amount, address) external override - creditFacadeOnly // F: [AAV2LP-1] + creditFacadeOnly // U:[AAVE2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[AAVE2-5A,5B] if (amount == type(uint256).max) { - (tokensToEnable, tokensToDisable) = _withdrawAll(creditAccount, asset); // F: [AAV2LP-5B] + (tokensToEnable, tokensToDisable) = _withdrawAll(creditAccount, asset); // U:[AAVE2-5B] } else { - (tokensToEnable, tokensToDisable) = _withdraw(creditAccount, asset, amount); // F: [AAV2LP-5A] + (tokensToEnable, tokensToDisable) = _withdraw(creditAccount, asset, amount); // U:[AAVE2-5A] } } @@ -110,11 +112,11 @@ contract AaveV2_LendingPoolAdapter is AbstractAdapter, IAaveV2_LendingPoolAdapte function withdrawAll(address asset) external override - creditFacadeOnly // F: [AAV2LP-1] + creditFacadeOnly // U:[AAVE2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); - (tokensToEnable, tokensToDisable) = _withdrawAll(creditAccount, asset); // F: [AAV2LP-6] + address creditAccount = _creditAccount(); // U:[AAVE2-6] + (tokensToEnable, tokensToDisable) = _withdrawAll(creditAccount, asset); // U:[AAVE2-6] } /// @dev Internal implementation of `withdraw` functionality @@ -127,7 +129,7 @@ contract AaveV2_LendingPoolAdapter is AbstractAdapter, IAaveV2_LendingPoolAdapte returns (uint256 tokensToEnable, uint256 tokensToDisable) { (tokensToEnable, tokensToDisable,) = - _executeSwapNoApprove(_aToken(asset), asset, _encodeWithdraw(creditAccount, asset, amount), false); // F: [AAV2LP-2] + _executeSwapNoApprove(_aToken(asset), asset, _encodeWithdraw(creditAccount, asset, amount), false); // U:[AAVE2-5A] } /// @dev Internal implementation of `withdrawAll` functionality @@ -140,14 +142,14 @@ contract AaveV2_LendingPoolAdapter is AbstractAdapter, IAaveV2_LendingPoolAdapte returns (uint256 tokensToEnable, uint256 tokensToDisable) { address aToken = _aToken(asset); - uint256 amount = IERC20(aToken).balanceOf(creditAccount); + uint256 amount = IERC20(aToken).balanceOf(creditAccount); // U:[AAVE2-5B,6] if (amount <= 1) return (0, 0); unchecked { - --amount; + --amount; // U:[AAVE2-5B,6] } (tokensToEnable, tokensToDisable,) = - _executeSwapNoApprove(aToken, asset, _encodeWithdraw(creditAccount, asset, amount), true); // F: [AAV2LP-2] + _executeSwapNoApprove(aToken, asset, _encodeWithdraw(creditAccount, asset, amount), true); // U:[AAVE2-5B,6] } /// @dev Returns calldata for `ILendingPool.withdraw` call diff --git a/contracts/adapters/aave/AaveV2_WrappedATokenAdapter.sol b/contracts/adapters/aave/AaveV2_WrappedATokenAdapter.sol index e9e23d06..f93bf4c8 100644 --- a/contracts/adapters/aave/AaveV2_WrappedATokenAdapter.sol +++ b/contracts/adapters/aave/AaveV2_WrappedATokenAdapter.sol @@ -35,14 +35,16 @@ contract AaveV2_WrappedATokenAdapter is AbstractAdapter, IAaveV2_WrappedATokenAd /// @notice Constructor /// @param _creditManager Credit manager address /// @param _waToken Wrapped aToken address - constructor(address _creditManager, address _waToken) AbstractAdapter(_creditManager, _waToken) { - waTokenMask = _getMaskOrRevert(targetContract); // F: [AAV2W-1, AAV2W-2] + constructor(address _creditManager, address _waToken) + AbstractAdapter(_creditManager, _waToken) // U:[AAVE2W-1] + { + waTokenMask = _getMaskOrRevert(targetContract); // U:[AAVE2W-1] - aToken = WrappedAToken(targetContract).aToken(); // F: [AAV2W-2] - aTokenMask = _getMaskOrRevert(aToken); // F: [AAV2W-2] + aToken = WrappedAToken(targetContract).aToken(); // U:[AAVE2W-1] + aTokenMask = _getMaskOrRevert(aToken); // U:[AAVE2W-1] - underlying = WrappedAToken(targetContract).underlying(); // F: [AAV2W-2] - tokenMask = _getMaskOrRevert(underlying); // F: [AAV2W-2] + underlying = WrappedAToken(targetContract).underlying(); // U:[AAVE2W-1] + tokenMask = _getMaskOrRevert(underlying); // U:[AAVE2W-1] } // -------- // @@ -54,20 +56,20 @@ contract AaveV2_WrappedATokenAdapter is AbstractAdapter, IAaveV2_WrappedATokenAd function deposit(uint256 assets) external override - creditFacadeOnly // F: [AAV2W-3] + creditFacadeOnly // U:[AAVE2W-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _deposit(assets, false); // F: [AAV2W-4] + (tokensToEnable, tokensToDisable) = _deposit(assets, false); // U:[AAVE2W-3] } /// @notice Deposit all balance of aTokens, disables aToken function depositAll() external override - creditFacadeOnly // F: [AAV2W-3] + creditFacadeOnly // U:[AAVE2W-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _depositAll(false); // F: [AAV2W-5] + (tokensToEnable, tokensToDisable) = _depositAll(false); // U:[AAVE2W-4] } /// @notice Deposit given amount underlying tokens @@ -75,20 +77,20 @@ contract AaveV2_WrappedATokenAdapter is AbstractAdapter, IAaveV2_WrappedATokenAd function depositUnderlying(uint256 assets) external override - creditFacadeOnly // F: [AAV2W-3] + creditFacadeOnly // U:[AAVE2W-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _deposit(assets, true); // F: [AAV2W-4] + (tokensToEnable, tokensToDisable) = _deposit(assets, true); // U:[AAVE2W-5] } /// @notice Deposit all balance of underlying tokens, disables underlying function depositAllUnderlying() external override - creditFacadeOnly // F: [AAV2W-3] + creditFacadeOnly // U:[AAVE2W-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _depositAll(true); // F: [AAV2W-5] + (tokensToEnable, tokensToDisable) = _depositAll(true); // U:[AAVE2W-6] } /// @dev Internal implementation of `deposit` and `depositUnderlying` @@ -101,10 +103,10 @@ contract AaveV2_WrappedATokenAdapter is AbstractAdapter, IAaveV2_WrappedATokenAd { address tokenIn = fromUnderlying ? underlying : aToken; - _approveToken(tokenIn, type(uint256).max); - _execute(_encodeDeposit(assets, fromUnderlying)); - _approveToken(tokenIn, 1); - (tokensToEnable, tokensToDisable) = (waTokenMask, 0); + _approveToken(tokenIn, type(uint256).max); // U:[AAVE2W-3,5] + _execute(_encodeDeposit(assets, fromUnderlying)); // U:[AAVE2W-3,5] + _approveToken(tokenIn, 1); // U:[AAVE2W-3,5] + (tokensToEnable, tokensToDisable) = (waTokenMask, 0); // U:[AAVE2W-3,5] } /// @dev Internal implementation of `deposit` and `depositUnderlying` @@ -112,19 +114,19 @@ contract AaveV2_WrappedATokenAdapter is AbstractAdapter, IAaveV2_WrappedATokenAd /// - waToken is enabled after the call /// - underlying / aToken is disabled after the call because operation spends the entire balance function _depositAll(bool fromUnderlying) internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[AAVE2W-4,6] address tokenIn = fromUnderlying ? underlying : aToken; - uint256 assets = IERC20(tokenIn).balanceOf(creditAccount); + uint256 assets = IERC20(tokenIn).balanceOf(creditAccount); // U:[AAVE2W-4,6] if (assets <= 1) return (0, 0); unchecked { - --assets; + --assets; // U:[AAVE2W-4,6] } - _approveToken(tokenIn, type(uint256).max); - _execute(_encodeDeposit(assets, fromUnderlying)); - _approveToken(tokenIn, 1); - (tokensToEnable, tokensToDisable) = (waTokenMask, fromUnderlying ? tokenMask : aTokenMask); + _approveToken(tokenIn, type(uint256).max); // U:[AAVE2W-4,6] + _execute(_encodeDeposit(assets, fromUnderlying)); // U:[AAVE2W-4,6] + _approveToken(tokenIn, 1); // U:[AAVE2W-4,6] + (tokensToEnable, tokensToDisable) = (waTokenMask, fromUnderlying ? tokenMask : aTokenMask); // U:[AAVE2W-4,6] } /// @dev Returns data for `WrappedAToken`'s `deposit` or `depositUnderlying` call @@ -143,20 +145,20 @@ contract AaveV2_WrappedATokenAdapter is AbstractAdapter, IAaveV2_WrappedATokenAd function withdraw(uint256 shares) external override - creditFacadeOnly // F: [AAV2W-3] + creditFacadeOnly // U:[AAVE2W-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _withdraw(shares, false); // F: [AAV2W-6] + (tokensToEnable, tokensToDisable) = _withdraw(shares, false); // U:[AAVE2W-7] } /// @notice Withdraw all balance of waTokens for aTokens, disables waToken function withdrawAll() external override - creditFacadeOnly // F: [AAV2W-3] + creditFacadeOnly // U:[AAVE2W-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _withdrawAll(false); // F: [AAV2W-7] + (tokensToEnable, tokensToDisable) = _withdrawAll(false); // U:[AAVE2W-8] } /// @notice Withdraw given amount of waTokens for underlying tokens @@ -164,20 +166,20 @@ contract AaveV2_WrappedATokenAdapter is AbstractAdapter, IAaveV2_WrappedATokenAd function withdrawUnderlying(uint256 shares) external override - creditFacadeOnly // F: [AAV2W-3] + creditFacadeOnly // U:[AAVE2W-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _withdraw(shares, true); // F: [AAV2W-6] + (tokensToEnable, tokensToDisable) = _withdraw(shares, true); // U:[AAVE2W-9] } /// @notice Withdraw all balance of waTokens for underlying tokens, disables waToken function withdrawAllUnderlying() external override - creditFacadeOnly // F: [AAV2W-3] + creditFacadeOnly // U:[AAVE2W-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _withdrawAll(true); // F: [AAV2W-7] + (tokensToEnable, tokensToDisable) = _withdrawAll(true); // U:[AAVE2W-10] } /// @dev Internal implementation of `withdraw` and `withdrawUnderlying` @@ -188,8 +190,8 @@ contract AaveV2_WrappedATokenAdapter is AbstractAdapter, IAaveV2_WrappedATokenAd internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _execute(_encodeWithdraw(shares, toUnderlying)); - (tokensToEnable, tokensToDisable) = (toUnderlying ? tokenMask : aTokenMask, 0); + _execute(_encodeWithdraw(shares, toUnderlying)); // U:[AAVE2W-7,9] + (tokensToEnable, tokensToDisable) = (toUnderlying ? tokenMask : aTokenMask, 0); // U:[AAVE2W-7,9] } /// @dev Internal implementation of `withdraw` and `withdrawUnderlying` @@ -197,16 +199,16 @@ contract AaveV2_WrappedATokenAdapter is AbstractAdapter, IAaveV2_WrappedATokenAd /// - underlying / aToken is enabled after the call /// - waToken is disabled after the call because operation spends the entire balance function _withdrawAll(bool toUnderlying) internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[AAVE2W-8,10] - uint256 shares = IERC20(targetContract).balanceOf(creditAccount); + uint256 shares = IERC20(targetContract).balanceOf(creditAccount); // U:[AAVE2W-8,10] if (shares <= 1) return (0, 0); unchecked { - --shares; + --shares; // U:[AAVE2W-8,10] } - _execute(_encodeWithdraw(shares, toUnderlying)); - (tokensToEnable, tokensToDisable) = (toUnderlying ? tokenMask : aTokenMask, waTokenMask); + _execute(_encodeWithdraw(shares, toUnderlying)); // U:[AAVE2W-8,10] + (tokensToEnable, tokensToDisable) = (toUnderlying ? tokenMask : aTokenMask, waTokenMask); // U:[AAVE2W-8,10] } /// @dev Returns data for `WrappedAToken`'s `withdraw` or `withdrawUnderlying` call diff --git a/contracts/adapters/balancer/BalancerV2VaultAdapter.sol b/contracts/adapters/balancer/BalancerV2VaultAdapter.sol index 9f0b01c1..516dbdc1 100644 --- a/contracts/adapters/balancer/BalancerV2VaultAdapter.sol +++ b/contracts/adapters/balancer/BalancerV2VaultAdapter.sol @@ -39,7 +39,9 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { /// @notice Constructor /// @param _creditManager Credit manager address /// @param _vault Balancer vault address - constructor(address _creditManager, address _vault) AbstractAdapter(_creditManager, _vault) {} + constructor(address _creditManager, address _vault) + AbstractAdapter(_creditManager, _vault) // U:[BAL2-1] + {} // ----- // // SWAPS // @@ -61,19 +63,19 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { function swap(SingleSwap memory singleSwap, FundManagement memory, uint256 limit, uint256 deadline) external override - creditFacadeOnly + creditFacadeOnly // U:[BAL2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { if (poolStatus[singleSwap.poolId] == PoolStatus.NOT_ALLOWED) { - revert PoolNotSupportedException(); + revert PoolNotSupportedException(); // U:[BAL2-3] } - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[BAL2-3] address tokenIn = address(singleSwap.assetIn); address tokenOut = address(singleSwap.assetOut); - FundManagement memory fundManagement = _getDefaultFundManagement(creditAccount); + FundManagement memory fundManagement = _getDefaultFundManagement(creditAccount); // U:[BAL2-3] // calling `_executeSwap` because we need to check if output token is registered as collateral token in the CM (tokensToEnable, tokensToDisable,) = _executeSwapSafeApprove( @@ -81,7 +83,7 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { tokenOut, abi.encodeCall(IBalancerV2Vault.swap, (singleSwap, fundManagement, limit, deadline)), false - ); // F: [ABV2-1] + ); // U:[BAL2-3] } /// @notice Swaps the entire balance of a token for another token within a single pool, disables input token @@ -96,26 +98,26 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { function swapAll(SingleSwapAll memory singleSwapAll, uint256 limitRateRAY, uint256 deadline) external override - creditFacadeOnly + creditFacadeOnly // U:[BAL2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { if (poolStatus[singleSwapAll.poolId] == PoolStatus.NOT_ALLOWED) { - revert PoolNotSupportedException(); + revert PoolNotSupportedException(); // U:[BAL2-4] } - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[BAL2-4] address tokenIn = address(singleSwapAll.assetIn); address tokenOut = address(singleSwapAll.assetOut); - uint256 balanceInBefore = IERC20(tokenIn).balanceOf(creditAccount); + uint256 balanceInBefore = IERC20(tokenIn).balanceOf(creditAccount); // U:[BAL2-4] if (balanceInBefore <= 1) return (0, 0); unchecked { - balanceInBefore--; + balanceInBefore--; // U:[BAL2-4] } - FundManagement memory fundManagement = _getDefaultFundManagement(creditAccount); + FundManagement memory fundManagement = _getDefaultFundManagement(creditAccount); // U:[BAL2-4] // calling `_executeSwap` because we need to check if output token is registered as collateral token in the CM (tokensToEnable, tokensToDisable,) = _executeSwapSafeApprove( @@ -138,7 +140,7 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { ) ), true - ); // F: [ABV2-2] + ); // U:[BAL2-4] } /// @notice Performs a multi-hop swap through several Balancer pools @@ -163,31 +165,36 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { FundManagement memory, int256[] memory limits, uint256 deadline - ) external override creditFacadeOnly returns (uint256 tokensToEnable, uint256 tokensToDisable) { + ) + external + override + creditFacadeOnly // U:[BAL2-2] + returns (uint256 tokensToEnable, uint256 tokensToDisable) + { unchecked { for (uint256 i; i < swaps.length; ++i) { if (poolStatus[swaps[i].poolId] == PoolStatus.NOT_ALLOWED) { - revert PoolNotSupportedException(); + revert PoolNotSupportedException(); // U:[BAL2-5] } } } - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[BAL2-5] - FundManagement memory fundManagement = _getDefaultFundManagement(creditAccount); + FundManagement memory fundManagement = _getDefaultFundManagement(creditAccount); // U:[BAL2-5] - _approveAssets(assets, limits, type(uint256).max); + _approveAssets(assets, limits, type(uint256).max); // U:[BAL2-5] int256[] memory assetDeltas = abi.decode( _execute( abi.encodeCall(IBalancerV2Vault.batchSwap, (kind, swaps, assets, fundManagement, limits, deadline)) ), (int256[]) - ); // F: [ABV2-3] + ); // U:[BAL2-5] - _approveAssets(assets, limits, 1); + _approveAssets(assets, limits, 1); // U:[BAL2-5] - (tokensToEnable, tokensToDisable) = (_getTokensToEnable(assets, assetDeltas), 0); + (tokensToEnable, tokensToDisable) = (_getTokensToEnable(assets, assetDeltas), 0); // U:[BAL2-5] } // --------- // @@ -208,23 +215,23 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { function joinPool(bytes32 poolId, address, address, JoinPoolRequest memory request) external override - creditFacadeOnly + creditFacadeOnly // U:[BAL2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { if (poolStatus[poolId] != PoolStatus.ALLOWED) { - revert PoolNotSupportedException(); + revert PoolNotSupportedException(); // U:[BAL2-6] } - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[BAL2-6] (address bpt,) = IBalancerV2Vault(targetContract).getPool(poolId); - request.fromInternalBalance = false; + request.fromInternalBalance = false; // U:[BAL2-6] - _approveAssets(request.assets, request.maxAmountsIn, type(uint256).max); - _execute(abi.encodeCall(IBalancerV2Vault.joinPool, (poolId, creditAccount, creditAccount, request))); // F: [ABV2-4] - _approveAssets(request.assets, request.maxAmountsIn, 1); - (tokensToEnable, tokensToDisable) = (_getMaskOrRevert(bpt), 0); + _approveAssets(request.assets, request.maxAmountsIn, type(uint256).max); // U:[BAL2-6] + _execute(abi.encodeCall(IBalancerV2Vault.joinPool, (poolId, creditAccount, creditAccount, request))); // U:[BAL2-6] + _approveAssets(request.assets, request.maxAmountsIn, 1); // U:[BAL2-6] + (tokensToEnable, tokensToDisable) = (_getMaskOrRevert(bpt), 0); // U:[BAL2-6] } /// @notice Deposits single asset as liquidity into a Balancer pool @@ -236,14 +243,14 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { function joinPoolSingleAsset(bytes32 poolId, IAsset assetIn, uint256 amountIn, uint256 minAmountOut) external override - creditFacadeOnly + creditFacadeOnly // U:[BAL2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { if (poolStatus[poolId] != PoolStatus.ALLOWED) { - revert PoolNotSupportedException(); + revert PoolNotSupportedException(); // U:[BAL2-7] } - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[BAL2-7] (address bpt,) = IBalancerV2Vault(targetContract).getPool(poolId); @@ -261,7 +268,7 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { ) ), false - ); // F: [ABV2-5] + ); // U:[BAL2-7] } /// @notice Deposits the entire balance of given asset as liquidity into a Balancer pool, disables said asset @@ -272,20 +279,20 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { function joinPoolSingleAssetAll(bytes32 poolId, IAsset assetIn, uint256 minRateRAY) external override - creditFacadeOnly + creditFacadeOnly // U:[BAL2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { if (poolStatus[poolId] != PoolStatus.ALLOWED) { - revert PoolNotSupportedException(); + revert PoolNotSupportedException(); // U:[BAL2-8] } - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[BAL2-8] - uint256 balanceInBefore = IERC20(address(assetIn)).balanceOf(creditAccount); + uint256 balanceInBefore = IERC20(address(assetIn)).balanceOf(creditAccount); // U:[BAL2-8] if (balanceInBefore <= 1) return (0, 0); unchecked { - balanceInBefore--; + balanceInBefore--; // U:[BAL2-8] } (address bpt,) = IBalancerV2Vault(targetContract).getPool(poolId); @@ -305,7 +312,7 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { ) ), true - ); // F: [ABV2-6] + ); // U:[BAL2-8] } /// @dev Internal function that builds a `JoinPoolRequest` struct for one-sided deposits @@ -329,7 +336,7 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { request.assets[i] = IAsset(address(tokens[i])); if (request.assets[i] == assetIn) { - request.maxAmountsIn[i] = amountIn; + request.maxAmountsIn[i] = amountIn; // U:[BAL2-7,8] } if (address(request.assets[i]) == bpt) { @@ -338,7 +345,7 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { } } - request.userData = abi.encode(uint256(1), _removeIndex(request.maxAmountsIn, bptIndex), minAmountOut); + request.userData = abi.encode(uint256(1), _removeIndex(request.maxAmountsIn, bptIndex), minAmountOut); // U:[BAL2-7,8] } // --------- // @@ -358,20 +365,20 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { function exitPool(bytes32 poolId, address, address payable, ExitPoolRequest memory request) external override - creditFacadeOnly + creditFacadeOnly // U:[BAL2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[BAL2-9] (address bpt,) = IBalancerV2Vault(targetContract).getPool(poolId); - request.toInternalBalance = false; + request.toInternalBalance = false; // U:[BAL2-9] - _execute(abi.encodeCall(IBalancerV2Vault.exitPool, (poolId, creditAccount, payable(creditAccount), request))); // F: [ABV2-7] + _execute(abi.encodeCall(IBalancerV2Vault.exitPool, (poolId, creditAccount, payable(creditAccount), request))); // U:[BAL2-9] - _getMaskOrRevert(bpt); + _getMaskOrRevert(bpt); // U:[BAL2-9] (tokensToEnable, tokensToDisable) = - (_getTokensToEnable(request.assets, _getBalancesFilter(creditAccount, request.assets)), 0); + (_getTokensToEnable(request.assets, _getBalancesFilter(creditAccount, request.assets)), 0); // U:[BAL2-9] } /// @notice Withdraws liquidity from a Balancer pool, burning BPT and receiving a single asset @@ -382,10 +389,10 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { function exitPoolSingleAsset(bytes32 poolId, IAsset assetOut, uint256 amountIn, uint256 minAmountOut) external override - creditFacadeOnly + creditFacadeOnly // U:[BAL2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[BAL2-10] (address bpt,) = IBalancerV2Vault(targetContract).getPool(poolId); @@ -403,7 +410,7 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { ) ), false - ); // F: [ABV2-8] + ); // U:[BAL2-10] } /// @notice Withdraws liquidity from a Balancer pool, burning BPT and receiving a single asset, disables BPT @@ -413,21 +420,21 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { function exitPoolSingleAssetAll(bytes32 poolId, IAsset assetOut, uint256 minRateRAY) external override - creditFacadeOnly + creditFacadeOnly // U:[BAL2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[BAL2-11] (address bpt,) = IBalancerV2Vault(targetContract).getPool(poolId); - uint256 balanceInBefore = IERC20(bpt).balanceOf(creditAccount); + uint256 balanceInBefore = IERC20(bpt).balanceOf(creditAccount); // U:[BAL2-11] if (balanceInBefore <= 1) return (0, 0); unchecked { - balanceInBefore--; + balanceInBefore--; // U:[BAL2-11] } - uint256 amountOutMin = (balanceInBefore * minRateRAY) / RAY; + uint256 amountOutMin = (balanceInBefore * minRateRAY) / RAY; // U:[BAL2-11] // calling `_executeSwap` because we need to check if asset is registered as collateral token in the CM (tokensToEnable, tokensToDisable,) = _executeSwapNoApprove( bpt, @@ -442,7 +449,7 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { ) ), true - ); // F: [ABV2-9] + ); // U:[BAL2-11] } /// @dev Internal function that builds an `ExitPoolRequest` struct for one-sided withdrawals @@ -467,7 +474,7 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { request.assets[i] = IAsset(address(tokens[i])); if (request.assets[i] == assetOut) { - request.minAmountsOut[i] = minAmountOut; + request.minAmountsOut[i] = minAmountOut; // U:[BAL2-10,11] tokenIndex = i; } @@ -477,7 +484,7 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { } } - tokenIndex = tokenIndex > bptIndex ? tokenIndex - 1 : tokenIndex; + tokenIndex = tokenIndex > bptIndex ? tokenIndex - 1 : tokenIndex; // U:[BAL2-10,11] request.userData = abi.encode(uint256(0), amountIn, tokenIndex); } @@ -551,6 +558,7 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { }); } + /// @dev Returns copy of `array` without an element at `index` function _removeIndex(uint256[] memory array, uint256 index) internal pure returns (uint256[] memory res) { uint256 len = array.length; @@ -580,10 +588,14 @@ contract BalancerV2VaultAdapter is AbstractAdapter, IBalancerV2VaultAdapter { // ------------- // /// @notice Sets the pool status - function setPoolStatus(bytes32 poolId, PoolStatus newStatus) external override configuratorOnly { + function setPoolStatus(bytes32 poolId, PoolStatus newStatus) + external + override + configuratorOnly // U:[BAL2-12] + { if (poolStatus[poolId] != newStatus) { - poolStatus[poolId] = newStatus; - emit SetPoolStatus(poolId, newStatus); + poolStatus[poolId] = newStatus; // U:[BAL2-12] + emit SetPoolStatus(poolId, newStatus); // U:[BAL2-12] } } } diff --git a/contracts/adapters/compound/CompoundV2_CErc20Adapter.sol b/contracts/adapters/compound/CompoundV2_CErc20Adapter.sol index c084c4c7..39f6c465 100644 --- a/contracts/adapters/compound/CompoundV2_CErc20Adapter.sol +++ b/contracts/adapters/compound/CompoundV2_CErc20Adapter.sol @@ -28,16 +28,18 @@ contract CompoundV2_CErc20Adapter is CompoundV2_CTokenAdapter { /// @notice Constructor /// @param _creditManager Credit manager address /// @param _cToken CErc20 token address - constructor(address _creditManager, address _cToken) CompoundV2_CTokenAdapter(_creditManager, _cToken) { - underlying = ICErc20(targetContract).underlying(); // F: [ACV2CERC-2] + constructor(address _creditManager, address _cToken) + CompoundV2_CTokenAdapter(_creditManager, _cToken) // U:[COMP2T-1] + { + underlying = ICErc20(targetContract).underlying(); // U:[COMP2T-1] - cTokenMask = _getMaskOrRevert(targetContract); // F: [ACV2CERC-1, ACV2CERC-2] - tokenMask = _getMaskOrRevert(underlying); // F: [ACV2CERC-2] + cTokenMask = _getMaskOrRevert(targetContract); // U:[COMP2T-1] + tokenMask = _getMaskOrRevert(underlying); // U:[COMP2T-1] } /// @notice cToken that this adapter is connected to function cToken() external view override returns (address) { - return targetContract; // F: [ACV2CERC-2] + return targetContract; // U:[COMP2T-1] } /// @dev Internal implementation of `mint` @@ -49,9 +51,9 @@ contract CompoundV2_CErc20Adapter is CompoundV2_CTokenAdapter { override returns (uint256 tokensToEnable, uint256 tokensToDisable, uint256 error) { - _approveToken(underlying, type(uint256).max); - error = abi.decode(_execute(_encodeMint(amount)), (uint256)); - _approveToken(underlying, 1); + _approveToken(underlying, type(uint256).max); // U:[COMP2T-4] + error = abi.decode(_execute(_encodeMint(amount)), (uint256)); // U:[COMP2T-4] + _approveToken(underlying, 1); // U:[COMP2T-4] (tokensToEnable, tokensToDisable) = (cTokenMask, 0); } @@ -60,17 +62,17 @@ contract CompoundV2_CErc20Adapter is CompoundV2_CTokenAdapter { /// - cToken is enabled after the call /// - underlying is disabled after the call because operation spends the entire balance function _mintAll() internal override returns (uint256 tokensToEnable, uint256 tokensToDisable, uint256 error) { - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[COMP2T-5] - uint256 amount = IERC20(underlying).balanceOf(creditAccount); + uint256 amount = IERC20(underlying).balanceOf(creditAccount); // U:[COMP2T-5] if (amount <= 1) return (0, 0, 0); unchecked { - --amount; + --amount; // U:[COMP2T-5] } - _approveToken(underlying, type(uint256).max); - error = abi.decode(_execute(_encodeMint(amount)), (uint256)); - _approveToken(underlying, 1); + _approveToken(underlying, type(uint256).max); // U:[COMP2T-5] + error = abi.decode(_execute(_encodeMint(amount)), (uint256)); // U:[COMP2T-5] + _approveToken(underlying, 1); // U:[COMP2T-5] (tokensToEnable, tokensToDisable) = (cTokenMask, tokenMask); } @@ -83,7 +85,7 @@ contract CompoundV2_CErc20Adapter is CompoundV2_CTokenAdapter { override returns (uint256 tokensToEnable, uint256 tokensToDisable, uint256 error) { - error = abi.decode(_execute(_encodeRedeem(amount)), (uint256)); + error = abi.decode(_execute(_encodeRedeem(amount)), (uint256)); // U:[COMP2T-6] (tokensToEnable, tokensToDisable) = (tokenMask, 0); } @@ -92,15 +94,15 @@ contract CompoundV2_CErc20Adapter is CompoundV2_CTokenAdapter { /// - underlying is enabled after the call /// - cToken is disabled after the call because operation spends the entire balance function _redeemAll() internal override returns (uint256 tokensToEnable, uint256 tokensToDisable, uint256 error) { - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[COMP2T-7] - uint256 amount = ICErc20(targetContract).balanceOf(creditAccount); + uint256 amount = ICErc20(targetContract).balanceOf(creditAccount); // U:[COMP2T-7] if (amount <= 1) return (0, 0, 0); unchecked { - --amount; + --amount; // U:[COMP2T-6] } - error = abi.decode(_execute(_encodeRedeem(amount)), (uint256)); + error = abi.decode(_execute(_encodeRedeem(amount)), (uint256)); // U:[COMP2T-7] (tokensToEnable, tokensToDisable) = (tokenMask, cTokenMask); } @@ -113,7 +115,7 @@ contract CompoundV2_CErc20Adapter is CompoundV2_CTokenAdapter { override returns (uint256 tokensToEnable, uint256 tokensToDisable, uint256 error) { - error = abi.decode(_execute(_encodeRedeemUnderlying(amount)), (uint256)); + error = abi.decode(_execute(_encodeRedeemUnderlying(amount)), (uint256)); // U:[COMP2T-8] (tokensToEnable, tokensToDisable) = (tokenMask, 0); } } diff --git a/contracts/adapters/compound/CompoundV2_CEtherAdapter.sol b/contracts/adapters/compound/CompoundV2_CEtherAdapter.sol index 9bc643ab..9106adc6 100644 --- a/contracts/adapters/compound/CompoundV2_CEtherAdapter.sol +++ b/contracts/adapters/compound/CompoundV2_CEtherAdapter.sol @@ -32,12 +32,14 @@ contract CompoundV2_CEtherAdapter is CompoundV2_CTokenAdapter { /// @notice Constructor /// @param _creditManager Credit manager address /// @param _cethGateway CEther gateway contract address - constructor(address _creditManager, address _cethGateway) CompoundV2_CTokenAdapter(_creditManager, _cethGateway) { - cToken = CEtherGateway(payable(targetContract)).ceth(); // F: [ACV2CETH-1] - underlying = CEtherGateway(payable(targetContract)).weth(); // F: [ACV2CETH-1] + constructor(address _creditManager, address _cethGateway) + CompoundV2_CTokenAdapter(_creditManager, _cethGateway) // U:[COMP2E-1] + { + cToken = CEtherGateway(payable(targetContract)).ceth(); // U:[COMP2E-1] + underlying = CEtherGateway(payable(targetContract)).weth(); // U:[COMP2E-1] - cTokenMask = _getMaskOrRevert(cToken); // F: [ACV2CETH-1] - tokenMask = _getMaskOrRevert(underlying); // F: [ACV2CETH-1] + cTokenMask = _getMaskOrRevert(cToken); // U:[COMP2E-1] + tokenMask = _getMaskOrRevert(underlying); // U:[COMP2E-1] } /// @dev Internal implementation of `mint` @@ -49,9 +51,9 @@ contract CompoundV2_CEtherAdapter is CompoundV2_CTokenAdapter { override returns (uint256 tokensToEnable, uint256 tokensToDisable, uint256 error) { - _approveToken(underlying, type(uint256).max); - error = abi.decode(_execute(_encodeMint(amount)), (uint256)); - _approveToken(underlying, 1); + _approveToken(underlying, type(uint256).max); // U:[COMP2E-4] + error = abi.decode(_execute(_encodeMint(amount)), (uint256)); // U:[COMP2E-4] + _approveToken(underlying, 1); // U:[COMP2E-4] (tokensToEnable, tokensToDisable) = (cTokenMask, 0); } @@ -60,17 +62,17 @@ contract CompoundV2_CEtherAdapter is CompoundV2_CTokenAdapter { /// - cETH is enabled after the call /// - WETH is disabled after the call because operation spends the entire balance function _mintAll() internal override returns (uint256 tokensToEnable, uint256 tokensToDisable, uint256 error) { - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[COMP2E-5] - uint256 amount = IERC20(underlying).balanceOf(creditAccount); + uint256 amount = IERC20(underlying).balanceOf(creditAccount); // U:[COMP2E-5] if (amount <= 1) return (0, 0, 0); unchecked { - --amount; + --amount; // U:[COMP2E-5] } - _approveToken(underlying, type(uint256).max); - error = abi.decode(_execute(_encodeMint(amount)), (uint256)); - _approveToken(underlying, 1); + _approveToken(underlying, type(uint256).max); // U:[COMP2E-5] + error = abi.decode(_execute(_encodeMint(amount)), (uint256)); // U:[COMP2E-5] + _approveToken(underlying, 1); // U:[COMP2E-5] (tokensToEnable, tokensToDisable) = (cTokenMask, tokenMask); } @@ -83,9 +85,9 @@ contract CompoundV2_CEtherAdapter is CompoundV2_CTokenAdapter { override returns (uint256 tokensToEnable, uint256 tokensToDisable, uint256 error) { - _approveToken(cToken, type(uint256).max); - error = abi.decode(_execute(_encodeRedeem(amount)), (uint256)); - _approveToken(cToken, 1); + _approveToken(cToken, type(uint256).max); // U:[COMP2E-6] + error = abi.decode(_execute(_encodeRedeem(amount)), (uint256)); // U:[COMP2E-6] + _approveToken(cToken, 1); // U:[COMP2E-6] (tokensToEnable, tokensToDisable) = (tokenMask, 0); } @@ -94,17 +96,17 @@ contract CompoundV2_CEtherAdapter is CompoundV2_CTokenAdapter { /// - WETH is enabled after the call /// - cETH is disabled after the call because operation spends the entire balance function _redeemAll() internal override returns (uint256 tokensToEnable, uint256 tokensToDisable, uint256 error) { - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[COMP2E-7] - uint256 amount = ICEther(cToken).balanceOf(creditAccount); + uint256 amount = ICEther(cToken).balanceOf(creditAccount); // U:[COMP2E-7] if (amount <= 1) return (0, 0, 0); unchecked { - --amount; + --amount; // U:[COMP2E-7] } - _approveToken(cToken, type(uint256).max); - error = abi.decode(_execute(_encodeRedeem(amount)), (uint256)); - _approveToken(cToken, 1); + _approveToken(cToken, type(uint256).max); // U:[COMP2E-7] + error = abi.decode(_execute(_encodeRedeem(amount)), (uint256)); // U:[COMP2E-7] + _approveToken(cToken, 1); // U:[COMP2E-7] (tokensToEnable, tokensToDisable) = (tokenMask, cTokenMask); } @@ -117,9 +119,9 @@ contract CompoundV2_CEtherAdapter is CompoundV2_CTokenAdapter { override returns (uint256 tokensToEnable, uint256 tokensToDisable, uint256 error) { - _approveToken(cToken, type(uint256).max); - error = abi.decode(_execute(_encodeRedeemUnderlying(amount)), (uint256)); - _approveToken(cToken, 1); + _approveToken(cToken, type(uint256).max); // U:[COMP2E-8] + error = abi.decode(_execute(_encodeRedeemUnderlying(amount)), (uint256)); // U:[COMP2E-8] + _approveToken(cToken, 1); // U:[COMP2E-8] (tokensToEnable, tokensToDisable) = (tokenMask, 0); } } diff --git a/contracts/adapters/compound/CompoundV2_CTokenAdapter.sol b/contracts/adapters/compound/CompoundV2_CTokenAdapter.sol index 993ca0aa..42e4c324 100644 --- a/contracts/adapters/compound/CompoundV2_CTokenAdapter.sol +++ b/contracts/adapters/compound/CompoundV2_CTokenAdapter.sol @@ -15,11 +15,13 @@ abstract contract CompoundV2_CTokenAdapter is AbstractAdapter, ICompoundV2_CToke /// @notice Constructor /// @param _creditManager Credit manager address /// @param _targetContract Target contract address, must implement `ICErc20Actions` - constructor(address _creditManager, address _targetContract) AbstractAdapter(_creditManager, _targetContract) {} + constructor(address _creditManager, address _targetContract) + AbstractAdapter(_creditManager, _targetContract) // U:[COMP2-1] + {} /// @dev Reverts if CToken operation produced non-zero error code function _revertOnError(uint256 error) internal pure { - if (error != 0) revert CTokenError(error); + if (error != 0) revert CTokenError(error); // U:[COMP2-3] } // ------- // @@ -31,24 +33,24 @@ abstract contract CompoundV2_CTokenAdapter is AbstractAdapter, ICompoundV2_CToke function mint(uint256 amount) external override - creditFacadeOnly // F: [ACV2CT-1] + creditFacadeOnly // U:[COMP2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { uint256 error; - (tokensToEnable, tokensToDisable, error) = _mint(amount); // F: [ACV2CT-2, ACV2CT-3] - _revertOnError(error); + (tokensToEnable, tokensToDisable, error) = _mint(amount); // U:[COMP2-4] + _revertOnError(error); // U:[COMP2-3] } /// @notice Deposit all underlying tokens into Compound in exchange for cTokens, disables underlying function mintAll() external override - creditFacadeOnly // F: [ACV2CT-1] + creditFacadeOnly // U:[COMP2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { uint256 error; - (tokensToEnable, tokensToDisable, error) = _mintAll(); // F: [ACV2CT-4, ACV2CT-5] - _revertOnError(error); + (tokensToEnable, tokensToDisable, error) = _mintAll(); // U:[COMP2-5] + _revertOnError(error); // U:[COMP2-3] } /// @dev Internal implementation of `mint` @@ -66,7 +68,7 @@ abstract contract CompoundV2_CTokenAdapter is AbstractAdapter, ICompoundV2_CToke /// @dev Encodes calldata for `ICErc20Actions.mint` call function _encodeMint(uint256 amount) internal pure returns (bytes memory callData) { - callData = abi.encodeCall(ICErc20Actions.mint, (amount)); // F: [ACV2CT-2, ACV2CT-4] + callData = abi.encodeCall(ICErc20Actions.mint, (amount)); // U:[COMP2-4,5] } // --------- // @@ -78,24 +80,24 @@ abstract contract CompoundV2_CTokenAdapter is AbstractAdapter, ICompoundV2_CToke function redeem(uint256 amount) external override - creditFacadeOnly // F: [ACV2CT-1] + creditFacadeOnly // U:[COMP2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { uint256 error; - (tokensToEnable, tokensToDisable, error) = _redeem(amount); // F: [ACV2CT-6, ACV2CT-7] - _revertOnError(error); + (tokensToEnable, tokensToDisable, error) = _redeem(amount); // U:[COMP2-6] + _revertOnError(error); // U:[COMP2-3] } /// @notice Withdraw all underlying tokens from Compound and burn cTokens, disables cToken function redeemAll() external override - creditFacadeOnly // F: [ACV2CT-1] + creditFacadeOnly // U:[COMP2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { uint256 error; - (tokensToEnable, tokensToDisable, error) = _redeemAll(); // F: [ACV2CT-8, ACV2CT-9] - _revertOnError(error); + (tokensToEnable, tokensToDisable, error) = _redeemAll(); // U:[COMP2-7] + _revertOnError(error); // U:[COMP2-3] } /// @dev Internal implementation of `redeem` @@ -113,7 +115,7 @@ abstract contract CompoundV2_CTokenAdapter is AbstractAdapter, ICompoundV2_CToke /// @dev Encodes calldata for `ICErc20Actions.redeem` call function _encodeRedeem(uint256 amount) internal pure returns (bytes memory callData) { - callData = abi.encodeCall(ICErc20Actions.redeem, (amount)); // F: [ACV2CT-6, ACV2CT-8] + callData = abi.encodeCall(ICErc20Actions.redeem, (amount)); // U:[COMP2-6,7] } // -------------------- // @@ -125,12 +127,12 @@ abstract contract CompoundV2_CTokenAdapter is AbstractAdapter, ICompoundV2_CToke function redeemUnderlying(uint256 amount) external override - creditFacadeOnly // F: [ACV2CT-1] + creditFacadeOnly // U:[COMP2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { uint256 error; - (tokensToEnable, tokensToDisable, error) = _redeemUnderlying(amount); // F: [ACV2CT-10, ACV2CT-11] - _revertOnError(error); + (tokensToEnable, tokensToDisable, error) = _redeemUnderlying(amount); // U:[COMP2-8] + _revertOnError(error); // U:[COMP2-3] } /// @dev Internal implementation of `redeemUnderlying` @@ -143,6 +145,6 @@ abstract contract CompoundV2_CTokenAdapter is AbstractAdapter, ICompoundV2_CToke /// @dev Encodes calldata for `ICErc20Actions.redeemUnderlying` call function _encodeRedeemUnderlying(uint256 amount) internal pure returns (bytes memory callData) { - callData = abi.encodeCall(ICErc20Actions.redeemUnderlying, (amount)); // F: [ACV2CT-10] + callData = abi.encodeCall(ICErc20Actions.redeemUnderlying, (amount)); // U:[COMP2-8] } } diff --git a/contracts/adapters/convex/ConvexV1_BaseRewardPool.sol b/contracts/adapters/convex/ConvexV1_BaseRewardPool.sol index 233a1a79..595a8e20 100644 --- a/contracts/adapters/convex/ConvexV1_BaseRewardPool.sol +++ b/contracts/adapters/convex/ConvexV1_BaseRewardPool.sol @@ -53,26 +53,26 @@ contract ConvexV1BaseRewardPoolAdapter is AbstractAdapter, IConvexV1BaseRewardPo /// @param _baseRewardPool BaseRewardPool address /// @param _stakedPhantomToken Reward pool stake token address constructor(address _creditManager, address _baseRewardPool, address _stakedPhantomToken) - AbstractAdapter(_creditManager, _baseRewardPool) + AbstractAdapter(_creditManager, _baseRewardPool) // U:[CVX1R-1] { - stakingToken = address(IBaseRewardPool(_baseRewardPool).stakingToken()); // F: [ACVX1_P-1] - stakingTokenMask = _getMaskOrRevert(stakingToken); // F: [ACVX1_P-1, ACVX1_P-2] + stakingToken = address(IBaseRewardPool(_baseRewardPool).stakingToken()); // U:[CVX1R-1] + stakingTokenMask = _getMaskOrRevert(stakingToken); // U:[CVX1R-1] - stakedPhantomToken = _stakedPhantomToken; // F: [ACVX1_P-1] - stakedTokenMask = _getMaskOrRevert(stakedPhantomToken); // F: [ACVX1_P-1, ACVX1_P-2] + stakedPhantomToken = _stakedPhantomToken; // U:[CVX1R-1] + stakedTokenMask = _getMaskOrRevert(stakedPhantomToken); // U:[CVX1R-1] address booster = IBaseRewardPool(_baseRewardPool).operator(); IBooster.PoolInfo memory poolInfo = IBooster(booster).poolInfo(IBaseRewardPool(_baseRewardPool).pid()); - curveLPtoken = poolInfo.lptoken; // F: [ACVX1_P-1] - curveLPTokenMask = _getMaskOrRevert(curveLPtoken); // F: [ACVX1_P-1, ACVX1_P-2] + curveLPtoken = poolInfo.lptoken; // U:[CVX1R-1] + curveLPTokenMask = _getMaskOrRevert(curveLPtoken); // U:[CVX1R-1] uint256 _rewardTokensMask; address rewardToken = address(IBaseRewardPool(_baseRewardPool).rewardToken()); - _rewardTokensMask = _rewardTokensMask.enable(_getMaskOrRevert(rewardToken)); // F: [ACVX1_P-2] + _rewardTokensMask = _rewardTokensMask.enable(_getMaskOrRevert(rewardToken)); // U:[CVX1R-1] address cvx = IBooster(booster).minter(); - _rewardTokensMask = _rewardTokensMask.enable(_getMaskOrRevert(cvx)); // F: [ACVX1_P-2] + _rewardTokensMask = _rewardTokensMask.enable(_getMaskOrRevert(cvx)); // U:[CVX1R-1] address _extraReward1; address _extraReward2; @@ -81,17 +81,17 @@ contract ConvexV1BaseRewardPoolAdapter is AbstractAdapter, IConvexV1BaseRewardPo if (extraRewardLength >= 1) { uint256 _extraRewardMask; (_extraReward1, _extraRewardMask) = _getExtraReward(0); - _rewardTokensMask = _rewardTokensMask.enable(_extraRewardMask); // F: [ACVX1_P-2] + _rewardTokensMask = _rewardTokensMask.enable(_extraRewardMask); if (extraRewardLength >= 2) { (_extraReward2, _extraRewardMask) = _getExtraReward(1); - _rewardTokensMask = _rewardTokensMask.enable(_extraRewardMask); // F: [ACVX1_P-2] + _rewardTokensMask = _rewardTokensMask.enable(_extraRewardMask); } } - extraReward1 = _extraReward1; // F: [ACVX1_P-1] - extraReward2 = _extraReward2; // F: [ACVX1_P-1] - rewardTokensMask = _rewardTokensMask; // F: [ACVX1_P-1] + extraReward1 = _extraReward1; // U:[CVX1R-2] + extraReward2 = _extraReward2; // U:[CVX1R-2] + rewardTokensMask = _rewardTokensMask; // U:[CVX1R-2] } /// @dev Returns `i`-th extra reward token and its collateral mask in the credit mnager @@ -121,15 +121,20 @@ contract ConvexV1BaseRewardPoolAdapter is AbstractAdapter, IConvexV1BaseRewardPo function stake(uint256) external override - creditFacadeOnly + creditFacadeOnly // U:[CVX1R-3] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _stake(msg.data, false); // F: [ACVX1_P-3] + (tokensToEnable, tokensToDisable) = _stake(msg.data, false); // U:[CVX1R-4] } /// @notice Stakes the entire balance of Convex LP token in the reward pool, disables LP token - function stakeAll() external override creditFacadeOnly returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _stake(msg.data, true); // F: [ACVX1_P-4] + function stakeAll() + external + override + creditFacadeOnly // U:[CVX1R-3] + returns (uint256 tokensToEnable, uint256 tokensToDisable) + { + (tokensToEnable, tokensToDisable) = _stake(msg.data, true); // U:[CVX1R-5] } /// @dev Internal implementation of `stake` and `stakeAll` @@ -140,9 +145,9 @@ contract ConvexV1BaseRewardPoolAdapter is AbstractAdapter, IConvexV1BaseRewardPo internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _approveToken(stakingToken, type(uint256).max); - _execute(callData); - _approveToken(stakingToken, 1); + _approveToken(stakingToken, type(uint256).max); // U:[CVX1R-4,5] + _execute(callData); // U:[CVX1R-4,5] + _approveToken(stakingToken, 1); // U:[CVX1R-4,5] (tokensToEnable, tokensToDisable) = (stakedTokenMask, disableStakingToken ? stakingTokenMask : 0); } @@ -151,9 +156,14 @@ contract ConvexV1BaseRewardPoolAdapter is AbstractAdapter, IConvexV1BaseRewardPo // ----- // /// @notice Claims rewards on the current position, enables reward tokens - function getReward() external override creditFacadeOnly returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _execute(msg.data); // F: [ACVX1_P-5] - (tokensToEnable, tokensToDisable) = (rewardTokensMask, 0); // F: [ACVX1_P-5] + function getReward() + external + override + creditFacadeOnly // U:[CVX1R-3] + returns (uint256 tokensToEnable, uint256 tokensToDisable) + { + _execute(msg.data); // U:[CVX1R-6] + (tokensToEnable, tokensToDisable) = (rewardTokensMask, 0); // U:[CVX1R-6] } // -------- // @@ -166,10 +176,10 @@ contract ConvexV1BaseRewardPoolAdapter is AbstractAdapter, IConvexV1BaseRewardPo function withdraw(uint256, bool claim) external override - creditFacadeOnly + creditFacadeOnly // U:[CVX1R-3] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _withdraw(msg.data, claim, false); // F: [ACVX1_P-6] + (tokensToEnable, tokensToDisable) = _withdraw(msg.data, claim, false); // U:[CVX1R-7] } /// @notice Withdraws the entire balance of Convex LP token from the reward pool, disables staked token @@ -177,10 +187,10 @@ contract ConvexV1BaseRewardPoolAdapter is AbstractAdapter, IConvexV1BaseRewardPo function withdrawAll(bool claim) external override - creditFacadeOnly + creditFacadeOnly // U:[CVX1R-3] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _withdraw(msg.data, claim, true); // F: [ACVX1_P-7] + (tokensToEnable, tokensToDisable) = _withdraw(msg.data, claim, true); // U:[CVX1R-8] } /// @dev Internal implementation of `withdraw` and `withdrawAll` @@ -191,7 +201,7 @@ contract ConvexV1BaseRewardPoolAdapter is AbstractAdapter, IConvexV1BaseRewardPo internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _execute(callData); + _execute(callData); // U:[CVX1R-7,8] (tokensToEnable, tokensToDisable) = (stakingTokenMask.enable(claim ? rewardTokensMask : 0), disableStakedToken ? stakedTokenMask : 0); } @@ -206,10 +216,10 @@ contract ConvexV1BaseRewardPoolAdapter is AbstractAdapter, IConvexV1BaseRewardPo function withdrawAndUnwrap(uint256, bool claim) external override - creditFacadeOnly + creditFacadeOnly // U:[CVX1R-3] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _withdrawAndUnwrap(msg.data, claim, false); // F: [ACVX1_P-8] + (tokensToEnable, tokensToDisable) = _withdrawAndUnwrap(msg.data, claim, false); // U:[CVX1R-9] } /// @notice Withdraws the entire balance of Convex LP token from the reward pool and unwraps it into Curve LP token, @@ -218,10 +228,10 @@ contract ConvexV1BaseRewardPoolAdapter is AbstractAdapter, IConvexV1BaseRewardPo function withdrawAllAndUnwrap(bool claim) external override - creditFacadeOnly + creditFacadeOnly // U:[CVX1R-3] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _withdrawAndUnwrap(msg.data, claim, true); // F: [ACVX1_P-9] + (tokensToEnable, tokensToDisable) = _withdrawAndUnwrap(msg.data, claim, true); // U:[CVX1R-10] } /// @dev Internal implementation of `withdrawAndUnwrap` and `withdrawAllAndUnwrap` @@ -232,7 +242,7 @@ contract ConvexV1BaseRewardPoolAdapter is AbstractAdapter, IConvexV1BaseRewardPo internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _execute(callData); + _execute(callData); // U:[CVX1R-9,10] (tokensToEnable, tokensToDisable) = (curveLPTokenMask.enable(claim ? rewardTokensMask : 0), disableStakedToken ? stakedTokenMask : 0); } diff --git a/contracts/adapters/convex/ConvexV1_Booster.sol b/contracts/adapters/convex/ConvexV1_Booster.sol index 7fc9136f..c1ca7cd5 100644 --- a/contracts/adapters/convex/ConvexV1_Booster.sol +++ b/contracts/adapters/convex/ConvexV1_Booster.sol @@ -27,7 +27,9 @@ contract ConvexV1BoosterAdapter is AbstractAdapter, IConvexV1BoosterAdapter { /// @notice Constructor /// @param _creditManager Credit manager address /// @param _booster Booster contract address - constructor(address _creditManager, address _booster) AbstractAdapter(_creditManager, _booster) {} + constructor(address _creditManager, address _booster) + AbstractAdapter(_creditManager, _booster) // U:[CVX1B-1] + {} // ------- // // DEPOSIT // @@ -40,10 +42,10 @@ contract ConvexV1BoosterAdapter is AbstractAdapter, IConvexV1BoosterAdapter { function deposit(uint256 _pid, uint256, bool _stake) external override - creditFacadeOnly + creditFacadeOnly // U:[CVX1B-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _deposit(_pid, _stake, msg.data, false); + (tokensToEnable, tokensToDisable) = _deposit(_pid, _stake, msg.data, false); // U:[CVX1B-3] } /// @notice Deposits the entire balance of Curve LP tokens into Booster, disables Curve LP token @@ -52,10 +54,10 @@ contract ConvexV1BoosterAdapter is AbstractAdapter, IConvexV1BoosterAdapter { function depositAll(uint256 _pid, bool _stake) external override - creditFacadeOnly + creditFacadeOnly // U:[CVX1B-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _deposit(_pid, _stake, msg.data, true); + (tokensToEnable, tokensToDisable) = _deposit(_pid, _stake, msg.data, true); // U:[CVX1B-4] } /// @dev Internal implementation of `deposit` and `depositAll` @@ -68,11 +70,11 @@ contract ConvexV1BoosterAdapter is AbstractAdapter, IConvexV1BoosterAdapter { { IBooster.PoolInfo memory pool = IBooster(targetContract).poolInfo(_pid); - address tokenIn = pool.lptoken; // F: [ACVX1_B-2, ACVX1_B-3] - address tokenOut = _stake ? pidToPhantomToken[_pid] : pool.token; // F: [ACVX1_B-2, ACVX1_B-3] + address tokenIn = pool.lptoken; // U:[CVX1B-3,4] + address tokenOut = _stake ? pidToPhantomToken[_pid] : pool.token; // U:[CVX1B-3,4] // using `_executeSwap` because tokens are not known in advance and need to check if they are registered - (tokensToEnable, tokensToDisable,) = _executeSwapSafeApprove(tokenIn, tokenOut, callData, disableCurveLP); + (tokensToEnable, tokensToDisable,) = _executeSwapSafeApprove(tokenIn, tokenOut, callData, disableCurveLP); // U:[CVX1B-3,4] } // -------- // @@ -85,10 +87,10 @@ contract ConvexV1BoosterAdapter is AbstractAdapter, IConvexV1BoosterAdapter { function withdraw(uint256 _pid, uint256) external override - creditFacadeOnly + creditFacadeOnly // U:[CVX1B-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _withdraw(_pid, msg.data, false); + (tokensToEnable, tokensToDisable) = _withdraw(_pid, msg.data, false); // U:[CVX1B-5] } /// @notice Withdraws all Curve LP tokens from Booster, disables Convex LP token @@ -97,10 +99,10 @@ contract ConvexV1BoosterAdapter is AbstractAdapter, IConvexV1BoosterAdapter { function withdrawAll(uint256 _pid) external override - creditFacadeOnly + creditFacadeOnly // U:[CVX1B-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _withdraw(_pid, msg.data, true); + (tokensToEnable, tokensToDisable) = _withdraw(_pid, msg.data, true); // U:[CVX1B-6] } /// @dev Internal implementation of `withdraw` and `withdrawAll` @@ -112,11 +114,11 @@ contract ConvexV1BoosterAdapter is AbstractAdapter, IConvexV1BoosterAdapter { { IBooster.PoolInfo memory pool = IBooster(targetContract).poolInfo(_pid); - address tokenIn = pool.token; // F: [ACVX1_B-4, ACVX1_B-5] - address tokenOut = pool.lptoken; // F: [ACVX1_B-4, ACVX1_B-5] + address tokenIn = pool.token; // U:[CVX1B-5,6] + address tokenOut = pool.lptoken; // U:[CVX1B-5,6] // using `_executeSwap` because tokens are not known in advance and need to check if they are registered - (tokensToEnable, tokensToDisable,) = _executeSwapNoApprove(tokenIn, tokenOut, callData, disableConvexLP); + (tokensToEnable, tokensToDisable,) = _executeSwapNoApprove(tokenIn, tokenOut, callData, disableConvexLP); // U:[CVX1B-5,6] } // ------------- // @@ -127,7 +129,7 @@ contract ConvexV1BoosterAdapter is AbstractAdapter, IConvexV1BoosterAdapter { function updateStakedPhantomTokensMap() external override - configuratorOnly // F: [ACVX1_B-1] + configuratorOnly // U:[CVX1B-7] { ICreditManagerV3 cm = ICreditManagerV3(creditManager); ICreditConfiguratorV3 cc = ICreditConfiguratorV3(cm.creditConfigurator()); diff --git a/contracts/adapters/curve/CurveV1_2.sol b/contracts/adapters/curve/CurveV1_2.sol index 628c31f8..2325b084 100644 --- a/contracts/adapters/curve/CurveV1_2.sol +++ b/contracts/adapters/curve/CurveV1_2.sol @@ -31,10 +31,11 @@ contract CurveV1Adapter2Assets is CurveV1AdapterBase, ICurveV1_2AssetsAdapter { /// @dev `min_mint_amount` parameter is ignored because calldata is passed directly to the target contract function add_liquidity(uint256[N_COINS] calldata amounts, uint256) external - creditFacadeOnly + override + creditFacadeOnly // U:[CRV2-1] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _add_liquidity(amounts[0] > 1, amounts[1] > 1, false, false); // F: [ACV1_2-4, ACV1S-1] + (tokensToEnable, tokensToDisable) = _add_liquidity(amounts[0] > 1, amounts[1] > 1, false, false); // U:[CRV2-2] } /// @dev Returns calldata for adding liquidity in coin `i` @@ -69,10 +70,10 @@ contract CurveV1Adapter2Assets is CurveV1AdapterBase, ICurveV1_2AssetsAdapter { function remove_liquidity(uint256, uint256[N_COINS] calldata) external virtual - creditFacadeOnly + creditFacadeOnly // U:[CRV2-1] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _remove_liquidity(); // F: [ACV1_2-5] + (tokensToEnable, tokensToDisable) = _remove_liquidity(); // U:[CRV2-3] } /// @notice Withdraw exact amounts of tokens from the pool @@ -82,9 +83,9 @@ contract CurveV1Adapter2Assets is CurveV1AdapterBase, ICurveV1_2AssetsAdapter { external virtual override - creditFacadeOnly + creditFacadeOnly // U:[CRV2-1] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _remove_liquidity_imbalance(amounts[0] > 1, amounts[1] > 1, false, false); // F: [ACV1_2-6] + (tokensToEnable, tokensToDisable) = _remove_liquidity_imbalance(amounts[0] > 1, amounts[1] > 1, false, false); // U:[CRV2-4] } } diff --git a/contracts/adapters/curve/CurveV1_3.sol b/contracts/adapters/curve/CurveV1_3.sol index 686ec329..b96153a7 100644 --- a/contracts/adapters/curve/CurveV1_3.sol +++ b/contracts/adapters/curve/CurveV1_3.sol @@ -31,10 +31,11 @@ contract CurveV1Adapter3Assets is CurveV1AdapterBase, ICurveV1_3AssetsAdapter { /// @dev `min_mint_amount` parameter is ignored because calldata is passed directly to the target contract function add_liquidity(uint256[N_COINS] calldata amounts, uint256) external - creditFacadeOnly + override + creditFacadeOnly // U:[CRV3-1] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _add_liquidity(amounts[0] > 1, amounts[1] > 1, amounts[2] > 1, false); // F: [ACV1_3-4] + (tokensToEnable, tokensToDisable) = _add_liquidity(amounts[0] > 1, amounts[1] > 1, amounts[2] > 1, false); // U:[CRV3-2] } /// @dev Returns calldata for adding liquidity in coin `i` @@ -69,10 +70,10 @@ contract CurveV1Adapter3Assets is CurveV1AdapterBase, ICurveV1_3AssetsAdapter { function remove_liquidity(uint256, uint256[N_COINS] calldata) external virtual - creditFacadeOnly + creditFacadeOnly // U:[CRV3-1] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _remove_liquidity(); // F: [ACV1_3-5] + (tokensToEnable, tokensToDisable) = _remove_liquidity(); // U:[CRV3-3] } /// @notice Withdraw exact amounts of tokens from the pool @@ -82,10 +83,10 @@ contract CurveV1Adapter3Assets is CurveV1AdapterBase, ICurveV1_3AssetsAdapter { external virtual override - creditFacadeOnly + creditFacadeOnly // U:[CRV3-1] returns (uint256 tokensToEnable, uint256 tokensToDisable) { (tokensToEnable, tokensToDisable) = - _remove_liquidity_imbalance(amounts[0] > 1, amounts[1] > 1, amounts[2] > 1, false); // F: [ACV1_3-6] + _remove_liquidity_imbalance(amounts[0] > 1, amounts[1] > 1, amounts[2] > 1, false); // U:[CRV3-4] } } diff --git a/contracts/adapters/curve/CurveV1_4.sol b/contracts/adapters/curve/CurveV1_4.sol index 8b15735f..33691384 100644 --- a/contracts/adapters/curve/CurveV1_4.sol +++ b/contracts/adapters/curve/CurveV1_4.sol @@ -31,11 +31,12 @@ contract CurveV1Adapter4Assets is CurveV1AdapterBase, ICurveV1_4AssetsAdapter { /// @dev `min_mint_amount` parameter is ignored because calldata is passed directly to the target contract function add_liquidity(uint256[N_COINS] calldata amounts, uint256) external - creditFacadeOnly + override + creditFacadeOnly // U:[CRV4-1] returns (uint256 tokensToEnable, uint256 tokensToDisable) { (tokensToEnable, tokensToDisable) = - _add_liquidity(amounts[0] > 1, amounts[1] > 1, amounts[2] > 1, amounts[3] > 1); // F: [ACV1_4-4] + _add_liquidity(amounts[0] > 1, amounts[1] > 1, amounts[2] > 1, amounts[3] > 1); // U:[CRV4-2] } /// @dev Returns calldata for adding liquidity in coin `i` @@ -70,10 +71,10 @@ contract CurveV1Adapter4Assets is CurveV1AdapterBase, ICurveV1_4AssetsAdapter { function remove_liquidity(uint256, uint256[N_COINS] calldata) external virtual - creditFacadeOnly + creditFacadeOnly // U:[CRV4-1] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _remove_liquidity(); // F: [ACV1_4-5] + (tokensToEnable, tokensToDisable) = _remove_liquidity(); // U:[CRV4-3] } /// @notice Withdraw exact amounts of tokens from the pool @@ -83,10 +84,10 @@ contract CurveV1Adapter4Assets is CurveV1AdapterBase, ICurveV1_4AssetsAdapter { external virtual override - creditFacadeOnly + creditFacadeOnly // U:[CRV4-1] returns (uint256 tokensToEnable, uint256 tokensToDisable) { (tokensToEnable, tokensToDisable) = - _remove_liquidity_imbalance(amounts[0] > 1, amounts[1] > 1, amounts[2] > 1, amounts[3] > 1); // F: [ACV1_4-6] + _remove_liquidity_imbalance(amounts[0] > 1, amounts[1] > 1, amounts[2] > 1, amounts[3] > 1); // U:[CRV4-4] } } diff --git a/contracts/adapters/curve/CurveV1_Base.sol b/contracts/adapters/curve/CurveV1_Base.sol index 9c51916b..34e85078 100644 --- a/contracts/adapters/curve/CurveV1_Base.sol +++ b/contracts/adapters/curve/CurveV1_Base.sol @@ -66,36 +66,36 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { /// @param _metapoolBase Metapool's base pool address (must have 2 or 3 coins) or zero address /// @param _nCoins Number of coins in the pool constructor(address _creditManager, address _curvePool, address _lp_token, address _metapoolBase, uint256 _nCoins) - AbstractAdapter(_creditManager, _curvePool) - nonZeroAddress(_lp_token) // F: [ACV1-1] + AbstractAdapter(_creditManager, _curvePool) // U:[CRVB-1] + nonZeroAddress(_lp_token) // U:[CRVB-1] { - lpTokenMask = _getMaskOrRevert(_lp_token); // F: [ACV1-2] + lpTokenMask = _getMaskOrRevert(_lp_token); // U:[CRVB-1] - token = _lp_token; // F: [ACV1-2] - lp_token = _lp_token; // F: [ACV1-2] - metapoolBase = _metapoolBase; // F: [ACV1-2] - nCoins = _nCoins; // F: [ACV1-2] + token = _lp_token; // U:[CRVB-1] + lp_token = _lp_token; // U:[CRVB-1] + metapoolBase = _metapoolBase; // U:[CRVB-1] + nCoins = _nCoins; // U:[CRVB-1] use256 = _use256(); address[4] memory tokens; uint256[4] memory tokenMasks; unchecked { for (uint256 i; i < nCoins; ++i) { - tokens[i] = _getCoin(_curvePool, i); - if (tokens[i] == address(0)) revert IncorrectParameterException(); // F: [ACV1-1] - tokenMasks[i] = _getMaskOrRevert(tokens[i]); + tokens[i] = _getCoin(_curvePool, i); // U:[CRVB-1] + if (tokens[i] == address(0)) revert IncorrectParameterException(); // U:[CRVB-1] + tokenMasks[i] = _getMaskOrRevert(tokens[i]); // U:[CRVB-1] } } - token0 = tokens[0]; // F: [ACV1-2] - token1 = tokens[1]; // F: [ACV1-2] - token2 = tokens[2]; // F: [ACV1-2] - token3 = tokens[3]; // F: [ACV1-2] + token0 = tokens[0]; + token1 = tokens[1]; + token2 = tokens[2]; + token3 = tokens[3]; - token0Mask = tokenMasks[0]; // F: [ACV1-2] - token1Mask = tokenMasks[1]; // F: [ACV1-2] - token2Mask = tokenMasks[2]; // F: [ACV1-2] - token3Mask = tokenMasks[3]; // F: [ACV1-2] + token0Mask = tokenMasks[0]; + token1Mask = tokenMasks[1]; + token2Mask = tokenMasks[2]; + token3Mask = tokenMasks[3]; // underlying tokens (only relevant for meta and lending pools) address[4] memory underlyings; @@ -103,31 +103,31 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { unchecked { for (uint256 i; i < 4; ++i) { if (_metapoolBase != address(0)) { - underlyings[i] = i == 0 ? token0 : _getCoin(_metapoolBase, i - 1); + underlyings[i] = i == 0 ? token0 : _getCoin(_metapoolBase, i - 1); // U:[CRVB-1] } else { // some pools are proxy contracts and return empty data when there is no function with given signature, // which later results in revert when trying to decode the result, so low-level call is used instead (bool success, bytes memory returnData) = _callWithAlternative( abi.encodeWithSignature("underlying_coins(uint256)", i), abi.encodeWithSignature("underlying_coins(int128)", i) - ); + ); // U:[CRVB-1] if (success && returnData.length > 0) underlyings[i] = abi.decode(returnData, (address)); else break; } - if (underlyings[i] != address(0)) underlyingMasks[i] = _getMaskOrRevert(underlyings[i]); // F: [ACV1-1] + if (underlyings[i] != address(0)) underlyingMasks[i] = _getMaskOrRevert(underlyings[i]); // U:[CRVB-1] } } - underlying0 = underlyings[0]; // F: [ACV1-2] - underlying1 = underlyings[1]; // F: [ACV1-2] - underlying2 = underlyings[2]; // F: [ACV1-2] - underlying3 = underlyings[3]; // F: [ACV1-2] + underlying0 = underlyings[0]; + underlying1 = underlyings[1]; + underlying2 = underlyings[2]; + underlying3 = underlyings[3]; - underlying0Mask = underlyingMasks[0]; // F: [ACV1-2] - underlying1Mask = underlyingMasks[1]; // F: [ACV1-2] - underlying2Mask = underlyingMasks[2]; // F: [ACV1-2] - underlying3Mask = underlyingMasks[3]; // F: [ACV1-2] + underlying0Mask = underlyingMasks[0]; + underlying1Mask = underlyingMasks[1]; + underlying2Mask = underlyingMasks[2]; + underlying3Mask = underlyingMasks[3]; } // -------- // @@ -142,20 +142,20 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external override - creditFacadeOnly + creditFacadeOnly // U:[CRVB-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - return _exchange(i, j, dx, min_dy); + return _exchange(i, j, dx, min_dy); // U:[CRVB-3] } /// @dev Same as the previous one but accepts coin indexes as `int128` function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external override - creditFacadeOnly + creditFacadeOnly // U:[CRVB-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - return _exchange(_toU256(i), _toU256(j), dx, min_dy); + return _exchange(_toU256(i), _toU256(j), dx, min_dy); // U:[CRVB-3] } /// @dev Implementation of both versions of `exchange` @@ -163,7 +163,7 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _exchange_impl(i, j, _getExchangeCallData(i, j, dx, min_dy), false); // F: [ACV1-4] + (tokensToEnable, tokensToDisable) = _exchange_impl(i, j, _getExchangeCallData(i, j, dx, min_dy), false); // U:[CRVB-3] } /// @notice Exchanges the entire balance of one pool asset to another, disables input asset @@ -173,20 +173,20 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { function exchange_all(uint256 i, uint256 j, uint256 rateMinRAY) external override - creditFacadeOnly + creditFacadeOnly // U:[CRVB-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - return _exchange_all(i, j, rateMinRAY); + return _exchange_all(i, j, rateMinRAY); // U:[CRVB-4] } /// @dev Same as the previous one but accepts coin indexes as `int128` function exchange_all(int128 i, int128 j, uint256 rateMinRAY) external override - creditFacadeOnly + creditFacadeOnly // U:[CRVB-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - return _exchange_all(_toU256(i), _toU256(j), rateMinRAY); + return _exchange_all(_toU256(i), _toU256(j), rateMinRAY); // U:[CRVB-4] } /// @dev Implementation of both versions of `exchange_all` @@ -194,17 +194,17 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // F: [ACV1-3] + address creditAccount = _creditAccount(); // U:[CRVB-4] - address tokenIn = _get_token(i); // F: [ACV1-5] - uint256 dx = IERC20(tokenIn).balanceOf(creditAccount); // F: [ACV1-5] + address tokenIn = _get_token(i); // U:[CRVB-4] + uint256 dx = IERC20(tokenIn).balanceOf(creditAccount); // U:[CRVB-4] if (dx <= 1) return (0, 0); unchecked { - dx--; + dx--; // U:[CRVB-4] } - uint256 min_dy = (dx * rateMinRAY) / RAY; // F: [ACV1-5] - (tokensToEnable, tokensToDisable) = _exchange_impl(i, j, _getExchangeCallData(i, j, dx, min_dy), true); // F: [ACV1-5] + uint256 min_dy = (dx * rateMinRAY) / RAY; // U:[CRVB-4] + (tokensToEnable, tokensToDisable) = _exchange_impl(i, j, _getExchangeCallData(i, j, dx, min_dy), true); // U:[CRVB-4] } /// @dev Internal implementation of `exchange` and `exchange_all` @@ -216,10 +216,10 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _approveToken(_get_token(i), type(uint256).max); - _execute(callData); - _approveToken(_get_token(i), 1); - (tokensToEnable, tokensToDisable) = (_get_token_mask(j), disableTokenIn ? _get_token_mask(i) : 0); + _approveToken(_get_token(i), type(uint256).max); // U:[CRVB-3,4] + _execute(callData); // U:[CRVB-3,4] + _approveToken(_get_token(i), 1); // U:[CRVB-3,4] + (tokensToEnable, tokensToDisable) = (_get_token_mask(j), disableTokenIn ? _get_token_mask(i) : 0); // U:[CRVB-3,4] } /// @dev Returns calldata for `exchange` and `exchange_all` calls @@ -230,7 +230,7 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { { return use256 ? abi.encodeWithSignature("exchange(uint256,uint256,uint256,uint256)", i, j, dx, min_dy) - : abi.encodeWithSignature("exchange(int128,int128,uint256,uint256)", i, j, dx, min_dy); + : abi.encodeWithSignature("exchange(int128,int128,uint256,uint256)", i, j, dx, min_dy); // U:[CRVB-3,4] } /// @notice Exchanges one pool's underlying asset to another @@ -241,20 +241,20 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { function exchange_underlying(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external override - creditFacadeOnly + creditFacadeOnly // U:[CRVB-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - return _exchange_underlying(i, j, dx, min_dy); + return _exchange_underlying(i, j, dx, min_dy); // U:[CRVB-5] } /// @dev Same as the previous one but accepts coin indexes as `int128` function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 min_dy) external override - creditFacadeOnly + creditFacadeOnly // U:[CRVB-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - return _exchange_underlying(_toU256(i), _toU256(j), dx, min_dy); + return _exchange_underlying(_toU256(i), _toU256(j), dx, min_dy); // U:[CRVB-5] } /// @dev Implementation of both versions of `exchange_underlying` @@ -263,7 +263,7 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { returns (uint256 tokensToEnable, uint256 tokensToDisable) { (tokensToEnable, tokensToDisable) = - _exchange_underlying_impl(i, j, _getExchangeUnderlyingCallData(i, j, dx, min_dy), false); // F: [ACV1-6] + _exchange_underlying_impl(i, j, _getExchangeUnderlyingCallData(i, j, dx, min_dy), false); // U:[CRVB-5] } /// @notice Exchanges the entire balance of one pool's underlying asset to another, disables input asset @@ -273,20 +273,20 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { function exchange_all_underlying(uint256 i, uint256 j, uint256 rateMinRAY) external override - creditFacadeOnly + creditFacadeOnly // U:[CRVB-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - return _exchange_all_underlying(i, j, rateMinRAY); + return _exchange_all_underlying(i, j, rateMinRAY); // U:[CRVB-6] } /// @dev Same as the previous one but accepts coin indexes as `int128` function exchange_all_underlying(int128 i, int128 j, uint256 rateMinRAY) external override - creditFacadeOnly + creditFacadeOnly // U:[CRVB-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - return _exchange_all_underlying(_toU256(i), _toU256(j), rateMinRAY); + return _exchange_all_underlying(_toU256(i), _toU256(j), rateMinRAY); // U:[CRVB-6] } /// @dev Implementation of both versions of `exchange_all_underlying` @@ -294,18 +294,18 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); //F: [ACV1-3] + address creditAccount = _creditAccount(); // U:[CRVB-6] - address tokenIn = _get_underlying(i); // F: [ACV1-7] - uint256 dx = IERC20(tokenIn).balanceOf(creditAccount); // F: [ACV1-7] + address tokenIn = _get_underlying(i); // U:[CRVB-6] + uint256 dx = IERC20(tokenIn).balanceOf(creditAccount); // U:[CRVB-6] if (dx <= 1) return (0, 0); unchecked { - dx--; // F: [ACV1-7] + dx--; // U:[CRVB-6] } - uint256 min_dy = (dx * rateMinRAY) / RAY; // F: [ACV1-7] + uint256 min_dy = (dx * rateMinRAY) / RAY; // U:[CRVB-6] (tokensToEnable, tokensToDisable) = - _exchange_underlying_impl(i, j, _getExchangeUnderlyingCallData(i, j, dx, min_dy), true); // F: [ACV1-7] + _exchange_underlying_impl(i, j, _getExchangeUnderlyingCallData(i, j, dx, min_dy), true); // U:[CRVB-6] } /// @dev Internal implementation of `exchange_underlying` and `exchange_all_underlying` @@ -317,10 +317,10 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _approveToken(_get_underlying(i), type(uint256).max); - _execute(callData); - _approveToken(_get_underlying(i), 1); - (tokensToEnable, tokensToDisable) = (_get_underlying_mask(j), disableTokenIn ? _get_underlying_mask(i) : 0); + _approveToken(_get_underlying(i), type(uint256).max); // U:[CRVB-5,6] + _execute(callData); // U:[CRVB-5,6] + _approveToken(_get_underlying(i), 1); // U:[CRVB-5,6] + (tokensToEnable, tokensToDisable) = (_get_underlying_mask(j), disableTokenIn ? _get_underlying_mask(i) : 0); // U:[CRVB-5,6] } /// @dev Returns calldata for `exchange_underlying` and `exchange_all_underlying` calls @@ -331,7 +331,7 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { { return use256 ? abi.encodeWithSignature("exchange_underlying(uint256,uint256,uint256,uint256)", i, j, dx, min_dy) - : abi.encodeWithSignature("exchange_underlying(int128,int128,uint256,uint256)", i, j, dx, min_dy); + : abi.encodeWithSignature("exchange_underlying(int128,int128,uint256,uint256)", i, j, dx, min_dy); // U:[CRVB-5,6] } // ------------- // @@ -346,10 +346,10 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _approveTokens(t0Approve, t1Approve, t2Approve, t3Approve, type(uint256).max); - _execute(msg.data); - _approveTokens(t0Approve, t1Approve, t2Approve, t3Approve, 1); - (tokensToEnable, tokensToDisable) = (lpTokenMask, 0); + _approveTokens(t0Approve, t1Approve, t2Approve, t3Approve, type(uint256).max); // U:[CRV2-2, CRV3-2, CRV4-2] + _execute(msg.data); // U:[CRV2-2, CRV3-2, CRV4-2] + _approveTokens(t0Approve, t1Approve, t2Approve, t3Approve, 1); // U:[CRV2-2, CRV3-2, CRV4-2] + (tokensToEnable, tokensToDisable) = (lpTokenMask, 0); // U:[CRV2-2, CRV3-2, CRV4-2] } /// @notice Adds given amount of asset as liquidity to the pool @@ -359,11 +359,11 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { function add_liquidity_one_coin(uint256 amount, uint256 i, uint256 minAmount) external override - creditFacadeOnly + creditFacadeOnly // U:[CRVB-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { (tokensToEnable, tokensToDisable) = - _add_liquidity_one_coin_impl(i, _getAddLiquidityOneCoinCallData(i, amount, minAmount), false); // F: [ACV1-8] + _add_liquidity_one_coin_impl(i, _getAddLiquidityOneCoinCallData(i, amount, minAmount), false); // U:[CRVB-7] } /// @notice Adds the entire balance of asset as liquidity to the pool, disables this asset @@ -372,21 +372,21 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { function add_all_liquidity_one_coin(uint256 i, uint256 rateMinRAY) external override - creditFacadeOnly + creditFacadeOnly // U:[CRVB-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[CRVB-8] address tokenIn = _get_token(i); - uint256 amount = IERC20(tokenIn).balanceOf(creditAccount); // F: [ACV1-9] + uint256 amount = IERC20(tokenIn).balanceOf(creditAccount); // U:[CRVB-8] if (amount <= 1) return (0, 0); unchecked { - amount--; // F: [ACV1-9] + amount--; // U:[CRVB-8] } - uint256 minAmount = (amount * rateMinRAY) / RAY; // F: [ACV1-9] + uint256 minAmount = (amount * rateMinRAY) / RAY; // U:[CRVB-8] (tokensToEnable, tokensToDisable) = - _add_liquidity_one_coin_impl(i, _getAddLiquidityOneCoinCallData(i, amount, minAmount), true); // F: [ACV1-9] + _add_liquidity_one_coin_impl(i, _getAddLiquidityOneCoinCallData(i, amount, minAmount), true); // U:[CRVB-8] } /// @dev Internal implementation of `add_liquidity_one_coin` and `add_all_liquidity_one_coin` @@ -398,10 +398,10 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _approveToken(_get_token(i), type(uint256).max); - _execute(callData); - _approveToken(_get_token(i), 1); - (tokensToEnable, tokensToDisable) = (lpTokenMask, disableTokenIn ? _get_token_mask(i) : 0); + _approveToken(_get_token(i), type(uint256).max); // U:[CRVB-7,8] + _execute(callData); // U:[CRVB-7,8] + _approveToken(_get_token(i), 1); // U:[CRVB-7,8] + (tokensToEnable, tokensToDisable) = (lpTokenMask, disableTokenIn ? _get_token_mask(i) : 0); // U:[CRVB-7,8] } /// @notice Returns the amount of LP token received for adding a single asset to the pool @@ -442,8 +442,8 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { /// - passes calldata to the target contract /// - enables all pool tokens function _remove_liquidity() internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _execute(msg.data); - (tokensToEnable, tokensToDisable) = (token0Mask | token1Mask | token2Mask | token3Mask, 0); // F: [ACV1_2-5, ACV1_3-5, ACV1_4-5] + _execute(msg.data); // U:[CRV2-3, CRV3-3, CRV4-3] + (tokensToEnable, tokensToDisable) = (token0Mask | token1Mask | token2Mask | token3Mask, 0); // U:[CRV2-3, CRV3-3, CRV4-3] } /// @dev Internal implementation of `remove_liquidity_imbalance` @@ -453,13 +453,13 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _execute(msg.data); + _execute(msg.data); // U:[CRV2-4, CRV3-4, CRV4-4] - if (t0Enable) tokensToEnable = tokensToEnable.enable(token0Mask); // F: [ACV1_2-6, ACV1_3-6, ACV1_4-6] - if (t1Enable) tokensToEnable = tokensToEnable.enable(token1Mask); // F: [ACV1_2-6, ACV1_3-6, ACV1_4-6] - if (t2Enable) tokensToEnable = tokensToEnable.enable(token2Mask); // F: [ACV1_3-6, ACV1_4-6] - if (t3Enable) tokensToEnable = tokensToEnable.enable(token3Mask); // F: [ACV1_4-6] - tokensToDisable = 0; // F: [ACV1_2-6, ACV1_3-6, ACV1_4-6] + if (t0Enable) tokensToEnable = tokensToEnable.enable(token0Mask); // U:[CRV2-4, CRV3-4, CRV4-4] + if (t1Enable) tokensToEnable = tokensToEnable.enable(token1Mask); // U:[CRV2-4, CRV3-4, CRV4-4] + if (t2Enable) tokensToEnable = tokensToEnable.enable(token2Mask); // U:[CRV3-4, CRV4-4] + if (t3Enable) tokensToEnable = tokensToEnable.enable(token3Mask); // U:[CRV4-4] + tokensToDisable = 0; // U:[CRV2-4, CRV3-4, CRV4-4] } /// @notice Removes liquidity from the pool in a specified asset @@ -470,10 +470,10 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { external virtual override - creditFacadeOnly + creditFacadeOnly // U:[CRVB-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _remove_liquidity_one_coin(amount, i, minAmount); + (tokensToEnable, tokensToDisable) = _remove_liquidity_one_coin(amount, i, minAmount); // U:[CRVB-9] } /// @dev Same as the previous one but accepts coin indexes as `int128` @@ -481,10 +481,10 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { external virtual override - creditFacadeOnly + creditFacadeOnly // U:[CRVB-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _remove_liquidity_one_coin(amount, _toU256(i), minAmount); + (tokensToEnable, tokensToDisable) = _remove_liquidity_one_coin(amount, _toU256(i), minAmount); // U:[CRVB-9] } /// @dev Implementation of both versions of `remove_liquidity_one_coin` @@ -493,7 +493,7 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { returns (uint256 tokensToEnable, uint256 tokensToDisable) { (tokensToEnable, tokensToDisable) = - _remove_liquidity_one_coin_impl(i, _getRemoveLiquidityOneCoinCallData(i, amount, minAmount), false); // F: [ACV1-10] + _remove_liquidity_one_coin_impl(i, _getRemoveLiquidityOneCoinCallData(i, amount, minAmount), false); // U:[CRVB-9] } /// @notice Removes all liquidity from the pool in a specified asset @@ -503,10 +503,10 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { external virtual override - creditFacadeOnly + creditFacadeOnly // U:[CRVB-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _remove_all_liquidity_one_coin(i, rateMinRAY); + (tokensToEnable, tokensToDisable) = _remove_all_liquidity_one_coin(i, rateMinRAY); // U:[CRVB-10] } /// @dev Same as the previous one but accepts coin indexes as `int128` @@ -514,10 +514,10 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { external virtual override - creditFacadeOnly + creditFacadeOnly // U:[CRVB-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _remove_all_liquidity_one_coin(_toU256(i), rateMinRAY); + (tokensToEnable, tokensToDisable) = _remove_all_liquidity_one_coin(_toU256(i), rateMinRAY); // U:[CRVB-10] } /// @dev Implementation of both versions of `remove_all_liquidity_one_coin` @@ -525,17 +525,17 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[CRVB-10] - uint256 amount = IERC20(lp_token).balanceOf(creditAccount); // F: [ACV1-11] + uint256 amount = IERC20(lp_token).balanceOf(creditAccount); // U:[CRVB-10] if (amount <= 1) return (0, 0); unchecked { - amount--; // F: [ACV1-11] + amount--; // U:[CRVB-10] } - uint256 minAmount = (amount * rateMinRAY) / RAY; // F: [ACV1-11] + uint256 minAmount = (amount * rateMinRAY) / RAY; // U:[CRVB-10] (tokensToEnable, tokensToDisable) = - _remove_liquidity_one_coin_impl(i, _getRemoveLiquidityOneCoinCallData(i, amount, minAmount), true); // F: [ACV1-11] + _remove_liquidity_one_coin_impl(i, _getRemoveLiquidityOneCoinCallData(i, amount, minAmount), true); // U:[CRVB-10] } /// @dev Internal implementation of `remove_liquidity_one_coin` and `remove_all_liquidity_one_coin` @@ -547,7 +547,7 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { returns (uint256 tokensToEnable, uint256 tokensToDisable) { _execute(callData); - (tokensToEnable, tokensToDisable) = (_get_token_mask(i), disableLP ? lpTokenMask : 0); + (tokensToEnable, tokensToDisable) = (_get_token_mask(i), disableLP ? lpTokenMask : 0); // U:[CRVB-9,10] } /// @dev Returns calldata for `remove_liquidity_one_coin` and `remove_all_liquidity_one_coin` calls @@ -558,7 +558,7 @@ abstract contract CurveV1AdapterBase is AbstractAdapter, ICurveV1Adapter { { return use256 ? abi.encodeWithSignature("remove_liquidity_one_coin(uint256,uint256,uint256)", amount, i, minAmount) - : abi.encodeWithSignature("remove_liquidity_one_coin(uint256,int128,uint256)", amount, i, minAmount); + : abi.encodeWithSignature("remove_liquidity_one_coin(uint256,int128,uint256)", amount, i, minAmount); // U:[CRVB-9,10] } // ------- // diff --git a/contracts/adapters/curve/CurveV1_stETH.sol b/contracts/adapters/curve/CurveV1_stETH.sol index 1e694c4e..a2d9f429 100644 --- a/contracts/adapters/curve/CurveV1_stETH.sol +++ b/contracts/adapters/curve/CurveV1_stETH.sol @@ -40,10 +40,10 @@ contract CurveV1AdapterStETH is CurveV1Adapter2Assets { external override creditFacadeOnly - withLPTokenApproval // F: [ACV1S-2] + withLPTokenApproval returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _remove_liquidity(); // F: [ACV1S-2] + (tokensToEnable, tokensToDisable) = _remove_liquidity(); } /// @inheritdoc CurveV1Adapter2Assets @@ -52,10 +52,10 @@ contract CurveV1AdapterStETH is CurveV1Adapter2Assets { external override creditFacadeOnly - withLPTokenApproval // F: [ACV1S-6] + withLPTokenApproval returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _remove_liquidity_imbalance(amounts[0] > 1, amounts[1] > 1, false, false); // F: [ACV1S-6] + (tokensToEnable, tokensToDisable) = _remove_liquidity_imbalance(amounts[0] > 1, amounts[1] > 1, false, false); } /// @inheritdoc CurveV1AdapterBase @@ -64,10 +64,10 @@ contract CurveV1AdapterStETH is CurveV1Adapter2Assets { public override(CurveV1AdapterBase, ICurveV1Adapter) creditFacadeOnly - withLPTokenApproval // F: [ACV1S-4] + withLPTokenApproval returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _remove_liquidity_one_coin(amount, i, minAmount); // F: [ACV1S-4] + (tokensToEnable, tokensToDisable) = _remove_liquidity_one_coin(amount, i, minAmount); } /// @inheritdoc CurveV1AdapterBase @@ -76,10 +76,10 @@ contract CurveV1AdapterStETH is CurveV1Adapter2Assets { public override(CurveV1AdapterBase, ICurveV1Adapter) creditFacadeOnly - withLPTokenApproval // F: [ACV1S-4] + withLPTokenApproval returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _remove_liquidity_one_coin(amount, _toU256(i), minAmount); // F: [ACV1S-4] + (tokensToEnable, tokensToDisable) = _remove_liquidity_one_coin(amount, _toU256(i), minAmount); } /// @inheritdoc CurveV1AdapterBase @@ -88,10 +88,10 @@ contract CurveV1AdapterStETH is CurveV1Adapter2Assets { public override(CurveV1AdapterBase, ICurveV1Adapter) creditFacadeOnly - withLPTokenApproval // F: [ACV1S-5] + withLPTokenApproval returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _remove_all_liquidity_one_coin(i, rateMinRAY); // F: [ACV1S-5] + (tokensToEnable, tokensToDisable) = _remove_all_liquidity_one_coin(i, rateMinRAY); } /// @inheritdoc CurveV1AdapterBase @@ -100,9 +100,9 @@ contract CurveV1AdapterStETH is CurveV1Adapter2Assets { public override(CurveV1AdapterBase, ICurveV1Adapter) creditFacadeOnly - withLPTokenApproval // F: [ACV1S-5] + withLPTokenApproval returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _remove_all_liquidity_one_coin(_toU256(i), rateMinRAY); // F: [ACV1S-5] + (tokensToEnable, tokensToDisable) = _remove_all_liquidity_one_coin(_toU256(i), rateMinRAY); } } diff --git a/contracts/adapters/erc4626/ERC4626Adapter.sol b/contracts/adapters/erc4626/ERC4626Adapter.sol index 0be98099..79559f99 100644 --- a/contracts/adapters/erc4626/ERC4626Adapter.sol +++ b/contracts/adapters/erc4626/ERC4626Adapter.sol @@ -29,10 +29,12 @@ contract ERC4626Adapter is AbstractAdapter, IERC4626Adapter { /// @notice Constructor /// @param _creditManager Credit manager address /// @param _vault ERC4626 vault address - constructor(address _creditManager, address _vault) AbstractAdapter(_creditManager, _vault) { - asset = IERC4626(_vault).asset(); - assetMask = _getMaskOrRevert(asset); - sharesMask = _getMaskOrRevert(_vault); + constructor(address _creditManager, address _vault) + AbstractAdapter(_creditManager, _vault) // U:[TV-1] + { + asset = IERC4626(_vault).asset(); // U:[TV-1] + assetMask = _getMaskOrRevert(asset); // U:[TV-1] + sharesMask = _getMaskOrRevert(_vault); // U:[TV-1] } /// @notice Deposits a specified amount of underlying asset from the Credit Account @@ -41,28 +43,28 @@ contract ERC4626Adapter is AbstractAdapter, IERC4626Adapter { function deposit(uint256 assets, address) external override - creditFacadeOnly + creditFacadeOnly // U:[TV-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); - (tokensToEnable, tokensToDisable) = _deposit(creditAccount, assets, false); + address creditAccount = _creditAccount(); // U:[TV-3] + (tokensToEnable, tokensToDisable) = _deposit(creditAccount, assets, false); // U:[TV-3] } /// @notice Deposits the entire balance of underlying asset from the Credit Account function depositAll() external override - creditFacadeOnly + creditFacadeOnly // U:[TV-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); - uint256 balance = IERC20(asset).balanceOf(creditAccount); + address creditAccount = _creditAccount(); // U:[TV-4] + uint256 balance = IERC20(asset).balanceOf(creditAccount); // U:[TV-4] if (balance <= 1) return (0, 0); unchecked { - balance--; + balance--; // U:[TV-4] } - (tokensToEnable, tokensToDisable) = _deposit(creditAccount, balance, true); + (tokensToEnable, tokensToDisable) = _deposit(creditAccount, balance, true); // U:[TV-4] } /// @dev Implementation for the deposit function @@ -71,7 +73,7 @@ contract ERC4626Adapter is AbstractAdapter, IERC4626Adapter { returns (uint256 tokensToEnable, uint256 tokensToDisable) { (tokensToEnable, tokensToDisable) = - _executeDeposit(disableTokenIn, abi.encodeCall(IERC4626.deposit, (assets, creditAccount))); + _executeDeposit(disableTokenIn, abi.encodeCall(IERC4626.deposit, (assets, creditAccount))); // U:[TV-3,4] } /// @notice Deposits an amount of asset required to mint exactly 'shares' of Vault shares @@ -80,12 +82,12 @@ contract ERC4626Adapter is AbstractAdapter, IERC4626Adapter { function mint(uint256 shares, address) external override - creditFacadeOnly + creditFacadeOnly // U:[TV-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[TV-5] (tokensToEnable, tokensToDisable) = - _executeDeposit(false, abi.encodeCall(IERC4626.mint, (shares, creditAccount))); + _executeDeposit(false, abi.encodeCall(IERC4626.mint, (shares, creditAccount))); // U:[TV-5] } /// @notice Burns an amount of shares required to get exactly `assets` of asset @@ -94,12 +96,12 @@ contract ERC4626Adapter is AbstractAdapter, IERC4626Adapter { function withdraw(uint256 assets, address, address) external override - creditFacadeOnly + creditFacadeOnly // U:[TV-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); + address creditAccount = _creditAccount(); // U:[TV-6] (tokensToEnable, tokensToDisable) = - _executeWithdrawal(false, abi.encodeCall(IERC4626.withdraw, (assets, creditAccount, creditAccount))); + _executeWithdrawal(false, abi.encodeCall(IERC4626.withdraw, (assets, creditAccount, creditAccount))); // U:[TV-6] } /// @notice Burns a specified amount of shares from the Credit Account @@ -108,22 +110,27 @@ contract ERC4626Adapter is AbstractAdapter, IERC4626Adapter { function redeem(uint256 shares, address, address) external override - creditFacadeOnly + creditFacadeOnly // U:[TV-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); - (tokensToEnable, tokensToDisable) = _redeem(creditAccount, shares, false); + address creditAccount = _creditAccount(); // U:[TV-7] + (tokensToEnable, tokensToDisable) = _redeem(creditAccount, shares, false); // U:[TV-7] } /// @notice Burns the entire balance of shares from the Credit Account - function redeemAll() external override creditFacadeOnly returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); - uint256 balance = IERC20(targetContract).balanceOf(creditAccount); + function redeemAll() + external + override + creditFacadeOnly // U:[TV-2] + returns (uint256 tokensToEnable, uint256 tokensToDisable) + { + address creditAccount = _creditAccount(); // U:[TV-8] + uint256 balance = IERC20(targetContract).balanceOf(creditAccount); // U:[TV-8] if (balance <= 1) return (0, 0); unchecked { - balance--; + balance--; // U:[TV-8] } - (tokensToEnable, tokensToDisable) = _redeem(creditAccount, balance, true); + (tokensToEnable, tokensToDisable) = _redeem(creditAccount, balance, true); // U:[TV-8] } /// @dev Implementation for the redeem function @@ -132,7 +139,7 @@ contract ERC4626Adapter is AbstractAdapter, IERC4626Adapter { returns (uint256 tokensToEnable, uint256 tokensToDisable) { (tokensToEnable, tokensToDisable) = - _executeWithdrawal(disableTokenIn, abi.encodeCall(IERC4626.redeem, (shares, creditAccount, creditAccount))); + _executeWithdrawal(disableTokenIn, abi.encodeCall(IERC4626.redeem, (shares, creditAccount, creditAccount))); // U:[TV-7,8] } /// @dev Implementation for deposit (asset => shares) actions execution @@ -142,9 +149,9 @@ contract ERC4626Adapter is AbstractAdapter, IERC4626Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _approveToken(asset, type(uint256).max); - _execute(callData); - _approveToken(asset, 1); + _approveToken(asset, type(uint256).max); // U:[TV-3,4,5] + _execute(callData); // U:[TV-3,4,5] + _approveToken(asset, 1); // U:[TV-3,4,5] tokensToEnable = sharesMask; tokensToDisable = disableAsset ? assetMask : 0; } @@ -156,7 +163,7 @@ contract ERC4626Adapter is AbstractAdapter, IERC4626Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _execute(callData); + _execute(callData); // U:[TV-6,7,8] tokensToEnable = assetMask; tokensToDisable = disableShares ? sharesMask : 0; } diff --git a/contracts/adapters/lido/LidoV1.sol b/contracts/adapters/lido/LidoV1.sol index 5d065c1e..5ada93fc 100644 --- a/contracts/adapters/lido/LidoV1.sol +++ b/contracts/adapters/lido/LidoV1.sol @@ -42,14 +42,16 @@ contract LidoV1Adapter is AbstractAdapter, ILidoV1Adapter { /// @notice Constructor /// @param _creditManager Credit manager address /// @param _lidoGateway Lido gateway address - constructor(address _creditManager, address _lidoGateway) AbstractAdapter(_creditManager, _lidoGateway) { - stETH = LidoV1Gateway(payable(_lidoGateway)).stETH(); // F: [LDOV1-1] - stETHTokenMask = _getMaskOrRevert(stETH); // F: [LDOV1-1] + constructor(address _creditManager, address _lidoGateway) + AbstractAdapter(_creditManager, _lidoGateway) // U:[LDO1-1] + { + stETH = LidoV1Gateway(payable(_lidoGateway)).stETH(); // U:[LDO1-1] + stETHTokenMask = _getMaskOrRevert(stETH); // U:[LDO1-1] - weth = LidoV1Gateway(payable(_lidoGateway)).weth(); // F: [LDOV1-1] - wethTokenMask = _getMaskOrRevert(weth); // F: [LDOV1-1] + weth = LidoV1Gateway(payable(_lidoGateway)).weth(); // U:[LDO1-1] + wethTokenMask = _getMaskOrRevert(weth); // U:[LDO1-1] - treasury = IAddressProviderV3(addressProvider).getAddressOrRevert(AP_TREASURY, NO_VERSION_CONTROL); // F: [LDOV1-1] + treasury = IAddressProviderV3(addressProvider).getAddressOrRevert(AP_TREASURY, NO_VERSION_CONTROL); // U:[LDO1-1] } /// @notice Stakes given amount of WETH in Lido via Gateway @@ -58,21 +60,26 @@ contract LidoV1Adapter is AbstractAdapter, ILidoV1Adapter { function submit(uint256 amount) external override - creditFacadeOnly + creditFacadeOnly // U:[LDO1-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _submit(amount, false); // F: [LDOV1-3] + (tokensToEnable, tokensToDisable) = _submit(amount, false); // U:[LDO1-3] } /// @notice Stakes the entire balance of WETH in Lido via Gateway, disables WETH /// @dev The referral address is set to Gearbox treasury - function submitAll() external override creditFacadeOnly returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // F: [LDOV1-2] + function submitAll() + external + override + creditFacadeOnly // U:[LDO1-2] + returns (uint256 tokensToEnable, uint256 tokensToDisable) + { + address creditAccount = _creditAccount(); // U:[LDO1-4] - uint256 balance = IERC20(weth).balanceOf(creditAccount); + uint256 balance = IERC20(weth).balanceOf(creditAccount); // U:[LDO1-4] if (balance > 1) { unchecked { - (tokensToEnable, tokensToDisable) = _submit(balance - 1, true); // F: [LDOV1-4] + (tokensToEnable, tokensToDisable) = _submit(balance - 1, true); // U:[LDO1-4] } } } @@ -85,9 +92,9 @@ contract LidoV1Adapter is AbstractAdapter, ILidoV1Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _approveToken(weth, type(uint256).max); - _execute(abi.encodeCall(LidoV1Gateway.submit, (amount, treasury))); - _approveToken(weth, 1); + _approveToken(weth, type(uint256).max); // U:[LDO1-3,4] + _execute(abi.encodeCall(LidoV1Gateway.submit, (amount, treasury))); // U:[LDO1-3,4] + _approveToken(weth, 1); // U:[LDO1-3,4] (tokensToEnable, tokensToDisable) = (stETHTokenMask, disableWETH ? wethTokenMask : 0); } } diff --git a/contracts/adapters/lido/WstETHV1.sol b/contracts/adapters/lido/WstETHV1.sol index 8b0f33be..da4dab0d 100644 --- a/contracts/adapters/lido/WstETHV1.sol +++ b/contracts/adapters/lido/WstETHV1.sol @@ -29,10 +29,12 @@ contract WstETHV1Adapter is AbstractAdapter, IwstETHV1Adapter { /// @notice Constructor /// @param _creditManager Credit manager address /// @param _wstETH wstETH token address - constructor(address _creditManager, address _wstETH) AbstractAdapter(_creditManager, _wstETH) { - stETH = IwstETH(_wstETH).stETH(); // F: [AWSTV1-1] - wstETHTokenMask = _getMaskOrRevert(_wstETH); // F: [AWSTV1-1, AWSTV1-2] - stETHTokenMask = _getMaskOrRevert(stETH); // F: [AWSTV1-1, AWSTV1-2] + constructor(address _creditManager, address _wstETH) + AbstractAdapter(_creditManager, _wstETH) // U:[LDO1W-1] + { + stETH = IwstETH(_wstETH).stETH(); // U:[LDO1W-1] + wstETHTokenMask = _getMaskOrRevert(_wstETH); // U:[LDO1W-1] + stETHTokenMask = _getMaskOrRevert(stETH); // U:[LDO1W-1] } // ---- // @@ -44,20 +46,25 @@ contract WstETHV1Adapter is AbstractAdapter, IwstETHV1Adapter { function wrap(uint256 amount) external override - creditFacadeOnly + creditFacadeOnly // U:[LDO1W-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _wrap(amount, false); // F: [AWSTV1-5] + (tokensToEnable, tokensToDisable) = _wrap(amount, false); // U:[LDO1W-3] } /// @notice Wraps the entire balance of stETH into wstETH, disables stETH - function wrapAll() external override creditFacadeOnly returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // F: [AWSTV1-3] + function wrapAll() + external + override + creditFacadeOnly // U:[LDO1W-2] + returns (uint256 tokensToEnable, uint256 tokensToDisable) + { + address creditAccount = _creditAccount(); // U:[LDO1W-4] - uint256 balance = IERC20(stETH).balanceOf(creditAccount); + uint256 balance = IERC20(stETH).balanceOf(creditAccount); // U:[LDO1W-4] if (balance > 1) { unchecked { - (tokensToEnable, tokensToDisable) = _wrap(balance - 1, true); // F: [AWSTV1-4] + (tokensToEnable, tokensToDisable) = _wrap(balance - 1, true); // U:[LDO1W-4] } } } @@ -70,9 +77,9 @@ contract WstETHV1Adapter is AbstractAdapter, IwstETHV1Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _approveToken(stETH, type(uint256).max); - _execute(abi.encodeCall(IwstETH.wrap, (amount))); - _approveToken(stETH, 1); + _approveToken(stETH, type(uint256).max); // U:[LDO1W-3,4] + _execute(abi.encodeCall(IwstETH.wrap, (amount))); // U:[LDO1W-3,4] + _approveToken(stETH, 1); // U:[LDO1W-3,4] (tokensToEnable, tokensToDisable) = (wstETHTokenMask, disableStETH ? stETHTokenMask : 0); } @@ -85,20 +92,25 @@ contract WstETHV1Adapter is AbstractAdapter, IwstETHV1Adapter { function unwrap(uint256 amount) external override - creditFacadeOnly + creditFacadeOnly // U:[LDO1W-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _unwrap(amount, false); // F: [AWSTV1-7] + (tokensToEnable, tokensToDisable) = _unwrap(amount, false); // U:[LDO1W-5] } /// @notice Unwraps the entire balance of wstETH to stETH, disables wstETH - function unwrapAll() external override creditFacadeOnly returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // F: [AWSTV1-3] + function unwrapAll() + external + override + creditFacadeOnly // U:[LDO1W-2] + returns (uint256 tokensToEnable, uint256 tokensToDisable) + { + address creditAccount = _creditAccount(); // U:[LDO1W-6] - uint256 balance = IERC20(targetContract).balanceOf(creditAccount); + uint256 balance = IERC20(targetContract).balanceOf(creditAccount); // U:[LDO1W-6] if (balance > 1) { unchecked { - (tokensToEnable, tokensToDisable) = _unwrap(balance - 1, true); // F: [AWSTV1-6] + (tokensToEnable, tokensToDisable) = _unwrap(balance - 1, true); // U:[LDO1W-6] } } } @@ -111,7 +123,7 @@ contract WstETHV1Adapter is AbstractAdapter, IwstETHV1Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _execute(abi.encodeCall(IwstETH.unwrap, (amount))); + _execute(abi.encodeCall(IwstETH.unwrap, (amount))); // U:[LDO1W-5,6] (tokensToEnable, tokensToDisable) = (stETHTokenMask, disableWstETH ? wstETHTokenMask : 0); } } diff --git a/contracts/adapters/uniswap/UniswapV2.sol b/contracts/adapters/uniswap/UniswapV2.sol index 577720af..aad536ee 100644 --- a/contracts/adapters/uniswap/UniswapV2.sol +++ b/contracts/adapters/uniswap/UniswapV2.sol @@ -13,7 +13,7 @@ import {AdapterType} from "@gearbox-protocol/sdk-gov/contracts/AdapterType.sol"; import {IUniswapV2Router02} from "../../integrations/uniswap/IUniswapV2Router02.sol"; import {IUniswapV2Adapter, UniswapV2PairStatus} from "../../interfaces/uniswap/IUniswapV2Adapter.sol"; -/// @title Uniswap V2 Router adapter interface +/// @title Uniswap V2 Router adapter /// @notice Implements logic allowing CAs to perform swaps via Uniswap V2 and its forks contract UniswapV2Adapter is AbstractAdapter, IUniswapV2Adapter { AdapterType public constant override _gearboxAdapterType = AdapterType.UNISWAP_V2_ROUTER; @@ -25,7 +25,9 @@ contract UniswapV2Adapter is AbstractAdapter, IUniswapV2Adapter { /// @notice Constructor /// @param _creditManager Credit manager address /// @param _router Uniswap V2 Router address - constructor(address _creditManager, address _router) AbstractAdapter(_creditManager, _router) {} + constructor(address _creditManager, address _router) + AbstractAdapter(_creditManager, _router) // U:[UNI2-1] + {} /// @notice Swap input token for given amount of output token /// @param amountOut Amount of output token to receive @@ -40,11 +42,16 @@ contract UniswapV2Adapter is AbstractAdapter, IUniswapV2Adapter { address[] calldata path, address, uint256 deadline - ) external override creditFacadeOnly returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // F: [AUV2-1] + ) + external + override + creditFacadeOnly // U:[UNI2-2] + returns (uint256 tokensToEnable, uint256 tokensToDisable) + { + address creditAccount = _creditAccount(); // U:[UNI2-3] - (bool valid, address tokenIn, address tokenOut) = _validatePath(path); // F: [AUV2-2] - if (!valid) revert InvalidPathException(); // F: [AUV2-5] + (bool valid, address tokenIn, address tokenOut) = _validatePath(path); + if (!valid) revert InvalidPathException(); // U:[UNI2-3] // calling `_executeSwap` because we need to check if output token is registered as collateral token in the CM (tokensToEnable, tokensToDisable,) = _executeSwapSafeApprove( @@ -54,7 +61,7 @@ contract UniswapV2Adapter is AbstractAdapter, IUniswapV2Adapter { IUniswapV2Router02.swapTokensForExactTokens, (amountOut, amountInMax, path, creditAccount, deadline) ), false - ); // F: [AUV2-2] + ); // U:[UNI2-3] } /// @notice Swap given amount of input token to output token @@ -70,11 +77,16 @@ contract UniswapV2Adapter is AbstractAdapter, IUniswapV2Adapter { address[] calldata path, address, uint256 deadline - ) external override creditFacadeOnly returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // F: [AUV2-1] + ) + external + override + creditFacadeOnly // U:[UNI2-2] + returns (uint256 tokensToEnable, uint256 tokensToDisable) + { + address creditAccount = _creditAccount(); // U:[UNI2-4] - (bool valid, address tokenIn, address tokenOut) = _validatePath(path); // F: [AUV2-3] - if (!valid) revert InvalidPathException(); // F: [AUV2-5] + (bool valid, address tokenIn, address tokenOut) = _validatePath(path); + if (!valid) revert InvalidPathException(); // U:[UNI2-4] // calling `_executeSwap` because we need to check if output token is registered as collateral token in the CM (tokensToEnable, tokensToDisable,) = _executeSwapSafeApprove( @@ -84,7 +96,7 @@ contract UniswapV2Adapter is AbstractAdapter, IUniswapV2Adapter { IUniswapV2Router02.swapExactTokensForTokens, (amountIn, amountOutMin, path, creditAccount, deadline) ), false - ); // F: [AUV2-3] + ); // U:[UNI2-4] } /// @notice Swap the entire balance of input token to output token, disables input token @@ -95,19 +107,19 @@ contract UniswapV2Adapter is AbstractAdapter, IUniswapV2Adapter { function swapAllTokensForTokens(uint256 rateMinRAY, address[] calldata path, uint256 deadline) external override - creditFacadeOnly + creditFacadeOnly // U:[UNI2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // F: [AUV2-1] + address creditAccount = _creditAccount(); // U:[UNI2-5] - (bool valid, address tokenIn, address tokenOut) = _validatePath(path); // F: [AUV2-4] - if (!valid) revert InvalidPathException(); // F: [AUV2-5] + (bool valid, address tokenIn, address tokenOut) = _validatePath(path); + if (!valid) revert InvalidPathException(); // U:[UNI2-5] - uint256 balance = IERC20(tokenIn).balanceOf(creditAccount); // F: [AUV2-4] + uint256 balance = IERC20(tokenIn).balanceOf(creditAccount); // U:[UNI2-5] if (balance <= 1) return (0, 0); unchecked { - balance--; + balance--; // U:[UNI2-5] } // calling `_executeSwap` because we need to check if output token is registered as collateral token in the CM @@ -119,7 +131,7 @@ contract UniswapV2Adapter is AbstractAdapter, IUniswapV2Adapter { (balance, (balance * rateMinRAY) / RAY, path, creditAccount, deadline) ), true - ); // F: [AUV2-4] + ); // U:[UNI2-5] } // ------------- // @@ -134,13 +146,17 @@ contract UniswapV2Adapter is AbstractAdapter, IUniswapV2Adapter { /// @notice Sets status for a batch of pairs /// @param pairs Array of `UniswapV2PairStatus` objects - function setPairStatusBatch(UniswapV2PairStatus[] calldata pairs) external override configuratorOnly { + function setPairStatusBatch(UniswapV2PairStatus[] calldata pairs) + external + override + configuratorOnly // U:[UNI2-6] + { uint256 len = pairs.length; unchecked { for (uint256 i; i < len; ++i) { (address token0, address token1) = _sortTokens(pairs[i].token0, pairs[i].token1); - _pairStatus[token0][token1] = pairs[i].allowed; - emit SetPairStatus(token0, token1, pairs[i].allowed); + _pairStatus[token0][token1] = pairs[i].allowed; // U:[UNI2-6] + emit SetPairStatus(token0, token1, pairs[i].allowed); // U:[UNI2-6] } } } @@ -158,14 +174,14 @@ contract UniswapV2Adapter is AbstractAdapter, IUniswapV2Adapter { returns (bool valid, address tokenIn, address tokenOut) { uint256 len = path.length; - if (len < 2 || len > 4) return (false, tokenIn, tokenOut); + if (len < 2 || len > 4) return (false, tokenIn, tokenOut); // U:[UNI2-7] - tokenIn = path[0]; - tokenOut = path[len - 1]; + tokenIn = path[0]; // U:[UNI2-7] + tokenOut = path[len - 1]; // U:[UNI2-7] valid = isPairAllowed(path[0], path[1]); if (valid && len > 2) { - valid = isPairAllowed(path[1], path[2]); - if (valid && len > 3) valid = isPairAllowed(path[2], path[3]); + valid = isPairAllowed(path[1], path[2]); // U:[UNI2-7] + if (valid && len > 3) valid = isPairAllowed(path[2], path[3]); // U:[UNI2-7] } } diff --git a/contracts/adapters/uniswap/UniswapV3.sol b/contracts/adapters/uniswap/UniswapV3.sol index 13906ff8..5e8a3f2d 100644 --- a/contracts/adapters/uniswap/UniswapV3.sol +++ b/contracts/adapters/uniswap/UniswapV3.sol @@ -14,7 +14,7 @@ import {ISwapRouter} from "../../integrations/uniswap/IUniswapV3.sol"; import {BytesLib} from "../../integrations/uniswap/BytesLib.sol"; import {IUniswapV3Adapter, UniswapV3PoolStatus} from "../../interfaces/uniswap/IUniswapV3Adapter.sol"; -/// @title Uniswap V3 Router adapter interface +/// @title Uniswap V3 Router adapter /// @notice Implements logic allowing CAs to perform swaps via Uniswap V3 contract UniswapV3Adapter is AbstractAdapter, IUniswapV3Adapter { using BytesLib for bytes; @@ -46,7 +46,9 @@ contract UniswapV3Adapter is AbstractAdapter, IUniswapV3Adapter { /// @notice Constructor /// @param _creditManager Credit manager address /// @param _router Uniswap V3 Router address - constructor(address _creditManager, address _router) AbstractAdapter(_creditManager, _router) {} + constructor(address _creditManager, address _router) + AbstractAdapter(_creditManager, _router) // U:[UNI3-1] + {} /// @notice Swaps given amount of input token for output token through a single pool /// @param params Swap params, see `ISwapRouter.ExactInputSingleParams` for details @@ -54,18 +56,18 @@ contract UniswapV3Adapter is AbstractAdapter, IUniswapV3Adapter { function exactInputSingle(ISwapRouter.ExactInputSingleParams calldata params) external override - creditFacadeOnly + creditFacadeOnly // U:[UNI3-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // F: [AUV3-1] + address creditAccount = _creditAccount(); // U:[UNI3-3] - ISwapRouter.ExactInputSingleParams memory paramsUpdate = params; // F: [AUV3-2] - paramsUpdate.recipient = creditAccount; // F: [AUV3-2] + ISwapRouter.ExactInputSingleParams memory paramsUpdate = params; // U:[UNI3-3] + paramsUpdate.recipient = creditAccount; // U:[UNI3-3] // // calling `_executeSwap` because we need to check if output token is registered as collateral token in the CM (tokensToEnable, tokensToDisable,) = _executeSwapSafeApprove( params.tokenIn, params.tokenOut, abi.encodeCall(ISwapRouter.exactInputSingle, (paramsUpdate)), false - ); // F: [AUV3-2] + ); // U:[UNI3-3] } /// @notice Swaps all balance of input token for output token through a single pool, disables input token @@ -73,16 +75,16 @@ contract UniswapV3Adapter is AbstractAdapter, IUniswapV3Adapter { function exactAllInputSingle(ExactAllInputSingleParams calldata params) external override - creditFacadeOnly + creditFacadeOnly // U:[UNI3-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // F: [AUV3-1] + address creditAccount = _creditAccount(); // U:[UNI3-4] - uint256 balance = IERC20(params.tokenIn).balanceOf(creditAccount); // F: [AUV3-3] + uint256 balance = IERC20(params.tokenIn).balanceOf(creditAccount); // U:[UNI3-4] if (balance <= 1) return (0, 0); unchecked { - balance--; + balance--; // U:[UNI3-4] } ISwapRouter.ExactInputSingleParams memory paramsUpdate = ISwapRouter.ExactInputSingleParams({ @@ -94,12 +96,12 @@ contract UniswapV3Adapter is AbstractAdapter, IUniswapV3Adapter { amountIn: balance, amountOutMinimum: (balance * params.rateMinRAY) / RAY, sqrtPriceLimitX96: params.sqrtPriceLimitX96 - }); // F: [AUV3-3] + }); // U:[UNI3-4] // calling `_executeSwap` because we need to check if output token is registered as collateral token in the CM (tokensToEnable, tokensToDisable,) = _executeSwapSafeApprove( params.tokenIn, params.tokenOut, abi.encodeCall(ISwapRouter.exactInputSingle, (paramsUpdate)), true - ); // F: [AUV3-3] + ); // U:[UNI3-4] } /// @notice Swaps given amount of input token for output token through multiple pools @@ -109,20 +111,20 @@ contract UniswapV3Adapter is AbstractAdapter, IUniswapV3Adapter { function exactInput(ISwapRouter.ExactInputParams calldata params) external override - creditFacadeOnly + creditFacadeOnly // U:[UNI3-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // F: [AUV3-1] + address creditAccount = _creditAccount(); // U:[UNI3-5] (bool valid, address tokenIn, address tokenOut) = _validatePath(params.path); - if (!valid) revert InvalidPathException(); // F: [AUV3-9] + if (!valid) revert InvalidPathException(); // U:[UNI3-5] - ISwapRouter.ExactInputParams memory paramsUpdate = params; // F: [AUV3-4] - paramsUpdate.recipient = creditAccount; // F: [AUV3-4] + ISwapRouter.ExactInputParams memory paramsUpdate = params; // U:[UNI3-5] + paramsUpdate.recipient = creditAccount; // U:[UNI3-5] // calling `_executeSwap` because we need to check if output token is registered as collateral token in the CM (tokensToEnable, tokensToDisable,) = - _executeSwapSafeApprove(tokenIn, tokenOut, abi.encodeCall(ISwapRouter.exactInput, (paramsUpdate)), false); // F: [AUV3-4] + _executeSwapSafeApprove(tokenIn, tokenOut, abi.encodeCall(ISwapRouter.exactInput, (paramsUpdate)), false); // U:[UNI3-5] } /// @notice Swaps all balance of input token for output token through multiple pools, disables input token @@ -131,19 +133,19 @@ contract UniswapV3Adapter is AbstractAdapter, IUniswapV3Adapter { function exactAllInput(ExactAllInputParams calldata params) external override - creditFacadeOnly + creditFacadeOnly // U:[UNI3-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // F: [AUV3-1] + address creditAccount = _creditAccount(); // U:[UNI3-6] (bool valid, address tokenIn, address tokenOut) = _validatePath(params.path); - if (!valid) revert InvalidPathException(); // F: [AUV3-9] + if (!valid) revert InvalidPathException(); // U:[UNI3-6] - uint256 balance = IERC20(tokenIn).balanceOf(creditAccount); // F: [AUV3-5] + uint256 balance = IERC20(tokenIn).balanceOf(creditAccount); // U:[UNI3-6] if (balance <= 1) return (0, 0); unchecked { - balance--; + balance--; // U:[UNI3-6] } ISwapRouter.ExactInputParams memory paramsUpdate = ISwapRouter.ExactInputParams({ path: params.path, @@ -151,11 +153,11 @@ contract UniswapV3Adapter is AbstractAdapter, IUniswapV3Adapter { deadline: params.deadline, amountIn: balance, amountOutMinimum: (balance * params.rateMinRAY) / RAY - }); // F: [AUV3-5] + }); // U:[UNI3-6] // calling `_executeSwap` because we need to check if output token is registered as collateral token in the CM (tokensToEnable, tokensToDisable,) = - _executeSwapSafeApprove(tokenIn, tokenOut, abi.encodeCall(ISwapRouter.exactInput, (paramsUpdate)), true); // F: [AUV3-5] + _executeSwapSafeApprove(tokenIn, tokenOut, abi.encodeCall(ISwapRouter.exactInput, (paramsUpdate)), true); // U:[UNI3-6] } /// @notice Swaps input token for given amount of output token through a single pool @@ -164,18 +166,18 @@ contract UniswapV3Adapter is AbstractAdapter, IUniswapV3Adapter { function exactOutputSingle(ISwapRouter.ExactOutputSingleParams calldata params) external override - creditFacadeOnly + creditFacadeOnly // U:[UNI3-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // F: [AUV3-1] + address creditAccount = _creditAccount(); // U:[UNI3-7] - ISwapRouter.ExactOutputSingleParams memory paramsUpdate = params; // F: [AUV3-6] - paramsUpdate.recipient = creditAccount; // F: [AUV3-6] + ISwapRouter.ExactOutputSingleParams memory paramsUpdate = params; // U:[UNI3-7] + paramsUpdate.recipient = creditAccount; // U:[UNI3-7] // calling `_executeSwap` because we need to check if output token is registered as collateral token in the CM (tokensToEnable, tokensToDisable,) = _executeSwapSafeApprove( params.tokenIn, params.tokenOut, abi.encodeCall(ISwapRouter.exactOutputSingle, (paramsUpdate)), false - ); // F: [AUV3-6] + ); // U:[UNI3-7] } /// @notice Swaps input token for given amount of output token through multiple pools @@ -185,20 +187,20 @@ contract UniswapV3Adapter is AbstractAdapter, IUniswapV3Adapter { function exactOutput(ISwapRouter.ExactOutputParams calldata params) external override - creditFacadeOnly + creditFacadeOnly // U:[UNI3-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // F: [AUV3-1] + address creditAccount = _creditAccount(); // U:[UNI3-8] (bool valid, address tokenOut, address tokenIn) = _validatePath(params.path); - if (!valid) revert InvalidPathException(); // F: [AUV3-9] + if (!valid) revert InvalidPathException(); // U:[UNI3-8] - ISwapRouter.ExactOutputParams memory paramsUpdate = params; // F: [AUV3-7] - paramsUpdate.recipient = creditAccount; // F: [AUV3-7] + ISwapRouter.ExactOutputParams memory paramsUpdate = params; // U:[UNI3-8] + paramsUpdate.recipient = creditAccount; // U:[UNI3-8] // calling `_executeSwap` because we need to check if output token is registered as collateral token in the CM (tokensToEnable, tokensToDisable,) = - _executeSwapSafeApprove(tokenIn, tokenOut, abi.encodeCall(ISwapRouter.exactOutput, (paramsUpdate)), false); // F: [AUV3-7] + _executeSwapSafeApprove(tokenIn, tokenOut, abi.encodeCall(ISwapRouter.exactOutput, (paramsUpdate)), false); // U:[UNI3-8] } // ------------- // @@ -213,13 +215,17 @@ contract UniswapV3Adapter is AbstractAdapter, IUniswapV3Adapter { /// @notice Sets status for a batch of pools /// @param pools Array of `UniswapV3PoolStatus` objects - function setPoolStatusBatch(UniswapV3PoolStatus[] calldata pools) external override configuratorOnly { + function setPoolStatusBatch(UniswapV3PoolStatus[] calldata pools) + external + override + configuratorOnly // U:[UNI3-9] + { uint256 len = pools.length; unchecked { for (uint256 i; i < len; ++i) { (address token0, address token1) = _sortTokens(pools[i].token0, pools[i].token1); - _poolStatus[token0][token1][pools[i].fee] = pools[i].allowed; - emit SetPoolStatus(token0, token1, pools[i].fee, pools[i].allowed); + _poolStatus[token0][token1][pools[i].fee] = pools[i].allowed; // U:[UNI3-9] + emit SetPoolStatus(token0, token1, pools[i].fee, pools[i].allowed); // U:[UNI3-9] } } } @@ -233,24 +239,24 @@ contract UniswapV3Adapter is AbstractAdapter, IUniswapV3Adapter { /// - Each swap must be through an allowed pool function _validatePath(bytes memory path) internal view returns (bool valid, address tokenIn, address tokenOut) { uint256 len = path.length; - if (len != PATH_2_LENGTH && len != PATH_3_LENGTH && len != PATH_4_LENGTH) return (false, tokenIn, tokenOut); + if (len != PATH_2_LENGTH && len != PATH_3_LENGTH && len != PATH_4_LENGTH) return (false, tokenIn, tokenOut); // U:[UNI3-10] - tokenIn = path.toAddress(0); + tokenIn = path.toAddress(0); // U:[UNI3-10] uint24 fee = path.toUint24(ADDR_SIZE); - tokenOut = path.toAddress(NEXT_OFFSET); - valid = isPoolAllowed(tokenIn, tokenOut, fee); + tokenOut = path.toAddress(NEXT_OFFSET); // U:[UNI3-10] + valid = isPoolAllowed(tokenIn, tokenOut, fee); // U:[UNI3-10] if (valid && len > PATH_2_LENGTH) { address tokenMid = tokenOut; fee = path.toUint24(NEXT_OFFSET + ADDR_SIZE); - tokenOut = path.toAddress(2 * NEXT_OFFSET); - valid = isPoolAllowed(tokenMid, tokenOut, fee); + tokenOut = path.toAddress(2 * NEXT_OFFSET); // U:[UNI3-10] + valid = isPoolAllowed(tokenMid, tokenOut, fee); // U:[UNI3-10] if (valid && len > PATH_3_LENGTH) { tokenMid = tokenOut; fee = path.toUint24(2 * NEXT_OFFSET + ADDR_SIZE); - tokenOut = path.toAddress(3 * NEXT_OFFSET); - valid = isPoolAllowed(tokenMid, tokenOut, fee); + tokenOut = path.toAddress(3 * NEXT_OFFSET); // U:[UNI3-10] + valid = isPoolAllowed(tokenMid, tokenOut, fee); // U:[UNI3-10] } } } diff --git a/contracts/adapters/yearn/YearnV2.sol b/contracts/adapters/yearn/YearnV2.sol index a86ef245..05a55416 100644 --- a/contracts/adapters/yearn/YearnV2.sol +++ b/contracts/adapters/yearn/YearnV2.sol @@ -29,10 +29,12 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { /// @notice Constructor /// @param _creditManager Credit manager address /// @param _vault Yearn vault address - constructor(address _creditManager, address _vault) AbstractAdapter(_creditManager, _vault) { - token = IYVault(targetContract).token(); // F: [AYV2-1] - tokenMask = _getMaskOrRevert(token); // F: [AYV2-1, AYV2-2] - yTokenMask = _getMaskOrRevert(_vault); // F: [AYV2-1, AYV2-2] + constructor(address _creditManager, address _vault) + AbstractAdapter(_creditManager, _vault) // U:[YFI2-1] + { + token = IYVault(targetContract).token(); // U:[YFI2-1] + tokenMask = _getMaskOrRevert(token); // U:[YFI2-1] + yTokenMask = _getMaskOrRevert(_vault); // U:[YFI2-1] } // -------- // @@ -40,13 +42,18 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { // -------- // /// @notice Deposit the entire balance of underlying tokens into the vault, disables underlying - function deposit() external override creditFacadeOnly returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // F: [AYV2-3] + function deposit() + external + override + creditFacadeOnly // U:[YFI2-2] + returns (uint256 tokensToEnable, uint256 tokensToDisable) + { + address creditAccount = _creditAccount(); // U:[YFI2-3] - uint256 balance = IERC20(token).balanceOf(creditAccount); + uint256 balance = IERC20(token).balanceOf(creditAccount); // U:[YFI2-3] if (balance > 1) { unchecked { - (tokensToEnable, tokensToDisable) = _deposit(balance - 1, true); // F: [AYV2-4] + (tokensToEnable, tokensToDisable) = _deposit(balance - 1, true); // U:[YFI2-3] } } } @@ -56,10 +63,10 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { function deposit(uint256 amount) external override - creditFacadeOnly + creditFacadeOnly // U:[YFI2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _deposit(amount, false); // F: [AYV2-5] + (tokensToEnable, tokensToDisable) = _deposit(amount, false); // U:[YFI2-4] } /// @notice Deposit given amount of underlying tokens into the vault @@ -68,10 +75,10 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { function deposit(uint256 amount, address) external override - creditFacadeOnly + creditFacadeOnly // U:[YFI2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _deposit(amount, false); // F: [AYV2-6] + (tokensToEnable, tokensToDisable) = _deposit(amount, false); // U:[YFI2-5] } /// @dev Internal implementation of `deposit` functions @@ -82,9 +89,9 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _approveToken(token, type(uint256).max); - _execute(abi.encodeWithSignature("deposit(uint256)", amount)); - _approveToken(token, 1); + _approveToken(token, type(uint256).max); // U:[YFI2-3,4,5] + _execute(abi.encodeWithSignature("deposit(uint256)", amount)); // U:[YFI2-3,4,5] + _approveToken(token, 1); // U:[YFI2-3,4,5] (tokensToEnable, tokensToDisable) = (yTokenMask, disableTokenIn ? tokenMask : 0); } @@ -93,14 +100,19 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { // ----------- // /// @notice Withdraw the entire balance of underlying from the vault, disables yToken - function withdraw() external override creditFacadeOnly returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // F: [AYV2-3] + function withdraw() + external + override + creditFacadeOnly // U:[YFI2-2] + returns (uint256 tokensToEnable, uint256 tokensToDisable) + { + address creditAccount = _creditAccount(); // U:[YFI2-6] - uint256 balance = IERC20(targetContract).balanceOf(creditAccount); + uint256 balance = IERC20(targetContract).balanceOf(creditAccount); // U:[YFI2-6] if (balance > 1) { unchecked { - (tokensToEnable, tokensToDisable) = _withdraw(balance - 1, true); // F: [AYV2-7] + (tokensToEnable, tokensToDisable) = _withdraw(balance - 1, true); // U:[YFI2-6] } } } @@ -110,10 +122,10 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { function withdraw(uint256 maxShares) external override - creditFacadeOnly + creditFacadeOnly // U:[YFI2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _withdraw(maxShares, false); // F: [AYV2-8] + (tokensToEnable, tokensToDisable) = _withdraw(maxShares, false); // U:[YFI2-7] } /// @notice Burn given amount of yTokens to withdraw corresponding amount of underlying from the vault @@ -122,10 +134,10 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { function withdraw(uint256 maxShares, address) external override - creditFacadeOnly + creditFacadeOnly // U:[YFI2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _withdraw(maxShares, false); // F: [AYV2-9] + (tokensToEnable, tokensToDisable) = _withdraw(maxShares, false); // U:[YFI2-8] } /// @notice Burn given amount of yTokens to withdraw corresponding amount of underlying from the vault @@ -135,11 +147,11 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { function withdraw(uint256 maxShares, address, uint256 maxLoss) external override - creditFacadeOnly + creditFacadeOnly // U:[YFI2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // F: [AYV2-3] - (tokensToEnable, tokensToDisable) = _withdraw(maxShares, creditAccount, maxLoss); // F: [AYV2-10, AYV2-11] + address creditAccount = _creditAccount(); // U:[YFI2-9] + (tokensToEnable, tokensToDisable) = _withdraw(maxShares, creditAccount, maxLoss); // U:[YFI2-9] } /// @dev Internal implementation of `withdraw` functions @@ -150,7 +162,7 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _execute(abi.encodeWithSignature("withdraw(uint256)", maxShares)); + _execute(abi.encodeWithSignature("withdraw(uint256)", maxShares)); // U:[YFI2-6,7,8] (tokensToEnable, tokensToDisable) = (tokenMask, disableTokenIn ? yTokenMask : 0); } @@ -162,7 +174,7 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _execute(abi.encodeWithSignature("withdraw(uint256,address,uint256)", maxShares, creditAccount, maxLoss)); + _execute(abi.encodeWithSignature("withdraw(uint256,address,uint256)", maxShares, creditAccount, maxLoss)); // U:[YFI2-9] (tokensToEnable, tokensToDisable) = (tokenMask, 0); } } diff --git a/contracts/helpers/compound/CompoundV2_CEtherGateway.sol b/contracts/helpers/compound/CompoundV2_CEtherGateway.sol index 5bc38fb1..27cee4b2 100644 --- a/contracts/helpers/compound/CompoundV2_CEtherGateway.sol +++ b/contracts/helpers/compound/CompoundV2_CEtherGateway.sol @@ -17,6 +17,7 @@ import {ICErc20Actions} from "../../integrations/compound/ICErc20.sol"; contract CEtherGateway is SanityCheckTrait, ICErc20Actions, ICompoundV2_Exceptions { /// @notice WETH token address address public immutable weth; + /// @notice cETH token address address public immutable ceth; @@ -24,15 +25,15 @@ contract CEtherGateway is SanityCheckTrait, ICErc20Actions, ICompoundV2_Exceptio /// @param _weth WETH token address /// @param _ceth cETH token address constructor(address _weth, address _ceth) - nonZeroAddress(_weth) // F: [CEG-1] - nonZeroAddress(_ceth) // F: [CEG-1] + nonZeroAddress(_weth) // U:[CEG-1] + nonZeroAddress(_ceth) // U:[CEG-1] { - weth = _weth; // F: [CEG-2] - ceth = _ceth; // F: [CEG-2] + weth = _weth; // U:[CEG-1] + ceth = _ceth; // U:[CEG-1] } /// @notice Allows receiving ETH - receive() external payable {} // F: [CEG-3] + receive() external payable {} // U:[CEG-2] /// @notice Deposit given amount of WETH into Compound /// WETH must be approved from caller to gateway before the call @@ -41,11 +42,11 @@ contract CEtherGateway is SanityCheckTrait, ICErc20Actions, ICompoundV2_Exceptio function mint(uint256 mintAmount) external override returns (uint256 error) { // transfer WETH from caller and unwrap it IERC20(weth).transferFrom(msg.sender, address(this), mintAmount); - IWETH(weth).withdraw(mintAmount); // F: [CEG-4] + IWETH(weth).withdraw(mintAmount); // U:[CEG-3] // deposit ETH to Compound - ICEther(ceth).mint{value: mintAmount}(); // F: [CEG-4] - error = 0; + ICEther(ceth).mint{value: mintAmount}(); // U:[CEG-3] + error = 0; // U:[CEG-3] // send cETH to caller IERC20(ceth).transfer(msg.sender, IERC20(ceth).balanceOf(address(this))); @@ -60,12 +61,12 @@ contract CEtherGateway is SanityCheckTrait, ICErc20Actions, ICompoundV2_Exceptio IERC20(ceth).transferFrom(msg.sender, address(this), redeemTokens); // redeem ETH from Compound - error = ICEther(ceth).redeem(redeemTokens); // F: [CEG-5] - if (error != 0) revert CTokenError(error); // F: [CEG-6] + error = ICEther(ceth).redeem(redeemTokens); // U:[CEG-4] + if (error != 0) revert CTokenError(error); // U:[CEG-6] // wrap ETH and send to caller uint256 ethBalance = address(this).balance; - IWETH(weth).deposit{value: ethBalance}(); // F: [CEG-5] + IWETH(weth).deposit{value: ethBalance}(); // U:[CEG-4] IERC20(weth).transfer(msg.sender, ethBalance); } @@ -78,8 +79,8 @@ contract CEtherGateway is SanityCheckTrait, ICErc20Actions, ICompoundV2_Exceptio IERC20(ceth).transferFrom(msg.sender, address(this), IERC20(ceth).balanceOf(msg.sender)); // redeem ETH from Compound - error = ICEther(ceth).redeemUnderlying(redeemAmount); // F: [CEG-7] - if (error != 0) revert CTokenError(error); // F: [CEG-8] + error = ICEther(ceth).redeemUnderlying(redeemAmount); // U:[CEG-5] + if (error != 0) revert CTokenError(error); // U:[CEG-6] // return the remaining cETH (if any) back to caller uint256 cethBalance = IERC20(ceth).balanceOf(address(this)); @@ -87,7 +88,7 @@ contract CEtherGateway is SanityCheckTrait, ICErc20Actions, ICompoundV2_Exceptio // wrap ETH and send to caller uint256 ethBalance = address(this).balance; - IWETH(weth).deposit{value: ethBalance}(); // F: [CEG-7] + IWETH(weth).deposit{value: ethBalance}(); // U:[CEG-5] IERC20(weth).transfer(msg.sender, ethBalance); } } diff --git a/contracts/helpers/lido/LidoV1_WETHGateway.sol b/contracts/helpers/lido/LidoV1_WETHGateway.sol index d61b251e..284bf182 100644 --- a/contracts/helpers/lido/LidoV1_WETHGateway.sol +++ b/contracts/helpers/lido/LidoV1_WETHGateway.sol @@ -23,14 +23,17 @@ contract LidoV1Gateway is SanityCheckTrait { /// @notice Constructor /// @param _weth WETH token address /// @param _stETH stETH contract address - constructor(address _weth, address _stETH) nonZeroAddress(_weth) nonZeroAddress(_stETH) { - weth = _weth; - stETH = _stETH; + constructor(address _weth, address _stETH) + nonZeroAddress(_weth) // U:[LWG-1] + nonZeroAddress(_stETH) // U:[LWG-1] + { + weth = _weth; // U:[LWG-1] + stETH = _stETH; // U:[LWG-1] } /// @notice Allows this contract to unwrap WETH, forbids receiving ETH in other ways receive() external payable { - if (msg.sender != weth) revert ReceiveIsNotAllowedException(); + if (msg.sender != weth) revert ReceiveIsNotAllowedException(); // U:[LWG-2] } /// @notice Submits WETH to the stETH contract by first unwrapping it @@ -38,9 +41,9 @@ contract LidoV1Gateway is SanityCheckTrait { /// @param _referral The address of the referrer function submit(uint256 amount, address _referral) external returns (uint256 value) { IERC20(weth).transferFrom(msg.sender, address(this), amount); - IWETH(weth).withdraw(amount); + IWETH(weth).withdraw(amount); // U:[LWG-3] - value = IstETH(stETH).submit{value: amount}(_referral); + value = IstETH(stETH).submit{value: amount}(_referral); // U:[LWG-3] IERC20(stETH).transfer(msg.sender, IERC20(stETH).balanceOf(address(this))); } } diff --git a/contracts/test/mocks/credit/CreditManagerMock.sol b/contracts/test/mocks/credit/CreditManagerV3Mock.sol similarity index 51% rename from contracts/test/mocks/credit/CreditManagerMock.sol rename to contracts/test/mocks/credit/CreditManagerV3Mock.sol index 8f603204..e7b981cf 100644 --- a/contracts/test/mocks/credit/CreditManagerMock.sol +++ b/contracts/test/mocks/credit/CreditManagerV3Mock.sol @@ -3,23 +3,25 @@ // (c) Gearbox Foundation, 2023. pragma solidity ^0.8.17; -interface CreditManagerMockEvents { +interface CreditManagerV3MockEvents { event Approve(address token, uint256 amount); event Execute(); } -contract CreditManagerMock is CreditManagerMockEvents { +contract CreditManagerV3Mock is CreditManagerV3MockEvents { address public addressProvider; address public creditFacade; + address public creditConfigurator; - address public getActiveCreditAccountOrRevert; - mapping(address => uint256) public getTokenMaskOrRevert; + address internal _activeAccount; + mapping(address => uint256) internal _tokenMasks; bytes _result; - constructor(address _addressProvider, address _creditFacade) { + constructor(address _addressProvider, address _creditFacade, address _creditConfigurator) { addressProvider = _addressProvider; creditFacade = _creditFacade; + creditConfigurator = _creditConfigurator; } function approveCreditAccount(address token, uint256 amount) external { @@ -31,12 +33,22 @@ contract CreditManagerMock is CreditManagerMockEvents { return _result; } + function getTokenMaskOrRevert(address token) external view returns (uint256 mask) { + mask = _tokenMasks[token]; + require(mask != 0, "Token not recognized"); + } + + function getActiveCreditAccountOrRevert() external view returns (address creditAccount) { + creditAccount = _activeAccount; + require(creditAccount != address(0), "Active account not set"); + } + function setActiveCreditAccount(address creditAccount) external { - getActiveCreditAccountOrRevert = creditAccount; + _activeAccount = creditAccount; } function setMask(address token, uint256 mask) external { - getTokenMaskOrRevert[token] = mask; + _tokenMasks[token] = mask; } function setExecuteResult(bytes memory result) external { diff --git a/contracts/test/mocks/integrations/BPTMock.sol b/contracts/test/mocks/integrations/BPTMock.sol deleted file mode 100644 index c4eaa36f..00000000 --- a/contracts/test/mocks/integrations/BPTMock.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -contract BPTMock is ERC20, Ownable { - uint8 private immutable _decimals; - uint256[] weights; - bytes32 poolId; - - constructor(string memory name_, string memory symbol_, uint8 decimals_, uint256[] memory _weights, bytes32 _poolId) - ERC20(name_, symbol_) - { - _decimals = decimals_; - weights = _weights; - poolId = _poolId; - } - - function decimals() public view override returns (uint8) { - return _decimals; - } - - function mint(address to, uint256 amount) external { - _mint(to, amount); - } - - function burn(address to, uint256 amount) external returns (bool) { - _burn(to, amount); - return true; - } - - function getNormalizedWeights() external view returns (uint256[] memory) { - return weights; - } - - function getPoolId() external view returns (bytes32) { - return poolId; - } -} diff --git a/contracts/test/mocks/integrations/BPTStableMock.sol b/contracts/test/mocks/integrations/BPTStableMock.sol deleted file mode 100644 index e4cffd4f..00000000 --- a/contracts/test/mocks/integrations/BPTStableMock.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -contract BPTStableMock is ERC20, Ownable { - uint8 private immutable _decimals; - bytes32 poolId; - uint256 rate; - - constructor(string memory name_, string memory symbol_, uint8 decimals_, bytes32 _poolId) ERC20(name_, symbol_) { - _decimals = decimals_; - poolId = _poolId; - rate = 1e18; - } - - function decimals() public view override returns (uint8) { - return _decimals; - } - - function mint(address to, uint256 amount) external { - _mint(to, amount); - } - - function burn(address to, uint256 amount) external returns (bool) { - _burn(to, amount); - return true; - } - - function setRate(uint256 newRate) external { - rate = newRate; - } - - function getRate() external view returns (uint256) { - return rate; - } - - function getPoolId() external view returns (bytes32) { - return poolId; - } -} diff --git a/contracts/test/mocks/integrations/BalancerVaultMock.sol b/contracts/test/mocks/integrations/BalancerVaultMock.sol deleted file mode 100644 index 9a844a7e..00000000 --- a/contracts/test/mocks/integrations/BalancerVaultMock.sol +++ /dev/null @@ -1,441 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import { - IBalancerV2Vault, - PoolSpecialization, - SingleSwap, - BatchSwapStep, - FundManagement, - SwapKind, - JoinPoolRequest, - ExitPoolRequest, - JoinKind, - ExitKind -} from "../../../integrations/balancer/IBalancerV2Vault.sol"; -import {IAsset} from "../../../integrations/balancer/IAsset.sol"; -import {BPTMock} from "./BPTMock.sol"; -import {BPTStableMock} from "./BPTStableMock.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {RAY} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol"; - -struct PoolData { - address pool; - address[] assets; - uint256[] balances; - mapping(address => mapping(address => uint256)) ratesRAY; - mapping(address => uint256) depositRatesRAY; - mapping(address => uint256) withdrawalRatesRAY; - PoolSpecialization specialization; - uint24 fee; -} - -contract BalancerVaultMock is IBalancerV2Vault { - mapping(bytes32 => PoolData) poolData; - - function addPool( - bytes32 poolId, - address[] memory assets, - uint256[] memory weights, - PoolSpecialization specialization, - uint24 fee - ) external { - address pool = address(new BPTMock("Balancer Pool Token", "BPT", 18, weights, poolId)); - - poolData[poolId].pool = pool; - poolData[poolId].assets = assets; - poolData[poolId].specialization = specialization; - poolData[poolId].fee = fee; - } - - function addStablePool(bytes32 poolId, address[] memory assets, uint24 fee) external { - address pool = address(new BPTStableMock("Balancer Stable Pool Token", "BSPT", 18, poolId)); - - poolData[poolId].pool = pool; - poolData[poolId].assets = assets; - poolData[poolId].specialization = PoolSpecialization.MINIMAL_SWAP_INFO; - poolData[poolId].fee = fee; - } - - function setRate(bytes32 poolId, address asset0, address asset1, uint256 rateRAY) external { - poolData[poolId].ratesRAY[asset0][asset1] = rateRAY; - poolData[poolId].ratesRAY[asset1][asset0] = (RAY * RAY) / rateRAY; - } - - function setDepositRate(bytes32 poolId, address asset, uint256 rateRAY) external { - poolData[poolId].depositRatesRAY[asset] = rateRAY; - } - - function setWithdrawalRate(bytes32 poolId, address asset, uint256 rateRAY) external { - poolData[poolId].withdrawalRatesRAY[asset] = rateRAY; - } - - function setAssetBalances(bytes32 poolId, uint256[] memory balances) external { - require(poolData[poolId].assets.length == balances.length); - poolData[poolId].balances = balances; - } - - function mintBPT(bytes32 poolId, address to, uint256 amount) external { - BPTMock(poolData[poolId].pool).mint(to, amount); - } - - function swap(SingleSwap memory singleSwap, FundManagement memory funds, uint256 limit, uint256 deadline) - external - returns (uint256 amountCalculated) - { - require( - !funds.fromInternalBalance && !funds.toInternalBalance && funds.sender == funds.recipient - && funds.sender == msg.sender, - "BalancerVault: Unsupported funds struct" - ); - - require(block.timestamp <= deadline, "BalancerVault: Deadline passed"); - - require(poolData[singleSwap.poolId].pool != address(0), "BalancerVault: Unknown pool"); - - uint256 rate = poolData[singleSwap.poolId].ratesRAY[address(singleSwap.assetIn)][address(singleSwap.assetOut)]; - - require(rate != 0, "BalancerVault: Rate not set"); - - amountCalculated = - singleSwap.kind == SwapKind.GIVEN_IN ? (singleSwap.amount * rate) / RAY : (singleSwap.amount * RAY) / rate; - - if (singleSwap.kind == SwapKind.GIVEN_IN) { - amountCalculated = - (singleSwap.amount * rate * (10000 - uint256(poolData[singleSwap.poolId].fee))) / (RAY * 10000); - - require(amountCalculated >= limit, "BalancerVault: GIVEN_IN output below limit"); - - IERC20(address(singleSwap.assetIn)).transferFrom(funds.sender, address(this), singleSwap.amount); - IERC20(address(singleSwap.assetOut)).transfer(funds.recipient, amountCalculated); - } else { - amountCalculated = - (singleSwap.amount * RAY * 10000) / (rate * (10000 - uint256(poolData[singleSwap.poolId].fee))); - - require(amountCalculated <= limit, "BalancerVault: GIVEN_OUT input above limit"); - - IERC20(address(singleSwap.assetIn)).transferFrom(funds.sender, address(this), amountCalculated); - IERC20(address(singleSwap.assetOut)).transfer(funds.recipient, singleSwap.amount); - } - } - - function batchSwap( - SwapKind kind, - BatchSwapStep[] memory swaps, - IAsset[] memory assets, - FundManagement memory funds, - int256[] memory limits, - uint256 deadline - ) external returns (int256[] memory assetDeltas) { - require( - !funds.fromInternalBalance && !funds.toInternalBalance && funds.sender == funds.recipient - && funds.sender == msg.sender, - "BalancerVault: Unsupported funds struct" - ); - - require(block.timestamp <= deadline, "BalancerVault: Deadline passed"); - - assetDeltas = queryBatchSwap(kind, swaps, assets, funds); - - for (uint256 i = 0; i < assetDeltas.length; ++i) { - require(assetDeltas[i] <= limits[i], "BalancerVault: BatchSwap output outside limits"); - - if (assetDeltas[i] < 0) { - IERC20(address(assets[i])).transfer(funds.recipient, uint256(-assetDeltas[i])); - } else if (assetDeltas[i] > 0) { - IERC20(address(assets[i])).transferFrom(funds.sender, address(this), uint256(assetDeltas[i])); - } - } - } - - function queryBatchSwap(SwapKind kind, BatchSwapStep[] memory swaps, IAsset[] memory assets, FundManagement memory) - public - view - returns (int256[] memory assetDeltas) - { - assetDeltas = new int256[](assets.length); - - if (kind == SwapKind.GIVEN_IN) { - for (uint256 i = 0; i < swaps.length; ++i) { - require(poolData[swaps[i].poolId].pool != address(0), "BalancerVault: Unknown pool"); - - address assetIn = address(assets[swaps[i].assetInIndex]); - address assetOut = address(assets[swaps[i].assetOutIndex]); - - uint256 amountIn; - - if (swaps[i].amount > 0) { - amountIn = swaps[i].amount; - assetDeltas[swaps[i].assetInIndex] += int256(swaps[i].amount); - } else { - amountIn = uint256(-assetDeltas[swaps[i].assetInIndex]); - assetDeltas[swaps[i].assetInIndex] = 0; - } - - uint256 rate = poolData[swaps[i].poolId].ratesRAY[assetIn][assetOut]; - - require(rate != 0, "BalancerVault: Rate not set"); - - assetDeltas[swaps[i].assetOutIndex] -= - int256((amountIn * rate * (10000 - uint256(poolData[swaps[i].poolId].fee))) / (RAY * 10000)); - } - } else { - for (uint256 i = swaps.length; i >= 0; --i) { - require(poolData[swaps[i].poolId].pool != address(0), "BalancerVault: Unknown pool"); - - address assetIn = address(assets[swaps[i].assetInIndex]); - address assetOut = address(assets[swaps[i].assetOutIndex]); - - uint256 amountOut; - - if (swaps[i].amount > 0) { - amountOut = swaps[i].amount; - assetDeltas[swaps[i].assetOutIndex] -= int256(swaps[i].amount); - } else { - amountOut = uint256(assetDeltas[swaps[i].assetOutIndex]); - assetDeltas[swaps[i].assetOutIndex] = 0; - } - - uint256 rate = poolData[swaps[i].poolId].ratesRAY[assetIn][assetOut]; - - require(rate != 0, "BalancerVault: Rate not set"); - - assetDeltas[swaps[i].assetInIndex] += - int256((amountOut * RAY * 10000) / (rate * (10000 - uint256(poolData[swaps[i].poolId].fee)))); - } - } - } - - function joinPool(bytes32 poolId, address sender, address recipient, JoinPoolRequest memory request) external { - require(sender == recipient && sender == msg.sender, "BalancerVault: Unsupported sender or recipient"); - - require(poolData[poolId].pool != address(0), "BalancerVault: Unknown pool"); - - JoinKind kind = _joinKind(request.userData); - - if (kind == JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT) { - (uint256[] memory amountsIn, uint256 minBPTAmountOut) = _exactTokensInForBPTOut(request.userData); - - uint256 bptOut; - - for (uint256 i = 0; i < request.assets.length; ++i) { - require(amountsIn[i] <= request.maxAmountsIn[i], "BalancerVault: Asset deposit exceeds limit"); - - IERC20(address(request.assets[i])).transferFrom(sender, address(this), amountsIn[i]); - - poolData[poolId].balances[i] += amountsIn[i]; - - uint256 rate = poolData[poolId].depositRatesRAY[address(request.assets[i])]; - - require(rate != 0, "BalancerVault: Deposit rate not set"); - - bptOut += (amountsIn[i] * poolData[poolId].depositRatesRAY[address(request.assets[i])]) / RAY; - } - - require(bptOut >= minBPTAmountOut, "BalancerVault: Insufficient BPT out amount"); - - BPTMock(poolData[poolId].pool).mint(recipient, bptOut); - } else if (kind == JoinKind.TOKEN_IN_FOR_EXACT_BPT_OUT) { - (uint256 bptAmountOut, uint256 tokenIndex) = _tokenInForExactBPTOut(request.userData); - - address asset = address(request.assets[tokenIndex]); - - uint256 rate = poolData[poolId].depositRatesRAY[asset]; - - require(rate != 0, "BalancerVault: Deposit rate not set"); - - uint256 amountIn = (bptAmountOut * RAY) / rate; - - require(amountIn <= request.maxAmountsIn[tokenIndex], "BalancerVault: Asset deposit exceeds limit"); - - IERC20(address(request.assets[tokenIndex])).transferFrom(sender, address(this), amountIn); - - poolData[poolId].balances[tokenIndex] += amountIn; - - BPTMock(poolData[poolId].pool).mint(recipient, bptAmountOut); - } else if (kind == JoinKind.ALL_TOKENS_IN_FOR_EXACT_BPT_OUT) { - uint256 bptAmountOut = _allTokensInForExactBptOut(request.userData); - - uint256 nAssets = request.assets.length; - - for (uint256 i = 0; i < nAssets; ++i) { - address asset = address(request.assets[i]); - - uint256 rate = poolData[poolId].depositRatesRAY[asset]; - - require(rate != 0, "BalancerVault: Deposit rate not set"); - - uint256 amountIn = (bptAmountOut * RAY) / (rate * nAssets); - - require(amountIn <= request.maxAmountsIn[i], "BalancerVault: Asset deposit exceeds limit"); - - IERC20(address(request.assets[i])).transferFrom(sender, address(this), amountIn); - - poolData[poolId].balances[i] += amountIn; - } - - BPTMock(poolData[poolId].pool).mint(recipient, bptAmountOut); - } - } - - function _joinKind(bytes memory userData) internal pure returns (JoinKind) { - return abi.decode(userData, (JoinKind)); - } - - function _exactTokensInForBPTOut(bytes memory userData) - internal - pure - returns (uint256[] memory amountsIn, uint256 minBPTAmountOut) - { - (, amountsIn, minBPTAmountOut) = abi.decode(userData, (JoinKind, uint256[], uint256)); - } - - function _tokenInForExactBPTOut(bytes memory userData) - internal - pure - returns (uint256 bptAmountOut, uint256 tokenIndex) - { - (, bptAmountOut, tokenIndex) = abi.decode(userData, (JoinKind, uint256, uint256)); - } - - function _allTokensInForExactBptOut(bytes memory userData) internal pure returns (uint256 bptAmountOut) { - (, bptAmountOut) = abi.decode(userData, (JoinKind, uint256)); - } - - function exitPool(bytes32 poolId, address sender, address payable recipient, ExitPoolRequest memory request) - external - { - require(sender == recipient && sender == msg.sender, "BalancerVault: Unsupported sender or recipient"); - - require(poolData[poolId].pool != address(0), "BalancerVault: Unknown pool"); - - ExitKind kind = _exitKind(request.userData); - - if (kind == ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT) { - (uint256 bptAmountIn, uint256 tokenIndex) = _exactBptInForTokenOut(request.userData); - - address asset = address(request.assets[tokenIndex]); - - uint256 rate = poolData[poolId].withdrawalRatesRAY[asset]; - - require(rate != 0, "BalancerVault: Withdrawal rate not set"); - - uint256 amountOut = (bptAmountIn * rate) / RAY; - - require(amountOut >= request.minAmountsOut[tokenIndex], "BalancerVault: Insufficient asset amount out"); - - BPTMock(poolData[poolId].pool).burn(sender, bptAmountIn); - - IERC20(asset).transfer(recipient, amountOut); - - poolData[poolId].balances[tokenIndex] -= amountOut; - } else if (kind == ExitKind.EXACT_BPT_IN_FOR_TOKENS_OUT) { - uint256 bptAmountIn = _exactBptInForTokensOut(request.userData); - - uint256 nAssets = request.assets.length; - - for (uint256 i = 0; i < nAssets; ++i) { - address asset = address(request.assets[i]); - - uint256 rate = poolData[poolId].withdrawalRatesRAY[asset]; - - require(rate != 0, "BalancerVault: Withdrawal rate not set"); - - uint256 amountOut = (bptAmountIn * rate) / (nAssets * RAY); - - require(amountOut >= request.minAmountsOut[i], "BalancerVault: Insufficient asset amount out"); - - IERC20(asset).transfer(recipient, amountOut); - - poolData[poolId].balances[i] -= amountOut; - } - - BPTMock(poolData[poolId].pool).burn(sender, bptAmountIn); - } else if (kind == ExitKind.BPT_IN_FOR_EXACT_TOKENS_OUT) { - (uint256[] memory amountsOut, uint256 maxBPTAmountIn) = _bptInForExactTokensOut(request.userData); - - uint256 nAssets = request.assets.length; - - uint256 bptIn = 0; - - for (uint256 i = 0; i < nAssets; ++i) { - require(amountsOut[i] >= request.minAmountsOut[i], "BalancerVault: Insufficient asset amount out"); - - address asset = address(request.assets[i]); - - uint256 rate = poolData[poolId].withdrawalRatesRAY[asset]; - - require(rate != 0, "BalancerVault: Withdrawal rate not set"); - - bptIn += (amountsOut[i] * RAY) / rate; - - IERC20(asset).transfer(recipient, amountsOut[i]); - - poolData[poolId].balances[i] -= amountsOut[i]; - } - - require(bptIn <= maxBPTAmountIn, "BalancerVault: BPT amount burned larger than limit"); - - BPTMock(poolData[poolId].pool).burn(sender, bptIn); - } - } - - function _exitKind(bytes memory userData) internal pure returns (ExitKind) { - return abi.decode(userData, (ExitKind)); - } - - function _exactBptInForTokenOut(bytes memory userData) - internal - pure - returns (uint256 bptAmountIn, uint256 tokenIndex) - { - (, bptAmountIn, tokenIndex) = abi.decode(userData, (ExitKind, uint256, uint256)); - } - - function _exactBptInForTokensOut(bytes memory userData) internal pure returns (uint256 bptAmountIn) { - (, bptAmountIn) = abi.decode(userData, (ExitKind, uint256)); - } - - function _bptInForExactTokensOut(bytes memory userData) - internal - pure - returns (uint256[] memory amountsOut, uint256 maxBPTAmountIn) - { - (, amountsOut, maxBPTAmountIn) = abi.decode(userData, (ExitKind, uint256[], uint256)); - } - - function getPool(bytes32 poolId) external view returns (address, PoolSpecialization) { - require(poolData[poolId].pool != address(0), "BalancerVault: Unknown pool"); - - return (poolData[poolId].pool, poolData[poolId].specialization); - } - - function getPoolTokens(bytes32 poolId) - external - view - returns (IERC20[] memory tokens, uint256[] memory balances, uint256 lastChangeBlock) - { - require(poolData[poolId].pool != address(0), "BalancerVault: Unknown pool"); - - uint256 len = poolData[poolId].assets.length; - - tokens = new IERC20[](len); - balances = new uint256[](len); - lastChangeBlock = block.timestamp; - - for (uint256 i = 0; i < len; ++i) { - tokens[i] = IERC20(poolData[poolId].assets[i]); - balances[i] = poolData[poolId].balances[i]; - } - } - - function getPoolTokenInfo(bytes32, IERC20) - external - view - returns (uint256 cash, uint256 managed, uint256 lastChangeBlock, address assetManager) - { - return (0, 0, block.timestamp, address(this)); - } -} diff --git a/contracts/test/mocks/integrations/ConvexBaseRewardPoolMock.sol b/contracts/test/mocks/integrations/ConvexBaseRewardPoolMock.sol deleted file mode 100644 index 5775d553..00000000 --- a/contracts/test/mocks/integrations/ConvexBaseRewardPoolMock.sol +++ /dev/null @@ -1,238 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.10; - -import "../../../integrations/convex/Interfaces.sol"; -import "@openzeppelin/contracts/utils/math/SafeMath.sol"; -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; - -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {ERC20Mock} from "@gearbox-protocol/core-v3/contracts/test/mocks/token/ERC20Mock.sol"; -import {Test} from "forge-std/Test.sol"; - -contract BaseRewardPoolMock is Test { - using SafeMath for uint256; - using SafeERC20 for IERC20; - using SafeERC20 for ERC20Mock; - - ERC20Mock public rewardToken; - IERC20 public stakingToken; - uint256 public constant duration = 7 days; - - address public operator; - address public rewardManager; - - uint256 public pid; - uint256 public periodFinish = 0; - uint256 public rewardRate = 0; - uint256 public lastUpdateTime; - uint256 public rewardPerTokenStored; - uint256 public queuedRewards = 0; - uint256 public currentRewards = 0; - uint256 public historicalRewards = 0; - uint256 public constant newRewardRatio = 830; - uint256 private _totalSupply; - mapping(address => uint256) public userRewardPerTokenPaid; - mapping(address => uint256) public rewards; - mapping(address => uint256) private _balances; - - address[] public extraRewards; - - /// MOCK PARAMS - uint256 totalRewards = 0; - uint256 index = 0; - - constructor(uint256 pid_, address stakingToken_, address rewardToken_, address operator_) { - pid = pid_; - stakingToken = IERC20(stakingToken_); - rewardToken = ERC20Mock(rewardToken_); - operator = operator_; - } - - function totalSupply() public view returns (uint256) { - return _totalSupply; - } - - function balanceOf(address account) public view returns (uint256) { - return _balances[account]; - } - - function extraRewardsLength() external view returns (uint256) { - return extraRewards.length; - } - - function addExtraReward(address _reward) external returns (bool) { - require(_reward != address(0), "!reward setting"); - - extraRewards.push(_reward); - return true; - } - - function clearExtraRewards() external { - delete extraRewards; - } - - modifier updateReward(address account) { - if (account != address(0)) { - rewards[account] = earned(account); - userRewardPerTokenPaid[account] = rewardPerTokenStored; - } - _; - } - - function rewardPerToken() public view returns (uint256) { - return rewardPerTokenStored; - } - - function earned(address account) public view returns (uint256) { - return balanceOf(account).mul(rewardPerToken().sub(userRewardPerTokenPaid[account])).div(1e18).add( - rewards[account] - ); - } - - function lastTimeRewardApplicable() public view returns (uint256) { - return Math.min(block.timestamp, periodFinish); - } - - function stake(uint256 _amount) public updateReward(msg.sender) returns (bool) { - require(_amount > 0, "RewardPool : Cannot stake 0"); - - //also stake to linked rewards - for (uint256 i = 0; i < extraRewards.length; i++) { - IRewards(extraRewards[i]).stake(msg.sender, _amount); - } - - _totalSupply = _totalSupply.add(_amount); - _balances[msg.sender] = _balances[msg.sender].add(_amount); - - stakingToken.safeTransferFrom(msg.sender, address(this), _amount); - - index += 1; - - return true; - } - - function stakeAll() external returns (bool) { - uint256 balance = stakingToken.balanceOf(msg.sender); - stake(balance); - return true; - } - - function stakeFor(address _for, uint256 _amount) public updateReward(_for) returns (bool) { - require(_amount > 0, "RewardPool : Cannot stake 0"); - - //also stake to linked rewards - for (uint256 i = 0; i < extraRewards.length; i++) { - IRewards(extraRewards[i]).stake(_for, _amount); - } - - //give to _for - _totalSupply = _totalSupply.add(_amount); - _balances[_for] = _balances[_for].add(_amount); - - //take away from sender - stakingToken.safeTransferFrom(msg.sender, address(this), _amount); - - index += 1; - - return true; - } - - function withdraw(uint256 amount, bool claim) public updateReward(msg.sender) returns (bool) { - require(amount > 0, "RewardPool : Cannot withdraw 0"); - - //also withdraw from linked rewards - for (uint256 i = 0; i < extraRewards.length; i++) { - IRewards(extraRewards[i]).withdraw(msg.sender, amount); - } - - _totalSupply = _totalSupply.sub(amount); - _balances[msg.sender] = _balances[msg.sender].sub(amount); - - stakingToken.safeTransfer(msg.sender, amount); - - if (claim) { - getReward(msg.sender, true); - } - - index += 1; - return true; - } - - function withdrawAll(bool claim) external { - withdraw(_balances[msg.sender], claim); - } - - function withdrawAndUnwrap(uint256 amount, bool claim) public updateReward(msg.sender) returns (bool) { - //also withdraw from linked rewards - for (uint256 i = 0; i < extraRewards.length; i++) { - IRewards(extraRewards[i]).withdraw(msg.sender, amount); - } - - _totalSupply = _totalSupply.sub(amount); - _balances[msg.sender] = _balances[msg.sender].sub(amount); - - //tell operator to withdraw from here directly to user - IDeposit(operator).withdrawTo(pid, amount, msg.sender); - - //get rewards too - if (claim) { - getReward(msg.sender, true); - } - - index += 1; - return true; - } - - function withdrawAllAndUnwrap(bool claim) external { - withdrawAndUnwrap(_balances[msg.sender], claim); - } - - function getReward(address _account, bool _claimExtras) public updateReward(_account) returns (bool) { - uint256 reward = earned(_account); - if (reward > 0) { - rewards[_account] = 0; - rewardToken.safeTransfer(_account, reward); - IDeposit(operator).rewardClaimed(pid, _account, reward); - } - - //also get rewards from linked rewards - if (_claimExtras) { - for (uint256 i = 0; i < extraRewards.length; i++) { - IRewards(extraRewards[i]).getReward(_account); - } - } - - index += 1; - return true; - } - - function getReward() external returns (bool) { - getReward(msg.sender, true); - return true; - } - - function donate(uint256) external pure returns (bool) { - return true; - } - - function queueNewRewards(uint256) external pure returns (bool) { - return true; - } - - function notifyRewardAmount(uint256 reward) internal updateReward(address(0)) {} - - /// - /// MOCK FUNCTIONS - /// - - function addRewardAmount(uint256 amount) public { - vm.prank(rewardToken.minter()); - rewardToken.mint(address(this), amount); - - if (totalSupply() != 0) { - rewardPerTokenStored += (amount * 1e18) / totalSupply(); - } - } -} diff --git a/contracts/test/mocks/integrations/ConvexBoosterMock.sol b/contracts/test/mocks/integrations/ConvexBoosterMock.sol deleted file mode 100644 index 004cb8b5..00000000 --- a/contracts/test/mocks/integrations/ConvexBoosterMock.sol +++ /dev/null @@ -1,190 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.10; - -import "./ConvexBaseRewardPoolMock.sol"; - -contract BoosterMock is Test { - using SafeERC20 for IERC20; - using Address for address; - using SafeMath for uint256; - - address public crv; - address public constant registry = address(0x0000000022D53366457F9d5E68Ec105046FC4383); - uint256 public constant distributionAddressId = 4; - address public constant voteOwnership = address(0xE478de485ad2fe566d49342Cbd03E49ed7DB3356); - address public constant voteParameter = address(0xBCfF8B0b9419b9A88c44546519b1e909cF330399); - - uint256 public lockIncentive = 1000; //incentive to crv stakers - uint256 public stakerIncentive = 450; //incentive to native token stakers - uint256 public earmarkIncentive = 50; //incentive to users who spend gas to make calls - uint256 public platformFee = 0; //possible fee to build treasury - uint256 public constant MaxFees = 2000; - uint256 public constant FEE_DENOMINATOR = 10000; - - address public owner; - address public feeManager; - address public poolManager; - address public immutable staker; - address public immutable minter; - address public rewardFactory; - address public stashFactory; - address public tokenFactory; - address public rewardArbitrator; - address public voteDelegate; - address public treasury; - address public stakerRewards; //cvx rewards - address public lockRewards; //cvxCrv rewards(crv) - address public lockFees; //cvxCrv vecrv fees - address public feeDistro; - address public feeToken; - - bool public isShutdown; - - /// MOCK PARAMS - uint256 public index = 0; - - struct PoolInfo { - address lptoken; - address token; - address gauge; - address crvRewards; - address stash; - bool shutdown; - } - - //index(pid) -> pool - PoolInfo[] public poolInfo; - mapping(address => bool) public gaugeMap; - - constructor(address _crv, address _cvx) { - isShutdown = false; - staker = address(0); - owner = msg.sender; - voteDelegate = msg.sender; - feeManager = msg.sender; - poolManager = msg.sender; - feeDistro = address(0); //address(0xA464e6DCda8AC41e03616F95f4BC98a13b8922Dc); - feeToken = address(0); //address(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490); - treasury = address(0); - minter = _cvx; - crv = _crv; - } - - /// END SETTER SECTION /// - - function poolLength() external view returns (uint256) { - return poolInfo.length; - } - - //create a new pool - function addPool(address _lptoken) external returns (bool) { - require(_lptoken != address(0), "!param"); - - //the next pool's pid - uint256 pid = poolInfo.length; - - //create a tokenized deposit - address token = address(new ERC20Mock("ConvexToken", "CVXTOKEN", 18)); - //create a reward contract for crv rewards - address newRewardPool = address(new BaseRewardPoolMock(pid, token, crv, address(this))); - - //add the new pool - poolInfo.push( - PoolInfo({ - lptoken: _lptoken, - token: token, - gauge: address(1), - crvRewards: newRewardPool, - stash: address(2), - shutdown: false - }) - ); - - return true; - } - - //deposit lp tokens and stake - function deposit(uint256 _pid, uint256 _amount, bool _stake) public returns (bool) { - PoolInfo storage pool = poolInfo[_pid]; - - //send to proxy to stake - address lptoken = pool.lptoken; - IERC20(lptoken).safeTransferFrom(msg.sender, address(this), _amount); - - address token = pool.token; - if (_stake) { - //mint here and send to rewards on user behalf - ITokenMinter(token).mint(address(this), _amount); - address rewardContract = pool.crvRewards; - IERC20(token).safeApprove(rewardContract, 0); - IERC20(token).safeApprove(rewardContract, _amount); - IRewards(rewardContract).stakeFor(msg.sender, _amount); - } else { - //add user balance directly - ITokenMinter(token).mint(msg.sender, _amount); - } - - index += 1; - return true; - } - - //deposit all lp tokens and stake - function depositAll(uint256 _pid, bool _stake) external returns (bool) { - address lptoken = poolInfo[_pid].lptoken; - uint256 balance = IERC20(lptoken).balanceOf(msg.sender); - deposit(_pid, balance, _stake); - return true; - } - - //withdraw lp tokens - function _withdraw(uint256 _pid, uint256 _amount, address _from, address _to) internal { - PoolInfo storage pool = poolInfo[_pid]; - address lptoken = pool.lptoken; - - //remove lp balance - address token = pool.token; - ITokenMinter(token).burn(_from, _amount); - - //return lp tokens - IERC20(lptoken).safeTransfer(_to, _amount); - - index += 1; - } - - //withdraw lp tokens - function withdraw(uint256 _pid, uint256 _amount) public returns (bool) { - _withdraw(_pid, _amount, msg.sender, msg.sender); - return true; - } - - //withdraw all lp tokens - function withdrawAll(uint256 _pid) public returns (bool) { - address token = poolInfo[_pid].token; - uint256 userBal = IERC20(token).balanceOf(msg.sender); - withdraw(_pid, userBal); - return true; - } - - //allow reward contracts to send here and withdraw to user - function withdrawTo(uint256 _pid, uint256 _amount, address _to) external returns (bool) { - address rewardContract = poolInfo[_pid].crvRewards; - require(msg.sender == rewardContract, "!auth"); - - _withdraw(_pid, _amount, msg.sender, _to); - return true; - } - - //callback from reward contract when crv is received. - function rewardClaimed(uint256 _pid, address _address, uint256 _amount) external returns (bool) { - address rewardContract = poolInfo[_pid].crvRewards; - require(msg.sender == rewardContract, "!auth"); - - //mint reward tokens - vm.prank(ERC20Mock(minter).minter()); - ITokenMinter(minter).mint(_address, _amount); - - return true; - } -} diff --git a/contracts/test/mocks/integrations/ConvexExtraRewardPoolMock.sol b/contracts/test/mocks/integrations/ConvexExtraRewardPoolMock.sol deleted file mode 100644 index 9d52157d..00000000 --- a/contracts/test/mocks/integrations/ConvexExtraRewardPoolMock.sol +++ /dev/null @@ -1,125 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.10; - -import "../../../integrations/convex/Interfaces.sol"; -import "@openzeppelin/contracts/utils/math/SafeMath.sol"; - -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {ERC20Mock} from "@gearbox-protocol/core-v3/contracts/test/mocks/token/ERC20Mock.sol"; -import {Test} from "forge-std/Test.sol"; - -contract VirtualBalanceWrapper is Test { - using SafeMath for uint256; - using SafeERC20 for IERC20; - - IDeposit public deposits; - - function totalSupply() public view returns (uint256) { - return deposits.totalSupply(); - } - - function balanceOf(address account) public view returns (uint256) { - return deposits.balanceOf(account); - } -} - -contract ExtraRewardPoolMock is VirtualBalanceWrapper { - using SafeMath for uint256; - using SafeERC20 for IERC20; - using SafeERC20 for ERC20Mock; - - ERC20Mock public rewardToken; - uint256 public constant duration = 7 days; - - address public operator; - - uint256 public periodFinish = 0; - uint256 public rewardRate = 0; - uint256 public lastUpdateTime; - uint256 public rewardPerTokenStored; - uint256 public queuedRewards = 0; - uint256 public currentRewards = 0; - uint256 public historicalRewards = 0; - uint256 public constant newRewardRatio = 830; - mapping(address => uint256) public userRewardPerTokenPaid; - mapping(address => uint256) public rewards; - - /// MOCK PARAMS - uint256 totalRewards = 0; - uint256 index = 0; - - constructor(address deposit_, address reward_, address op_) { - deposits = IDeposit(deposit_); - rewardToken = ERC20Mock(reward_); - operator = op_; - } - - modifier updateReward(address account) { - if (account != address(0)) { - rewards[account] = earned(account); - userRewardPerTokenPaid[account] = rewardPerTokenStored; - } - _; - } - - function rewardPerToken() public view returns (uint256) { - return rewardPerTokenStored; - } - - function earned(address account) public view returns (uint256) { - return balanceOf(account).mul(rewardPerToken().sub(userRewardPerTokenPaid[account])).div(1e18).add( - rewards[account] - ); - } - - function stake(address _account, uint256) public updateReward(_account) returns (bool) { - index += 1; - return true; - } - - function withdraw(address _account, uint256) public updateReward(_account) returns (bool) { - index += 1; - return true; - } - - function getReward(address _account) public updateReward(_account) returns (bool) { - uint256 reward = earned(_account); - if (reward > 0) { - rewards[_account] = 0; - rewardToken.safeTransfer(_account, reward); - } - - index += 1; - return true; - } - - function getReward() external returns (bool) { - getReward(msg.sender); - return true; - } - - function donate(uint256) external pure returns (bool) { - return true; - } - - function queueNewRewards(uint256) external pure returns (bool) { - return true; - } - - function notifyRewardAmount(uint256 reward) internal updateReward(address(0)) {} - - /// - /// MOCK FUNCTIONS - /// - - function addRewardAmount(uint256 amount) public { - vm.prank(rewardToken.minter()); - rewardToken.mint(address(this), amount); - - if (totalSupply() != 0) { - rewardPerTokenStored += (amount * 1e18) / totalSupply(); - } - } -} diff --git a/contracts/test/mocks/integrations/ConvexTokenRewardContractMock.sol b/contracts/test/mocks/integrations/ConvexTokenRewardContractMock.sol deleted file mode 100644 index 50bdda11..00000000 --- a/contracts/test/mocks/integrations/ConvexTokenRewardContractMock.sol +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.10; - -import "../../../integrations/convex/Interfaces.sol"; -import "@openzeppelin/contracts/utils/math/SafeMath.sol"; - -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {ERC20Mock} from "@gearbox-protocol/core-v3/contracts/test/mocks/token/ERC20Mock.sol"; - -import {VirtualBalanceWrapper} from "./ConvexExtraRewardPoolMock.sol"; - -contract TokenRewardContractMock is VirtualBalanceWrapper { - using SafeMath for uint256; - using SafeERC20 for IERC20; - using SafeERC20 for ERC20Mock; - - uint256 public constant duration = 7 days; - - address public operator; - - /// MOCK PARAMS - mapping(address => uint256) totalRewards; - - constructor(address deposit_, address op_) { - deposits = IDeposit(deposit_); - operator = op_; - } - - function stake(address, uint256) public pure returns (bool) { - return true; - } - - function withdraw(address, uint256) public pure returns (bool) { - return true; - } - - function getReward(address _account, address _token) public returns (bool) { - uint256 reward = totalRewards[_token]; - if (reward > 0) { - totalRewards[_token] = 0; - ERC20Mock(_token).safeTransfer(_account, reward); - } - return true; - } - - function getReward(address _token) external returns (bool) { - getReward(msg.sender, _token); - return true; - } - - /// - /// MOCK FUNCTIONS - /// - - function addRewardAmount(address token, uint256 amount) public { - vm.prank(ERC20Mock(token).minter()); - ERC20Mock(token).mint(address(this), amount); - - totalRewards[token] += amount; - } -} diff --git a/contracts/test/mocks/integrations/CurveV1MetapoolMock.sol b/contracts/test/mocks/integrations/CurveV1MetapoolMock.sol deleted file mode 100644 index 4a6cb399..00000000 --- a/contracts/test/mocks/integrations/CurveV1MetapoolMock.sol +++ /dev/null @@ -1,358 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.10; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {WAD, RAY} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol"; - -import {ICurvePool} from "../../../integrations/curve/ICurvePool.sol"; -import {N_COINS, ICurvePool2Assets} from "../../../integrations/curve/ICurvePool_2.sol"; -import {ICRVToken} from "../../../integrations/curve/ICRVToken.sol"; - -import {ERC20Mock} from "@gearbox-protocol/core-v3/contracts/test/mocks/token/ERC20Mock.sol"; - -// EXCEPTIONS - -contract CurveV1MetapoolMock is ICurvePool, ICurvePool2Assets { - using SafeERC20 for IERC20; - - address public override token; - address public lp_token; - - address[] internal _coins; - address public basePool; - mapping(int128 => mapping(int128 => uint256)) rates_RAY; - mapping(int128 => mapping(int128 => uint256)) rates_RAY_underlying; - mapping(int128 => uint256) public withdraw_rates_RAY; - mapping(int128 => uint256) public deposit_rates_RAY; - - uint256 public virtualPrice; - - bool real_liquidity_mode; - - bool isCryptoPool; - - constructor(address _token0, address _basePool) { - _coins.push(_token0); - _coins.push(ICurvePool(_basePool).token()); - - address _token = address(new ERC20Mock("CRVMock", "CRV for CurvePoolMock", 18)); - token = _token; - lp_token = _token; - virtualPrice = WAD; - basePool = _basePool; - } - - function setIsCryptoPool(bool _isCryptoPool) external { - isCryptoPool = _isCryptoPool; - } - - function setRealLiquidityMode(bool val) external { - real_liquidity_mode = val; - } - - function setRate(int128 i, int128 j, uint256 rate_RAY) external { - rates_RAY[i][j] = rate_RAY; - rates_RAY[j][i] = (RAY * RAY) / rate_RAY; - } - - function setRateUnderlying(int128 i, int128 j, uint256 rate_RAY) external { - rates_RAY_underlying[i][j] = rate_RAY; - rates_RAY_underlying[j][i] = (RAY * RAY) / rate_RAY; - } - - function setWithdrawRate(int128 i, uint256 rate_RAY) external { - withdraw_rates_RAY[i] = rate_RAY; - } - - function setDepositRate(int128 i, uint256 rate_RAY) external { - deposit_rates_RAY[i] = rate_RAY; - } - - function add_liquidity(uint256[N_COINS] memory amounts, uint256 min_mint_amount) external { - for (uint256 i = 0; i < N_COINS; i++) { - if (amounts[i] > 0) { - IERC20(_coins[i]).transferFrom(msg.sender, address(this), amounts[i]); - } - } - - if (real_liquidity_mode) { - uint256 mintAmount = calc_token_amount(amounts, true); - require(mintAmount >= min_mint_amount); - ICRVToken(token).mint(msg.sender, mintAmount); - } else { - ICRVToken(token).mint(msg.sender, min_mint_amount); - } - } - - function remove_liquidity(uint256 _amount, uint256[N_COINS] memory min_amounts) external { - for (uint256 i = 0; i < N_COINS; i++) { - if (real_liquidity_mode) { - uint256 amountOut = ((_amount * withdraw_rates_RAY[int128(uint128(i))]) / N_COINS) / RAY; - require(amountOut > min_amounts[i]); - IERC20(_coins[i]).transfer(msg.sender, amountOut); - } else if (min_amounts[i] > 0) { - IERC20(_coins[i]).transfer(msg.sender, min_amounts[i]); - } - } - - ICRVToken(token).burnFrom(msg.sender, _amount); - } - - function remove_liquidity_imbalance(uint256[N_COINS] memory amounts, uint256 max_burn_amount) external { - for (uint256 i = 0; i < N_COINS; i++) { - if (amounts[i] > 0) { - IERC20(_coins[i]).transfer(msg.sender, amounts[i]); - } - } - - if (real_liquidity_mode) { - uint256 burnAmount = calc_token_amount(amounts, false); - require(burnAmount <= max_burn_amount); - ICRVToken(token).burnFrom(msg.sender, burnAmount); - } else { - ICRVToken(token).burnFrom(msg.sender, max_burn_amount); - } - } - - function calc_token_amount(uint256[N_COINS] memory amounts, bool deposit) public view returns (uint256 amount) { - for (uint256 i = 0; i < N_COINS; ++i) { - if (deposit) { - amount += (amounts[i] * deposit_rates_RAY[int128(int256(i))]) / RAY; - } else { - amount += (amounts[i] * RAY) / withdraw_rates_RAY[int128(int256(i))]; - } - } - } - - function get_twap_balances(uint256[N_COINS] calldata _first_balances, uint256[N_COINS] calldata, uint256) - external - pure - returns (uint256[N_COINS] memory) - { - return _first_balances; - } - - function get_balances() external pure returns (uint256[N_COINS] memory) { - return [uint256(0), uint256(0)]; - } - - function get_previous_balances() external pure returns (uint256[N_COINS] memory) { - return [uint256(0), uint256(0)]; - } - - function get_price_cumulative_last() external pure returns (uint256[N_COINS] memory) { - return [uint256(0), uint256(0)]; - } - - function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external override { - exchange(int128(int256(i)), int128(int256(j)), dx, min_dy); - } - - function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) public override { - uint256 dy = get_dy(i, j, dx); - - require(dy >= min_dy, "CurveV1Mock: INSUFFICIENT_OUTPUT_AMOUNT"); - - IERC20(_coins[uint256(uint128(i))]).safeTransferFrom(msg.sender, address(this), dx); - IERC20(_coins[uint256(uint128(j))]).safeTransfer(msg.sender, dy); - } - - function exchange_underlying(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external override { - exchange_underlying(int128(int256(i)), int128(int256(j)), dx, min_dy); - } - - function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 min_dy) public override { - if (i == 0) { - IERC20(_coins[0]).transferFrom(msg.sender, address(this), dx); - } else { - address tokenIn = underlying_coins(uint256(uint128(i))); - IERC20(tokenIn).transferFrom(msg.sender, address(this), dx); - } - - int128 base_i = i > 0 ? int128(uint128(1)) : int128(uint128(0)); - int128 base_j = j > 0 ? int128(uint128(1)) : int128(uint128(0)); - - uint256 dy = get_dy(base_i, base_j, dx); - uint256 dy_underlying = get_dy_underlying(i, j, dx); - require(dy_underlying >= min_dy, "CurveV1Mock: INSUFFICIENT_OUTPUT_AMOUNT"); - - if (j == 0) { - IERC20(_coins[0]).transfer(msg.sender, dy_underlying); - } else { - address tokenOut = underlying_coins(uint256(uint128(j))); - ICurvePool(basePool).remove_liquidity_one_coin(dy, j - 1, dy_underlying); - IERC20(tokenOut).transfer(msg.sender, dy_underlying); - } - } - - function mintLP(address to, uint256 amount) external { - ICRVToken(token).mint(to, amount); - } - - function remove_liquidity_one_coin(uint256 _token_amount, uint256 i, uint256 min_amount) external override { - remove_liquidity_one_coin(_token_amount, int128(int256(i)), min_amount); - } - - function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount) public override { - ICRVToken(token).burnFrom(msg.sender, _token_amount); - - uint256 amountOut; - - if (real_liquidity_mode) { - amountOut = calc_withdraw_one_coin(_token_amount, i); - } else { - amountOut = min_amount; - } - - IERC20(_coins[uint256(uint128(i))]).safeTransfer(msg.sender, amountOut); - } - - function get_dy_underlying(uint256 i, uint256 j, uint256 dx) public view override returns (uint256) { - return get_dy_underlying(uint256(int256(i)), uint256(int256(j)), dx); - } - - function get_dy_underlying(int128 i, int128 j, uint256 dx) public view override returns (uint256) { - return (rates_RAY_underlying[i][j] * dx) / RAY; - } - - function get_dy(uint256 i, uint256 j, uint256 dx) public view override returns (uint256) { - return get_dy(uint256(int256(i)), uint256(int256(j)), dx); - } - - function get_dy(int128 i, int128 j, uint256 dx) public view override returns (uint256) { - return (rates_RAY[i][j] * dx) / RAY; - } - - function balances(uint256 i) public view returns (uint256) { - return IERC20(_coins[i]).balanceOf(address(this)); - } - - function get_virtual_price() external view override returns (uint256) { - return virtualPrice; - } - - function set_virtual_price(uint256 _price) external { - virtualPrice = _price; - } - - function virtual_price() external view override returns (uint256) { - return virtualPrice; - } - - function balances(int128 i) external view returns (uint256) { - return balances(uint256(uint128(i))); - } - - function coins(uint256 i) public view returns (address) { - return _coins[i]; - } - - function coins(int128 i) public view returns (address) { - return _coins[uint256(uint128(i))]; - } - - function underlying_coins(uint256 i) public view returns (address) { - if (i == 0) { - return _coins[0]; - } else { - return ICurvePool(basePool).coins(i - 1); - } - } - - function underlying_coins(int128 i) public view returns (address) { - if (i == 0) { - return _coins[0]; - } else { - return ICurvePool(basePool).coins(uint256(uint128(i)) - 1); - } - } - - function A() external pure returns (uint256) { - return 0; - } - - function A_precise() external pure returns (uint256) { - return 0; - } - - function calc_withdraw_one_coin(uint256 _burn_amount, uint256 i) external view returns (uint256) { - return calc_withdraw_one_coin(_burn_amount, int128(int256(i))); - } - - function calc_withdraw_one_coin(uint256 amount, int128 coin) public view returns (uint256) { - return (amount * withdraw_rates_RAY[coin]) / RAY; - } - - function admin_balances(uint256) external pure returns (uint256) { - return 0; - } - - function admin() external pure returns (address) { - return address(0); - } - - function fee() external pure returns (uint256) { - return 0; - } - - function mid_fee() external view returns (uint256) { - if (isCryptoPool) { - return 0; - } - - revert("Not a crypto pool"); - } - - function admin_fee() external pure returns (uint256) { - return 0; - } - - function block_timestamp_last() external pure returns (uint256) { - return 0; - } - - function initial_A() external pure returns (uint256) { - return 0; - } - - function future_A() external pure returns (uint256) { - return 0; - } - - function initial_A_time() external pure returns (uint256) { - return 0; - } - - function future_A_time() external pure returns (uint256) { - return 0; - } - - // Some pools implement ERC20 - - function name() external pure returns (string memory) { - return ""; - } - - function symbol() external pure returns (string memory) { - return ""; - } - - function decimals() external pure returns (uint256) { - return 0; - } - - function balanceOf(address) external pure returns (uint256) { - return 0; - } - - function allowance(address, address) external pure returns (uint256) { - return 0; - } - - function totalSupply() external pure returns (uint256) { - return 0; - } -} diff --git a/contracts/test/mocks/integrations/CurveV1Mock.sol b/contracts/test/mocks/integrations/CurveV1Mock.sol deleted file mode 100644 index c05fba05..00000000 --- a/contracts/test/mocks/integrations/CurveV1Mock.sol +++ /dev/null @@ -1,269 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.10; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {WAD, RAY} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol"; - -import {ICurvePool} from "../../../integrations/curve/ICurvePool.sol"; -import {ICRVToken} from "../../../integrations/curve/ICRVToken.sol"; - -import {ERC20Mock} from "@gearbox-protocol/core-v3/contracts/test/mocks/token/ERC20Mock.sol"; -import {cERC20Mock} from "../token/cERC20Mock.sol"; - -// EXCEPTIONS - -contract CurveV1Mock is ICurvePool { - using SafeERC20 for IERC20; - - address public override token; - address public lp_token; - - address[] internal _coins; - address[] internal _underlying_coins; - mapping(int128 => mapping(int128 => uint256)) rates_RAY; - mapping(int128 => mapping(int128 => uint256)) rates_RAY_underlying; - - mapping(int128 => uint256) public withdraw_rates_RAY; - mapping(int128 => uint256) public deposit_rates_RAY; - - uint256 public virtualPrice; - - bool public real_liquidity_mode; - - bool isCryptoPool; - - constructor(address[] memory coins_, address[] memory underlying_coins_) { - _coins = coins_; - _underlying_coins = underlying_coins_; - - address _token = address(new ERC20Mock("CRVMock", "CRV for CurvePoolMock", 18)); - token = _token; - lp_token = _token; - virtualPrice = WAD; - } - - function setIsCryptoPool(bool _isCryptoPool) external { - isCryptoPool = _isCryptoPool; - } - - function setRealLiquidityMode(bool val) external { - real_liquidity_mode = val; - } - - function setRate(int128 i, int128 j, uint256 rate_RAY) external { - rates_RAY[i][j] = rate_RAY; - rates_RAY[j][i] = (RAY * RAY) / rate_RAY; - } - - function setWithdrawRate(int128 i, uint256 rate_RAY) external { - withdraw_rates_RAY[i] = rate_RAY; - } - - function setDepositRate(int128 i, uint256 rate_RAY) external { - deposit_rates_RAY[i] = rate_RAY; - } - - function setRateUnderlying(int128 i, int128 j, uint256 rate_RAY) external { - rates_RAY_underlying[i][j] = rate_RAY; - rates_RAY_underlying[j][i] = (RAY * RAY) / rate_RAY; - } - - function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external override { - exchange(int128(int256(i)), int128(int256(j)), dx, min_dy); - } - - function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) public override { - uint256 dy = get_dy(i, j, dx); - - require(dy >= min_dy, "CurveV1Mock: INSUFFICIENT_OUTPUT_AMOUNT"); - - IERC20(_coins[uint256(uint128(i))]).safeTransferFrom(msg.sender, address(this), dx); - IERC20(_coins[uint256(uint128(j))]).safeTransfer(msg.sender, dy); - } - - function exchange_underlying(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external override { - exchange_underlying(int128(int256(i)), int128(int256(j)), dx, min_dy); - } - - function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 min_dy) public override { - address coinIn = _coins[uint256(uint128(i))]; - address underlyingIn = _underlying_coins[uint256(uint128(i))]; - - address coinOut = _coins[uint256(uint128(j))]; - address underlyingOut = _underlying_coins[uint256(uint128(j))]; - - uint256 dy = get_dy_underlying(i, j, dx); - - require(dy >= min_dy, "CurveV1Mock: INSUFFICIENT_OUTPUT_AMOUNT"); - - IERC20(underlyingIn).safeTransferFrom(msg.sender, address(this), dx); - IERC20(underlyingIn).approve(coinIn, dx); - cERC20Mock(coinIn).mint(address(this), dx); - - cERC20Mock(coinOut).redeem(address(this), dy); - IERC20(underlyingOut).safeTransfer(msg.sender, dy); - } - - function remove_liquidity_one_coin(uint256 _token_amount, uint256 i, uint256 min_amount) external override { - remove_liquidity_one_coin(_token_amount, int128(int256(i)), min_amount); - } - - function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount) public override { - ICRVToken(token).burnFrom(msg.sender, _token_amount); - - uint256 amountOut; - - if (real_liquidity_mode) { - amountOut = calc_withdraw_one_coin(_token_amount, i); - } else { - amountOut = min_amount; - } - - IERC20(_coins[uint256(uint128(i))]).safeTransfer(msg.sender, amountOut); - } - - function get_dy_underlying(int128 i, int128 j, uint256 dx) public view override returns (uint256) { - return (rates_RAY_underlying[i][j] * dx) / RAY; - } - - function get_dy_underlying(uint256 i, uint256 j, uint256 dx) public view override returns (uint256) { - return get_dy_underlying(uint256(int256(i)), uint256(int256(j)), dx); - } - - function get_dy(int128 i, int128 j, uint256 dx) public view override returns (uint256) { - return (rates_RAY[i][j] * dx) / RAY; - } - - function get_dy(uint256 i, uint256 j, uint256 dx) public view override returns (uint256) { - return get_dy(uint256(int256(i)), uint256(int256(j)), dx); - } - - function balances(uint256 i) external view override returns (uint256) { - return IERC20(_coins[i]).balanceOf(address(this)); - } - - function balances(int128 i) external view override returns (uint256) { - return IERC20(_coins[uint256(int256(i))]).balanceOf(address(this)); - } - - function coins(uint256 i) external view override returns (address) { - return _coins[i]; - } - - function coins(int128 i) external view override returns (address) { - return _coins[uint256(int256(i))]; - } - - function underlying_coins(uint256 i) external view override returns (address) { - return _underlying_coins[i]; - } - - function underlying_coins(int128 i) external view override returns (address) { - return _underlying_coins[uint256(int256(i))]; - } - - function get_virtual_price() external view override returns (uint256) { - return virtualPrice; - } - - function set_virtual_price(uint256 _price) external { - virtualPrice = _price; - } - - function virtual_price() external view override returns (uint256) { - return virtualPrice; - } - - function mintLP(address to, uint256 amount) external { - ICRVToken(token).mint(to, amount); - } - - function A() external pure returns (uint256) { - return 0; - } - - function A_precise() external pure returns (uint256) { - return 0; - } - - function calc_withdraw_one_coin(uint256 amount, int128 coin) public view returns (uint256) { - return (amount * withdraw_rates_RAY[coin]) / RAY; - } - - function calc_withdraw_one_coin(uint256 _burn_amount, uint256 i) external view returns (uint256) { - return calc_withdraw_one_coin(_burn_amount, int128(int256(i))); - } - - function admin_balances(uint256) external pure returns (uint256) { - return 0; - } - - function admin() external pure returns (address) { - return address(0); - } - - function fee() external pure returns (uint256) { - return 0; - } - - function admin_fee() external pure returns (uint256) { - return 0; - } - - function block_timestamp_last() external pure returns (uint256) { - return 0; - } - - function initial_A() external pure returns (uint256) { - return 0; - } - - function future_A() external pure returns (uint256) { - return 0; - } - - function initial_A_time() external pure returns (uint256) { - return 0; - } - - function future_A_time() external pure returns (uint256) { - return 0; - } - - function mid_fee() external view returns (uint256) { - if (isCryptoPool) { - return 0; - } - - revert("Not a crypto pool"); - } - - // Some pools implement ERC20 - - function name() external pure returns (string memory) { - return ""; - } - - function symbol() external pure returns (string memory) { - return ""; - } - - function decimals() external pure returns (uint256) { - return 0; - } - - function balanceOf(address) external pure returns (uint256) { - return 0; - } - - function allowance(address, address) external pure returns (uint256) { - return 0; - } - - function totalSupply() external pure returns (uint256) { - return 0; - } -} diff --git a/contracts/test/mocks/integrations/CurveV1Mock_2Assets.sol b/contracts/test/mocks/integrations/CurveV1Mock_2Assets.sol deleted file mode 100644 index ccb9467b..00000000 --- a/contracts/test/mocks/integrations/CurveV1Mock_2Assets.sol +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.10; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {ICRVToken} from "../../../integrations/curve/ICRVToken.sol"; -import {RAY} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol"; - -import {CurveV1Mock} from "./CurveV1Mock.sol"; -import {N_COINS, ICurvePool2Assets} from "../../../integrations/curve/ICurvePool_2.sol"; - -contract CurveV1Mock_2Assets is CurveV1Mock, ICurvePool2Assets { - using SafeERC20 for IERC20; - - constructor(address[] memory _coins, address[] memory _underlying_coins) CurveV1Mock(_coins, _underlying_coins) {} - - function add_liquidity(uint256[N_COINS] memory amounts, uint256 min_mint_amount) external { - for (uint256 i = 0; i < N_COINS; i++) { - if (amounts[i] > 0) { - IERC20(_coins[i]).transferFrom(msg.sender, address(this), amounts[i]); - } - } - - if (real_liquidity_mode) { - uint256 mintAmount = calc_token_amount(amounts, true); - require(mintAmount >= min_mint_amount); - ICRVToken(token).mint(msg.sender, mintAmount); - } else { - ICRVToken(token).mint(msg.sender, min_mint_amount); - } - } - - function remove_liquidity(uint256 _amount, uint256[N_COINS] memory min_amounts) external { - for (uint256 i = 0; i < N_COINS; i++) { - if (real_liquidity_mode) { - uint256 amountOut = ((_amount * withdraw_rates_RAY[int128(uint128(i))]) / N_COINS) / RAY; - require(amountOut > min_amounts[i]); - IERC20(_coins[i]).transfer(msg.sender, amountOut); - } else if (min_amounts[i] > 0) { - IERC20(_coins[i]).transfer(msg.sender, min_amounts[i]); - } - } - - ICRVToken(token).burnFrom(msg.sender, _amount); - } - - function remove_liquidity_imbalance(uint256[N_COINS] memory amounts, uint256 max_burn_amount) external { - for (uint256 i = 0; i < N_COINS; i++) { - if (amounts[i] > 0) { - IERC20(_coins[i]).transfer(msg.sender, amounts[i]); - } - } - - if (real_liquidity_mode) { - uint256 burnAmount = calc_token_amount(amounts, false); - require(burnAmount <= max_burn_amount); - ICRVToken(token).burnFrom(msg.sender, burnAmount); - } else { - ICRVToken(token).burnFrom(msg.sender, max_burn_amount); - } - } - - function calc_token_amount(uint256[N_COINS] memory amounts, bool deposit) public view returns (uint256 amount) { - for (uint256 i = 0; i < N_COINS; ++i) { - if (deposit) { - amount += (amounts[i] * deposit_rates_RAY[int128(int256(i))]) / RAY; - } else { - amount += (amounts[i] * RAY) / withdraw_rates_RAY[int128(int256(i))]; - } - } - } - - function get_twap_balances(uint256[N_COINS] calldata _first_balances, uint256[N_COINS] calldata, uint256) - external - pure - returns (uint256[N_COINS] memory) - { - return _first_balances; - } - - function get_balances() external pure returns (uint256[N_COINS] memory) { - return [uint256(0), uint256(0)]; - } - - function get_previous_balances() external pure returns (uint256[N_COINS] memory) { - return [uint256(0), uint256(0)]; - } - - function get_price_cumulative_last() external pure returns (uint256[N_COINS] memory) { - return [uint256(0), uint256(0)]; - } -} diff --git a/contracts/test/mocks/integrations/CurveV1Mock_3Assets.sol b/contracts/test/mocks/integrations/CurveV1Mock_3Assets.sol deleted file mode 100644 index 8561d149..00000000 --- a/contracts/test/mocks/integrations/CurveV1Mock_3Assets.sol +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.10; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {ICRVToken} from "../../../integrations/curve/ICRVToken.sol"; -import {RAY} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol"; - -import {CurveV1Mock} from "./CurveV1Mock.sol"; -import {N_COINS, ICurvePool3Assets} from "../../../integrations/curve/ICurvePool_3.sol"; - -contract CurveV1Mock_3Assets is CurveV1Mock, ICurvePool3Assets { - using SafeERC20 for IERC20; - - constructor(address[] memory _coins, address[] memory _underlying_coins) CurveV1Mock(_coins, _underlying_coins) {} - - function add_liquidity(uint256[N_COINS] memory amounts, uint256 min_mint_amount) external { - for (uint256 i = 0; i < N_COINS; i++) { - if (amounts[i] > 0) { - IERC20(_coins[i]).transferFrom(msg.sender, address(this), amounts[i]); - } - } - - if (real_liquidity_mode) { - uint256 mintAmount = calc_token_amount(amounts, true); - require(mintAmount >= min_mint_amount); - ICRVToken(token).mint(msg.sender, mintAmount); - } else { - ICRVToken(token).mint(msg.sender, min_mint_amount); - } - } - - function remove_liquidity(uint256 _amount, uint256[N_COINS] memory min_amounts) external { - for (uint256 i = 0; i < N_COINS; i++) { - if (real_liquidity_mode) { - uint256 amountOut = ((_amount * withdraw_rates_RAY[int128(uint128(i))]) / N_COINS) / RAY; - require(amountOut > min_amounts[i]); - IERC20(_coins[i]).transfer(msg.sender, amountOut); - } else if (min_amounts[i] > 0) { - IERC20(_coins[i]).transfer(msg.sender, min_amounts[i]); - } - } - - ICRVToken(token).burnFrom(msg.sender, _amount); - } - - function remove_liquidity_imbalance(uint256[N_COINS] memory amounts, uint256 max_burn_amount) external { - for (uint256 i = 0; i < N_COINS; i++) { - if (amounts[i] > 0) { - IERC20(_coins[i]).transfer(msg.sender, amounts[i]); - } - } - - if (real_liquidity_mode) { - uint256 burnAmount = calc_token_amount(amounts, false); - require(burnAmount <= max_burn_amount); - ICRVToken(token).burnFrom(msg.sender, burnAmount); - } else { - ICRVToken(token).burnFrom(msg.sender, max_burn_amount); - } - } - - function calc_token_amount(uint256[N_COINS] memory amounts, bool deposit) public view returns (uint256 amount) { - for (uint256 i = 0; i < N_COINS; ++i) { - if (deposit) { - amount += (amounts[i] * deposit_rates_RAY[int128(int256(i))]) / RAY; - } else { - amount += (amounts[i] * RAY) / withdraw_rates_RAY[int128(int256(i))]; - } - } - } - - function get_twap_balances(uint256[N_COINS] calldata _first_balances, uint256[N_COINS] calldata, uint256) - external - pure - returns (uint256[N_COINS] memory) - { - return _first_balances; - } - - function get_balances() external pure returns (uint256[N_COINS] memory) { - return [uint256(0), uint256(0), uint256(0)]; - } - - function get_previous_balances() external pure returns (uint256[N_COINS] memory) { - return [uint256(0), uint256(0), uint256(0)]; - } - - function get_price_cumulative_last() external pure returns (uint256[N_COINS] memory) { - return [uint256(0), uint256(0), uint256(0)]; - } -} diff --git a/contracts/test/mocks/integrations/CurveV1Mock_4Assets.sol b/contracts/test/mocks/integrations/CurveV1Mock_4Assets.sol deleted file mode 100644 index 8d0eed9b..00000000 --- a/contracts/test/mocks/integrations/CurveV1Mock_4Assets.sol +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.10; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {ICRVToken} from "../../../integrations/curve/ICRVToken.sol"; -import {RAY} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol"; - -import {CurveV1Mock} from "./CurveV1Mock.sol"; -import {N_COINS, ICurvePool4Assets} from "../../../integrations/curve/ICurvePool_4.sol"; - -contract CurveV1Mock_4Assets is CurveV1Mock, ICurvePool4Assets { - using SafeERC20 for IERC20; - - constructor(address[] memory _coins, address[] memory _underlying_coins) CurveV1Mock(_coins, _underlying_coins) {} - - function add_liquidity(uint256[N_COINS] memory amounts, uint256 min_mint_amount) external { - for (uint256 i = 0; i < N_COINS; i++) { - if (amounts[i] > 0) { - IERC20(_coins[i]).transferFrom(msg.sender, address(this), amounts[i]); - } - } - - if (real_liquidity_mode) { - uint256 mintAmount = calc_token_amount(amounts, true); - require(mintAmount >= min_mint_amount); - ICRVToken(token).mint(msg.sender, mintAmount); - } else { - ICRVToken(token).mint(msg.sender, min_mint_amount); - } - } - - function remove_liquidity(uint256 _amount, uint256[N_COINS] memory min_amounts) external { - for (uint256 i = 0; i < N_COINS; i++) { - if (real_liquidity_mode) { - uint256 amountOut = ((_amount * withdraw_rates_RAY[int128(uint128(i))]) / N_COINS) / RAY; - require(amountOut > min_amounts[i]); - IERC20(_coins[i]).transfer(msg.sender, amountOut); - } else if (min_amounts[i] > 0) { - IERC20(_coins[i]).transfer(msg.sender, min_amounts[i]); - } - } - - ICRVToken(token).burnFrom(msg.sender, _amount); - } - - function remove_liquidity_imbalance(uint256[N_COINS] memory amounts, uint256 max_burn_amount) external { - for (uint256 i = 0; i < N_COINS; i++) { - if (amounts[i] > 0) { - IERC20(_coins[i]).transfer(msg.sender, amounts[i]); - } - } - - if (real_liquidity_mode) { - uint256 burnAmount = calc_token_amount(amounts, false); - require(burnAmount <= max_burn_amount); - ICRVToken(token).burnFrom(msg.sender, burnAmount); - } else { - ICRVToken(token).burnFrom(msg.sender, max_burn_amount); - } - } - - function calc_token_amount(uint256[N_COINS] memory amounts, bool deposit) public view returns (uint256 amount) { - for (uint256 i = 0; i < N_COINS; ++i) { - if (deposit) { - amount += (amounts[i] * deposit_rates_RAY[int128(int256(i))]) / RAY; - } else { - amount += (amounts[i] * RAY) / withdraw_rates_RAY[int128(int256(i))]; - } - } - } - - function get_twap_balances(uint256[N_COINS] calldata _first_balances, uint256[N_COINS] calldata, uint256) - external - pure - returns (uint256[N_COINS] memory) - { - return _first_balances; - } - - function get_balances() external pure returns (uint256[N_COINS] memory) { - return [uint256(0), uint256(0), uint256(0), uint256(0)]; - } - - function get_previous_balances() external pure returns (uint256[N_COINS] memory) { - return [uint256(0), uint256(0), uint256(0), uint256(0)]; - } - - function get_price_cumulative_last() external pure returns (uint256[N_COINS] memory) { - return [uint256(0), uint256(0), uint256(0), uint256(0)]; - } -} diff --git a/contracts/test/mocks/integrations/CurveV1StETHMock.sol b/contracts/test/mocks/integrations/CurveV1StETHMock.sol deleted file mode 100644 index b037508f..00000000 --- a/contracts/test/mocks/integrations/CurveV1StETHMock.sol +++ /dev/null @@ -1,229 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.10; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {WAD, RAY} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol"; -import {N_COINS, ICurvePoolStETH} from "../../../integrations/curve/ICurvePoolStETH.sol"; -import {ICRVToken} from "../../../integrations/curve/ICRVToken.sol"; - -import {ERC20Mock} from "@gearbox-protocol/core-v3/contracts/test/mocks/token/ERC20Mock.sol"; - -contract CurveV1StETHMock is ICurvePoolStETH { - error NotImplementedException(); - - using SafeERC20 for IERC20; - - address public override lp_token; - address public token; - - address[] public override coins; - mapping(int128 => mapping(int128 => uint256)) rates_RAY; - - mapping(int128 => uint256) public withdraw_rates_RAY; - mapping(int128 => uint256) public deposit_rates_RAY; - - uint256 public virtualPrice; - - bool real_liquidity_mode; - - constructor(address[] memory _coins) { - coins = _coins; - lp_token = address(new ERC20Mock("CRVMock", "CRV for CurvePoolMock", 18)); - token = lp_token; - virtualPrice = WAD; - } - - function setRate(int128 i, int128 j, uint256 rate_RAY) external { - rates_RAY[i][j] = rate_RAY; - rates_RAY[j][i] = (RAY * RAY) / rate_RAY; - } - - function setRealLiquidityMode(bool val) external { - real_liquidity_mode = val; - } - - function setWithdrawRate(int128 i, uint256 rate_RAY) external { - withdraw_rates_RAY[i] = rate_RAY; - } - - function setDepositRate(int128 i, uint256 rate_RAY) external { - deposit_rates_RAY[i] = rate_RAY; - } - - function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external payable override { - uint256 dy = get_dy(i, j, dx); - - require(dy >= min_dy, "CurveV1Mock: INSUFFICIENT_OUTPUT_AMOUNT"); - - if (i == 0) { - require(msg.value == dx); - IERC20(coins[uint256(uint128(j))]).safeTransfer(msg.sender, dy); - } else { - require(msg.value == 0); - IERC20(coins[uint256(uint128(i))]).safeTransferFrom(msg.sender, address(this), dx); - payable(msg.sender).transfer(dy); - } - } - - function calc_token_amount(uint256[N_COINS] memory amounts, bool deposit) public view returns (uint256 amount) { - for (uint256 i = 0; i < N_COINS; ++i) { - if (deposit) { - amount += (amounts[i] * deposit_rates_RAY[int128(int256(i))]) / RAY; - } else { - amount += (amounts[i] * RAY) / withdraw_rates_RAY[int128(int256(i))]; - } - } - } - - function add_liquidity(uint256[N_COINS] memory amounts, uint256 min_mint_amount) external payable { - require(msg.value == amounts[0]); - IERC20(coins[1]).transferFrom(msg.sender, address(this), amounts[1]); - - if (real_liquidity_mode) { - uint256 mintAmount = calc_token_amount(amounts, true); - require(mintAmount >= min_mint_amount); - ICRVToken(token).mint(msg.sender, mintAmount); - } else { - ICRVToken(token).mint(msg.sender, min_mint_amount); - } - } - - function remove_liquidity(uint256 _amount, uint256[N_COINS] memory min_amounts) external { - if (real_liquidity_mode) { - uint256 amountOut = ((_amount * withdraw_rates_RAY[int128(uint128(0))]) / N_COINS) / RAY; - require(amountOut > min_amounts[0]); - payable(msg.sender).transfer(amountOut); - - amountOut = ((_amount * withdraw_rates_RAY[int128(uint128(1))]) / N_COINS) / RAY; - require(amountOut > min_amounts[1]); - IERC20(coins[1]).transfer(msg.sender, amountOut); - } else { - payable(msg.sender).transfer(min_amounts[0]); - IERC20(coins[1]).transfer(msg.sender, min_amounts[1]); - } - - ICRVToken(lp_token).burnFrom(msg.sender, _amount); - } - - function exchange_underlying( - int128, //i, - int128, //j, - uint256, // dx, - uint256 // min_dy - ) external pure override { - revert NotImplementedException(); - } - - function get_dy_underlying( - int128, //i, - int128, //j, - uint256 //dx - ) external pure override returns (uint256) { - revert NotImplementedException(); - } - - function get_dy(int128 i, int128 j, uint256 dx) public view override returns (uint256) { - return (rates_RAY[i][j] * dx) / RAY; - } - - function get_virtual_price() external view override returns (uint256) { - return virtualPrice; - } - - function set_virtual_price(uint256 _price) external { - virtualPrice = _price; - } - - function virtual_price() external view override returns (uint256) { - return virtualPrice; - } - - function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount) external { - ICRVToken(lp_token).burnFrom(msg.sender, _token_amount); - if (real_liquidity_mode) { - if (i == 0) { - payable(msg.sender).transfer(calc_withdraw_one_coin(_token_amount, 0)); - } else { - IERC20(coins[1]).transfer(msg.sender, calc_withdraw_one_coin(_token_amount, 1)); - } - } else { - if (i == 0) { - payable(msg.sender).transfer(min_amount); - } else { - IERC20(coins[1]).transfer(msg.sender, min_amount); - } - } - } - - function remove_liquidity_imbalance(uint256[N_COINS] memory amounts, uint256 max_burn_amount) external { - payable(msg.sender).transfer(amounts[0]); - IERC20(coins[1]).transfer(msg.sender, amounts[1]); - - if (real_liquidity_mode) { - uint256 burnAmount = calc_token_amount(amounts, false); - require(burnAmount <= max_burn_amount); - ICRVToken(token).burnFrom(msg.sender, burnAmount); - } else { - ICRVToken(token).burnFrom(msg.sender, max_burn_amount); - } - } - - function balances(uint256 i) external view returns (uint256) { - if (i == 0) { - return address(this).balance; - } else { - return IERC20(coins[1]).balanceOf(address(this)); - } - } - - function A() external pure returns (uint256) { - return 0; - } - - function A_precise() external pure returns (uint256) { - return 0; - } - - function calc_withdraw_one_coin(uint256 amount, int128 coin) public view returns (uint256) { - return (amount * withdraw_rates_RAY[coin]) / RAY; - } - - function admin_balances(uint256) external pure returns (uint256) { - return 0; - } - - function admin() external pure returns (address) { - return address(0); - } - - function fee() external pure returns (uint256) { - return 0; - } - - function admin_fee() external pure returns (uint256) { - return 0; - } - - function block_timestamp_last() external pure returns (uint256) { - return 0; - } - - function initial_A() external pure returns (uint256) { - return 0; - } - - function future_A() external pure returns (uint256) { - return 0; - } - - function initial_A_time() external pure returns (uint256) { - return 0; - } - - function future_A_time() external pure returns (uint256) { - return 0; - } -} diff --git a/contracts/test/mocks/integrations/LidoMock.sol b/contracts/test/mocks/integrations/LidoMock.sol deleted file mode 100644 index 9933254d..00000000 --- a/contracts/test/mocks/integrations/LidoMock.sol +++ /dev/null @@ -1,61 +0,0 @@ -pragma solidity ^0.8.10; - -import "../token/StETHMock.sol"; - -interface ILidoMockEvents { - event Mock_Submitted(address indexed sender, uint256 amount, address referral); -} - -contract LidoMock is StETHMock, ILidoMockEvents { - using SafeMath for uint256; - - receive() external payable { - _submit(address(0)); - } - - function submit(address _referral) external payable returns (uint256) { - return _submit(_referral); - } - - function burnShares(address _account, uint256 _sharesAmount) external { - _burnShares(_account, _sharesAmount); - } - - /** - * @dev Process user deposit, mints liquid tokens and increase the pool buffer - * @param _referral address of referral. - * @return amount of StETH shares generated - */ - function _submit(address _referral) internal returns (uint256) { - address sender = msg.sender; - uint256 deposit = msg.value; - require(deposit != 0, "ZERO_DEPOSIT"); - - uint256 sharesAmount = getSharesByPooledEth(deposit); - if (sharesAmount == 0) { - // totalControlledEther is 0: either the first-ever deposit or complete slashing - // assume that shares correspond to Ether 1-to-1 - sharesAmount = deposit; - } - - totalPooledEtherSynced += deposit; - - _mintShares(sender, sharesAmount); - _submitted(sender, deposit, _referral); - _emitTransferAfterMintingShares(sender, sharesAmount); - return sharesAmount; - } - - function _emitTransferAfterMintingShares(address _to, uint256 _sharesAmount) internal { - emit Transfer(address(0), _to, getPooledEthByShares(_sharesAmount)); - } - - function _submitted(address _sender, uint256 _value, address _referral) internal { - emit Mock_Submitted(_sender, _value, _referral); - } - - function syncExchangeRate(uint256 totalPooledEther, uint256 totalShares) external { - totalPooledEtherSynced = totalPooledEther; - totalSharesSynced = totalShares; - } -} diff --git a/contracts/test/mocks/integrations/UniswapV2Mock.sol b/contracts/test/mocks/integrations/UniswapV2Mock.sol deleted file mode 100644 index 9299c561..00000000 --- a/contracts/test/mocks/integrations/UniswapV2Mock.sol +++ /dev/null @@ -1,303 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.10; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -import {IUniswapV2Router02} from "../../../integrations/uniswap/IUniswapV2Router02.sol"; -import {RAY} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol"; - -contract UniswapV2Mock is IUniswapV2Router02 { - using SafeERC20 for IERC20; - - uint256 public constant FEE_MULTIPLIER = 997; - - mapping(address => mapping(address => uint256)) private _rates_RAY; - - modifier ensure(uint256 deadline) { - require(deadline >= block.timestamp, "UniswapV2Router: EXPIRED"); - _; - } - - function setRate(address tokenFrom, address tokenTo, uint256 rate_RAY) external { - _rates_RAY[tokenFrom][tokenTo] = rate_RAY; - _rates_RAY[tokenTo][tokenFrom] = (RAY * RAY) / rate_RAY; - } - - function getRate(address tokenIn, address tokenOut) public view returns (uint256 rate) { - rate = _rates_RAY[tokenIn][tokenOut]; - if (rate == 0) { - revert("Token pair not found"); - } - } - - function swapExactTokensForTokens( - uint256 amountIn, - uint256 amountOutMin, - address[] calldata path, - address to, - uint256 deadline - ) external override ensure(deadline) returns (uint256[] memory) { - uint256[] memory amounts = getAmountsOut(amountIn, path); - - uint256 amountOut = amounts[path.length - 1]; - require(amountOut >= amountOutMin, "UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT"); - - // tokenIN - IERC20(path[0]).safeTransferFrom(msg.sender, address(this), amountIn); - - // tokenOUT - IERC20(path[path.length - 1]).safeTransfer(to, amountOut); - - return amounts; - } - - function swapTokensForExactTokens( - uint256 amountOut, - uint256 amountInMax, - address[] calldata path, - address to, - uint256 deadline - ) external override ensure(deadline) returns (uint256[] memory) { - // uint256 rate_RAY = getRate(path); - // require(rate_RAY != 0, "UniswapMock: Rate is not setup"); - // // transfers - uint256[] memory amounts = getAmountsIn(amountOut, path); - - uint256 amountIn = amounts[0]; - - require(amountIn <= amountInMax, "UniswapV2Router: EXCESSIVE_INPUT_AMOUNT"); - - // tokenIN - IERC20(path[0]).safeTransferFrom(msg.sender, address(this), amountIn); - - // tokenOUT - IERC20(path[path.length - 1]).safeTransfer(to, amountOut); - - return amounts; - } - - //// OTHER STUFF - //// ALL OTHER FUNCTION DO NOTHING - - function removeLiquidityETHSupportingFeeOnTransferTokens( - address, // token, - uint256, // liquidity, - uint256, // amountTokenMin, - uint256, // amountETHMin, - address, // to, - uint256 //deadline - ) external pure override returns (uint256 amountETH) {} - - function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens( - address, // token, - uint256, // liquidity, - uint256, // amountTokenMin, - uint256, // amountETHMin, - address, // to, - uint256, // deadline, - bool, // approveMax, - uint8, // v, - bytes32, // r, - bytes32 // s - ) external pure override returns (uint256 amountETH) {} - - function swapExactTokensForTokensSupportingFeeOnTransferTokens( - uint256, // amountIn, - uint256, // amountOutMin, - address[] calldata, // path, - address, // to, - uint256 // deadline - ) external pure override {} - - function swapExactETHForTokensSupportingFeeOnTransferTokens( - uint256, // amountOutMin, - address[] calldata, // path, - address, // to, - uint256 // deadline - ) external payable override {} - - function swapExactTokensForETHSupportingFeeOnTransferTokens( - uint256, // amountIn, - uint256, // amountOutMin, - address[] calldata, // path, - address, // to, - uint256 // deadline - ) external pure override {} - - function swapExactETHForTokens( - uint256, // amountOutMin, - address[] calldata, // path, - address, // to, - uint256 // deadline - ) external payable override returns (uint256[] memory amounts) {} - - function swapTokensForExactETH( - uint256, // amountOut, - uint256, // amountInMax, - address[] calldata, // path, - address, // to, - uint256 // deadline - ) external pure override returns (uint256[] memory amounts) {} - - function swapExactTokensForETH( - uint256, // amountIn, - uint256, // amountOutMin, - address[] calldata, // path, - address, // to, - uint256 // deadline - ) external pure override returns (uint256[] memory amounts) {} - - function swapETHForExactTokens( - uint256, // amountOut, - address[] calldata, // path, - address, // to, - uint256 // deadline - ) external payable override returns (uint256[] memory) { - uint256[] memory amounts = new uint256[](1); - return amounts; - } - - function quote( - uint256, // amountA, - uint256, // reserveA, - uint256 // reserveB - ) external pure override returns (uint256 amountB) { - return 0; - } - - function getAmountOut( - uint256, // amountIn, - uint256, // reserveIn, - uint256 // reserveOut - ) external pure override returns (uint256 amountOut) { - return 0; - } - - function getAmountIn( - uint256, // amountOut, - uint256, // reserveIn, - uint256 // reserveOut - ) external pure override returns (uint256 amountIn) { - return 0; - } - - function getAmountsOut(uint256 amountIn, address[] calldata path) - public - view - override - returns (uint256[] memory amounts) - { - require(path.length >= 2, "Incorrect path length"); - - amounts = new uint256[](path.length); - amounts[0] = amountIn; - - for (uint256 i = 1; i < path.length; i++) { - uint256 rate_RAY = getRate(path[i - 1], path[i]); - - require(rate_RAY != 0, "UniswapMock: Rate is not setup"); - // transfers - - amounts[i] = (((amounts[i - 1] * rate_RAY) / RAY) * FEE_MULTIPLIER) / 1_000; - } - } - - function getAmountsIn(uint256 amountOut, address[] calldata path) - public - view - override - returns (uint256[] memory amounts) - { - require(path.length >= 2, "Incorrect path length"); - - // transfers - - amounts = new uint256[](path.length); - amounts[path.length - 1] = amountOut; - - for (uint256 i = path.length - 1; i > 0; i--) { - uint256 rate_RAY = getRate(path[i - 1], path[i]); - require(rate_RAY != 0, "UniswapMock: Rate is not setup"); - amounts[i - 1] = (((amounts[i] * RAY) / rate_RAY) * 1_000) / FEE_MULTIPLIER; - } - } - - function factory() external pure override returns (address) {} - - function WETH() external pure override returns (address) {} - - function addLiquidity( - address, // tokenA, - address, // tokenB, - uint256, // amountADesired, - uint256, // amountBDesired, - uint256, // amountAMin, - uint256, // amountBMin, - address, // to, - uint256 // deadline - ) external pure override returns (uint256 amountA, uint256 amountB, uint256 liquidity) {} - - function addLiquidityETH( - address, // token, - uint256, // amountTokenDesired, - uint256, // amountTokenMin, - uint256, // amountETHMin, - address, // to, - uint256 // deadline - ) external payable override returns (uint256 amountToken, uint256 amountETH, uint256 liquidity) {} - - function removeLiquidity( - address, // tokenA, - address, // tokenB, - uint256, // liquidity, - uint256, // amountAMin, - uint256, // amountBMin, - address, // to, - uint256 // deadline - ) external pure override returns (uint256 amountA, uint256 amountB) {} - - function removeLiquidityETH( - address, // token, - uint256, // liquidity, - uint256, // amountTokenMin, - uint256, // amountETHMin, - address, // to, - uint256 // deadline - ) external pure override returns (uint256 amountToken, uint256 amountETH) { - return (0, 0); - } - - function removeLiquidityWithPermit( - address, // tokenA, - address, // tokenB, - uint256, // liquidity, - uint256, // amountAMin, - uint256, // amountBMin, - address, // to, - uint256, // deadline, - bool, // approveMax, - uint8, // v, - bytes32, // r, - bytes32 // s - ) external pure override returns (uint256 amountA, uint256 amountB) { - return (0, 0); - } - - function removeLiquidityETHWithPermit( - address, // token, - uint256, // liquidity, - uint256, // amountTokenMin, - uint256, // amountETHMin, - address, // to, - uint256, // deadline, - bool, // approveMax, - uint8, // v, - bytes32, // r, - bytes32 // s - ) external pure override returns (uint256 amountToken, uint256 amountETH) { - return (0, 0); - } -} diff --git a/contracts/test/mocks/integrations/UniswapV3Mock.sol b/contracts/test/mocks/integrations/UniswapV3Mock.sol deleted file mode 100644 index abf93c60..00000000 --- a/contracts/test/mocks/integrations/UniswapV3Mock.sol +++ /dev/null @@ -1,162 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.10; - -import {Path} from "../../lib/Path.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -import {ISwapRouter} from "../../../integrations/uniswap/IUniswapV3.sol"; -import {RAY} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol"; -import {Test} from "forge-std/Test.sol"; - -uint256 constant ADDR_SIZE = 20; -uint256 constant FEE_SIZE = 3; - -contract UniswapV3Mock is ISwapRouter, Test { - using SafeERC20 for IERC20; - using Path for bytes; - - uint256 private constant FEE_MULTIPLIER = 997; - - mapping(address => mapping(address => mapping(uint24 => uint256))) public rates; - - modifier ensure(uint256 deadline) { - require(deadline >= block.timestamp, "UniswapV2Router: EXPIRED"); - _; - } - - function setRate(address tokenFrom, address tokenTo, uint256 rate_RAY) external { - setRate(tokenFrom, tokenTo, 3000, rate_RAY); - } - - function setRate(address tokenFrom, address tokenTo, uint24 fee, uint256 rate_RAY) public { - rates[tokenFrom][tokenTo][fee] = rate_RAY; - rates[tokenTo][tokenFrom][fee] = (RAY * RAY) / rate_RAY; - } - - function exactInputSingle(ExactInputSingleParams calldata params) - external - payable - override - returns (uint256 amountOut) - { - amountOut = quoteExactInputSingle(params.tokenIn, params.tokenOut, params.fee, params.amountIn, 0); - - require(amountOut >= params.amountOutMinimum, "UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT"); - - // tokenIN - IERC20(params.tokenIn).safeTransferFrom(msg.sender, address(this), params.amountIn); - - // tokenOUT - IERC20(params.tokenOut).safeTransfer(params.recipient, amountOut); - } - - function exactOutputSingle(ExactOutputSingleParams calldata params) - external - payable - override - returns (uint256 amountIn) - { - amountIn = quoteExactOutputSingle(params.tokenIn, params.tokenOut, params.fee, params.amountOut, 0); - - require(amountIn <= params.amountInMaximum, "UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT"); - - // tokenIN - IERC20(params.tokenIn).safeTransferFrom(msg.sender, address(this), amountIn); - - // tokenOUT - IERC20(params.tokenOut).safeTransfer(params.recipient, params.amountOut); - } - - function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut) { - (address tokenIn, address tokenOut) = _extractTokens(params.path); - - amountOut = quoteExactInput(params.path, params.amountIn); - - require(amountOut >= params.amountOutMinimum, "UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT"); - - // tokenIN - IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), params.amountIn); - - // tokenOUT - IERC20(tokenOut).safeTransfer(params.recipient, amountOut); - } - - function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn) { - (address tokenOut, address tokenIn) = _extractTokens(params.path); - - amountIn = quoteExactOutput(params.path, params.amountOut); - - require(amountIn <= params.amountInMaximum, "UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT"); - - // tokenIN - IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn); - - // tokenOUT - IERC20(tokenOut).safeTransfer(params.recipient, params.amountOut); - } - - function quoteExactInput(bytes memory path, uint256 amountIn) public view returns (uint256 amountOut) { - amountOut = amountIn; - - while (path.length >= ADDR_SIZE * 2 + FEE_SIZE) { - (address tokenA, address tokenB, uint24 fee) = path.decodeFirstPool(); - - uint256 rate = rates[tokenA][tokenB][fee]; - - require(rate != 0, "UniswapV3Mock: Rate is not setup"); - amountOut = (((amountOut * rate) / RAY) * ((1_000_000 - uint256(fee)))) / (1_000_000); - - path = path.skipToken(); - } - } - - function quoteExactInputSingle(address tokenIn, address tokenOut, uint24 fee, uint256 amountIn, uint160) - public - view - returns (uint256 amountOut) - { - uint256 rate = rates[tokenIn][tokenOut][fee]; - - require(rate != 0, "UniswapV3Mock: Rate is not setup"); - amountOut = (((amountIn * rate) / RAY) * ((1_000_000 - uint256(fee)))) / (1_000_000); - } - - function quoteExactOutput(bytes memory path, uint256 amountOut) public view returns (uint256 amountIn) { - amountIn = amountOut; - - while (path.length >= ADDR_SIZE * 2 + FEE_SIZE) { - (address tokenA, address tokenB, uint24 fee) = path.decodeFirstPool(); - - uint256 rate = rates[tokenB][tokenA][fee]; - - require(rate != 0, "UniswapV3Mock: Rate is not setup"); - amountIn = (((amountIn * RAY) / rate) * (1_000_000)) / ((1_000_000 - uint256(fee))); - - path = path.skipToken(); - } - } - - function quoteExactOutputSingle(address tokenIn, address tokenOut, uint24 fee, uint256 amountOut, uint160) - public - view - returns (uint256 amountIn) - { - uint256 rate = rates[tokenIn][tokenOut][fee]; - - require(rate != 0, "UniswapV3Mock: Rate is not setup"); - amountIn = (((amountOut * RAY) / rate) * (1_000_000)) / ((1_000_000 - uint256(fee))); - } - - function _extractTokens(bytes memory path) internal pure returns (address tokenA, address tokenB) { - (tokenA,,) = path.decodeFirstPool(); - - while (path.hasMultiplePools()) { - path = path.skipToken(); - } - - (, tokenB,) = path.decodeFirstPool(); - } -} diff --git a/contracts/test/mocks/integrations/WstETHV1Mock.sol b/contracts/test/mocks/integrations/WstETHV1Mock.sol deleted file mode 100644 index 3aa057b2..00000000 --- a/contracts/test/mocks/integrations/WstETHV1Mock.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.10; - -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; - -import {WAD} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol"; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -import {IwstETH} from "../../../integrations/lido/IwstETH.sol"; - -contract WstETHV1Mock is IwstETH, ERC20, Ownable { - using SafeERC20 for IERC20; - - address public immutable stETH; - uint256 immutable decimalsMul; - uint256 public override stEthPerToken; - - constructor(address _token) ERC20(string("Wrapped stETH"), string("wstETH")) { - stETH = _token; - decimalsMul = 10 ** ERC20.decimals(); - stEthPerToken = decimalsMul; - } - - function setStEthPerToken(uint256 value) external { - stEthPerToken = value; - } - - function tokensPerStEth() external view returns (uint256) { - return getWstETHByStETH(WAD); - } - - function wrap(uint256 _stETHAmount) external returns (uint256 wstETHAmount) { - IERC20(stETH).safeTransferFrom(msg.sender, address(this), _stETHAmount); - wstETHAmount = getWstETHByStETH(_stETHAmount); - _mint(msg.sender, wstETHAmount); - } - - function unwrap(uint256 _wstETHAmount) external returns (uint256 stETHAmount) { - stETHAmount = getStETHByWstETH(_wstETHAmount); - _burn(msg.sender, _wstETHAmount); - IERC20(stETH).safeTransfer(msg.sender, stETHAmount); - } - - function getStETHByWstETH(uint256 _wstETHAmount) public view returns (uint256) { - return (_wstETHAmount * stEthPerToken) / decimalsMul; - } - - function getWstETHByStETH(uint256 _stETHAmount) public view returns (uint256) { - return (_stETHAmount * decimalsMul) / stEthPerToken; - } -} diff --git a/contracts/test/mocks/integrations/YearnV2Mock.sol b/contracts/test/mocks/integrations/YearnV2Mock.sol deleted file mode 100644 index 89910cfd..00000000 --- a/contracts/test/mocks/integrations/YearnV2Mock.sol +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.10; - -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -import {IYVault} from "../../../integrations/yearn/IYVault.sol"; - -contract YearnV2Mock is IYVault, ERC20, Ownable { - using SafeERC20 for IERC20; - - address public override token; - uint256 public override pricePerShare; - - uint256 immutable decimalsMul; - - constructor(address _token) - ERC20( - string(abi.encodePacked("yearn ", ERC20(_token).name())), - string(abi.encodePacked("yv", ERC20(_token).symbol())) - ) - { - token = _token; - decimalsMul = 10 ** ERC20.decimals(); - pricePerShare = decimalsMul; - } - - function deposit() public override returns (uint256) { - return deposit(IERC20(token).balanceOf(msg.sender)); - } - - function deposit(uint256 _amount) public override returns (uint256) { - return deposit(_amount, msg.sender); - } - - function deposit(uint256 _amount, address recipient) public override returns (uint256 shares) { - IERC20(token).safeTransferFrom(msg.sender, address(this), _amount); - shares = (_amount * decimalsMul) / pricePerShare; - _mint(recipient, shares); - } - - function withdraw() external override returns (uint256) { - return withdraw(balanceOf(msg.sender)); - } - - function withdraw(uint256 maxShares) public override returns (uint256) { - return withdraw(maxShares, msg.sender); - } - - function withdraw(uint256 maxShares, address recipient) public override returns (uint256) { - return withdraw(maxShares, recipient, 1); - } - - function withdraw( - uint256 maxShares, - address, // recipient, - uint256 maxLoss - ) public override returns (uint256 amount) { - _burn(msg.sender, maxShares); - amount = (maxShares * pricePerShare) / decimalsMul; - - // pretend that loss is 1 basis point - require(maxLoss <= 1, "Loss too big"); - - IERC20(token).safeTransfer(msg.sender, amount); - } - - function setPricePerShare(uint256 newPrice) public { - pricePerShare = newPrice; - } - - function name() public view override(IYVault, ERC20) returns (string memory) { - return ERC20.name(); - } - - function symbol() public view override(IYVault, ERC20) returns (string memory) { - return ERC20.symbol(); - } - - function decimals() public view override(IYVault, ERC20) returns (uint8) { - return ERC20.decimals(); - } - - function mintShares(address to, uint256 amount) public { - _mint(to, amount); - } -} diff --git a/contracts/test/mocks/integrations/balancer/VaultMock.sol b/contracts/test/mocks/integrations/balancer/VaultMock.sol new file mode 100644 index 00000000..7c532738 --- /dev/null +++ b/contracts/test/mocks/integrations/balancer/VaultMock.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {IAsset, PoolSpecialization} from "../../../../integrations/balancer/IBalancerV2Vault.sol"; + +contract VaultMock { + struct PoolData { + address bpt; + IAsset[] tokens; + } + + mapping(bytes32 => PoolData) internal _poolData; + + function getPool(bytes32 poolId) external view returns (address, PoolSpecialization) { + return (_poolData[poolId].bpt, PoolSpecialization.GENERAL); + } + + function getPoolTokens(bytes32 poolId) + external + view + returns (IAsset[] memory tokens, uint256[] memory balances, uint256 lastChangeBlock) + { + tokens = _poolData[poolId].tokens; + balances = new uint256[](0); + lastChangeBlock = 0; + } + + function setPoolData(bytes32 poolId, address bpt, IAsset[] memory tokens) external { + _poolData[poolId] = PoolData(bpt, tokens); + } +} diff --git a/contracts/test/mocks/integrations/convex/BaseRewardPoolMock.sol b/contracts/test/mocks/integrations/convex/BaseRewardPoolMock.sol new file mode 100644 index 00000000..ebb7c775 --- /dev/null +++ b/contracts/test/mocks/integrations/convex/BaseRewardPoolMock.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +contract BaseRewardPoolMock { + uint256 public pid; + address public operator; + address public stakingToken; + address public rewardToken; + + uint256 numExtraRewards; + address[2] public extraRewards; + + constructor(uint256 _pid, address _operator, address _stakingToken, address _rewardToken) { + pid = _pid; + operator = _operator; + stakingToken = _stakingToken; + rewardToken = _rewardToken; + } + + function setExtraReward(uint256 i, address extraReward) external { + extraRewards[i] = extraReward; + } + + function setNumExtraRewards(uint256 num) external { + numExtraRewards = num; + } + + function extraRewardsLength() external view returns (uint256) { + return numExtraRewards; + } +} diff --git a/contracts/test/mocks/integrations/convex/BoosterMock.sol b/contracts/test/mocks/integrations/convex/BoosterMock.sol new file mode 100644 index 00000000..1ee37044 --- /dev/null +++ b/contracts/test/mocks/integrations/convex/BoosterMock.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {IBooster} from "../../../../integrations/convex/IBooster.sol"; + +contract BoosterMock { + address public minter; + + mapping(uint256 => IBooster.PoolInfo) public poolInfo; + + constructor(address cvx) { + minter = cvx; + } + + function setPoolInfo(uint256 pid, address curveLPToken, address convexStakingToken) external { + poolInfo[pid].lptoken = curveLPToken; + poolInfo[pid].token = convexStakingToken; + } +} diff --git a/contracts/test/mocks/integrations/convex/ExtraRewardWrapperMock.sol b/contracts/test/mocks/integrations/convex/ExtraRewardWrapperMock.sol new file mode 100644 index 00000000..187b9d9d --- /dev/null +++ b/contracts/test/mocks/integrations/convex/ExtraRewardWrapperMock.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +contract ExtraRewardWrapperMock { + address public token; + + constructor(address _token) { + token = _token; + } +} diff --git a/contracts/test/mocks/integrations/convex/RewardsMock.sol b/contracts/test/mocks/integrations/convex/RewardsMock.sol new file mode 100644 index 00000000..a6829a48 --- /dev/null +++ b/contracts/test/mocks/integrations/convex/RewardsMock.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +contract RewardsMock { + address public rewardToken; + + constructor(address _rewardToken) { + rewardToken = _rewardToken; + } +} diff --git a/contracts/test/mocks/integrations/curve/PoolMock.sol b/contracts/test/mocks/integrations/curve/PoolMock.sol new file mode 100644 index 00000000..3058f49f --- /dev/null +++ b/contracts/test/mocks/integrations/curve/PoolMock.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +enum PoolType { + Stable, + Crypto +} + +contract PoolMock { + PoolType poolType; + address[] public coins; + address[] public underlying_coins; + + constructor(PoolType _poolType, address[] memory _coins, address[] memory _underlying_coins) { + poolType = _poolType; + coins = _coins; + underlying_coins = _underlying_coins; + } + + function isCrypto() public view returns (bool) { + return poolType == PoolType.Crypto; + } + + function mid_fee() external view returns (uint256) { + if (poolType == PoolType.Stable) revert("Not a crypto pool"); + return 0; + } +} diff --git a/contracts/test/unit/adapters/AbstractAdapter.unit.t.sol b/contracts/test/unit/adapters/AbstractAdapter.unit.t.sol deleted file mode 100644 index c1d584cb..00000000 --- a/contracts/test/unit/adapters/AbstractAdapter.unit.t.sol +++ /dev/null @@ -1,171 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {TestHelper} from "@gearbox-protocol/core-v3/contracts/test/lib/helper.sol"; -import {AddressProviderV3ACLMock} from - "@gearbox-protocol/core-v3/contracts/test/mocks/core/AddressProviderV3ACLMock.sol"; -import { - CallerNotCreditFacadeException, - ZeroAddressException -} from "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol"; - -import {CreditManagerMock, CreditManagerMockEvents} from "../../mocks/credit/CreditManagerMock.sol"; - -import {AbstractAdapterHarness} from "./AbstractAdapterHarness.sol"; - -/// @title Abstract adapter unit test -/// @notice U:[AA]: `AbstractAdapter` unit tests -contract AbstractAdapterUnitTest is TestHelper, CreditManagerMockEvents { - AbstractAdapterHarness abstractAdapter; - AddressProviderV3ACLMock addressProvider; - CreditManagerMock creditManager; - - address facade; - address target; - - function setUp() public { - facade = makeAddr("CREDIT_FACADE"); - target = makeAddr("TARGET_CONTRACT"); - - addressProvider = new AddressProviderV3ACLMock(); - creditManager = new CreditManagerMock(address(addressProvider), facade); - abstractAdapter = new AbstractAdapterHarness(address(creditManager), target); - } - - /// @notice U:[AA-1A]: constructor reverts on zero address - function test_U_AA_01A_constructor_reverts_on_zero_address() public { - vm.expectRevert(); - new AbstractAdapterHarness(address(0), address(0)); - - vm.expectRevert(ZeroAddressException.selector); - new AbstractAdapterHarness(address(creditManager), address(0)); - } - - /// @notice U:[AA-1B]: constructor sets correct values - function test_U_AA_01B_constructor_sets_correct_values() public { - assertEq(abstractAdapter.creditManager(), address(creditManager), "Incorrect credit manager"); - assertEq(abstractAdapter.addressProvider(), address(addressProvider), "Incorrect address provider"); - assertEq(abstractAdapter.targetContract(), target, "Incorrect target contract"); - } - - /// @notice U:[AA-2]: `_revertIfCallerNotCreditFacade` works correctly - function test_U_AA_02_revertIfCallerNotCreditFacade_works_correctly(address caller) public { - vm.assume(caller != facade); - - vm.expectRevert(CallerNotCreditFacadeException.selector); - vm.prank(caller); - abstractAdapter.revertIfCallerNotCreditFacade(); - } - - /// @notice U:[AA-3]: `_creditAccount` works correctly - function test_U_AA_03_creditAccount_works_correctly(address creditAccount) public { - creditManager.setActiveCreditAccount(creditAccount); - - vm.expectCall(address(creditManager), abi.encodeCall(creditManager.getActiveCreditAccountOrRevert, ())); - assertEq(abstractAdapter.creditAccount(), creditAccount, "Incorrect external call credit account"); - } - - /// @notice U:[AA-4]: `_getMaskOrRevert` works correctly - function test_U_AA_04_getMaskOrRevert_works_correctly(address token, uint8 index) public { - uint256 mask = 1 << index; - creditManager.setMask(token, mask); - - vm.expectCall(address(creditManager), abi.encodeCall(creditManager.getTokenMaskOrRevert, (token))); - assertEq(abstractAdapter.getMaskOrRevert(token), mask, "Incorrect token mask"); - } - - /// @notice U:[AA-5]: `_approveToken` works correctly - function test_U_AA_05_approveToken_works_correctly(address token, uint256 amount) public { - vm.expectCall(address(creditManager), abi.encodeCall(creditManager.approveCreditAccount, (token, amount))); - abstractAdapter.approveToken(token, amount); - } - - /// @notice U:[AA-6]: `_execute` works correctly - function test_U_AA_06_execute_works_correctly(bytes memory data, bytes memory expectedResult) public { - creditManager.setExecuteResult(expectedResult); - - vm.expectCall(address(creditManager), abi.encodeCall(creditManager.execute, (data))); - assertEq(abstractAdapter.execute(data), expectedResult, "Incorrect result"); - } - - /// @notice U:[AA-7]: `_executeSwapNoApprove` works correctly - function test_U_AA_07_executeSwapNoApprove_works_correctly( - address tokenIn, - address tokenOut, - uint8 tokenInIndex, - uint8 tokenOutIndex, - bytes memory data, - bytes memory expectedResult - ) public { - if (tokenIn == tokenOut) tokenOutIndex = tokenInIndex; - creditManager.setMask(tokenIn, 1 << tokenInIndex); - creditManager.setMask(tokenOut, 1 << tokenOutIndex); - creditManager.setExecuteResult(expectedResult); - - uint256 snapshot = vm.snapshot(); - for (uint256 caseNumber; caseNumber < 2; ++caseNumber) { - bool disableTokenIn = caseNumber == 1; - string memory caseName = caseNumber == 1 ? "disableTokenIn = true" : "disableTokenIn = false"; - - vm.expectEmit(false, false, false, false); - emit Execute(); - - (uint256 tokensToEnable, uint256 tokensToDisable, bytes memory result) = - abstractAdapter.executeSwapNoApprove(tokenIn, tokenOut, data, disableTokenIn); - - assertEq(tokensToEnable, 1 << tokenOutIndex, _testCaseErr(caseName, "Incorrect tokensToEnable")); - assertEq( - tokensToDisable, - disableTokenIn ? 1 << tokenInIndex : 0, - _testCaseErr(caseName, "Incorrect tokensToDisable") - ); - assertEq(result, expectedResult, _testCaseErr(caseName, "Incorrect result")); - - vm.revertTo(snapshot); - } - } - - /// @notice U:[AA-8]: `_executeSwapSafeApprove` works correctly - function test_U_AA_08_executeSwapSafeApprove_works_correctly( - address tokenIn, - address tokenOut, - uint8 tokenInIndex, - uint8 tokenOutIndex, - bytes memory data, - bytes memory expectedResult - ) public { - if (tokenIn == tokenOut) tokenOutIndex = tokenInIndex; - creditManager.setMask(tokenIn, 1 << tokenInIndex); - creditManager.setMask(tokenOut, 1 << tokenOutIndex); - creditManager.setExecuteResult(expectedResult); - - uint256 snapshot = vm.snapshot(); - for (uint256 caseNumber; caseNumber < 2; ++caseNumber) { - bool disableTokenIn = caseNumber == 1; - string memory caseName = caseNumber == 1 ? "disableTokenIn = true" : "disableTokenIn = false"; - - // need to ensure that order is correct - vm.expectEmit(true, false, false, true); - emit Approve(tokenIn, type(uint256).max); - vm.expectEmit(false, false, false, false); - emit Execute(); - vm.expectEmit(true, false, false, true); - emit Approve(tokenIn, 1); - - (uint256 tokensToEnable, uint256 tokensToDisable, bytes memory result) = - abstractAdapter.executeSwapSafeApprove(tokenIn, tokenOut, data, disableTokenIn); - - assertEq(tokensToEnable, 1 << tokenOutIndex, _testCaseErr(caseName, "Incorrect tokensToEnable")); - assertEq( - tokensToDisable, - disableTokenIn ? 1 << tokenInIndex : 0, - _testCaseErr(caseName, "Incorrect tokensToDisable") - ); - assertEq(result, expectedResult, _testCaseErr(caseName, "Incorrect result")); - - vm.revertTo(snapshot); - } - } -} diff --git a/contracts/test/unit/adapters/AbstractAdapterHarness.sol b/contracts/test/unit/adapters/AbstractAdapterHarness.sol deleted file mode 100644 index bfb7f3c3..00000000 --- a/contracts/test/unit/adapters/AbstractAdapterHarness.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {AbstractAdapter} from "../../../adapters/AbstractAdapter.sol"; -import {AdapterType} from "@gearbox-protocol/sdk-gov/contracts/AdapterType.sol"; - -contract AbstractAdapterHarness is AbstractAdapter { - AdapterType public constant override _gearboxAdapterType = AdapterType.ABSTRACT; - uint16 public constant override _gearboxAdapterVersion = 3_00; - - constructor(address _creditManager, address _targetContract) AbstractAdapter(_creditManager, _targetContract) {} - - function revertIfCallerNotCreditFacade() external view { - _revertIfCallerNotCreditFacade(); - } - - function creditAccount() external view returns (address) { - return _creditAccount(); - } - - function getMaskOrRevert(address token) external view returns (uint256 tokenMask) { - return _getMaskOrRevert(token); - } - - function approveToken(address token, uint256 amount) external { - _approveToken(token, amount); - } - - function execute(bytes memory callData) external returns (bytes memory result) { - result = _execute(callData); - } - - function executeSwapNoApprove(address tokenIn, address tokenOut, bytes memory callData, bool disableTokenIn) - external - returns (uint256 tokensToEnable, uint256 tokensToDisable, bytes memory result) - { - return _executeSwapNoApprove(tokenIn, tokenOut, callData, disableTokenIn); - } - - function executeSwapSafeApprove(address tokenIn, address tokenOut, bytes memory callData, bool disableTokenIn) - external - returns (uint256 tokensToEnable, uint256 tokensToDisable, bytes memory result) - { - return _executeSwapSafeApprove(tokenIn, tokenOut, callData, disableTokenIn); - } -} diff --git a/contracts/test/unit/adapters/AdapterTestHelper.sol b/contracts/test/unit/adapters/AdapterTestHelper.sol deleted file mode 100644 index 582b399b..00000000 --- a/contracts/test/unit/adapters/AdapterTestHelper.sol +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import { - ICreditManagerV3, - ICreditManagerV3Events -} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; -import {ICreditFacadeV3Events} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; - -// TEST -import "../../lib/constants.sol"; - -// SUITES -import {TokensTestSuite} from "@gearbox-protocol/core-v3/contracts/test/suites/TokensTestSuite.sol"; -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; - -import {BalanceHelper} from "../../helpers/BalanceHelper.sol"; -import {CreditFacadeTestHelper} from "../../helpers/CreditFacadeTestHelper.sol"; - -contract AdapterTestHelper is - Test, - ICreditManagerV3Events, - ICreditFacadeV3Events, - BalanceHelper, - CreditFacadeTestHelper -{ - function _setUp() internal { - _setUp(Tokens.DAI); - } - - function _setUp(Tokens t) internal { - require(t == Tokens.DAI || t == Tokens.WETH || t == Tokens.STETH, "Unsupported token"); - - tokenTestSuite = new TokensTestSuite(); - tokenTestSuite.topUpWETH{value: 100 * WAD}(); - - // CreditConfig creditConfig = new CreditConfig(tokenTestSuite, t); - - // cft = new CreditFacadeV3TestSuite(creditConfig); - - // underlying = cft.underlying(); - - // CreditManagerV3 = cft.CreditManagerV3(); - // creditFacade = cft.creditFacade(); - // CreditConfiguratorV3 = cft.CreditConfiguratorV3(); - } - - function _getUniswapDeadline() internal view returns (uint256) { - return block.timestamp + 1; - } - - function expectMulticallStackCalls( - address creditAccount, - address targetContract, - address borrower, - bytes memory callData, - address tokenIn, - address, // tokenOut, - bool allowTokenIn - ) internal { - vm.expectEmit(true, false, false, false); - emit StartMultiCall(creditAccount, borrower); - - if (allowTokenIn) { - vm.expectCall( - address(creditManager), - abi.encodeCall(ICreditManagerV3.approveCreditAccount, (tokenIn, type(uint256).max)) - ); - } - - vm.expectCall(address(creditManager), abi.encodeCall(ICreditManagerV3.execute, (callData))); - - vm.expectEmit(true, false, false, false); - emit Execute(creditAccount, targetContract); - - if (allowTokenIn) { - vm.expectCall(address(creditManager), abi.encodeCall(ICreditManagerV3.approveCreditAccount, (tokenIn, 1))); - } - - vm.expectEmit(false, false, false, false); - emit FinishMultiCall(); - } -} diff --git a/contracts/test/unit/adapters/AdapterUnitTestHelper.sol b/contracts/test/unit/adapters/AdapterUnitTestHelper.sol new file mode 100644 index 00000000..21015bda --- /dev/null +++ b/contracts/test/unit/adapters/AdapterUnitTestHelper.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {Test} from "forge-std/Test.sol"; + +import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; +import { + CallerNotConfiguratorException, + CallerNotCreditFacadeException +} from "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol"; +import {AddressProviderV3ACLMock} from + "@gearbox-protocol/core-v3/contracts/test/mocks/core/AddressProviderV3ACLMock.sol"; +import {ERC20Mock} from "@gearbox-protocol/core-v3/contracts/test/mocks/token/ERC20Mock.sol"; + +import {CreditManagerV3Mock, CreditManagerV3MockEvents} from "../../mocks/credit/CreditManagerV3Mock.sol"; + +contract AdapterUnitTestHelper is Test, CreditManagerV3MockEvents { + address configurator; + address creditFacade; + address creditAccount; + address creditConfigurator; + CreditManagerV3Mock creditManager; + AddressProviderV3ACLMock addressProvider; + + address[8] tokens; + + function _setUp() internal { + configurator = makeAddr("CONFIGURATOR"); + creditFacade = makeAddr("CREDIT_FACADE"); + creditAccount = makeAddr("CREDIT_ACCOUNT"); + creditConfigurator = makeAddr("CREDIT_CONFIGURATOR"); + + vm.prank(configurator); + addressProvider = new AddressProviderV3ACLMock(); + + creditManager = new CreditManagerV3Mock(address(addressProvider), creditFacade, creditConfigurator); + + for (uint256 i; i < tokens.length; ++i) { + string memory name = string.concat("Test Token ", vm.toString(i)); + string memory symbol = string.concat("TEST", vm.toString(i)); + tokens[i] = address(new ERC20Mock(name, symbol, 18)); + creditManager.setMask(tokens[i], 1 << i); + } + + creditManager.setActiveCreditAccount(creditAccount); + } + + function _revertsOnNonConfiguratorCaller() internal { + vm.expectRevert(CallerNotConfiguratorException.selector); + } + + function _revertsOnNonFacadeCaller() internal { + vm.expectRevert(CallerNotCreditFacadeException.selector); + } + + function _readsTokenMask(address token) internal { + vm.expectCall(address(creditManager), abi.encodeCall(ICreditManagerV3.getTokenMaskOrRevert, (token))); + } + + function _readsActiveAccount() internal { + vm.expectCall(address(creditManager), abi.encodeCall(ICreditManagerV3.getActiveCreditAccountOrRevert, ())); + } + + function _executesSwap( + address tokenIn, + address tokenOut, + bytes memory callData, + bool requiresApproval, + bool validatesTokens + ) internal { + if (validatesTokens) { + vm.expectCall(address(creditManager), abi.encodeCall(ICreditManagerV3.getTokenMaskOrRevert, (tokenOut))); + + if (!requiresApproval) { + vm.expectCall(address(creditManager), abi.encodeCall(ICreditManagerV3.getTokenMaskOrRevert, (tokenIn))); + } + } + + if (requiresApproval) { + vm.expectCall( + address(creditManager), + abi.encodeCall(ICreditManagerV3.approveCreditAccount, (tokenIn, type(uint256).max)) + ); + + vm.expectEmit(false, false, false, true, address(creditManager)); + emit Approve(tokenIn, type(uint256).max); + } + + vm.expectCall(address(creditManager), abi.encodeCall(ICreditManagerV3.execute, (callData))); + vm.expectEmit(false, false, false, false, address(creditManager)); + emit Execute(); + + if (requiresApproval) { + vm.expectCall(address(creditManager), abi.encodeCall(ICreditManagerV3.approveCreditAccount, (tokenIn, 1))); + vm.expectEmit(false, false, false, true, address(creditManager)); + emit Approve(tokenIn, 1); + } + } + + function _executesCall(address[] memory tokensToApprove, address[] memory tokensToValidate, bytes memory callData) + internal + { + for (uint256 i; i < tokensToValidate.length; ++i) { + vm.expectCall( + address(creditManager), abi.encodeCall(ICreditManagerV3.getTokenMaskOrRevert, (tokensToValidate[i])) + ); + } + + for (uint256 i; i < tokensToApprove.length; ++i) { + vm.expectCall( + address(creditManager), + abi.encodeCall(ICreditManagerV3.approveCreditAccount, (tokensToApprove[i], type(uint256).max)) + ); + + vm.expectEmit(false, false, false, true, address(creditManager)); + emit Approve(tokensToApprove[i], type(uint256).max); + } + + vm.expectCall(address(creditManager), abi.encodeCall(ICreditManagerV3.execute, (callData))); + vm.expectEmit(false, false, false, false, address(creditManager)); + emit Execute(); + + for (uint256 i; i < tokensToApprove.length; ++i) { + vm.expectCall( + address(creditManager), abi.encodeCall(ICreditManagerV3.approveCreditAccount, (tokensToApprove[i], 1)) + ); + + vm.expectEmit(false, false, false, true, address(creditManager)); + emit Approve(tokensToApprove[i], 1); + } + } +} diff --git a/contracts/test/unit/adapters/aave/AaveTestHelper.sol b/contracts/test/unit/adapters/aave/AaveTestHelper.sol deleted file mode 100644 index 698d6a06..00000000 --- a/contracts/test/unit/adapters/aave/AaveTestHelper.sol +++ /dev/null @@ -1,171 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {RAY} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol"; -import {PERCENTAGE_FACTOR} from "@gearbox-protocol/core-v2/contracts/libraries/PercentageMath.sol"; -import { - CONFIGURATOR, - DAI_ACCOUNT_AMOUNT, - USDC_EXCHANGE_AMOUNT, - USER, - WETH_EXCHANGE_AMOUNT -} from "@gearbox-protocol/core-v3/contracts/test/lib/constants.sol"; - -import {WrappedAToken} from "../../../../helpers/aave/AaveV2_WrappedAToken.sol"; -import {IAToken} from "../../../../integrations/aave/IAToken.sol"; - -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; -import {ATokenMock} from "../../../mocks/integrations/aave/ATokenMock.sol"; -import {LendingPoolMock} from "../../../mocks/integrations/aave/LendingPoolMock.sol"; - -import {WrappedAaveV2PriceFeed} from "@gearbox-protocol/oracles-v3/contracts/oracles/aave/WrappedAaveV2PriceFeed.sol"; -import {AdapterTestHelper} from "../AdapterTestHelper.sol"; - -contract AaveTestHelper is AdapterTestHelper { - LendingPoolMock public lendingPool; - - // underlying tokens - address dai; - address usdc; - - // aTokens - address aDai; - address aUsdc; - address aWeth; - - // waTokens - address waDai; - address waUsdc; - address waWeth; - - function _setupAaveSuite(bool withWrappers) internal { - _setUp(); - - tokenTestSuite.mint(Tokens.DAI, USER, DAI_ACCOUNT_AMOUNT); - - dai = tokenTestSuite.addressOf(Tokens.DAI); - usdc = tokenTestSuite.addressOf(Tokens.USDC); - weth = tokenTestSuite.addressOf(Tokens.WETH); - - // setup the lending pool and aTokens - lendingPool = new LendingPoolMock(); - aDai = lendingPool.addReserve(dai, 100 * RAY / PERCENTAGE_FACTOR); - aUsdc = lendingPool.addReserve(usdc, 200 * RAY / PERCENTAGE_FACTOR); - aWeth = lendingPool.addReserve(weth, 500 * RAY / PERCENTAGE_FACTOR); - - // seed reserves with some tokens to pay interest - tokenTestSuite.mint(Tokens.DAI, aDai, 1_000_000e18); - tokenTestSuite.mint(Tokens.USDC, aUsdc, 1_000_000e6); - tokenTestSuite.mint(Tokens.WETH, aWeth, 1_000e18); - - vm.label(address(lendingPool), "LENDING_POOL_MOCK"); - vm.label(aDai, "aDAI"); - vm.label(aUsdc, "aUSDC"); - vm.label(aWeth, "aWETH"); - - vm.startPrank(CONFIGURATOR); - // add price feeds for aTokens to the oracle (they are the same as underlying oracles) - priceOracle.setPriceFeed(aWeth, priceOracle.priceFeeds(weth), 0); - priceOracle.setPriceFeed(aUsdc, priceOracle.priceFeeds(usdc), 0); - - // enable aTokens as collateral tokens in the credit manager - creditConfigurator.addCollateralToken(aWeth, 8300); - creditConfigurator.addCollateralToken(aUsdc, 8300); - vm.stopPrank(); - - vm.warp(block.timestamp + 365 days); - - if (withWrappers) _setupWrappers(); - } - - function _setupWrappers() internal { - waDai = address(new WrappedAToken(aDai)); - waUsdc = address(new WrappedAToken(aUsdc)); - waWeth = address(new WrappedAToken(aWeth)); - vm.label(waDai, "waDAI"); - vm.label(waUsdc, "waUSDC"); - vm.label(waWeth, "waWETH"); - - vm.startPrank(CONFIGURATOR); - // add price feeds for waTokens to the oracle - priceOracle.setPriceFeed( - waWeth, - address( - new WrappedAaveV2PriceFeed(address(addressProvider), waWeth, priceOracle.priceFeeds(weth), 48 hours) - ), - 0 - ); - priceOracle.setPriceFeed( - waUsdc, - address( - new WrappedAaveV2PriceFeed(address(addressProvider), waUsdc, priceOracle.priceFeeds(usdc), 48 hours) - ), - 0 - ); - - // enable waTokens as collateral tokens in the credit manager - creditConfigurator.addCollateralToken(waWeth, 8300); - creditConfigurator.addCollateralToken(waUsdc, 8300); - vm.stopPrank(); - - vm.warp(block.timestamp + 365 days); - } - - function _openAccountWithToken(Tokens token) internal returns (address creditAccount, uint256 balance) { - (creditAccount,) = _openTestCreditAccount(); - (address underlying,,, uint256 amount) = _tokenInfo(token); - balance = amount; - - tokenTestSuite.mint(underlying, USER, balance); - - tokenTestSuite.approve(underlying, USER, address(creditManager), balance); - vm.prank(USER); - // creditFacade.addCollateral(USER, underlying, balance); - } - - function _openAccountWithAToken(Tokens token) internal returns (address creditAccount, uint256 balance) { - (creditAccount,) = _openTestCreditAccount(); - (address underlying, address aToken,, uint256 amount) = _tokenInfo(token); - balance = amount; - - tokenTestSuite.mint(underlying, USER, balance); - - tokenTestSuite.approve(underlying, USER, address(lendingPool), balance); - vm.prank(USER); - lendingPool.deposit(underlying, balance, address(USER), 0); - - tokenTestSuite.approve(aToken, USER, address(creditManager), balance); - vm.prank(USER); - // creditFacade.addCollateral(USER, aToken, balance); - } - - function _openAccountWithWAToken(Tokens token) internal returns (address creditAccount, uint256 balance) { - (creditAccount,) = _openTestCreditAccount(); - (address underlying,, address waToken, uint256 amount) = _tokenInfo(token); - - tokenTestSuite.mint(token, USER, amount); - - tokenTestSuite.approve(underlying, USER, waToken, amount); - vm.prank(USER); - balance = WrappedAToken(waToken).depositUnderlying(amount); - - tokenTestSuite.approve(waToken, USER, address(creditManager), balance); - vm.prank(USER); - // creditFacade.addCollateral(USER, waToken, balance); - } - - function _tokenInfo(Tokens token) - internal - view - returns (address underlying, address aToken, address waToken, uint256 amount) - { - if (token == Tokens.USDC) { - return (usdc, aUsdc, waUsdc, USDC_EXCHANGE_AMOUNT); - } else if (token == Tokens.WETH) { - return (weth, aWeth, waWeth, WETH_EXCHANGE_AMOUNT); - } - revert("Token must be one of USDC or WETH"); - } -} diff --git a/contracts/test/unit/adapters/aave/AaveV2_LendingPoolAdapter.unit.t.sol b/contracts/test/unit/adapters/aave/AaveV2_LendingPoolAdapter.unit.t.sol index af60a47a..f5f61b17 100644 --- a/contracts/test/unit/adapters/aave/AaveV2_LendingPoolAdapter.unit.t.sol +++ b/contracts/test/unit/adapters/aave/AaveV2_LendingPoolAdapter.unit.t.sol @@ -3,256 +3,143 @@ // (c) Gearbox Foundation, 2023. pragma solidity ^0.8.17; -import {USER, CONFIGURATOR} from "@gearbox-protocol/core-v3/contracts/test/lib/constants.sol"; import {AaveV2_LendingPoolAdapter} from "../../../../adapters/aave/AaveV2_LendingPoolAdapter.sol"; -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; -import {AaveTestHelper} from "./AaveTestHelper.sol"; -import "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol"; +import {ILendingPool, DataTypes} from "../../../../integrations/aave/ILendingPool.sol"; +import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; -/// @title Aave V2 lending pool adapter tests -/// @notice [AAV2LP]: Unit tests for Aave V2 lending pool adapter -contract AaveV2_LendingPoolAdapter_Test is AaveTestHelper { - AaveV2_LendingPoolAdapter public adapter; +/// @title Aave v2 lending pool adapter unit test +/// @notice U:[AAVE2]: Unit tests for Aave v2 lending pool adapter +contract AaveV2_LendingPoolAdapterUnitTest is AdapterUnitTestHelper { + AaveV2_LendingPoolAdapter adapter; - function setUp() public { - _setupAaveSuite(false); - - // create a lending pool adapter and add it to the credit manager - vm.startPrank(CONFIGURATOR); - adapter = new AaveV2_LendingPoolAdapter(address(creditManager), address(lendingPool)); - creditConfigurator.allowAdapter(address(adapter)); - vm.label(address(adapter), "LENDING_POOL_ADAPTER"); - vm.stopPrank(); - } + address lendingPool; - /// @notice [AAV2LP-1]: All action functions revert if called not from the multicall - function test_AAV2LP_01_action_functions_revert_if_called_not_from_multicall() public { - vm.prank(USER); - vm.expectRevert(CallerNotCreditFacadeException.selector); - adapter.deposit(dai, 1, address(0), 0); + function setUp() public { + _setUp(); - vm.prank(USER); - vm.expectRevert(CallerNotCreditFacadeException.selector); - adapter.depositAll(dai); + lendingPool = makeAddr("LENDING_POOL"); - vm.prank(USER); - vm.expectRevert(CallerNotCreditFacadeException.selector); - adapter.withdraw(dai, 1, address(0)); + DataTypes.ReserveData memory data; + data.aTokenAddress = tokens[1]; + vm.mockCall(lendingPool, abi.encodeCall(ILendingPool.getReserveData, (tokens[0])), abi.encode(data)); - vm.prank(USER); - vm.expectRevert(CallerNotCreditFacadeException.selector); - adapter.withdrawAll(dai); + adapter = new AaveV2_LendingPoolAdapter(address(creditManager), lendingPool); } - /// @notice [AAV2LP-2]: All action functions revert if called on not registered token - function test_AAV2LP_02_action_functions_revert_if_called_on_not_registered_token() public { - (address creditAccount, uint256 balance) = _openTestCreditAccount(); - tokenTestSuite.approve(dai, creditAccount, address(lendingPool), balance / 2); - vm.prank(creditAccount); - lendingPool.deposit(dai, balance / 2, creditAccount, 0); - - vm.expectRevert(TokenNotAllowedException.selector); - executeOneLineMulticall( - creditAccount, address(adapter), abi.encodeCall(adapter.deposit, (dai, balance / 2, address(0), 0)) - ); - - vm.expectRevert(TokenNotAllowedException.selector); - executeOneLineMulticall(creditAccount, address(adapter), abi.encodeCall(adapter.depositAll, (dai))); - - vm.expectRevert(TokenNotAllowedException.selector); - executeOneLineMulticall( - creditAccount, address(adapter), abi.encodeCall(adapter.withdraw, (dai, balance / 2, address(0))) - ); - - vm.expectRevert(TokenNotAllowedException.selector); - executeOneLineMulticall(creditAccount, address(adapter), abi.encodeCall(adapter.withdrawAll, (dai))); + /// @notice U:[AAVE2-1]: Constructor works as expected + function test_U_AAVE2_01_constructor_works_as_expected() public { + assertEq(adapter.creditManager(), address(creditManager), "Incorrect creditManager"); + assertEq(adapter.addressProvider(), address(addressProvider), "Incorrect addressProvider"); + assertEq(adapter.targetContract(), lendingPool, "Incorrect targetContract"); } - /// @notice [AAV2LP-3]: `deposit` works correctly - /// @dev Fuzzing time before deposit to see if adapter handles interest properly - /// @dev Resulting aToken balance is allowed to deviate from the expected value by at most 1 - /// due to rounding errors in rebalancing - function test_AAV2LP_03_deposit_works_correctly(uint256 timedelta) public { - vm.assume(timedelta < 3 * 365 days); - uint256 snapshot = vm.snapshot(); - - for (uint256 isUsdc; isUsdc < 2; ++isUsdc) { - (address creditAccount, uint256 initialBalance) = - _openAccountWithToken(isUsdc == 1 ? Tokens.USDC : Tokens.WETH); - (address token, address aToken) = isUsdc == 1 ? (usdc, aUsdc) : (weth, aWeth); - - expectAllowance(token, creditAccount, address(lendingPool), 0); - vm.warp(block.timestamp + timedelta); + /// @notice U:[AAVE2-2]: Wrapper functions revert on wrong caller + function test_U_AAVE2_02_wrapper_functions_revert_on_wrong_caller() public { + _revertsOnNonFacadeCaller(); + adapter.deposit(address(0), 0, address(0), 0); - uint256 depositAmount = initialBalance / 2; + _revertsOnNonFacadeCaller(); + adapter.depositAll(address(0)); - bytes memory expectedCallData = - abi.encodeCall(lendingPool.deposit, (token, depositAmount, creditAccount, 0)); - expectMulticallStackCalls( - address(adapter), address(lendingPool), USER, expectedCallData, token, aToken, true - ); + _revertsOnNonFacadeCaller(); + adapter.withdraw(address(0), 0, address(0)); - bytes memory callData = abi.encodeCall(adapter.deposit, (token, depositAmount, address(0), 0)); - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(token, creditAccount, initialBalance - depositAmount); - // expectBalance(aToken, creditAccount, depositAmount); - expectBalanceGe(aToken, creditAccount, depositAmount - 1, ""); - expectBalanceLe(aToken, creditAccount, depositAmount + 1, ""); - - expectAllowance(token, creditAccount, address(lendingPool), 1); - - expectTokenIsEnabled(creditAccount, token, true); - expectTokenIsEnabled(creditAccount, aToken, true); - - vm.revertTo(snapshot); - } + _revertsOnNonFacadeCaller(); + adapter.withdrawAll(address(0)); } - /// @notice [AAV2LP-4]: `depositAll` works correctly - /// @dev Fuzzing time before deposit to see if adapter handles interest properly - /// @dev Resulting aToken balance is allowed to deviate from the expected value by at most 1 - /// due to rounding errors in rebalancing - function test_AAV2LP_04_depositAll_works_correctly(uint256 timedelta) public { - vm.assume(timedelta < 3 * 365 days); - uint256 snapshot = vm.snapshot(); - - for (uint256 isUsdc; isUsdc < 2; ++isUsdc) { - (address creditAccount, uint256 initialBalance) = - _openAccountWithToken(isUsdc == 1 ? Tokens.USDC : Tokens.WETH); - (address token, address aToken) = isUsdc == 1 ? (usdc, aUsdc) : (weth, aWeth); - - expectAllowance(token, creditAccount, address(lendingPool), 0); - vm.warp(block.timestamp + timedelta); - - bytes memory expectedCallData = - abi.encodeCall(lendingPool.deposit, (token, initialBalance - 1, creditAccount, 0)); - expectMulticallStackCalls( - address(adapter), address(lendingPool), USER, expectedCallData, token, aToken, true - ); - - bytes memory callData = abi.encodeCall(adapter.depositAll, (token)); - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(token, creditAccount, 1); - // expectBalance(aToken, creditAccount, initialBalance - 1); - expectBalanceGe(aToken, creditAccount, initialBalance - 2, ""); - expectBalanceLe(aToken, creditAccount, initialBalance, ""); - - expectAllowance(token, creditAccount, address(lendingPool), 1); - - expectTokenIsEnabled(creditAccount, token, false); - expectTokenIsEnabled(creditAccount, aToken, true); - - vm.revertTo(snapshot); - } + /// @notice U:[AAVE2-3]: `deposit` works as expected + function test_U_AAVE2_03_deposit_works_as_expected() public { + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[0], + tokenOut: tokens[1], + callData: abi.encodeCall(ILendingPool.deposit, (tokens[0], 123, creditAccount, 0)), + requiresApproval: true, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.deposit(tokens[0], 123, address(0), 0); + + assertEq(tokensToEnable, 2, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); } - /// @notice [AAV2LP-5A]: `withdraw` works correctly - /// @dev Fuzzing time before withdrawal to see if adapter handles interest properly - /// @dev Resulting aToken balance is allowed to deviate from the expected value by at most 1 - /// due to rounding errors in rebalancing - function test_AAV2LP_05A_withdraw_works_correctly(uint256 timedelta) public { - vm.assume(timedelta < 3 * 365 days); - uint256 snapshot = vm.snapshot(); - - for (uint256 isUsdc; isUsdc < 2; ++isUsdc) { - (address token, address aToken) = isUsdc == 1 ? (usdc, aUsdc) : (weth, aWeth); - (address creditAccount,) = _openAccountWithAToken(isUsdc == 1 ? Tokens.USDC : Tokens.WETH); - - vm.warp(block.timestamp + timedelta); - uint256 initialBalance = tokenTestSuite.balanceOf(aToken, creditAccount); - uint256 withdrawAmount = initialBalance / 2; + /// @notice U:[AAVE2-4]: `depositAll` works as expected + function test_U_AAVE2_04_depositAll_works_as_expected() public { + deal({token: tokens[0], to: creditAccount, give: 1001}); - bytes memory expectedCallData = abi.encodeCall(lendingPool.withdraw, (token, withdrawAmount, creditAccount)); - expectMulticallStackCalls( - address(adapter), address(lendingPool), USER, expectedCallData, aToken, token, false - ); + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[0], + tokenOut: tokens[1], + callData: abi.encodeCall(ILendingPool.deposit, (tokens[0], 1000, creditAccount, 0)), + requiresApproval: true, + validatesTokens: true + }); - bytes memory callData = abi.encodeCall(adapter.withdraw, (token, withdrawAmount, creditAccount)); - executeOneLineMulticall(creditAccount, address(adapter), callData); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.depositAll(tokens[0]); - expectBalance(token, creditAccount, withdrawAmount); - // expectBalance(aToken, creditAccount, initialBalance - withdrawAmount); - expectBalanceGe(aToken, creditAccount, initialBalance - withdrawAmount - 1, ""); - expectBalanceLe(aToken, creditAccount, initialBalance - withdrawAmount + 1, ""); - - expectAllowance(aToken, creditAccount, address(lendingPool), 0); - - expectTokenIsEnabled(creditAccount, token, true); - expectTokenIsEnabled(creditAccount, aToken, true); - - vm.revertTo(snapshot); - } + assertEq(tokensToEnable, 2, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 1, "Incorrect tokensToDisable"); } - /// @notice [AAV2LP-5B]: `withdraw` works correctly when `amount == type(uint256).max` - /// @dev Fuzzing time before withdrawal to see if adapter handles interest properly - /// @dev Resulting aToken balance is allowed to deviate from the expected value by at most 1 - /// due to rounding errors in rebalancing - function test_AAV2LP_05B_withdraw_works_correctly(uint256 timedelta) public { - vm.assume(timedelta < 3 * 365 days); - uint256 snapshot = vm.snapshot(); - - for (uint256 isUsdc; isUsdc < 2; ++isUsdc) { - (address token, address aToken) = isUsdc == 1 ? (usdc, aUsdc) : (weth, aWeth); - (address creditAccount,) = _openAccountWithAToken(isUsdc == 1 ? Tokens.USDC : Tokens.WETH); - - vm.warp(block.timestamp + timedelta); - uint256 initialBalance = tokenTestSuite.balanceOf(aToken, creditAccount); - - bytes memory expectedCallData = - abi.encodeCall(lendingPool.withdraw, (token, initialBalance - 1, creditAccount)); - expectMulticallStackCalls( - address(adapter), address(lendingPool), USER, expectedCallData, aToken, token, false - ); - - bytes memory callData = abi.encodeCall(adapter.withdraw, (token, type(uint256).max, creditAccount)); - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(token, creditAccount, initialBalance - 1); - // expectBalance(aToken, creditAccount, 1); - expectBalanceLe(aToken, creditAccount, 2, ""); - - expectTokenIsEnabled(creditAccount, token, true); - expectTokenIsEnabled(creditAccount, aToken, false); - - vm.revertTo(snapshot); - } + /// @notice U:[AAVE2-5A]: `withdraw` works as expected + function test_U_AAVE2_05A_withdraw_works_as_expected() public { + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[1], + tokenOut: tokens[0], + callData: abi.encodeCall(ILendingPool.withdraw, (tokens[0], 123, creditAccount)), + requiresApproval: false, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdraw(tokens[0], 123, address(0)); + + assertEq(tokensToEnable, 1, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); } - /// @notice [AAV2LP-6]: `withdrawAll` works correctly - /// @dev Fuzzing time before withdrawal to see if adapter handles interest properly - /// @dev Resulting aToken balance is allowed to deviate from the expected value by at most 1 - /// due to rounding errors in rebalancing - function test_AAV2LP_06_withdrawAll_works_correctly(uint256 timedelta) public { - vm.assume(timedelta < 3 * 365 days); - uint256 snapshot = vm.snapshot(); - - for (uint256 isUsdc; isUsdc < 2; ++isUsdc) { - (address token, address aToken) = isUsdc == 1 ? (usdc, aUsdc) : (weth, aWeth); - (address creditAccount,) = _openAccountWithAToken(isUsdc == 1 ? Tokens.USDC : Tokens.WETH); - - vm.warp(block.timestamp + timedelta); - uint256 initialBalance = tokenTestSuite.balanceOf(aToken, creditAccount); - - bytes memory expectedCallData = - abi.encodeCall(lendingPool.withdraw, (token, initialBalance - 1, creditAccount)); - expectMulticallStackCalls( - address(adapter), address(lendingPool), USER, expectedCallData, aToken, token, false - ); + /// @notice U:[AAVE2-5B]: `withdraw` works as expected with amx amount + function test_U_AAVE2_05B_withdraw_works_as_expected_with_max_amount() public { + deal({token: tokens[1], to: creditAccount, give: 1001}); + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[1], + tokenOut: tokens[0], + callData: abi.encodeCall(ILendingPool.withdraw, (tokens[0], 1000, creditAccount)), + requiresApproval: false, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdraw(tokens[0], type(uint256).max, address(0)); + + assertEq(tokensToEnable, 1, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 2, "Incorrect tokensToDisable"); + } - bytes memory callData = abi.encodeCall(adapter.withdrawAll, (token)); - executeOneLineMulticall(creditAccount, address(adapter), callData); + /// @notice U:[AAVE2-6]: `withdrawAll` works as expected + function test_U_AAVE2_06_withdrawAll_works_as_expected() public { + deal({token: tokens[1], to: creditAccount, give: 1001}); - expectBalance(token, creditAccount, initialBalance - 1); - // expectBalance(aToken, creditAccount, 1); - expectBalanceLe(aToken, creditAccount, 2, ""); + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[1], + tokenOut: tokens[0], + callData: abi.encodeCall(ILendingPool.withdraw, (tokens[0], 1000, creditAccount)), + requiresApproval: false, + validatesTokens: true + }); - expectTokenIsEnabled(creditAccount, token, true); - expectTokenIsEnabled(creditAccount, aToken, false); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdrawAll(tokens[0]); - vm.revertTo(snapshot); - } + assertEq(tokensToEnable, 1, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 2, "Incorrect tokensToDisable"); } } diff --git a/contracts/test/unit/adapters/aave/AaveV2_WrappedATokenAdapter.t.sol b/contracts/test/unit/adapters/aave/AaveV2_WrappedATokenAdapter.t.sol deleted file mode 100644 index 0bbc536a..00000000 --- a/contracts/test/unit/adapters/aave/AaveV2_WrappedATokenAdapter.t.sol +++ /dev/null @@ -1,209 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {WAD} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol"; -import {USER, CONFIGURATOR} from "@gearbox-protocol/core-v3/contracts/test/lib/constants.sol"; - -import {AaveV2_WrappedATokenAdapter} from "../../../../adapters/aave/AaveV2_WrappedATokenAdapter.sol"; -import {WrappedAToken} from "../../../../helpers/aave/AaveV2_WrappedAToken.sol"; -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; -import {AaveTestHelper} from "./AaveTestHelper.sol"; - -import "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol"; - -/// @title Aave V2 wrapped aToken adapter tests -/// @notice [AAV2W]: Unit tests for Aave V2 wrapper aToken adapter -contract AaveV2_WrappedATokenAdapter_Test is AaveTestHelper { - AaveV2_WrappedATokenAdapter public adapter; - - function setUp() public { - _setupAaveSuite(true); - - vm.startPrank(CONFIGURATOR); - adapter = new AaveV2_WrappedATokenAdapter(address(creditManager), waUsdc); - creditConfigurator.allowAdapter(address(adapter)); - vm.label(address(adapter), "waUSDC_ADAPTER"); - vm.stopPrank(); - } - - /// @notice [AAV2W-1]: Constructor reverts on not registered tokens - function test_AAV2W_01_constructor_reverts_on_not_registered_tokens() public { - vm.expectRevert(TokenNotAllowedException.selector); - new AaveV2_WrappedATokenAdapter(address(creditManager), waDai); - } - - /// @notice [AAV2W-2]: Constructor sets correct values - function test_AAV2W_02_constructor_sets_correct_values() public { - assertEq(adapter.aToken(), aUsdc, "Incorrect aUSDC address"); - assertEq(adapter.underlying(), usdc, "Incorrect USDC address"); - assertEq(adapter.waTokenMask(), creditManager.getTokenMaskOrRevert(waUsdc), "Incorrect waUSDC mask"); - assertEq(adapter.aTokenMask(), creditManager.getTokenMaskOrRevert(aUsdc), "Incorrect aUSDC mask"); - assertEq(adapter.tokenMask(), creditManager.getTokenMaskOrRevert(usdc), "Incorrect USDC mask"); - } - - /// @notice [AAV2W-3]: All action functions revert if called not from the multicall - function test_AAV2W_03_action_functions_revert_if_called_not_from_multicall() public { - _openTestCreditAccount(); - - vm.startPrank(USER); - vm.expectRevert(CallerNotCreditFacadeException.selector); - adapter.deposit(1); - - vm.expectRevert(CallerNotCreditFacadeException.selector); - adapter.depositAll(); - - vm.expectRevert(CallerNotCreditFacadeException.selector); - adapter.depositUnderlying(1); - - vm.expectRevert(CallerNotCreditFacadeException.selector); - adapter.depositAllUnderlying(); - - vm.expectRevert(CallerNotCreditFacadeException.selector); - adapter.withdraw(1); - - vm.expectRevert(CallerNotCreditFacadeException.selector); - adapter.withdrawAll(); - - vm.expectRevert(CallerNotCreditFacadeException.selector); - adapter.withdrawUnderlying(1); - - vm.expectRevert(CallerNotCreditFacadeException.selector); - adapter.withdrawAllUnderlying(); - vm.stopPrank(); - } - - /// @notice [AAV2W-4]: `deposit` and `depositUnderlying` work correctly - function test_AAV2W_04_deposit_works_correctly(uint256 timedelta) public { - vm.assume(timedelta < 3 * 365 days); - uint256 snapshot = vm.snapshot(); - - for (uint256 fromUnderlying; fromUnderlying < 2; ++fromUnderlying) { - (address creditAccount, uint256 initialBalance) = - fromUnderlying == 1 ? _openAccountWithToken(Tokens.USDC) : _openAccountWithAToken(Tokens.USDC); - address tokenIn = fromUnderlying == 1 ? usdc : aUsdc; - - expectAllowance(tokenIn, creditAccount, waUsdc, 0); - - vm.warp(block.timestamp + timedelta); - if (fromUnderlying == 0) initialBalance = tokenTestSuite.balanceOf(aUsdc, creditAccount); - uint256 depositAmount = initialBalance / 2; - - bytes memory callData = fromUnderlying == 1 - ? abi.encodeCall(WrappedAToken.depositUnderlying, (depositAmount)) - : abi.encodeCall(WrappedAToken.deposit, (depositAmount)); - expectMulticallStackCalls(address(adapter), waUsdc, USER, callData, tokenIn, waUsdc, true); - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(tokenIn, creditAccount, initialBalance - depositAmount); - expectBalance(waUsdc, creditAccount, depositAmount * WAD / WrappedAToken(waUsdc).exchangeRate()); - - expectAllowance(tokenIn, creditAccount, waUsdc, 1); - - expectTokenIsEnabled(creditAccount, tokenIn, true); - expectTokenIsEnabled(creditAccount, waUsdc, true); - - vm.revertTo(snapshot); - } - } - - /// @notice [AAV2W-5]: `depositAll` and `depositAllUnderlying` work correctly - /// @dev Fuzzing time before deposit to see if adapter handles interest properly - function test_AAV2W_05_depositAll_works_correctly(uint256 timedelta) public { - vm.assume(timedelta < 3 * 365 days); - uint256 snapshot = vm.snapshot(); - - for (uint256 fromUnderlying; fromUnderlying < 2; ++fromUnderlying) { - (address creditAccount, uint256 initialBalance) = - fromUnderlying == 1 ? _openAccountWithToken(Tokens.USDC) : _openAccountWithAToken(Tokens.USDC); - address tokenIn = fromUnderlying == 1 ? usdc : aUsdc; - - expectAllowance(tokenIn, creditAccount, waUsdc, 0); - - vm.warp(block.timestamp + timedelta); - if (fromUnderlying == 0) initialBalance = tokenTestSuite.balanceOf(aUsdc, creditAccount); - - bytes memory expectedCallData = fromUnderlying == 1 - ? abi.encodeCall(WrappedAToken.depositUnderlying, (initialBalance - 1)) - : abi.encodeCall(WrappedAToken.deposit, (initialBalance - 1)); - expectMulticallStackCalls(address(adapter), waUsdc, USER, expectedCallData, tokenIn, waUsdc, true); - - bytes memory callData = fromUnderlying == 1 - ? abi.encodeCall(adapter.depositAllUnderlying, ()) - : abi.encodeCall(adapter.depositAll, ()); - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(tokenIn, creditAccount, 1); - expectBalance(waUsdc, creditAccount, (initialBalance - 1) * WAD / WrappedAToken(waUsdc).exchangeRate()); - - expectAllowance(tokenIn, creditAccount, waUsdc, 1); - - expectTokenIsEnabled(creditAccount, tokenIn, false); - expectTokenIsEnabled(creditAccount, waUsdc, true); - - vm.revertTo(snapshot); - } - } - - /// @notice [AAV2W-6]: `withdraw` and `withdrawUnderlying` work correctly - /// @dev Fuzzing time before withdrawal to see if adapter handles interest properly - function test_AAV2W_06_withdraw_works_correctly(uint256 timedelta) public { - vm.assume(timedelta < 3 * 365 days); - uint256 snapshot = vm.snapshot(); - - for (uint256 toUnderlying; toUnderlying < 2; ++toUnderlying) { - (address creditAccount, uint256 initialBalance) = _openAccountWithWAToken(Tokens.USDC); - address tokenOut = toUnderlying == 1 ? usdc : aUsdc; - - vm.warp(block.timestamp + timedelta); - uint256 withdrawAmount = initialBalance / 2; - - bytes memory callData = toUnderlying == 1 - ? abi.encodeCall(WrappedAToken.withdrawUnderlying, (withdrawAmount)) - : abi.encodeCall(WrappedAToken.withdraw, (withdrawAmount)); - expectMulticallStackCalls(address(adapter), waUsdc, USER, callData, waUsdc, tokenOut, false); - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(waUsdc, creditAccount, initialBalance - withdrawAmount); - expectBalance(tokenOut, creditAccount, withdrawAmount * WrappedAToken(waUsdc).exchangeRate() / WAD); - - expectTokenIsEnabled(creditAccount, waUsdc, true); - expectTokenIsEnabled(creditAccount, tokenOut, true); - - vm.revertTo(snapshot); - } - } - - /// @notice [AAV2W-7]: `withdrawAll` and `withdrawAllUnderlying` work correctly - /// @dev Fuzzing time before withdrawal to see if adapter handles interest properly - function test_AAV2W_07_wirhdrawAll_works_correctly(uint256 timedelta) public { - vm.assume(timedelta < 3 * 365 days); - uint256 snapshot = vm.snapshot(); - - for (uint256 toUnderlying; toUnderlying < 2; ++toUnderlying) { - (address creditAccount, uint256 initialBalance) = _openAccountWithWAToken(Tokens.USDC); - address tokenOut = toUnderlying == 1 ? usdc : aUsdc; - - vm.warp(block.timestamp + timedelta); - - bytes memory expectedCallData = toUnderlying == 1 - ? abi.encodeCall(WrappedAToken.withdrawUnderlying, (initialBalance - 1)) - : abi.encodeCall(WrappedAToken.withdraw, (initialBalance - 1)); - expectMulticallStackCalls(address(adapter), waUsdc, USER, expectedCallData, waUsdc, tokenOut, false); - - bytes memory callData = toUnderlying == 1 - ? abi.encodeCall(adapter.withdrawAllUnderlying, ()) - : abi.encodeCall(adapter.withdrawAll, ()); - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(waUsdc, creditAccount, 1); - expectBalance(tokenOut, creditAccount, (initialBalance - 1) * WrappedAToken(waUsdc).exchangeRate() / WAD); - - expectTokenIsEnabled(creditAccount, waUsdc, false); - expectTokenIsEnabled(creditAccount, tokenOut, true); - - vm.revertTo(snapshot); - } - } -} diff --git a/contracts/test/unit/adapters/aave/AaveV2_WrappedATokenAdapter.unit.t.sol b/contracts/test/unit/adapters/aave/AaveV2_WrappedATokenAdapter.unit.t.sol new file mode 100644 index 00000000..a175c966 --- /dev/null +++ b/contracts/test/unit/adapters/aave/AaveV2_WrappedATokenAdapter.unit.t.sol @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {AaveV2_WrappedATokenAdapter} from "../../../../adapters/aave/AaveV2_WrappedATokenAdapter.sol"; +import {WrappedAToken} from "../../../../helpers/aave/AaveV2_WrappedAToken.sol"; + +import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; + +/// @title Aave v2 wrapped aToken adapter unit test +/// @notice U:[AAVE2W]: Unit tests for Aave v2 waToken adapter +contract AaveV2_WrappedATokenAdapterUnitTest is AdapterUnitTestHelper { + AaveV2_WrappedATokenAdapter adapter; + + address token; + address aToken; + address waToken; + + uint256 tokenMask; + uint256 aTokenMask; + uint256 waTokenMask; + + function setUp() public { + _setUp(); + + (token, tokenMask) = (tokens[0], 1); + (aToken, aTokenMask) = (tokens[1], 2); + (waToken, waTokenMask) = (tokens[2], 4); + + vm.mockCall(waToken, abi.encodeWithSignature("aToken()"), abi.encode(aToken)); + vm.mockCall(waToken, abi.encodeWithSignature("underlying()"), abi.encode(token)); + + adapter = new AaveV2_WrappedATokenAdapter(address(creditManager), waToken); + } + + /// @notice U:[AAVE2W-1]: Constructor works as expected + function test_U_AAVE2W_01_constructor_works_as_expected() public { + _readsTokenMask(token); + _readsTokenMask(aToken); + _readsTokenMask(waToken); + adapter = new AaveV2_WrappedATokenAdapter(address(creditManager), waToken); + + assertEq(adapter.creditManager(), address(creditManager), "Incorrect creditManager"); + assertEq(adapter.addressProvider(), address(addressProvider), "Incorrect addressProvider"); + assertEq(adapter.targetContract(), waToken, "Incorrect targetContract"); + assertEq(adapter.underlying(), token, "Incorrect underlying"); + assertEq(adapter.aToken(), aToken, "Incorrect aToken"); + assertEq(adapter.tokenMask(), tokenMask, "Incorrect tokenMask"); + assertEq(adapter.aTokenMask(), aTokenMask, "Incorrect aTokenMask"); + assertEq(adapter.waTokenMask(), waTokenMask, "Incorrect waTokenMask"); + } + + /// @notice U:[AAVE2W-2]: Wrapper functions revert on wrong caller + function test_U_AAVE2W_02_wrapper_functions_revert_on_wrong_caller() public { + _revertsOnNonFacadeCaller(); + adapter.deposit(0); + + _revertsOnNonFacadeCaller(); + adapter.depositAll(); + + _revertsOnNonFacadeCaller(); + adapter.depositUnderlying(0); + + _revertsOnNonFacadeCaller(); + adapter.depositAllUnderlying(); + + _revertsOnNonFacadeCaller(); + adapter.withdraw(0); + + _revertsOnNonFacadeCaller(); + adapter.withdrawAll(); + + _revertsOnNonFacadeCaller(); + adapter.withdrawUnderlying(0); + + _revertsOnNonFacadeCaller(); + adapter.withdrawAllUnderlying(); + } + + /// @notice U:[AAVE2W-3]: `deposit` works as expected + function test_U_AAVE2W_03_deposit_works_as_expected() public { + _executesSwap({ + tokenIn: aToken, + tokenOut: waToken, + callData: abi.encodeCall(WrappedAToken.deposit, (123)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.deposit(123); + + assertEq(tokensToEnable, waTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[AAVE2W-4]: `depositAll` works as expected + function test_U_AAVE2W_04_depositAll_works_as_expected() public { + deal({token: aToken, to: creditAccount, give: 1001}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: aToken, + tokenOut: waToken, + callData: abi.encodeCall(WrappedAToken.deposit, (1000)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.depositAll(); + + assertEq(tokensToEnable, waTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, aTokenMask, "Incorrect tokensToDisable"); + } + + /// @notice U:[AAVE2W-5]: `depositUnderlying` works as expected + function test_U_AAVE2W_05_depositUnderlying_works_as_expected() public { + _executesSwap({ + tokenIn: token, + tokenOut: waToken, + callData: abi.encodeCall(WrappedAToken.depositUnderlying, (123)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.depositUnderlying(123); + + assertEq(tokensToEnable, waTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[AAVE2W-6]: `depositAllUnderlying` works as expected + function test_U_AAVE2W_06_depositAllUnderlying_works_as_expected() public { + deal({token: token, to: creditAccount, give: 1001}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: token, + tokenOut: waToken, + callData: abi.encodeCall(WrappedAToken.depositUnderlying, (1000)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.depositAllUnderlying(); + + assertEq(tokensToEnable, waTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, tokenMask, "Incorrect tokensToDisable"); + } + + /// @notice U:[AAVE2W-7]: `withdraw` works as expected + function test_U_AAVE2W_07_withdraw_works_as_expected() public { + _executesSwap({ + tokenIn: waToken, + tokenOut: aToken, + callData: abi.encodeCall(WrappedAToken.withdraw, (123)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdraw(123); + + assertEq(tokensToEnable, aTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[AAVE2W-8]: `withdrawAll` works as expected + function test_U_AAVE2W_08_withdrawAll_works_as_expected() public { + deal({token: waToken, to: creditAccount, give: 1001}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: waToken, + tokenOut: aToken, + callData: abi.encodeCall(WrappedAToken.withdraw, (1000)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdrawAll(); + + assertEq(tokensToEnable, aTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, waTokenMask, "Incorrect tokensToDisable"); + } + + /// @notice U:[AAVE2W-9]: `withdrawUnderlying` works as expected + function test_U_AAVE2W_09_withdrawUnderlying_works_as_expected() public { + _executesSwap({ + tokenIn: waToken, + tokenOut: token, + callData: abi.encodeCall(WrappedAToken.withdrawUnderlying, (123)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdrawUnderlying(123); + + assertEq(tokensToEnable, tokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[AAVE2W-10]: `withdrawAllUnderlying` works as expected + function test_U_AAVE2W_10_withdrawAllUnderlying_works_as_expected() public { + deal({token: waToken, to: creditAccount, give: 1001}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: waToken, + tokenOut: token, + callData: abi.encodeCall(WrappedAToken.withdrawUnderlying, (1000)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdrawAllUnderlying(); + + assertEq(tokensToEnable, tokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, waTokenMask, "Incorrect tokensToDisable"); + } +} diff --git a/contracts/test/unit/adapters/balancer/BalancerV2VaultAdapter.t.sol b/contracts/test/unit/adapters/balancer/BalancerV2VaultAdapter.t.sol deleted file mode 100644 index 55ea867b..00000000 --- a/contracts/test/unit/adapters/balancer/BalancerV2VaultAdapter.t.sol +++ /dev/null @@ -1,1139 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {PriceFeedParams} from "@gearbox-protocol/oracles-v3/contracts/oracles/PriceFeedParams.sol"; -import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; - -import { - IBalancerV2Vault, - PoolSpecialization, - SingleSwap, - BatchSwapStep, - FundManagement, - SwapKind, - JoinKind, - ExitKind, - JoinPoolRequest, - ExitPoolRequest -} from "../../../../integrations/balancer/IBalancerV2Vault.sol"; -import { - IBalancerV2VaultAdapter, - IBalancerV2VaultAdapterExceptions, - SingleSwapAll, - PoolStatus -} from "../../../../interfaces/balancer/IBalancerV2VaultAdapter.sol"; -import {IAsset} from "../../../../integrations/balancer/IAsset.sol"; -import {BalancerV2VaultAdapter} from "../../../../adapters/balancer/BalancerV2VaultAdapter.sol"; -import {BPTStablePriceFeed} from "@gearbox-protocol/oracles-v3/contracts/oracles/balancer/BPTStablePriceFeed.sol"; -import {BPTWeightedPriceFeed} from "@gearbox-protocol/oracles-v3/contracts/oracles/balancer/BPTWeightedPriceFeed.sol"; -import {IBalancerV2VaultAdapter} from "../../../../interfaces/balancer/IBalancerV2VaultAdapter.sol"; -import {BalancerVaultMock} from "../../../mocks/integrations/BalancerVaultMock.sol"; - -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; - -// TEST -import "../../../lib/constants.sol"; -import {AdapterTestHelper} from "../AdapterTestHelper.sol"; - -bytes32 constant POOL_ID_1 = bytes32(uint256(1)); -bytes32 constant POOL_ID_2 = bytes32(uint256(2)); - -/// @title Balancer V2 Vault adapter test -/// @notice Designed for unit test purposes only -contract BalancerV2VaultAdapterTest is AdapterTestHelper, IBalancerV2VaultAdapterExceptions { - IBalancerV2VaultAdapter public adapter; - BalancerVaultMock public balancerMock; - uint256 public deadline; - - function setUp() public { - _setUp(); - - balancerMock = new BalancerVaultMock(); - - address[] memory assets = new address[](3); - - assets[0] = tokenTestSuite.addressOf(Tokens.DAI); - assets[1] = tokenTestSuite.addressOf(Tokens.USDC); - assets[2] = tokenTestSuite.addressOf(Tokens.USDT); - - balancerMock.addStablePool(POOL_ID_1, assets, 50); - - balancerMock.setRate( - POOL_ID_1, tokenTestSuite.addressOf(Tokens.DAI), tokenTestSuite.addressOf(Tokens.USDC), RAY / 1e12 - ); - - balancerMock.setRate( - POOL_ID_1, tokenTestSuite.addressOf(Tokens.USDT), tokenTestSuite.addressOf(Tokens.DAI), (99 * RAY) / 100 - ); - - balancerMock.setRate( - POOL_ID_1, - tokenTestSuite.addressOf(Tokens.USDT), - tokenTestSuite.addressOf(Tokens.USDC), - (99 * RAY) / (100 * 1e12) - ); - - balancerMock.setDepositRate(POOL_ID_1, tokenTestSuite.addressOf(Tokens.DAI), RAY); - - balancerMock.setDepositRate(POOL_ID_1, tokenTestSuite.addressOf(Tokens.USDC), RAY * 1e12); - - balancerMock.setDepositRate(POOL_ID_1, tokenTestSuite.addressOf(Tokens.USDT), RAY); - - balancerMock.setWithdrawalRate(POOL_ID_1, tokenTestSuite.addressOf(Tokens.DAI), RAY); - - balancerMock.setWithdrawalRate(POOL_ID_1, tokenTestSuite.addressOf(Tokens.USDC), RAY / 1e12); - - balancerMock.setWithdrawalRate(POOL_ID_1, tokenTestSuite.addressOf(Tokens.USDT), RAY); - - tokenTestSuite.mint(Tokens.DAI, address(balancerMock), RAY); - - tokenTestSuite.mint(Tokens.USDC, address(balancerMock), RAY); - - tokenTestSuite.mint(Tokens.USDT, address(balancerMock), RAY); - - uint256[] memory balances = new uint256[](3); - - balances[0] = 10000000 * WAD; - balances[1] = 10000000 * 1e6; - balances[2] = 10000000 * WAD; - - balancerMock.setAssetBalances(POOL_ID_1, balances); - - (address bpt,) = balancerMock.getPool(POOL_ID_1); - - PriceFeedParams[5] memory priceFeeds; - priceFeeds[0] = PriceFeedParams({priceFeed: priceOracle.priceFeeds(assets[0]), stalenessPeriod: 48 hours}); - priceFeeds[1] = PriceFeedParams({priceFeed: priceOracle.priceFeeds(assets[1]), stalenessPeriod: 48 hours}); - priceFeeds[2] = PriceFeedParams({priceFeed: priceOracle.priceFeeds(assets[2]), stalenessPeriod: 48 hours}); - - address bptPf = address( - new BPTStablePriceFeed( - address(addressProvider), - bpt, - priceFeeds - ) - ); - - vm.startPrank(CONFIGURATOR); - - priceOracle.setPriceFeed(bpt, bptPf, 0); - creditConfigurator.addCollateralToken(bpt, 9000); - - vm.stopPrank(); - - assets = new address[](2); - - assets[0] = tokenTestSuite.addressOf(Tokens.DAI); - assets[1] = tokenTestSuite.addressOf(Tokens.WETH); - - uint256[] memory weights = new uint256[](2); - - weights[0] = WAD / 2; - weights[1] = WAD / 2; - - balancerMock.addPool(POOL_ID_2, assets, weights, PoolSpecialization.MINIMAL_SWAP_INFO, 50); - - balancerMock.setRate( - POOL_ID_2, tokenTestSuite.addressOf(Tokens.DAI), tokenTestSuite.addressOf(Tokens.WETH), RAY / DAI_WETH_RATE - ); - - balancerMock.setDepositRate(POOL_ID_2, tokenTestSuite.addressOf(Tokens.DAI), RAY); - - balancerMock.setWithdrawalRate(POOL_ID_2, tokenTestSuite.addressOf(Tokens.DAI), RAY); - - balancerMock.setDepositRate(POOL_ID_2, tokenTestSuite.addressOf(Tokens.WETH), RAY * DAI_WETH_RATE); - - balancerMock.setWithdrawalRate(POOL_ID_2, tokenTestSuite.addressOf(Tokens.WETH), RAY / DAI_WETH_RATE); - - tokenTestSuite.mint(Tokens.DAI, address(balancerMock), RAY); - - tokenTestSuite.mint(Tokens.WETH, address(balancerMock), RAY); - - balances = new uint256[](2); - - balances[0] = 10000000 * WAD; - balances[1] = balances[0] / DAI_WETH_RATE; - - balancerMock.setAssetBalances(POOL_ID_2, balances); - - balancerMock.mintBPT(POOL_ID_2, FRIEND2, 20000000 * WAD); - - (bpt,) = balancerMock.getPool(POOL_ID_2); - - PriceFeedParams[] memory priceFeeds2 = new PriceFeedParams[](2); - - priceFeeds2[0] = PriceFeedParams({priceFeed: priceOracle.priceFeeds(assets[0]), stalenessPeriod: 48 hours}); - priceFeeds2[1] = PriceFeedParams({priceFeed: priceOracle.priceFeeds(assets[1]), stalenessPeriod: 48 hours}); - - bptPf = address( - new BPTWeightedPriceFeed( - address(addressProvider), - address(balancerMock), - bpt, - priceFeeds2 - ) - ); - - vm.startPrank(CONFIGURATOR); - - priceOracle.setPriceFeed(bpt, bptPf, 0); - creditConfigurator.addCollateralToken(bpt, 9000); - - vm.stopPrank(); - - adapter = new BalancerV2VaultAdapter( - address(creditManager), - address(balancerMock) - ); - - vm.prank(CONFIGURATOR); - creditConfigurator.allowAdapter(address(adapter)); - - vm.label(address(adapter), "ADAPTER"); - vm.label(address(balancerMock), "BALANCER_MOCK"); - - vm.startPrank(CONFIGURATOR); - BalancerV2VaultAdapter(address(adapter)).setPoolStatus(POOL_ID_1, PoolStatus.ALLOWED); - BalancerV2VaultAdapter(address(adapter)).setPoolStatus(POOL_ID_2, PoolStatus.ALLOWED); - vm.stopPrank(); - - deadline = _getUniswapDeadline(); - } - - function _standardFundManagement(address creditAccount) internal pure returns (FundManagement memory) { - return FundManagement({ - sender: creditAccount, - fromInternalBalance: false, - recipient: payable(creditAccount), - toInternalBalance: false - }); - } - - function expectBatchSwapStackCalls( - address targetContract, - address borrower, - address creditAccount, - bytes memory callData, - IAsset[] memory assets, - int256[] memory limits - ) internal { - vm.expectEmit(true, false, false, false); - emit StartMultiCall(creditAccount, borrower); - - for (uint256 i = 0; i < assets.length; ++i) { - if (limits[i] > 1) { - vm.expectCall( - address(creditManager), - abi.encodeCall(ICreditManagerV3.approveCreditAccount, (address(assets[i]), type(uint256).max)) - ); - } - } - - vm.expectCall(address(creditManager), abi.encodeCall(ICreditManagerV3.execute, (callData))); - - vm.expectEmit(true, false, false, false); - emit Execute(creditAccount, targetContract); - - for (uint256 i = 0; i < assets.length; ++i) { - if (limits[i] > 1) { - vm.expectCall( - address(creditManager), - abi.encodeCall(ICreditManagerV3.approveCreditAccount, (address(assets[i]), 1)) - ); - } - } - - vm.expectEmit(false, false, false, false); - emit FinishMultiCall(); - } - - function expectJoinPoolStackCalls( - address targetContract, - address borrower, - address creditAccount, - bytes32 poolId, - bytes memory callData, - IAsset[] memory assets, - uint256[] memory maxAmountsIn - ) internal { - vm.expectEmit(true, false, false, false); - emit StartMultiCall(creditAccount, borrower); - - for (uint256 i = 0; i < assets.length; ++i) { - if (maxAmountsIn[i] > 1) { - vm.expectCall( - address(creditManager), - abi.encodeCall(ICreditManagerV3.approveCreditAccount, (address(assets[i]), type(uint256).max)) - ); - } - } - - (address pool,) = balancerMock.getPool(poolId); - - vm.expectCall(address(creditManager), abi.encodeCall(ICreditManagerV3.execute, (callData))); - - vm.expectEmit(true, false, false, false); - emit Execute(creditAccount, targetContract); - - for (uint256 i = 0; i < assets.length; ++i) { - if (maxAmountsIn[i] > 1) { - vm.expectCall( - address(creditManager), - abi.encodeCall(ICreditManagerV3.approveCreditAccount, (address(assets[i]), 1)) - ); - } - } - - vm.expectEmit(false, false, false, false); - emit FinishMultiCall(); - } - - function expectExitPoolStackCalls( - address targetContract, - address borrower, - address creditAccount, - bytes memory callData, - IAsset[] memory assets - ) internal { - vm.expectEmit(true, false, false, false); - emit StartMultiCall(creditAccount, borrower); - - vm.expectCall(address(creditManager), abi.encodeCall(ICreditManagerV3.execute, (callData))); - - vm.expectEmit(true, false, false, false); - emit Execute(creditAccount, targetContract); - - vm.expectEmit(false, false, false, false); - emit FinishMultiCall(); - } - - /// - /// - /// TESTS - /// - /// - - /// @dev [ABV2-1]: swap works as expected - function test_ABV2_01_swap_works_as_expected() public { - setUp(); - (address creditAccount, uint256 initialDAIBalance) = _openTestCreditAccount(); - - FundManagement memory fundManagement = - FundManagement({sender: USER, fromInternalBalance: true, recipient: payable(USER), toInternalBalance: true}); - - SingleSwap memory singleSwapData = SingleSwap({ - poolId: POOL_ID_2, - kind: SwapKind.GIVEN_IN, - assetIn: IAsset(tokenTestSuite.addressOf(Tokens.DAI)), - assetOut: IAsset(tokenTestSuite.addressOf(Tokens.WETH)), - amount: DAI_EXCHANGE_AMOUNT, - userData: "" - }); - - expectAllowance(Tokens.DAI, creditAccount, address(balancerMock), 0); - - expectAllowance(Tokens.WETH, creditAccount, address(balancerMock), 0); - - bytes memory expectedCallData = abi.encodeWithSelector( - IBalancerV2Vault.swap.selector, - singleSwapData, - _standardFundManagement(creditAccount), - DAI_EXCHANGE_AMOUNT / (DAI_WETH_RATE * 2), - deadline - ); - - bytes memory passedCallData = abi.encodeWithSelector( - IBalancerV2Vault.swap.selector, - singleSwapData, - fundManagement, - DAI_EXCHANGE_AMOUNT / (DAI_WETH_RATE * 2), - deadline - ); - - expectMulticallStackCalls( - address(adapter), - address(balancerMock), - USER, - expectedCallData, - tokenTestSuite.addressOf(Tokens.DAI), - tokenTestSuite.addressOf(Tokens.WETH), - true - ); - - executeOneLineMulticall(creditAccount, address(adapter), passedCallData); - - expectBalance(Tokens.DAI, creditAccount, initialDAIBalance - DAI_EXCHANGE_AMOUNT); - - expectBalance(Tokens.WETH, creditAccount, ((DAI_EXCHANGE_AMOUNT / DAI_WETH_RATE) * 9950) / 10000); - - expectAllowance(Tokens.DAI, creditAccount, address(balancerMock), 1); - - expectTokenIsEnabled(creditAccount, Tokens.WETH, true); - } - - /// @dev [ABV2-2]: swapAll works as expected - function test_ABV2_02_swapAll_works_as_expected() public { - setUp(); - (address creditAccount, uint256 initialDAIBalance) = _openTestCreditAccount(); - - SingleSwapAll memory singleSwapAllData = SingleSwapAll({ - poolId: POOL_ID_2, - assetIn: IAsset(tokenTestSuite.addressOf(Tokens.DAI)), - assetOut: IAsset(tokenTestSuite.addressOf(Tokens.WETH)), - userData: "" - }); - - SingleSwap memory expectedSingleSwapData = SingleSwap({ - poolId: POOL_ID_2, - kind: SwapKind.GIVEN_IN, - assetIn: IAsset(tokenTestSuite.addressOf(Tokens.DAI)), - assetOut: IAsset(tokenTestSuite.addressOf(Tokens.WETH)), - amount: initialDAIBalance - 1, - userData: "" - }); - - bytes memory expectedCallData = abi.encodeWithSelector( - IBalancerV2Vault.swap.selector, - expectedSingleSwapData, - _standardFundManagement(creditAccount), - (initialDAIBalance - 1) / (DAI_WETH_RATE * 2), - deadline - ); - - bytes memory passedCallData = abi.encodeWithSelector( - IBalancerV2VaultAdapter.swapAll.selector, singleSwapAllData, RAY / (DAI_WETH_RATE * 2), deadline - ); - - expectAllowance(Tokens.DAI, creditAccount, address(balancerMock), 0); - - expectAllowance(Tokens.WETH, creditAccount, address(balancerMock), 0); - - expectMulticallStackCalls( - address(adapter), - address(balancerMock), - USER, - expectedCallData, - tokenTestSuite.addressOf(Tokens.DAI), - tokenTestSuite.addressOf(Tokens.WETH), - true - ); - - executeOneLineMulticall(creditAccount, address(adapter), passedCallData); - - expectBalance(Tokens.DAI, creditAccount, 1); - - expectBalance(Tokens.WETH, creditAccount, (((initialDAIBalance - 1) / DAI_WETH_RATE) * 9950) / 10000); - - expectAllowance(Tokens.DAI, creditAccount, address(balancerMock), 1); - - expectTokenIsEnabled(creditAccount, Tokens.DAI, false); - expectTokenIsEnabled(creditAccount, Tokens.WETH, true); - } - - /// @dev [ABV2-3]: batchSwap works as expected - function test_ABV2_03_batchSwap_works_as_expected() public { - for (uint256 st = 0; st < 3; ++st) { - // ST is swap type - // 0 = single swap from DAI to WETH - // 1 = parallel swap from DAI to WETH and USDC - // 2 = consecutive swap from WETH to DAI to USDC - - setUp(); - (address creditAccount, uint256 initialDAIBalance) = _openTestCreditAccount(); - - BatchSwapStep[] memory batchSteps; - IAsset[] memory assets; - int256[] memory limits; - - if (st == 0) { - batchSteps = new BatchSwapStep[](1); - assets = new IAsset[](2); - limits = new int256[](2); - - assets[0] = IAsset(tokenTestSuite.addressOf(Tokens.DAI)); - assets[1] = IAsset(tokenTestSuite.addressOf(Tokens.WETH)); - - limits[0] = int256(DAI_EXCHANGE_AMOUNT); - limits[1] = -int256(DAI_EXCHANGE_AMOUNT / (2 * DAI_WETH_RATE)); - - batchSteps[0] = BatchSwapStep({ - poolId: POOL_ID_2, - assetInIndex: 0, - assetOutIndex: 1, - amount: DAI_EXCHANGE_AMOUNT, - userData: "" - }); - } else if (st == 1) { - batchSteps = new BatchSwapStep[](2); - - assets = new IAsset[](3); - limits = new int256[](3); - - assets[0] = IAsset(tokenTestSuite.addressOf(Tokens.DAI)); - assets[1] = IAsset(tokenTestSuite.addressOf(Tokens.WETH)); - assets[2] = IAsset(tokenTestSuite.addressOf(Tokens.USDC)); - - limits[0] = int256(DAI_EXCHANGE_AMOUNT * 2); - limits[1] = -int256(DAI_EXCHANGE_AMOUNT / (2 * DAI_WETH_RATE)); - limits[2] = -int256(DAI_EXCHANGE_AMOUNT / (2 * 1e12)); - - batchSteps[0] = BatchSwapStep({ - poolId: POOL_ID_2, - assetInIndex: 0, - assetOutIndex: 1, - amount: DAI_EXCHANGE_AMOUNT, - userData: "" - }); - - batchSteps[1] = BatchSwapStep({ - poolId: POOL_ID_1, - assetInIndex: 0, - assetOutIndex: 2, - amount: DAI_EXCHANGE_AMOUNT, - userData: "" - }); - } else if (st == 2) { - addCollateral(Tokens.WETH, WETH_ACCOUNT_AMOUNT); - batchSteps = new BatchSwapStep[](2); - - assets = new IAsset[](3); - limits = new int256[](3); - - assets[0] = IAsset(tokenTestSuite.addressOf(Tokens.DAI)); - assets[1] = IAsset(tokenTestSuite.addressOf(Tokens.WETH)); - assets[2] = IAsset(tokenTestSuite.addressOf(Tokens.USDC)); - - limits[0] = 0; - limits[1] = int256(WETH_EXCHANGE_AMOUNT); - limits[2] = (-int256(WETH_EXCHANGE_AMOUNT * DAI_WETH_RATE)) / (2 * 1e12); - - batchSteps[0] = BatchSwapStep({ - poolId: POOL_ID_2, - assetInIndex: 1, - assetOutIndex: 0, - amount: WETH_EXCHANGE_AMOUNT, - userData: "" - }); - - batchSteps[1] = - BatchSwapStep({poolId: POOL_ID_1, assetInIndex: 0, assetOutIndex: 2, amount: 0, userData: ""}); - } - - FundManagement memory fundManagement = FundManagement({ - sender: USER, - fromInternalBalance: true, - recipient: payable(USER), - toInternalBalance: true - }); - - expectAllowance(Tokens.DAI, creditAccount, address(balancerMock), 0); - - expectAllowance(Tokens.WETH, creditAccount, address(balancerMock), 0); - - bytes memory expectedCallData = abi.encodeWithSelector( - IBalancerV2Vault.batchSwap.selector, - SwapKind.GIVEN_IN, - batchSteps, - assets, - _standardFundManagement(creditAccount), - limits, - deadline - ); - - expectBatchSwapStackCalls(address(balancerMock), USER, creditAccount, expectedCallData, assets, limits); - - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeWithSelector( - IBalancerV2Vault.batchSwap.selector, - SwapKind.GIVEN_IN, - batchSteps, - assets, - fundManagement, - limits, - deadline - ) - ); - - if (st == 0) { - expectBalance(Tokens.DAI, creditAccount, initialDAIBalance - DAI_EXCHANGE_AMOUNT); - - expectBalance(Tokens.WETH, creditAccount, ((DAI_EXCHANGE_AMOUNT / DAI_WETH_RATE) * 9950) / 10000); - - expectAllowance(Tokens.DAI, creditAccount, address(balancerMock), 1); - - expectAllowance(Tokens.WETH, creditAccount, address(balancerMock), 0); - - expectTokenIsEnabled(creditAccount, Tokens.WETH, true); - } else if (st == 1) { - expectBalance(Tokens.DAI, creditAccount, initialDAIBalance - 2 * DAI_EXCHANGE_AMOUNT); - - expectBalance(Tokens.WETH, creditAccount, ((DAI_EXCHANGE_AMOUNT / DAI_WETH_RATE) * 9950) / 10000); - - expectBalance(Tokens.USDC, creditAccount, ((DAI_EXCHANGE_AMOUNT / 1e12) * 9950) / 10000); - - expectAllowance(Tokens.DAI, creditAccount, address(balancerMock), 1); - - expectAllowance(Tokens.WETH, creditAccount, address(balancerMock), 0); - - expectAllowance(Tokens.USDC, creditAccount, address(balancerMock), 0); - - expectTokenIsEnabled(creditAccount, Tokens.WETH, true); - expectTokenIsEnabled(creditAccount, Tokens.USDC, true); - } else if (st == 2) { - expectBalance(Tokens.WETH, creditAccount, WETH_ACCOUNT_AMOUNT - WETH_EXCHANGE_AMOUNT); - expectBalance(Tokens.DAI, creditAccount, initialDAIBalance); - - uint256 expectedAmount = (WETH_EXCHANGE_AMOUNT * DAI_WETH_RATE * 9950) / 10000; - expectedAmount = ((expectedAmount / 1e12) * 9950) / 10000; - - expectBalance(Tokens.USDC, creditAccount, expectedAmount); - expectAllowance(Tokens.DAI, creditAccount, address(balancerMock), 0); - - expectAllowance(Tokens.WETH, creditAccount, address(balancerMock), 1); - - expectAllowance(Tokens.USDC, creditAccount, address(balancerMock), 0); - - expectTokenIsEnabled(creditAccount, Tokens.USDC, true); - } - } - } - - /// @dev [ABV2-4]: joinPool works as expected - function test_ABV2_04_joinPool_works_as_expected() public { - setUp(); - (address creditAccount, uint256 initialDAIBalance) = _openTestCreditAccount(); - - addCollateral(Tokens.USDT, DAI_ACCOUNT_AMOUNT); - - IAsset[] memory assets = new IAsset[](3); - - assets[0] = IAsset(tokenTestSuite.addressOf(Tokens.DAI)); - assets[1] = IAsset(tokenTestSuite.addressOf(Tokens.USDC)); - assets[2] = IAsset(tokenTestSuite.addressOf(Tokens.USDT)); - - uint256[] memory maxAmountsIn = new uint256[](3); - - maxAmountsIn[0] = DAI_EXCHANGE_AMOUNT; - maxAmountsIn[1] = 0; - maxAmountsIn[2] = DAI_EXCHANGE_AMOUNT; - - JoinPoolRequest memory request; - - { - bytes memory userData = abi.encode(JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT, maxAmountsIn, DAI_EXCHANGE_AMOUNT); - - request = JoinPoolRequest({ - assets: assets, - maxAmountsIn: maxAmountsIn, - userData: userData, - fromInternalBalance: true - }); - } - - bytes memory passedCallData = - abi.encodeWithSelector(IBalancerV2Vault.joinPool.selector, POOL_ID_1, USER, USER, request); - - request.fromInternalBalance = false; - - bytes memory expectedCallData = - abi.encodeWithSelector(IBalancerV2Vault.joinPool.selector, POOL_ID_1, creditAccount, creditAccount, request); - - request.fromInternalBalance = true; - - expectJoinPoolStackCalls( - address(balancerMock), USER, creditAccount, POOL_ID_1, expectedCallData, assets, maxAmountsIn - ); - - executeOneLineMulticall(creditAccount, address(adapter), passedCallData); - - expectBalance(Tokens.DAI, creditAccount, initialDAIBalance - DAI_EXCHANGE_AMOUNT); - expectBalance(Tokens.USDT, creditAccount, DAI_ACCOUNT_AMOUNT - DAI_EXCHANGE_AMOUNT); - - (address pool,) = balancerMock.getPool(POOL_ID_1); - - expectBalance(pool, creditAccount, DAI_EXCHANGE_AMOUNT * 2); - - expectAllowance(Tokens.DAI, creditAccount, address(balancerMock), 1); - - expectAllowance(Tokens.USDT, creditAccount, address(balancerMock), 1); - - expectTokenIsEnabled(creditAccount, pool, true); - } - - /// @dev [ABV2-5]: joinPoolSingleAsset works as expected - function test_ABV2_05_joinPoolSingleAsset_works_as_expected() public { - setUp(); - (address creditAccount, uint256 initialDAIBalance) = _openTestCreditAccount(); - - expectAllowance(Tokens.DAI, creditAccount, address(balancerMock), 0); - - bytes memory passedCallData = abi.encodeWithSelector( - IBalancerV2VaultAdapter.joinPoolSingleAsset.selector, - POOL_ID_1, - tokenTestSuite.addressOf(Tokens.DAI), - DAI_EXCHANGE_AMOUNT, - DAI_EXCHANGE_AMOUNT / 2 - ); - - IAsset[] memory assets = new IAsset[](3); - - assets[0] = IAsset(tokenTestSuite.addressOf(Tokens.DAI)); - assets[1] = IAsset(tokenTestSuite.addressOf(Tokens.USDC)); - assets[2] = IAsset(tokenTestSuite.addressOf(Tokens.USDT)); - - uint256[] memory maxAmountsIn = new uint256[](3); - - maxAmountsIn[0] = DAI_EXCHANGE_AMOUNT; - - JoinPoolRequest memory request; - - { - bytes memory userData = - abi.encode(JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT, maxAmountsIn, DAI_EXCHANGE_AMOUNT / 2); - - request = JoinPoolRequest({ - assets: assets, - maxAmountsIn: maxAmountsIn, - userData: userData, - fromInternalBalance: false - }); - } - - bytes memory expectedCallData = - abi.encodeWithSelector(IBalancerV2Vault.joinPool.selector, POOL_ID_1, creditAccount, creditAccount, request); - - (address pool,) = balancerMock.getPool(POOL_ID_1); - - expectMulticallStackCalls( - address(adapter), - address(balancerMock), - USER, - expectedCallData, - tokenTestSuite.addressOf(Tokens.DAI), - pool, - true - ); - - executeOneLineMulticall(creditAccount, address(adapter), passedCallData); - - expectBalance(Tokens.DAI, creditAccount, initialDAIBalance - DAI_EXCHANGE_AMOUNT); - - expectBalance(pool, creditAccount, DAI_EXCHANGE_AMOUNT); - - expectAllowance(Tokens.DAI, creditAccount, address(balancerMock), 1); - - expectTokenIsEnabled(creditAccount, pool, true); - } - - /// @dev [ABV2-6]: joinPoolSingleAssetAll works as expected - function test_ABV2_06_joinPoolSingleAssetAll_works_as_expected() public { - setUp(); - (address creditAccount, uint256 initialDAIBalance) = _openTestCreditAccount(); - - expectAllowance(Tokens.DAI, creditAccount, address(balancerMock), 0); - - bytes memory passedCallData = abi.encodeWithSelector( - IBalancerV2VaultAdapter.joinPoolSingleAssetAll.selector, - POOL_ID_1, - tokenTestSuite.addressOf(Tokens.DAI), - RAY / 2 - ); - - IAsset[] memory assets = new IAsset[](3); - - assets[0] = IAsset(tokenTestSuite.addressOf(Tokens.DAI)); - assets[1] = IAsset(tokenTestSuite.addressOf(Tokens.USDC)); - assets[2] = IAsset(tokenTestSuite.addressOf(Tokens.USDT)); - - uint256[] memory maxAmountsIn = new uint256[](3); - - maxAmountsIn[0] = initialDAIBalance - 1; - - JoinPoolRequest memory request; - - { - bytes memory userData = - abi.encode(JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT, maxAmountsIn, (initialDAIBalance - 1) / 2); - - request = JoinPoolRequest({ - assets: assets, - maxAmountsIn: maxAmountsIn, - userData: userData, - fromInternalBalance: false - }); - } - - bytes memory expectedCallData = - abi.encodeWithSelector(IBalancerV2Vault.joinPool.selector, POOL_ID_1, creditAccount, creditAccount, request); - - (address pool,) = balancerMock.getPool(POOL_ID_1); - - expectMulticallStackCalls( - address(adapter), - address(balancerMock), - USER, - expectedCallData, - tokenTestSuite.addressOf(Tokens.DAI), - pool, - true - ); - - executeOneLineMulticall(creditAccount, address(adapter), passedCallData); - - expectBalance(Tokens.DAI, creditAccount, 1); - - expectBalance(pool, creditAccount, initialDAIBalance - 1); - - expectAllowance(Tokens.DAI, creditAccount, address(balancerMock), 1); - - expectTokenIsEnabled(creditAccount, pool, true); - } - - /// @dev [ABV2-7]: exitPool works as expected - function test_ABV2_07_exitPool_works_as_expected() public { - setUp(); - (address creditAccount, uint256 initialDAIBalance) = _openTestCreditAccount(); - - balancerMock.mintBPT(POOL_ID_1, creditAccount, 50000 * WAD); - - IAsset[] memory assets = new IAsset[](3); - - assets[0] = IAsset(tokenTestSuite.addressOf(Tokens.DAI)); - assets[1] = IAsset(tokenTestSuite.addressOf(Tokens.USDC)); - assets[2] = IAsset(tokenTestSuite.addressOf(Tokens.USDT)); - - uint256[] memory minAmountsOut = new uint256[](3); - - minAmountsOut[0] = 9000 * WAD; - minAmountsOut[1] = 9000 * 1e6; - minAmountsOut[2] = 9000 * WAD; - - ExitPoolRequest memory request; - - { - bytes memory userData = abi.encode(ExitKind.EXACT_BPT_IN_FOR_TOKENS_OUT, 30000 * WAD); - - request = ExitPoolRequest({ - assets: assets, - minAmountsOut: minAmountsOut, - userData: userData, - toInternalBalance: true - }); - } - - bytes memory passedCallData = - abi.encodeWithSelector(IBalancerV2Vault.exitPool.selector, POOL_ID_1, USER, USER, request); - - request.toInternalBalance = false; - - bytes memory expectedCallData = - abi.encodeWithSelector(IBalancerV2Vault.exitPool.selector, POOL_ID_1, creditAccount, creditAccount, request); - - request.toInternalBalance = true; - - expectExitPoolStackCalls(address(balancerMock), USER, creditAccount, expectedCallData, assets); - - executeOneLineMulticall(creditAccount, address(adapter), passedCallData); - - expectBalance(Tokens.DAI, creditAccount, initialDAIBalance + 10000 * WAD); - expectBalance(Tokens.USDT, creditAccount, 10000 * WAD); - expectBalance(Tokens.USDC, creditAccount, 10000 * 1e6); - - (address pool,) = balancerMock.getPool(POOL_ID_1); - - expectBalance(pool, creditAccount, 20000 * WAD); - - expectTokenIsEnabled(creditAccount, Tokens.DAI, true); - expectTokenIsEnabled(creditAccount, Tokens.USDC, true); - expectTokenIsEnabled(creditAccount, Tokens.USDT, true); - } - - /// @dev [ABV2-8]: exitPoolSingleAsset works as expected - function test_ABV2_08_exitPoolSingleAsset_works_as_expected() public { - setUp(); - (address creditAccount,) = _openTestCreditAccount(); - - balancerMock.mintBPT(POOL_ID_1, creditAccount, DAI_ACCOUNT_AMOUNT); - - bytes memory passedCallData = abi.encodeWithSelector( - IBalancerV2VaultAdapter.exitPoolSingleAsset.selector, - POOL_ID_1, - tokenTestSuite.addressOf(Tokens.USDT), - DAI_EXCHANGE_AMOUNT, - DAI_EXCHANGE_AMOUNT / 2 - ); - - IAsset[] memory assets = new IAsset[](3); - - assets[0] = IAsset(tokenTestSuite.addressOf(Tokens.DAI)); - assets[1] = IAsset(tokenTestSuite.addressOf(Tokens.USDC)); - assets[2] = IAsset(tokenTestSuite.addressOf(Tokens.USDT)); - - uint256[] memory minAmountsOut = new uint256[](3); - - minAmountsOut[2] = DAI_EXCHANGE_AMOUNT / 2; - - ExitPoolRequest memory request; - - { - bytes memory userData = abi.encode(ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT, DAI_EXCHANGE_AMOUNT, 2); - - request = ExitPoolRequest({ - assets: assets, - minAmountsOut: minAmountsOut, - userData: userData, - toInternalBalance: false - }); - } - - bytes memory expectedCallData = - abi.encodeWithSelector(IBalancerV2Vault.exitPool.selector, POOL_ID_1, creditAccount, creditAccount, request); - - (address pool,) = balancerMock.getPool(POOL_ID_1); - - expectMulticallStackCalls( - address(adapter), - address(balancerMock), - USER, - expectedCallData, - pool, - tokenTestSuite.addressOf(Tokens.USDT), - false - ); - - executeOneLineMulticall(creditAccount, address(adapter), passedCallData); - - expectBalance(Tokens.USDT, creditAccount, DAI_EXCHANGE_AMOUNT); - - expectBalance(pool, creditAccount, DAI_ACCOUNT_AMOUNT - DAI_EXCHANGE_AMOUNT); - - expectTokenIsEnabled(creditAccount, Tokens.USDT, true); - } - - /// @dev [ABV2-9]: exitPoolSingleAssetAll works as expected - function test_ABV2_09_exitPoolSingleAssetAll_works_as_expected() public { - setUp(); - (address creditAccount,) = _openTestCreditAccount(); - - balancerMock.mintBPT(POOL_ID_1, creditAccount, DAI_ACCOUNT_AMOUNT); - - bytes memory passedCallData = abi.encodeWithSelector( - IBalancerV2VaultAdapter.exitPoolSingleAssetAll.selector, - POOL_ID_1, - tokenTestSuite.addressOf(Tokens.USDT), - RAY / 2 - ); - - IAsset[] memory assets = new IAsset[](3); - - assets[0] = IAsset(tokenTestSuite.addressOf(Tokens.DAI)); - assets[1] = IAsset(tokenTestSuite.addressOf(Tokens.USDC)); - assets[2] = IAsset(tokenTestSuite.addressOf(Tokens.USDT)); - - uint256[] memory minAmountsOut = new uint256[](3); - - minAmountsOut[2] = (DAI_ACCOUNT_AMOUNT - 1) / 2; - - ExitPoolRequest memory request; - - { - bytes memory userData = abi.encode(ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT, DAI_ACCOUNT_AMOUNT - 1, 2); - - request = ExitPoolRequest({ - assets: assets, - minAmountsOut: minAmountsOut, - userData: userData, - toInternalBalance: false - }); - } - - bytes memory expectedCallData = - abi.encodeWithSelector(IBalancerV2Vault.exitPool.selector, POOL_ID_1, creditAccount, creditAccount, request); - - (address pool,) = balancerMock.getPool(POOL_ID_1); - - expectMulticallStackCalls( - address(adapter), - address(balancerMock), - USER, - expectedCallData, - pool, - tokenTestSuite.addressOf(Tokens.USDT), - false - ); - - executeOneLineMulticall(creditAccount, address(adapter), passedCallData); - - expectBalance(Tokens.USDT, creditAccount, DAI_ACCOUNT_AMOUNT - 1); - - expectBalance(pool, creditAccount, 1); - - expectTokenIsEnabled(creditAccount, Tokens.USDT, true); - } - - /// @dev [ABV2-10]: swap and joinPool functions revert when the pool doesn't have appropriate status - function test_ABV2_10_swap_join_revert_on_poolId_status() public { - (address creditAccount, uint256 initialDAIBalance) = _openTestCreditAccount(); - - vm.startPrank(CONFIGURATOR); - BalancerV2VaultAdapter(address(adapter)).setPoolStatus(POOL_ID_1, PoolStatus.SWAP_ONLY); - BalancerV2VaultAdapter(address(adapter)).setPoolStatus(POOL_ID_2, PoolStatus.NOT_ALLOWED); - vm.stopPrank(); - - { - FundManagement memory fundManagement = FundManagement({ - sender: USER, - fromInternalBalance: true, - recipient: payable(USER), - toInternalBalance: true - }); - - SingleSwap memory singleSwapData = SingleSwap({ - poolId: POOL_ID_2, - kind: SwapKind.GIVEN_IN, - assetIn: IAsset(tokenTestSuite.addressOf(Tokens.DAI)), - assetOut: IAsset(tokenTestSuite.addressOf(Tokens.WETH)), - amount: DAI_EXCHANGE_AMOUNT, - userData: "" - }); - - bytes memory passedCallData = abi.encodeWithSelector( - IBalancerV2Vault.swap.selector, - singleSwapData, - fundManagement, - DAI_EXCHANGE_AMOUNT / (DAI_WETH_RATE * 2), - deadline - ); - - vm.expectRevert(PoolNotSupportedException.selector); - executeOneLineMulticall(creditAccount, address(adapter), passedCallData); - } - - { - SingleSwapAll memory singleSwapAllData = SingleSwapAll({ - poolId: POOL_ID_2, - assetIn: IAsset(tokenTestSuite.addressOf(Tokens.DAI)), - assetOut: IAsset(tokenTestSuite.addressOf(Tokens.WETH)), - userData: "" - }); - - bytes memory passedCallData = abi.encodeWithSelector( - IBalancerV2VaultAdapter.swapAll.selector, singleSwapAllData, RAY / (DAI_WETH_RATE * 2), deadline - ); - - vm.expectRevert(PoolNotSupportedException.selector); - executeOneLineMulticall(creditAccount, address(adapter), passedCallData); - } - - { - BatchSwapStep[] memory batchSteps = new BatchSwapStep[](2); - - IAsset[] memory assets = new IAsset[](3); - int256[] memory limits = new int256[](3); - - assets[0] = IAsset(tokenTestSuite.addressOf(Tokens.DAI)); - assets[1] = IAsset(tokenTestSuite.addressOf(Tokens.WETH)); - assets[2] = IAsset(tokenTestSuite.addressOf(Tokens.USDC)); - - limits[0] = 0; - limits[1] = int256(WETH_EXCHANGE_AMOUNT); - limits[2] = (-int256(WETH_EXCHANGE_AMOUNT * DAI_WETH_RATE)) / (2 * 1e12); - - batchSteps[0] = BatchSwapStep({ - poolId: POOL_ID_2, - assetInIndex: 1, - assetOutIndex: 0, - amount: WETH_EXCHANGE_AMOUNT, - userData: "" - }); - - batchSteps[1] = - BatchSwapStep({poolId: POOL_ID_1, assetInIndex: 0, assetOutIndex: 2, amount: 0, userData: ""}); - - FundManagement memory fundManagement = FundManagement({ - sender: USER, - fromInternalBalance: true, - recipient: payable(USER), - toInternalBalance: true - }); - - vm.expectRevert(PoolNotSupportedException.selector); - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeWithSelector( - IBalancerV2Vault.batchSwap.selector, - SwapKind.GIVEN_IN, - batchSteps, - assets, - fundManagement, - limits, - deadline - ) - ); - } - - { - IAsset[] memory assets = new IAsset[](3); - - assets[0] = IAsset(tokenTestSuite.addressOf(Tokens.DAI)); - assets[1] = IAsset(tokenTestSuite.addressOf(Tokens.USDC)); - assets[2] = IAsset(tokenTestSuite.addressOf(Tokens.USDT)); - - uint256[] memory maxAmountsIn = new uint256[](3); - - maxAmountsIn[0] = DAI_EXCHANGE_AMOUNT; - maxAmountsIn[1] = 0; - maxAmountsIn[2] = DAI_EXCHANGE_AMOUNT; - - JoinPoolRequest memory request; - - { - bytes memory userData = - abi.encode(JoinKind.EXACT_TOKENS_IN_FOR_BPT_OUT, maxAmountsIn, DAI_EXCHANGE_AMOUNT); - - request = JoinPoolRequest({ - assets: assets, - maxAmountsIn: maxAmountsIn, - userData: userData, - fromInternalBalance: true - }); - } - - bytes memory passedCallData = - abi.encodeWithSelector(IBalancerV2Vault.joinPool.selector, POOL_ID_1, USER, USER, request); - - vm.expectRevert(PoolNotSupportedException.selector); - executeOneLineMulticall(creditAccount, address(adapter), passedCallData); - } - - { - bytes memory passedCallData = abi.encodeWithSelector( - IBalancerV2VaultAdapter.joinPoolSingleAsset.selector, - POOL_ID_1, - tokenTestSuite.addressOf(Tokens.DAI), - DAI_EXCHANGE_AMOUNT, - DAI_EXCHANGE_AMOUNT / 2 - ); - - vm.expectRevert(PoolNotSupportedException.selector); - executeOneLineMulticall(creditAccount, address(adapter), passedCallData); - } - - { - bytes memory passedCallData = abi.encodeWithSelector( - IBalancerV2VaultAdapter.joinPoolSingleAssetAll.selector, - POOL_ID_1, - tokenTestSuite.addressOf(Tokens.DAI), - RAY / 2 - ); - - vm.expectRevert(PoolNotSupportedException.selector); - executeOneLineMulticall(creditAccount, address(adapter), passedCallData); - } - } -} diff --git a/contracts/test/unit/adapters/balancer/BalancerV2VaultAdapter.unit.t.sol b/contracts/test/unit/adapters/balancer/BalancerV2VaultAdapter.unit.t.sol new file mode 100644 index 00000000..dcd71d7a --- /dev/null +++ b/contracts/test/unit/adapters/balancer/BalancerV2VaultAdapter.unit.t.sol @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {BalancerV2VaultAdapter} from "../../../../adapters/balancer/BalancerV2VaultAdapter.sol"; +import { + IAsset, + IBalancerV2Vault, + SwapKind, + SingleSwap, + FundManagement, + BatchSwapStep, + JoinPoolRequest, + ExitPoolRequest +} from "../../../../integrations/balancer/IBalancerV2Vault.sol"; +import { + IBalancerV2VaultAdapterEvents, + IBalancerV2VaultAdapterExceptions, + PoolStatus, + SingleSwapAll +} from "../../../../interfaces/balancer/IBalancerV2VaultAdapter.sol"; + +import {VaultMock} from "../../../mocks/integrations/balancer/VaultMock.sol"; + +import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; + +/// @title Balancer v2 vault adapter unit test +/// @notice U:[BAL2]: Unit tests for Balancer v2 vault adapter +contract BalancerV2VaultAdapterUnitTest is + AdapterUnitTestHelper, + IBalancerV2VaultAdapterEvents, + IBalancerV2VaultAdapterExceptions +{ + BalancerV2VaultAdapter adapter; + + VaultMock vault; + + bytes32 poolId = "POOL ID"; + bytes32 pool2Id = "POOL 2 ID"; + + function setUp() public { + _setUp(); + + vault = new VaultMock(); + vault.setPoolData(poolId, tokens[0], _assets(0, 1, 2, 3)); + vault.setPoolData(pool2Id, tokens[4], _assets(5, 6)); + + adapter = new BalancerV2VaultAdapter(address(creditManager), address(vault)); + } + + /// @notice U:[BAL2-1]: Constructor works as expected + function test_U_BAL2_01_constructor_works_as_expected() public { + assertEq(adapter.creditManager(), address(creditManager), "Incorrect creditManager"); + assertEq(adapter.addressProvider(), address(addressProvider), "Incorrect addressProvider"); + assertEq(adapter.targetContract(), address(vault), "Incorrect targetContract"); + } + + /// @notice U:[BAL2-2]: Wrapper functions revert on wrong caller + function test_U_BAL2_02_wrapper_functions_revert_on_wrong_caller() public { + IAsset asset; + SwapKind swapKind; + IAsset[] memory assets; + int256[] memory limits; + SingleSwap memory singleSwap; + SingleSwapAll memory singleSwapAll; + FundManagement memory fundManagement; + BatchSwapStep[] memory batchSwapSteps; + JoinPoolRequest memory joinPoolRequest; + ExitPoolRequest memory exitPoolRequest; + + _revertsOnNonFacadeCaller(); + adapter.swap(singleSwap, fundManagement, 0, 0); + + _revertsOnNonFacadeCaller(); + adapter.swapAll(singleSwapAll, 0, 0); + + _revertsOnNonFacadeCaller(); + adapter.batchSwap(swapKind, batchSwapSteps, assets, fundManagement, limits, 0); + + _revertsOnNonFacadeCaller(); + adapter.joinPool(bytes32(0), address(0), address(0), joinPoolRequest); + + _revertsOnNonFacadeCaller(); + adapter.joinPoolSingleAsset(bytes32(0), asset, 0, 0); + + _revertsOnNonFacadeCaller(); + adapter.joinPoolSingleAssetAll(bytes32(0), asset, 0); + + _revertsOnNonFacadeCaller(); + adapter.exitPool(bytes32(0), address(0), payable(0), exitPoolRequest); + + _revertsOnNonFacadeCaller(); + adapter.exitPoolSingleAsset(bytes32(0), asset, 0, 0); + + _revertsOnNonFacadeCaller(); + adapter.exitPoolSingleAssetAll(bytes32(0), asset, 0); + } + + // ----- // + // SWAPS // + // ----- // + + /// @notice U:[BAL2-3]: `swap` works as expected + function test_U_BAL2_03_swap_works_as_expected() public { + SingleSwap memory singleSwap = SingleSwap({ + poolId: poolId, + kind: SwapKind.GIVEN_IN, + assetIn: _asset(1), + assetOut: _asset(2), + amount: 1000, + userData: "DUMMY DATA" + }); + + vm.expectRevert(PoolNotSupportedException.selector); + vm.prank(creditFacade); + adapter.swap(singleSwap, _getFundManagement(address(0)), 0, 0); + + vm.prank(configurator); + adapter.setPoolStatus(poolId, PoolStatus.ALLOWED); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[1], + tokenOut: tokens[2], + callData: abi.encodeCall(IBalancerV2Vault.swap, (singleSwap, _getFundManagement(creditAccount), 500, 456)), + requiresApproval: true, + validatesTokens: true + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = + adapter.swap(singleSwap, _getFundManagement(address(0)), 500, 456); + + assertEq(tokensToEnable, 4, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[BAL2-4]: `swapAll` works as expected + function test_U_BAL2_04_swapAll_works_as_expected() public { + deal({token: tokens[1], to: creditAccount, give: 1001}); + + SingleSwapAll memory singleSwapAll = + SingleSwapAll({poolId: poolId, assetIn: _asset(1), assetOut: _asset(2), userData: "DUMMY DATA"}); + + SingleSwap memory singleSwap = SingleSwap({ + poolId: poolId, + kind: SwapKind.GIVEN_IN, + assetIn: _asset(1), + assetOut: _asset(2), + amount: 1000, + userData: "DUMMY DATA" + }); + + vm.expectRevert(PoolNotSupportedException.selector); + vm.prank(creditFacade); + adapter.swapAll(singleSwapAll, 0, 0); + + vm.prank(configurator); + adapter.setPoolStatus(poolId, PoolStatus.ALLOWED); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[1], + tokenOut: tokens[2], + callData: abi.encodeCall(IBalancerV2Vault.swap, (singleSwap, _getFundManagement(creditAccount), 500, 456)), + requiresApproval: true, + validatesTokens: true + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.swapAll(singleSwapAll, 0.5e27, 456); + + assertEq(tokensToEnable, 4, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 2, "Incorrect tokensToDisable"); + } + + /// @notice U:[BAL2-5]: `batchSwap` works as expected + function test_U_BAL2_05_batchSwap_works_as_expected() public { + BatchSwapStep[] memory swaps = new BatchSwapStep[](2); + swaps[0] = + BatchSwapStep({poolId: poolId, assetInIndex: 0, assetOutIndex: 1, amount: 1000, userData: "DUMMY_DATA"}); + swaps[1] = + BatchSwapStep({poolId: pool2Id, assetInIndex: 2, assetOutIndex: 3, amount: 1000, userData: "DUMMY_DATA"}); + + IAsset[] memory assets = _assets(1, 3, 5, 6); + int256[] memory limits = new int256[](4); + limits[0] = 1000; + limits[1] = -500; + limits[2] = 1000; + limits[3] = -500; + + creditManager.setExecuteResult(abi.encode(limits)); + + vm.expectRevert(PoolNotSupportedException.selector); + vm.prank(creditFacade); + adapter.batchSwap(SwapKind.GIVEN_IN, swaps, assets, _getFundManagement(address(0)), limits, 456); + + vm.startPrank(configurator); + adapter.setPoolStatus(poolId, PoolStatus.ALLOWED); + adapter.setPoolStatus(pool2Id, PoolStatus.SWAP_ONLY); + vm.stopPrank(); + + address[] memory tokensToApprove = new address[](2); + tokensToApprove[0] = tokens[1]; + tokensToApprove[1] = tokens[5]; + _readsActiveAccount(); + _executesCall({ + tokensToApprove: tokensToApprove, + tokensToValidate: new address[](0), + callData: abi.encodeCall( + IBalancerV2Vault.batchSwap, + (SwapKind.GIVEN_IN, swaps, assets, _getFundManagement(creditAccount), limits, 456) + ) + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = + adapter.batchSwap(SwapKind.GIVEN_IN, swaps, assets, _getFundManagement(address(0)), limits, 456); + assertEq(tokensToEnable, 8 + 64, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + // ----- // + // JOINS // + // ----- // + + /// @notice U:[BAL2-6]: `joinPool` works as expected + function test_U_BAL2_06_joinPool_works_as_expected() public { + JoinPoolRequest memory request; + request.assets = _assets(0, 1, 2, 3); + request.maxAmountsIn = new uint256[](4); + request.maxAmountsIn[1] = 500; + request.maxAmountsIn[3] = 800; + request.userData = "DUMMY DATA"; + request.fromInternalBalance = false; + + vm.expectRevert(PoolNotSupportedException.selector); + vm.prank(creditFacade); + adapter.joinPool(poolId, address(0), address(0), request); + + vm.prank(configurator); + adapter.setPoolStatus(poolId, PoolStatus.SWAP_ONLY); + + vm.expectRevert(PoolNotSupportedException.selector); + vm.prank(creditFacade); + adapter.joinPool(poolId, address(0), address(0), request); + + vm.prank(configurator); + adapter.setPoolStatus(poolId, PoolStatus.ALLOWED); + + address[] memory tokensToApprove = new address[](2); + tokensToApprove[0] = tokens[1]; + tokensToApprove[1] = tokens[3]; + address[] memory tokensToValidate = new address[](1); + tokensToValidate[0] = tokens[0]; + + _readsActiveAccount(); + _executesCall({ + tokensToApprove: tokensToApprove, + tokensToValidate: tokensToValidate, + callData: abi.encodeCall(IBalancerV2Vault.joinPool, (poolId, creditAccount, creditAccount, request)) + }); + + vm.prank(creditFacade); + request.fromInternalBalance = true; + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.joinPool(poolId, address(0), address(0), request); + assertEq(tokensToEnable, 1, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[BAL2-7]: `joinPoolSingleAsset` works as expected + function test_U_BAL2_07_joinPoolSingleAsset_works_as_expected() public { + vm.expectRevert(PoolNotSupportedException.selector); + vm.prank(creditFacade); + adapter.joinPoolSingleAsset(poolId, _asset(2), 1000, 500); + + vm.prank(configurator); + adapter.setPoolStatus(poolId, PoolStatus.SWAP_ONLY); + + vm.expectRevert(PoolNotSupportedException.selector); + vm.prank(creditFacade); + adapter.joinPoolSingleAsset(poolId, _asset(2), 1000, 500); + + vm.prank(configurator); + adapter.setPoolStatus(poolId, PoolStatus.ALLOWED); + + JoinPoolRequest memory request; + request.assets = _assets(0, 1, 2, 3); + request.maxAmountsIn = new uint256[](4); + request.maxAmountsIn[2] = 1000; + + uint256[] memory maxAmountsInWithoutBPT = new uint256[](3); + maxAmountsInWithoutBPT[1] = 1000; + request.userData = abi.encode(uint256(1), maxAmountsInWithoutBPT, 500); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[2], + tokenOut: tokens[0], + callData: abi.encodeCall(IBalancerV2Vault.joinPool, (poolId, creditAccount, creditAccount, request)), + requiresApproval: true, + validatesTokens: true + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.joinPoolSingleAsset(poolId, _asset(2), 1000, 500); + assertEq(tokensToEnable, 1, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[BAL2-8]: `joinPoolSingleAssetAll` works as expected + function test_U_BAL2_08_joinPoolSingleAssetAll_works_as_expected() public { + deal({token: tokens[2], to: creditAccount, give: 1001}); + + vm.expectRevert(PoolNotSupportedException.selector); + vm.prank(creditFacade); + adapter.joinPoolSingleAssetAll(poolId, _asset(2), 0.5e27); + + vm.prank(configurator); + adapter.setPoolStatus(poolId, PoolStatus.SWAP_ONLY); + + vm.expectRevert(PoolNotSupportedException.selector); + vm.prank(creditFacade); + adapter.joinPoolSingleAssetAll(poolId, _asset(2), 0.5e27); + + vm.prank(configurator); + adapter.setPoolStatus(poolId, PoolStatus.ALLOWED); + + JoinPoolRequest memory request; + request.assets = _assets(0, 1, 2, 3); + request.maxAmountsIn = new uint256[](4); + request.maxAmountsIn[2] = 1000; + + uint256[] memory maxAmountsInWithoutBPT = new uint256[](3); + maxAmountsInWithoutBPT[1] = 1000; + request.userData = abi.encode(uint256(1), maxAmountsInWithoutBPT, 500); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[2], + tokenOut: tokens[0], + callData: abi.encodeCall(IBalancerV2Vault.joinPool, (poolId, creditAccount, creditAccount, request)), + requiresApproval: true, + validatesTokens: true + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.joinPoolSingleAssetAll(poolId, _asset(2), 0.5e27); + assertEq(tokensToEnable, 1, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 4, "Incorrect tokensToDisable"); + } + + // ----- // + // EXITS // + // ----- // + + /// @notice U:[BAL2-9]: `exitPool` works as expected + function test_U_BAL2_09_exitPool_works_as_expected() public { + deal({token: tokens[1], to: creditAccount, give: 2}); + deal({token: tokens[3], to: creditAccount, give: 2}); + + ExitPoolRequest memory request; + request.assets = _assets(0, 1, 2, 3); + + address[] memory tokensToValidate = new address[](1); + tokensToValidate[0] = tokens[0]; + + _readsActiveAccount(); + _executesCall({ + tokensToApprove: new address[](0), + tokensToValidate: tokensToValidate, + callData: abi.encodeCall(IBalancerV2Vault.exitPool, (poolId, creditAccount, payable(creditAccount), request)) + }); + + vm.prank(creditFacade); + request.toInternalBalance = true; + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.exitPool(poolId, address(0), payable(0), request); + assertEq(tokensToEnable, 2 + 8, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[BAL2-10]: `exitPoolSingleAsset` works as expected + function test_U_BAL2_10_exitPoolSingleAsset_works_as_expected() public { + ExitPoolRequest memory request; + request.assets = _assets(0, 1, 2, 3); + request.minAmountsOut = new uint256[](4); + request.minAmountsOut[2] = 500; + request.userData = abi.encode(uint256(0), 1000, 1); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[0], + tokenOut: tokens[2], + callData: abi.encodeCall(IBalancerV2Vault.exitPool, (poolId, creditAccount, payable(creditAccount), request)), + requiresApproval: false, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.exitPoolSingleAsset(poolId, _asset(2), 1000, 500); + + assertEq(tokensToEnable, 4, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[BAL2-11]: `exitPoolSingleAssetAll` works as expected + function test_U_BAL2_11_exitPoolSingleAssetAll_works_as_expected() public { + deal({token: tokens[0], to: creditAccount, give: 1001}); + + ExitPoolRequest memory request; + request.assets = _assets(0, 1, 2, 3); + request.minAmountsOut = new uint256[](4); + request.minAmountsOut[2] = 500; + request.userData = abi.encode(uint256(0), 1000, 1); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[0], + tokenOut: tokens[2], + callData: abi.encodeCall(IBalancerV2Vault.exitPool, (poolId, creditAccount, payable(creditAccount), request)), + requiresApproval: false, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.exitPoolSingleAssetAll(poolId, _asset(2), 0.5e27); + + assertEq(tokensToEnable, 4, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 1, "Incorrect tokensToDisable"); + } + + // ------- // + // CONFIG // + // ------ // + + /// @notice U:[BAL2-12]: `setPoolStatus` works as expected + function test_U_BAL2_12_setPoolStatus_works_as_expected() public { + _revertsOnNonConfiguratorCaller(); + adapter.setPoolStatus(poolId, PoolStatus.ALLOWED); + + vm.expectEmit(true, false, false, true); + emit SetPoolStatus(poolId, PoolStatus.ALLOWED); + + vm.prank(configurator); + adapter.setPoolStatus(poolId, PoolStatus.ALLOWED); + assertEq(uint256(adapter.poolStatus(poolId)), uint256(PoolStatus.ALLOWED)); + } + + // ------- // + // HELPERS // + // ------- // + + function _asset(uint256 index) internal view returns (IAsset) { + return IAsset(tokens[index]); + } + + function _assets() internal view returns (IAsset[] memory) {} + + function _assets(uint256 i) internal view returns (IAsset[] memory assets) { + assets = new IAsset[](1); + assets[0] = _asset(i); + } + + function _assets(uint256 i1, uint256 i2) internal view returns (IAsset[] memory assets) { + assets = new IAsset[](2); + assets[0] = _asset(i1); + assets[1] = _asset(i2); + } + + function _assets(uint256 i1, uint256 i2, uint256 i3) internal view returns (IAsset[] memory assets) { + assets = new IAsset[](3); + assets[0] = _asset(i1); + assets[1] = _asset(i2); + assets[2] = _asset(i3); + } + + function _assets(uint256 i1, uint256 i2, uint256 i3, uint256 i4) internal view returns (IAsset[] memory assets) { + assets = new IAsset[](4); + assets[0] = _asset(i1); + assets[1] = _asset(i2); + assets[2] = _asset(i3); + assets[3] = _asset(i4); + } + + function _getFundManagement(address creditAccount) internal pure returns (FundManagement memory) { + return FundManagement({ + sender: creditAccount, + fromInternalBalance: false, + recipient: payable(creditAccount), + toInternalBalance: false + }); + } +} diff --git a/contracts/test/unit/adapters/compound/CompoundTestHelper.sol b/contracts/test/unit/adapters/compound/CompoundTestHelper.sol deleted file mode 100644 index d3167aef..00000000 --- a/contracts/test/unit/adapters/compound/CompoundTestHelper.sol +++ /dev/null @@ -1,127 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import { - CONFIGURATOR, - DAI_ACCOUNT_AMOUNT, - USDC_EXCHANGE_AMOUNT, - USER, - WETH_EXCHANGE_AMOUNT -} from "@gearbox-protocol/core-v3/contracts/test/lib/constants.sol"; - -import {CEtherGateway} from "../../../../helpers/compound/CompoundV2_CEtherGateway.sol"; - -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; -import {CErc20Mock} from "../../../mocks/integrations/compound/CErc20Mock.sol"; -import {CEtherMock} from "../../../mocks/integrations/compound/CEtherMock.sol"; -import {AdapterTestHelper} from "../AdapterTestHelper.sol"; -import {CompoundV2PriceFeed} from "@gearbox-protocol/oracles-v3/contracts/oracles/compound/CompoundV2PriceFeed.sol"; - -contract CompoundTestHelper is AdapterTestHelper { - // underlying tokens - - address usdc; - address dai; - - // cTokens - address ceth; - address cusdc; - address cdai; - - // cETH gateway - CEtherGateway gateway; - - function _setupCompoundSuite() internal { - _setUp(); - - tokenTestSuite.mint(Tokens.DAI, USER, DAI_ACCOUNT_AMOUNT); - - usdc = tokenTestSuite.addressOf(Tokens.USDC); - weth = tokenTestSuite.addressOf(Tokens.WETH); - dai = tokenTestSuite.addressOf(Tokens.DAI); - - // setup cTokens, top them up with some liquidity - ceth = address(new CEtherMock(0.02 ether, 0.05 ether)); - cusdc = address(new CErc20Mock(usdc, 0.02 ether, 0.025 ether)); - cdai = address(new CErc20Mock(dai, 0.02 ether, 0.02 ether)); - vm.deal(ceth, 100e18); - tokenTestSuite.mint(Tokens.USDC, cusdc, 100_000e6); - tokenTestSuite.mint(Tokens.DAI, cdai, 100_000e18); - - // setup cETH gateway - gateway = new CEtherGateway(weth, address(ceth)); - - vm.label(ceth, "cETH"); - vm.label(cusdc, "cUSDC"); - vm.label(cdai, "cDAI"); - vm.label(address(gateway), "cETH_GATEWAY"); - - vm.startPrank(CONFIGURATOR); - // add price feeds for cTokens to the oracle - priceOracle.setPriceFeed( - ceth, - address(new CompoundV2PriceFeed(address(addressProvider), ceth, priceOracle.priceFeeds(weth), 48 hours)), - 0 - ); - priceOracle.setPriceFeed( - cusdc, - address(new CompoundV2PriceFeed(address(addressProvider), cusdc, priceOracle.priceFeeds(usdc), 48 hours)), - 0 - ); - - // enable cTokens as collateral tokens in the credit manager - creditConfigurator.addCollateralToken(ceth, 8300); - creditConfigurator.addCollateralToken(cusdc, 8300); - vm.stopPrank(); - } - - function _openAccountWithToken(Tokens token) - internal - returns (address creditAccount, address underlying, address cToken, uint256 balance) - { - (creditAccount,) = _openTestCreditAccount(); - (underlying, cToken, balance) = _tokenInfo(token); - - tokenTestSuite.mint(underlying, USER, balance); - - tokenTestSuite.approve(underlying, USER, address(creditManager), balance); - vm.prank(USER); - // creditFacade.addCollateral(USER, underlying, balance); - } - - function _openAccountWithCToken(Tokens token) - internal - returns (address creditAccount, address underlying, address cToken, uint256 balance) - { - (creditAccount,) = _openTestCreditAccount(); - uint256 amount; - (underlying, cToken, amount) = _tokenInfo(token); - - tokenTestSuite.mint(underlying, USER, amount); - if (token == Tokens.WETH) { - tokenTestSuite.approve(underlying, USER, address(gateway), amount); - vm.prank(USER); - gateway.mint(amount); - } else { - tokenTestSuite.approve(underlying, USER, cToken, amount); - vm.prank(USER); - CErc20Mock(cToken).mint(amount); - } - - balance = tokenTestSuite.balanceOf(cToken, USER); - tokenTestSuite.approve(cToken, USER, address(creditManager), balance); - vm.prank(USER); - // creditFacade.addCollateral(USER, cToken, balance); - } - - function _tokenInfo(Tokens token) internal view returns (address underlying, address cToken, uint256 amount) { - if (token == Tokens.USDC) { - return (usdc, cusdc, USDC_EXCHANGE_AMOUNT); - } else if (token == Tokens.WETH) { - return (weth, ceth, WETH_EXCHANGE_AMOUNT); - } - revert("Token must be one of USDC or WETH"); - } -} diff --git a/contracts/test/unit/adapters/compound/CompoundV2_CErc20Adapter.t.sol b/contracts/test/unit/adapters/compound/CompoundV2_CErc20Adapter.t.sol deleted file mode 100644 index 2eec0f3b..00000000 --- a/contracts/test/unit/adapters/compound/CompoundV2_CErc20Adapter.t.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {CONFIGURATOR} from "@gearbox-protocol/core-v3/contracts/test/lib/constants.sol"; -import {CompoundV2_CErc20Adapter} from "../../../../adapters/compound/CompoundV2_CErc20Adapter.sol"; -import {CompoundTestHelper} from "./CompoundTestHelper.sol"; -import "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol"; - -/// @title Compound V2 CErc20 adapter test -/// @notice [ACV2CERC]: Unit tests for Compound V2 CErc20 adapter -contract CompoundV2_CErc20Adapter_Test is CompoundTestHelper { - CompoundV2_CErc20Adapter adapter; - - function setUp() public { - _setupCompoundSuite(); - - vm.startPrank(CONFIGURATOR); - adapter = new CompoundV2_CErc20Adapter(address(creditManager), cusdc); - creditConfigurator.allowAdapter(address(adapter)); - vm.label(address(adapter), "cUSDC_ADAPTER"); - vm.stopPrank(); - } - - /// @notice [ACV2CERC-1]: Constructor reverts on not registered tokens - function test_ACV2CERC_01_constructor_reverts_on_not_registered_tokens() public { - vm.expectRevert(TokenNotAllowedException.selector); - new CompoundV2_CErc20Adapter(address(creditManager), cdai); - } - - /// @notice [ACV2CERC-2]: Constructor sets correct values - function test_ACV2CERC_02_constructor_sets_correct_values() public { - assertEq(adapter.underlying(), usdc, "Incorrect USDC address"); - assertEq(adapter.cToken(), cusdc, "Incorrect cUSDC address"); - assertEq(adapter.tokenMask(), creditManager.getTokenMaskOrRevert(usdc), "Incorrect USDC mask"); - assertEq(adapter.cTokenMask(), creditManager.getTokenMaskOrRevert(cusdc), "Incorrect cUSDC mask"); - } -} diff --git a/contracts/test/unit/adapters/compound/CompoundV2_CErc20Adapter.unit.t.sol b/contracts/test/unit/adapters/compound/CompoundV2_CErc20Adapter.unit.t.sol new file mode 100644 index 00000000..127a49e6 --- /dev/null +++ b/contracts/test/unit/adapters/compound/CompoundV2_CErc20Adapter.unit.t.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {CompoundV2_CErc20Adapter} from "../../../../adapters/compound/CompoundV2_CErc20Adapter.sol"; +import {ICErc20, ICErc20Actions} from "../../../../integrations/compound/ICErc20.sol"; +import {ICompoundV2_Exceptions} from "../../../../interfaces/compound/ICompoundV2_CTokenAdapter.sol"; +import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; + +/// @title Compound v2 CErc20 adapter unit test +/// @notice U:[COMP2T]: Unit tests for Compound v2 CErc20 token adapter +contract CompoundV2_CErc20AdapterUnitTest is AdapterUnitTestHelper, ICompoundV2_Exceptions { + CompoundV2_CErc20Adapter adapter; + + address token; + address cToken; + + uint256 tokenMask; + uint256 cTokenMask; + + function setUp() public { + _setUp(); + + creditManager.setExecuteResult(abi.encode(0)); + + (token, tokenMask) = (tokens[0], 1); + (cToken, cTokenMask) = (tokens[1], 2); + vm.mockCall(cToken, abi.encodeCall(ICErc20.underlying, ()), abi.encode(token)); + + adapter = new CompoundV2_CErc20Adapter(address(creditManager), cToken); + } + + /// @notice U:[COMP2T-1]: Constructor works as expected + function test_U_COMP2T_01_constructor_works_as_expected() public { + _readsTokenMask(token); + _readsTokenMask(cToken); + adapter = new CompoundV2_CErc20Adapter(address(creditManager), cToken); + + assertEq(adapter.creditManager(), address(creditManager), "Incorrect creditManager"); + assertEq(adapter.addressProvider(), address(addressProvider), "Incorrect addressProvider"); + assertEq(adapter.targetContract(), cToken, "Incorrect targetContract"); + assertEq(adapter.cToken(), cToken, "Incorrect cToken"); + assertEq(adapter.underlying(), token, "Incorrect underlying"); + assertEq(adapter.tokenMask(), tokenMask, "Incorrect tokenMask"); + assertEq(adapter.cTokenMask(), cTokenMask, "Incorrect cTokenMask"); + } + + /// @notice U:[COMP2T-2]: Wrapper functions revert on wrong caller + function test_U_COMP2T_02_wrapper_functions_revert_on_wrong_caller() public { + _revertsOnNonFacadeCaller(); + adapter.mint(0); + + _revertsOnNonFacadeCaller(); + adapter.mintAll(); + + _revertsOnNonFacadeCaller(); + adapter.redeem(0); + + _revertsOnNonFacadeCaller(); + adapter.redeemAll(); + + _revertsOnNonFacadeCaller(); + adapter.redeemUnderlying(0); + } + + /// @notice U:[COMP2T-3]: Wrapper functions revert on cToken error + function test_U_COMP2T_03_wrapper_functions_revert_on_cToken_error() public { + creditManager.setExecuteResult(abi.encode(1)); + deal({token: token, to: creditAccount, give: 2}); + deal({token: cToken, to: creditAccount, give: 2}); + + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, 1)); + vm.prank(creditFacade); + adapter.mint(1); + + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, 1)); + vm.prank(creditFacade); + adapter.mintAll(); + + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, 1)); + vm.prank(creditFacade); + adapter.redeem(1); + + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, 1)); + vm.prank(creditFacade); + adapter.redeemAll(); + + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, 1)); + vm.prank(creditFacade); + adapter.redeemUnderlying(1); + } + + /// @notice U:[COMP2T-4]: `mint` works as expected + function test_U_COMP2T_04_mint_works_as_expected() public { + _executesSwap({ + tokenIn: token, + tokenOut: cToken, + callData: abi.encodeCall(ICErc20Actions.mint, (1000)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.mint(1000); + + assertEq(tokensToEnable, cTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[COMP2T-5]: `mintAll` works as expected + function test_U_COMP2T_05_mintAll_works_as_expected() public { + deal({token: token, to: creditAccount, give: 1001}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: token, + tokenOut: cToken, + callData: abi.encodeCall(ICErc20Actions.mint, (1000)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.mintAll(); + + assertEq(tokensToEnable, cTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, tokenMask, "Incorrect tokensToDisable"); + } + + /// @notice U:[COMP2T-6]: `redeem` works as expected + function test_U_COMP2T_06_redeem_works_as_expected() public { + _executesSwap({ + tokenIn: cToken, + tokenOut: token, + callData: abi.encodeCall(ICErc20Actions.redeem, (1000)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.redeem(1000); + + assertEq(tokensToEnable, tokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[COMP2T-7]: `redeemAll` works as expected + function test_U_COMP2T_07_redeemAll_works_as_expected() public { + deal({token: cToken, to: creditAccount, give: 1001}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: cToken, + tokenOut: token, + callData: abi.encodeCall(ICErc20Actions.redeem, (1000)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.redeemAll(); + + assertEq(tokensToEnable, tokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, cTokenMask, "Incorrect tokensToDisable"); + } + + /// @notice U:[COMP2T-8]: `redeemUnderlying` works as expected + function test_U_COMP2T_08_redeemUnderlying_works_as_expected() public { + _executesSwap({ + tokenIn: cToken, + tokenOut: token, + callData: abi.encodeCall(ICErc20Actions.redeemUnderlying, (1000)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.redeemUnderlying(1000); + + assertEq(tokensToEnable, tokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } +} diff --git a/contracts/test/unit/adapters/compound/CompoundV2_CEtherAdapter.t.sol b/contracts/test/unit/adapters/compound/CompoundV2_CEtherAdapter.t.sol deleted file mode 100644 index c46d2348..00000000 --- a/contracts/test/unit/adapters/compound/CompoundV2_CEtherAdapter.t.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {CONFIGURATOR} from "@gearbox-protocol/core-v3/contracts/test/lib/constants.sol"; - -import {CompoundV2_CEtherAdapter} from "../../../../adapters/compound/CompoundV2_CEtherAdapter.sol"; - -import {CompoundTestHelper} from "./CompoundTestHelper.sol"; - -/// @title Compound V2 CEther adapter test -/// @notice [ACV2CETH]: Unit tests for Compound V2 CEther adapter -contract CompoundV2_CEtherAdapter_Test is CompoundTestHelper { - CompoundV2_CEtherAdapter adapter; - - function setUp() public { - _setupCompoundSuite(); - - vm.startPrank(CONFIGURATOR); - adapter = new CompoundV2_CEtherAdapter(address(creditManager), address(gateway)); - creditConfigurator.allowAdapter(address(adapter)); - vm.label(address(adapter), "cETH_ADAPTER"); - vm.stopPrank(); - } - - /// @notice [ACV2CETH-1]: Constructor sets correct values - function test_ACV2CETH_01_constructor_sets_correct_values() public { - assertEq(adapter.underlying(), weth, "Incorrect WETH address"); - assertEq(adapter.cToken(), ceth, "Incorrect cETH address"); - assertEq(adapter.tokenMask(), creditManager.getTokenMaskOrRevert(weth), "Incorrect WETH mask"); - assertEq(adapter.cTokenMask(), creditManager.getTokenMaskOrRevert(ceth), "Incorrect cETH mask"); - } -} diff --git a/contracts/test/unit/adapters/compound/CompoundV2_CEtherAdapter.unit.t.sol b/contracts/test/unit/adapters/compound/CompoundV2_CEtherAdapter.unit.t.sol new file mode 100644 index 00000000..f9c0b418 --- /dev/null +++ b/contracts/test/unit/adapters/compound/CompoundV2_CEtherAdapter.unit.t.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {CompoundV2_CEtherAdapter} from "../../../../adapters/compound/CompoundV2_CEtherAdapter.sol"; +import {ICErc20Actions} from "../../../../integrations/compound/ICErc20.sol"; +import {ICEther} from "../../../../integrations/compound/ICEther.sol"; +import {ICompoundV2_Exceptions} from "../../../../interfaces/compound/ICompoundV2_CTokenAdapter.sol"; +import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; + +/// @title Compound v2 CEther adapter unit test +/// @notice U:[COMP2E]: Unit tests for Compound v2 CEther adapter +contract CompoundV2_CEtherAdapterUnitTest is AdapterUnitTestHelper, ICompoundV2_Exceptions { + CompoundV2_CEtherAdapter adapter; + + address gateway; + + address token; + address cToken; + + uint256 tokenMask; + uint256 cTokenMask; + + function setUp() public { + _setUp(); + + creditManager.setExecuteResult(abi.encode(0)); + + gateway = makeAddr("GATEWAY"); + (token, tokenMask) = (tokens[0], 1); + (cToken, cTokenMask) = (tokens[1], 2); + vm.mockCall(gateway, abi.encodeWithSignature("weth()"), abi.encode(token)); + vm.mockCall(gateway, abi.encodeWithSignature("ceth()"), abi.encode(cToken)); + + adapter = new CompoundV2_CEtherAdapter(address(creditManager), gateway); + } + + /// @notice U:[COMP2E-1]: Constructor works as expected + function test_U_COMP2E_01_constructor_works_as_expected() public { + _readsTokenMask(token); + _readsTokenMask(cToken); + adapter = new CompoundV2_CEtherAdapter(address(creditManager), gateway); + + assertEq(adapter.creditManager(), address(creditManager), "Incorrect creditManager"); + assertEq(adapter.addressProvider(), address(addressProvider), "Incorrect addressProvider"); + assertEq(adapter.targetContract(), gateway, "Incorrect targetContract"); + assertEq(adapter.cToken(), cToken, "Incorrect cToken"); + assertEq(adapter.underlying(), token, "Incorrect underlying"); + assertEq(adapter.tokenMask(), tokenMask, "Incorrect tokenMask"); + assertEq(adapter.cTokenMask(), cTokenMask, "Incorrect cTokenMask"); + } + + /// @notice U:[COMP2E-2]: Wrapper functions revert on wrong caller + function test_U_COMP2E_02_wrapper_functions_revert_on_wrong_caller() public { + _revertsOnNonFacadeCaller(); + adapter.mint(0); + + _revertsOnNonFacadeCaller(); + adapter.mintAll(); + + _revertsOnNonFacadeCaller(); + adapter.redeem(0); + + _revertsOnNonFacadeCaller(); + adapter.redeemAll(); + + _revertsOnNonFacadeCaller(); + adapter.redeemUnderlying(0); + } + + /// @notice U:[COMP2E-3]: Wrapper functions revert on cToken error + function test_U_COMP2E_03_wrapper_functions_revert_on_cToken_error() public { + creditManager.setExecuteResult(abi.encode(1)); + deal({token: token, to: creditAccount, give: 2}); + deal({token: cToken, to: creditAccount, give: 2}); + + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, 1)); + vm.prank(creditFacade); + adapter.mint(1); + + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, 1)); + vm.prank(creditFacade); + adapter.mintAll(); + + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, 1)); + vm.prank(creditFacade); + adapter.redeem(1); + + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, 1)); + vm.prank(creditFacade); + adapter.redeemAll(); + + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, 1)); + vm.prank(creditFacade); + adapter.redeemUnderlying(1); + } + + /// @notice U:[COMP2E-4]: `mint` works as expected + function test_U_COMP2E_04_mint_works_as_expected() public { + _executesSwap({ + tokenIn: token, + tokenOut: cToken, + callData: abi.encodeCall(ICErc20Actions.mint, (1000)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.mint(1000); + + assertEq(tokensToEnable, cTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[COMP2E-5]: `mintAll` works as expected + function test_U_COMP2E_05_mintAll_works_as_expected() public { + deal({token: token, to: creditAccount, give: 1001}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: token, + tokenOut: cToken, + callData: abi.encodeCall(ICErc20Actions.mint, (1000)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.mintAll(); + + assertEq(tokensToEnable, cTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, tokenMask, "Incorrect tokensToDisable"); + } + + /// @notice U:[COMP2E-6]: `redeem` works as expected + function test_U_COMP2E_06_redeem_works_as_expected() public { + _executesSwap({ + tokenIn: cToken, + tokenOut: token, + callData: abi.encodeCall(ICErc20Actions.redeem, (1000)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.redeem(1000); + + assertEq(tokensToEnable, tokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[COMP2E-7]: `redeemAll` works as expected + function test_U_COMP2E_07_redeemAll_works_as_expected() public { + deal({token: cToken, to: creditAccount, give: 1001}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: cToken, + tokenOut: token, + callData: abi.encodeCall(ICErc20Actions.redeem, (1000)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.redeemAll(); + + assertEq(tokensToEnable, tokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, cTokenMask, "Incorrect tokensToDisable"); + } + + /// @notice U:[COMP2E-8]: `redeemUnderlying` works as expected + function test_U_COMP2E_08_redeemUnderlying_works_as_expected() public { + _executesSwap({ + tokenIn: cToken, + tokenOut: token, + callData: abi.encodeCall(ICErc20Actions.redeemUnderlying, (1000)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.redeemUnderlying(1000); + + assertEq(tokensToEnable, tokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } +} diff --git a/contracts/test/unit/adapters/compound/CompoundV2_CTokenAdapter.t.sol b/contracts/test/unit/adapters/compound/CompoundV2_CTokenAdapter.t.sol deleted file mode 100644 index e4455b5f..00000000 --- a/contracts/test/unit/adapters/compound/CompoundV2_CTokenAdapter.t.sol +++ /dev/null @@ -1,315 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {WAD} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol"; - -import {CONFIGURATOR, USER} from "@gearbox-protocol/core-v3/contracts/test/lib/constants.sol"; - -import {CompoundV2_CErc20Adapter} from "../../../../adapters/compound/CompoundV2_CErc20Adapter.sol"; -import {CompoundV2_CEtherAdapter} from "../../../../adapters/compound/CompoundV2_CEtherAdapter.sol"; -import {CompoundV2_CTokenAdapter} from "../../../../adapters/compound/CompoundV2_CTokenAdapter.sol"; -import {ICompoundV2_Exceptions} from "../../../../interfaces/compound/ICompoundV2_CTokenAdapter.sol"; - -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; - -import { - CErc20Mock, - MINT_ERROR, - REDEEM_ERROR, - REDEEM_UNDERLYING_ERROR -} from "../../../mocks/integrations/compound/CErc20Mock.sol"; - -import {CompoundTestHelper} from "./CompoundTestHelper.sol"; - -import "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol"; - -/// @title Compound V2 CToken adapter test -/// @notice [ACV2CT]: Unit tests for Compound V2 CToken adapter -contract CompoundV2_CTokenAdapter_Test is CompoundTestHelper { - CompoundV2_CEtherAdapter cethAdapter; - CompoundV2_CErc20Adapter cusdcAdapter; - - function setUp() public { - _setupCompoundSuite(); - - vm.startPrank(CONFIGURATOR); - cethAdapter = new CompoundV2_CEtherAdapter(address(creditManager), address(gateway)); - creditConfigurator.allowAdapter(address(cethAdapter)); - vm.label(address(cethAdapter), "cETH_ADAPTER"); - - cusdcAdapter = new CompoundV2_CErc20Adapter(address(creditManager), cusdc); - creditConfigurator.allowAdapter(address(cusdcAdapter)); - vm.label(address(cusdcAdapter), "cUSDC_ADAPTER"); - vm.stopPrank(); - } - - /// @notice [ACV2CT-1]: All action functions revert if called not from the multicall - function test_ACV2CT_01_action_functions_revert_if_called_not_from_multicall() public { - _openTestCreditAccount(); - - vm.startPrank(USER); - for (uint256 isUsdc; isUsdc < 2; ++isUsdc) { - CompoundV2_CTokenAdapter adapter = - isUsdc == 1 ? CompoundV2_CTokenAdapter(cusdcAdapter) : CompoundV2_CTokenAdapter(cethAdapter); - - vm.expectRevert(CallerNotCreditFacadeException.selector); - adapter.mint(1); - - vm.expectRevert(CallerNotCreditFacadeException.selector); - adapter.mintAll(); - - vm.expectRevert(CallerNotCreditFacadeException.selector); - adapter.redeem(1); - - vm.expectRevert(CallerNotCreditFacadeException.selector); - adapter.redeemAll(); - - vm.expectRevert(CallerNotCreditFacadeException.selector); - adapter.redeemUnderlying(1); - } - vm.stopPrank(); - } - - /// @notice [ACV2CT-2] `mint` works correctly - /// @dev Fuzzing time before deposit to see if adapter handles interest correctly - function test_ACV2CT_02_mint_works_correctly(uint256 timedelta) public { - vm.assume(timedelta < 3 * 365 days); - uint256 snapshot = vm.snapshot(); - - for (uint256 isUsdc; isUsdc < 2; ++isUsdc) { - CompoundV2_CTokenAdapter adapter = - isUsdc == 1 ? CompoundV2_CTokenAdapter(cusdcAdapter) : CompoundV2_CTokenAdapter(cethAdapter); - - (address creditAccount, address underlying, address cToken, uint256 initialBalance) = - _openAccountWithToken(isUsdc == 1 ? Tokens.USDC : Tokens.WETH); - - address targetContract = isUsdc == 1 ? cusdc : address(gateway); - - expectAllowance(underlying, creditAccount, targetContract, 0); - - vm.warp(block.timestamp + timedelta); - uint256 amountIn = initialBalance / 2; - uint256 amountOutExpected = amountIn * WAD / CErc20Mock(cToken).exchangeRateCurrent(); - - bytes memory callData = abi.encodeCall(adapter.mint, (amountIn)); - expectMulticallStackCalls(address(adapter), targetContract, USER, callData, underlying, cToken, true); - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(underlying, creditAccount, initialBalance - amountIn); - expectBalance(cToken, creditAccount, amountOutExpected); - - expectAllowance(underlying, creditAccount, targetContract, 1); - - expectTokenIsEnabled(creditAccount, underlying, true); - expectTokenIsEnabled(creditAccount, cusdc, true); - - vm.revertTo(snapshot); - } - } - - /// @notice [ACV2CT-3] `mint` reverts on non-zero Compound error code - /// @dev This is only relevant for CErc20 adapter because for CEther gateway reverts even sooner - function test_ACV2CT_03_mint_reverts_on_non_zero_compound_error_code() public { - (address creditAccount,,,) = _openAccountWithToken(Tokens.USDC); - - CErc20Mock(cusdc).setFailing(true); - - vm.expectRevert(abi.encodeWithSelector(ICompoundV2_Exceptions.CTokenError.selector, MINT_ERROR)); - executeOneLineMulticall(creditAccount, address(cusdcAdapter), abi.encodeCall(cusdcAdapter.mint, (1))); - } - - /// @notice [ACV2CT-4] `mintAll` works correctly - /// @dev Fuzzing time before deposit to see if adapter handles interest correctly - function test_ACV2CT_04_mintAll_works_correctly(uint256 timedelta) public { - vm.assume(timedelta < 3 * 365 days); - uint256 snapshot = vm.snapshot(); - - for (uint256 isUsdc; isUsdc < 2; ++isUsdc) { - CompoundV2_CTokenAdapter adapter = - isUsdc == 1 ? CompoundV2_CTokenAdapter(cusdcAdapter) : CompoundV2_CTokenAdapter(cethAdapter); - - (address creditAccount, address underlying, address cToken, uint256 initialBalance) = - _openAccountWithToken(isUsdc == 1 ? Tokens.USDC : Tokens.WETH); - - address targetContract = isUsdc == 1 ? cusdc : address(gateway); - - expectAllowance(underlying, creditAccount, targetContract, 0); - - vm.warp(block.timestamp + timedelta); - uint256 amountInExpected = initialBalance - 1; - uint256 amountOutExpected = amountInExpected * WAD / CErc20Mock(cToken).exchangeRateCurrent(); - - bytes memory expectedCallData = abi.encodeCall(CErc20Mock.mint, (amountInExpected)); - expectMulticallStackCalls( - address(adapter), targetContract, USER, expectedCallData, underlying, cToken, true - ); - - bytes memory callData = abi.encodeCall(adapter.mintAll, ()); - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(underlying, creditAccount, initialBalance - amountInExpected); - expectBalance(cToken, creditAccount, amountOutExpected); - - expectAllowance(underlying, creditAccount, targetContract, 1); - - expectTokenIsEnabled(creditAccount, underlying, false); - expectTokenIsEnabled(creditAccount, cusdc, true); - - vm.revertTo(snapshot); - } - } - - /// @notice [ACV2CT-5] `mintAll` reverts on non-zero Compound error code - /// @dev This is only relevant for CErc20 adapter because for CEther gateway reverts even sooner - function test_ACV2CT_05_mintAll_reverts_on_non_zero_compound_error_code() public { - (address creditAccount,,,) = _openAccountWithToken(Tokens.USDC); - - CErc20Mock(cusdc).setFailing(true); - - vm.expectRevert(abi.encodeWithSelector(ICompoundV2_Exceptions.CTokenError.selector, MINT_ERROR)); - executeOneLineMulticall(creditAccount, address(cusdcAdapter), abi.encodeCall(cusdcAdapter.mintAll, ())); - } - - /// @notice [ACV2CT-6] `redeem` works correctly - /// @dev Fuzzing time before withdrawal to see if adapter handles interest correctly - function test_ACV2CT_06_redeem_works_correctly(uint256 timedelta) public { - vm.assume(timedelta < 3 * 365 days); - uint256 snapshot = vm.snapshot(); - - for (uint256 isUsdc; isUsdc < 2; ++isUsdc) { - CompoundV2_CTokenAdapter adapter = - isUsdc == 1 ? CompoundV2_CTokenAdapter(cusdcAdapter) : CompoundV2_CTokenAdapter(cethAdapter); - - (address creditAccount, address underlying, address cToken, uint256 initialBalance) = - _openAccountWithCToken(isUsdc == 1 ? Tokens.USDC : Tokens.WETH); - - address targetContract = isUsdc == 1 ? cusdc : address(gateway); - - vm.warp(block.timestamp + timedelta); - uint256 amountIn = initialBalance / 2; - uint256 amountOutExpected = amountIn * CErc20Mock(cToken).exchangeRateCurrent() / WAD; - - bytes memory callData = abi.encodeCall(adapter.redeem, (amountIn)); - bool cethOnly = isUsdc == 0; - expectMulticallStackCalls(address(adapter), targetContract, USER, callData, cToken, underlying, cethOnly); - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(underlying, creditAccount, amountOutExpected); - expectBalance(cToken, creditAccount, initialBalance - amountIn); - - expectTokenIsEnabled(creditAccount, underlying, true); - expectTokenIsEnabled(creditAccount, cToken, true); - - vm.revertTo(snapshot); - } - } - - /// @notice [ACV2CT-7] `redeem` reverts on non-zero Compound error code - /// @dev This is only relevant for CErc20 adapter because for CEther gateway reverts even sooner - function test_ACV2CT_07_redeem_reverts_on_non_zero_compound_error_code() public { - (address creditAccount,,,) = _openAccountWithCToken(Tokens.USDC); - - CErc20Mock(cusdc).setFailing(true); - - vm.expectRevert(abi.encodeWithSelector(ICompoundV2_Exceptions.CTokenError.selector, REDEEM_ERROR)); - executeOneLineMulticall(creditAccount, address(cusdcAdapter), abi.encodeCall(cusdcAdapter.redeem, (1))); - } - - /// @notice [ACV2CT-8] `redeemAll` works correctly - /// @dev Fuzzing time before withdrawal to see if adapter handles interest correctly - function test_ACV2CT_08_redeemAll_works_correctly(uint256 timedelta) public { - vm.assume(timedelta < 3 * 365 days); - uint256 snapshot = vm.snapshot(); - - for (uint256 isUsdc; isUsdc < 2; ++isUsdc) { - CompoundV2_CTokenAdapter adapter = - isUsdc == 1 ? CompoundV2_CTokenAdapter(cusdcAdapter) : CompoundV2_CTokenAdapter(cethAdapter); - - (address creditAccount, address underlying, address cToken, uint256 initialBalance) = - _openAccountWithCToken(isUsdc == 1 ? Tokens.USDC : Tokens.WETH); - - address targetContract = isUsdc == 1 ? cusdc : address(gateway); - - vm.warp(block.timestamp + timedelta); - uint256 amountInExpected = initialBalance - 1; - uint256 amountOutExpected = amountInExpected * CErc20Mock(cToken).exchangeRateCurrent() / WAD; - - bool cethOnly = isUsdc == 0; - bytes memory expectedCallData = abi.encodeCall(CErc20Mock.redeem, (amountInExpected)); - expectMulticallStackCalls( - address(adapter), targetContract, USER, expectedCallData, cToken, underlying, cethOnly - ); - - bytes memory callData = abi.encodeCall(adapter.redeemAll, ()); - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(underlying, creditAccount, amountOutExpected); - expectBalance(cToken, creditAccount, initialBalance - amountInExpected); - - expectTokenIsEnabled(creditAccount, underlying, true); - expectTokenIsEnabled(creditAccount, cToken, false); - - vm.revertTo(snapshot); - } - } - - /// @notice [ACV2CT-9] `redeemAll` reverts on non-zero Compound error code - /// @dev This is only relevant for CErc20 adapter because for CEther gateway reverts even sooner - function test_ACV2CT_09_redeemAll_reverts_on_non_zero_compound_error_code() public { - (address creditAccount,,,) = _openAccountWithCToken(Tokens.USDC); - - CErc20Mock(cusdc).setFailing(true); - - vm.expectRevert(abi.encodeWithSelector(ICompoundV2_Exceptions.CTokenError.selector, REDEEM_ERROR)); - executeOneLineMulticall(creditAccount, address(cusdcAdapter), abi.encodeCall(cusdcAdapter.redeemAll, ())); - } - - /// @notice [ACV2CT-10] `redeemUnderlying` works correctly - /// @dev Fuzzing time before withdrawal to see if adapter handles interest correctly - function test_ACV2CT_10_redeemUnderlying_works_correctly(uint256 timedelta) public { - vm.assume(timedelta < 3 * 365 days); - uint256 snapshot = vm.snapshot(); - - for (uint256 isUsdc; isUsdc < 2; ++isUsdc) { - CompoundV2_CTokenAdapter adapter = - isUsdc == 1 ? CompoundV2_CTokenAdapter(cusdcAdapter) : CompoundV2_CTokenAdapter(cethAdapter); - - (address creditAccount, address underlying, address cToken, uint256 initialBalance) = - _openAccountWithCToken(isUsdc == 1 ? Tokens.USDC : Tokens.WETH); - - address targetContract = isUsdc == 1 ? cusdc : address(gateway); - - vm.warp(block.timestamp + timedelta); - uint256 amountOut = (initialBalance / 2) * CErc20Mock(cToken).exchangeRateCurrent() / WAD; - uint256 amountInExpected = amountOut * WAD / CErc20Mock(cToken).exchangeRateCurrent(); - - bytes memory callData = abi.encodeCall(adapter.redeemUnderlying, (amountOut)); - bool cethOnly = isUsdc == 0; - expectMulticallStackCalls(address(adapter), targetContract, USER, callData, cToken, underlying, cethOnly); - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(underlying, creditAccount, amountOut); - expectBalance(cToken, creditAccount, initialBalance - amountInExpected); - - expectTokenIsEnabled(creditAccount, underlying, true); - expectTokenIsEnabled(creditAccount, cToken, true); - - vm.revertTo(snapshot); - } - } - - /// @notice [ACV2CT-11] `redeemUnderlying` reverts on non-zero Compound error code - /// @dev This is only relevant for CErc20 adapter because for CEther gateway reverts even sooner - function test_ACV2CT_11_redeemUnderlying_reverts_on_non_zero_compound_error_code() public { - (address creditAccount,,,) = _openAccountWithCToken(Tokens.USDC); - - CErc20Mock(cusdc).setFailing(true); - - vm.expectRevert(abi.encodeWithSelector(ICompoundV2_Exceptions.CTokenError.selector, REDEEM_UNDERLYING_ERROR)); - executeOneLineMulticall( - creditAccount, address(cusdcAdapter), abi.encodeCall(cusdcAdapter.redeemUnderlying, (1)) - ); - } -} diff --git a/contracts/test/unit/adapters/convex/ConvexAdapterHelper.sol b/contracts/test/unit/adapters/convex/ConvexAdapterHelper.sol deleted file mode 100644 index 85443818..00000000 --- a/contracts/test/unit/adapters/convex/ConvexAdapterHelper.sol +++ /dev/null @@ -1,328 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {IBooster} from "../../../../integrations/convex/IBooster.sol"; -import {IBaseRewardPool} from "../../../../integrations/convex/IBaseRewardPool.sol"; - -import {ConvexV1BaseRewardPoolAdapter} from "../../../../adapters/convex/ConvexV1_BaseRewardPool.sol"; -import {ConvexV1BoosterAdapter} from "../../../../adapters/convex/ConvexV1_Booster.sol"; -import {ConvexStakedPositionToken} from "../../../../helpers/convex/ConvexV1_StakedPositionToken.sol"; - -import {BoosterMock} from "../../../mocks/integrations/ConvexBoosterMock.sol"; -import {BaseRewardPoolMock} from "../../../mocks/integrations/ConvexBaseRewardPoolMock.sol"; -import {ExtraRewardPoolMock} from "../../../mocks/integrations/ConvexExtraRewardPoolMock.sol"; - -import {PriceFeedMock} from "@gearbox-protocol/core-v3/contracts/test/mocks/oracles/PriceFeedMock.sol"; - -import {WAD, RAY} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol"; -import {ERC20Mock} from "@gearbox-protocol/core-v3/contracts/test/mocks/token/ERC20Mock.sol"; - -import {AdapterTestHelper} from "../AdapterTestHelper.sol"; -import "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol"; - -import {USER, CONFIGURATOR, DAI_MIN_BORROWED_AMOUNT, DAI_MAX_BORROWED_AMOUNT} from "../../../lib/constants.sol"; - -uint256 constant CURVE_LP_AMOUNT = 10000 * RAY; -uint256 constant DAI_ACCOUNT_AMOUNT = 10000 * WAD; -uint256 constant REWARD_AMOUNT = RAY; -uint256 constant REWARD_AMOUNT1 = RAY * 33; -uint256 constant REWARD_AMOUNT2 = RAY * 4; - -/// @title ConvexAdapterHelper -/// @notice Designed for unit test purposes only -contract ConvexAdapterHelper is AdapterTestHelper { - PriceFeedMock public feed; - - address public crv; - address public cvx; - - address public curveLPToken; - address public convexLPToken; - address public phantomToken; - address public extraRewardToken1; - address public extraRewardToken2; - - BoosterMock public boosterMock; - BaseRewardPoolMock public basePoolMock; - ExtraRewardPoolMock public extraPoolMock1; - ExtraRewardPoolMock public extraPoolMock2; - - ConvexV1BaseRewardPoolAdapter public basePoolAdapter; - ConvexV1BoosterAdapter public boosterAdapter; - - struct PoolInfo { - address lptoken; - address token; - address gauge; - address crvRewards; - address stash; - bool shutdown; - } - - function _setupConvexSuite(uint256 extraRewardsCount) internal { - _setUp(); - - feed = new PriceFeedMock(1000, 8); - priceOracle = priceOracle; - - curveLPToken = address(new ERC20Mock("Curve LP Token", "CRVLP", 18)); - _addToken(curveLPToken); - - crv = address(new ERC20Mock("Curve", "CRV", 18)); - _addToken(crv); - - cvx = address(new ERC20Mock("Convex", "CVX", 18)); - _addToken(cvx); - - if (extraRewardsCount >= 1) { - extraRewardToken1 = address(new ERC20Mock("Extra Reward 1", "EXTR1", 18)); - _addToken(extraRewardToken1); - } - - if (extraRewardsCount >= 2) { - extraRewardToken2 = address(new ERC20Mock("Extra Reward 2", "EXTR2", 18)); - _addToken(extraRewardToken2); - } - - boosterMock = new BoosterMock(crv, cvx); - boosterMock.addPool(curveLPToken); - - IBooster.PoolInfo memory pool = IBooster(address(boosterMock)).poolInfo(0); - - convexLPToken = pool.token; - _addToken(convexLPToken); - - basePoolMock = BaseRewardPoolMock(pool.crvRewards); - - if (extraRewardsCount >= 1) { - extraPoolMock1 = new ExtraRewardPoolMock( - address(basePoolMock), - extraRewardToken1, - address(boosterMock) - ); - - basePoolMock.addExtraReward(address(extraPoolMock1)); - } - - if (extraRewardsCount >= 2) { - extraPoolMock2 = new ExtraRewardPoolMock( - address(basePoolMock), - extraRewardToken2, - address(boosterMock) - ); - - basePoolMock.addExtraReward(address(extraPoolMock2)); - } - - phantomToken = address(new ConvexStakedPositionToken(address(basePoolMock), convexLPToken)); - _addToken(phantomToken); - - basePoolAdapter = new ConvexV1BaseRewardPoolAdapter( - address(creditManager), - address(basePoolMock), - phantomToken - ); - - vm.prank(CONFIGURATOR); - creditConfigurator.allowAdapter(address(basePoolAdapter)); - - boosterAdapter = new ConvexV1BoosterAdapter( - address(creditManager), - address(boosterMock) - ); - - vm.prank(CONFIGURATOR); - creditConfigurator.allowAdapter(address(boosterAdapter)); - - vm.prank(CONFIGURATOR); - boosterAdapter.updateStakedPhantomTokensMap(); - } - - function _checkPoolAdapterConstructorRevert(uint256 forgottenToken) internal { - address curveLPToken_c = address(new ERC20Mock("Curve LP Token", "CRVLP", 18)); - - address crv_c = address(new ERC20Mock("Curve", "CRV", 18)); - address cvx_c = address(new ERC20Mock("Convex", "CVX", 18)); - address extraRewardToken1_c = address(new ERC20Mock("Extra Reward 1", "EXTR1", 18)); - address extraRewardToken2_c = address(new ERC20Mock("Extra Reward 2", "EXTR2", 18)); - - BoosterMock boosterMock_c = new BoosterMock(crv_c, cvx_c); - boosterMock_c.addPool(curveLPToken_c); - - IBooster.PoolInfo memory pool = IBooster(address(boosterMock_c)).poolInfo(0); - - BaseRewardPoolMock basePoolMock_c = BaseRewardPoolMock(pool.crvRewards); - - ExtraRewardPoolMock extraPoolMock1_c = new ExtraRewardPoolMock( - address(basePoolMock_c), - extraRewardToken1_c, - address(boosterMock_c) - ); - basePoolMock_c.addExtraReward(address(extraPoolMock1_c)); - - ExtraRewardPoolMock extraPoolMock2_c = new ExtraRewardPoolMock( - address(basePoolMock_c), - extraRewardToken2_c, - address(boosterMock_c) - ); - basePoolMock_c.addExtraReward(address(extraPoolMock2_c)); - - address convexLPToken_c = pool.token; - address phantomToken_c = address(new ConvexStakedPositionToken(address(basePoolMock_c), convexLPToken_c)); - - address forgottenTokenAddr; - - if (forgottenToken == 0) { - forgottenTokenAddr = convexLPToken_c; - } else { - _addToken(convexLPToken_c); - } - - if (forgottenToken == 1) { - forgottenTokenAddr = phantomToken_c; - } else { - _addToken(phantomToken_c); - } - - if (forgottenToken == 2) { - forgottenTokenAddr = curveLPToken_c; - } else { - _addToken(curveLPToken_c); - } - - if (forgottenToken == 3) { - forgottenTokenAddr = crv_c; - } else { - _addToken(crv_c); - } - - if (forgottenToken == 4) { - forgottenTokenAddr = cvx_c; - } else { - _addToken(cvx_c); - } - - if (forgottenToken == 5) { - forgottenTokenAddr = extraRewardToken1_c; - } else { - _addToken(extraRewardToken1_c); - } - - if (forgottenToken == 6) { - forgottenTokenAddr = extraRewardToken2_c; - } else { - _addToken(extraRewardToken2_c); - } - - vm.expectRevert(TokenNotAllowedException.selector); - new ConvexV1BaseRewardPoolAdapter( - address(creditManager), - address(basePoolMock_c), - phantomToken_c - ); - } - - function _addToken(address token) internal { - vm.startPrank(CONFIGURATOR); - priceOracle.setPriceFeed(token, address(feed), 0); - creditConfigurator.addCollateralToken(token, 9300); - vm.stopPrank(); - } - - function _makeRewardTokensMask(uint256 numExtras) internal view returns (uint256) { - uint256 rewardTokensMask = creditManager.getTokenMaskOrRevert(crv) | creditManager.getTokenMaskOrRevert(cvx); - if (numExtras >= 1) rewardTokensMask |= creditManager.getTokenMaskOrRevert(extraRewardToken1); - if (numExtras >= 2) rewardTokensMask |= creditManager.getTokenMaskOrRevert(extraRewardToken2); - return rewardTokensMask; - } - - function expectDepositStackCalls(address borrower, uint256 amount, bool stake, bool depositAll) internal { - bytes memory callData = depositAll - ? abi.encodeCall(IBooster.depositAll, (0, stake)) - : abi.encodeCall(IBooster.deposit, (0, amount, stake)); - - expectMulticallStackCalls( - address(boosterAdapter), - address(boosterMock), - borrower, - callData, - curveLPToken, - stake ? phantomToken : convexLPToken, - true - ); - } - - function expectWithdrawStackCalls(address borrower, uint256 amount, bool withdrawAll) internal { - bytes memory callData = - withdrawAll ? abi.encodeCall(IBooster.withdrawAll, (0)) : abi.encodeCall(IBooster.withdraw, (0, amount)); - - expectMulticallStackCalls( - address(boosterAdapter), address(boosterMock), borrower, callData, convexLPToken, curveLPToken, false - ); - } - - function expectStakeStackCalls(address borrower, uint256 amount, bool stakeAll) internal { - bytes memory callData = - stakeAll ? abi.encodeCall(IBaseRewardPool.stakeAll, ()) : abi.encodeCall(IBaseRewardPool.stake, (amount)); - - expectMulticallStackCalls( - address(basePoolAdapter), address(basePoolMock), borrower, callData, convexLPToken, phantomToken, true - ); - } - - function expectPoolWithdrawStackCalls( - address borrower, - uint256 amount, - bool withdrawAll, - bool unwrap, - uint256 numExtras - ) internal { - bytes memory callData; - - if (unwrap) { - callData = withdrawAll - ? abi.encodeCall(IBaseRewardPool.withdrawAllAndUnwrap, (true)) - : abi.encodeCall(IBaseRewardPool.withdrawAndUnwrap, (amount, true)); - } else { - callData = withdrawAll - ? abi.encodeCall(IBaseRewardPool.withdrawAll, (true)) - : abi.encodeCall(IBaseRewardPool.withdraw, (amount, true)); - } - - expectMulticallStackCalls( - address(basePoolAdapter), - address(basePoolMock), - borrower, - callData, - phantomToken, - unwrap ? curveLPToken : convexLPToken, - false - ); - - // uint256 stakingTokenMask = creditManager.getTokenMaskOrRevert(unwrap ? curveLPToken : convexLPToken); - // uint256 stakedTokenMask = creditManager.getTokenMaskOrRevert(phantomToken); - // uint256 rewardTokensMask = _makeRewardTokensMask(numExtras); - // vm.expectCall( - // address(creditManager), - // abi.encodeCall( - // creditManager.changeEnabledTokens, - // (rewardTokensMask | stakingTokenMask, withdrawAll ? stakedTokenMask : 0) - // ) - // ); - } - - function expectClaimStackCalls(address creditAccount, address borrower, uint256 numExtras) internal { - vm.expectEmit(true, false, false, false); - emit StartMultiCall(creditAccount, borrower); - - vm.expectEmit(true, false, false, false); - emit Execute(creditAccount, address(basePoolMock)); - - vm.expectCall(address(basePoolMock), abi.encodeWithSignature("getReward()")); - - vm.expectEmit(false, false, false, false); - emit FinishMultiCall(); - } -} diff --git a/contracts/test/unit/adapters/convex/ConvexV1BaseRewardPoolAdapter.unit.t.sol b/contracts/test/unit/adapters/convex/ConvexV1BaseRewardPoolAdapter.unit.t.sol new file mode 100644 index 00000000..b22fa385 --- /dev/null +++ b/contracts/test/unit/adapters/convex/ConvexV1BaseRewardPoolAdapter.unit.t.sol @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {ConvexV1BaseRewardPoolAdapter} from "../../../../adapters/convex/ConvexV1_BaseRewardPool.sol"; +import {BaseRewardPoolMock} from "../../../mocks/integrations/convex/BaseRewardPoolMock.sol"; +import {BoosterMock} from "../../../mocks/integrations/convex/BoosterMock.sol"; +import {ExtraRewardWrapperMock} from "../../../mocks/integrations/convex/ExtraRewardWrapperMock.sol"; +import {RewardsMock} from "../../../mocks/integrations/convex/RewardsMock.sol"; +import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; + +/// @title Convex v1 base reward pool adapter unit test +/// @notice U:[CVX1R]: Unit tests for Convex v1 base reward pool adapter +contract ConvexV1BaseRewardPoolAdapterUnitTest is AdapterUnitTestHelper { + ConvexV1BaseRewardPoolAdapter adapter; + + BaseRewardPoolMock baseRewardPool; + BoosterMock booster; + + RewardsMock extraReward1; + RewardsMock extraReward2; + + address curveLPToken; + address convexStakingToken; + address stakedPhantomToken; + address crv; + address cvx; + + function setUp() public { + _setUp(); + + curveLPToken = tokens[0]; + convexStakingToken = tokens[1]; + stakedPhantomToken = tokens[2]; + crv = tokens[3]; + cvx = tokens[4]; + + booster = new BoosterMock(cvx); + booster.setPoolInfo(42, curveLPToken, convexStakingToken); + + extraReward1 = new RewardsMock(tokens[5]); + extraReward2 = new RewardsMock(address(new ExtraRewardWrapperMock(tokens[6]))); + + baseRewardPool = new BaseRewardPoolMock(42, address(booster), convexStakingToken, crv); + baseRewardPool.setExtraReward(0, address(extraReward1)); + baseRewardPool.setExtraReward(1, address(extraReward2)); + + adapter = new ConvexV1BaseRewardPoolAdapter( + address(creditManager), + address(baseRewardPool), + stakedPhantomToken + ); + } + + /// @notice U:[CVX1R-1]: Constructor works as expected + function test_U_CVX1R_01_constructor_works_as_expected() public { + _readsTokenMask(curveLPToken); + _readsTokenMask(convexStakingToken); + _readsTokenMask(stakedPhantomToken); + _readsTokenMask(crv); + _readsTokenMask(cvx); + adapter = new ConvexV1BaseRewardPoolAdapter( + address(creditManager), + address(baseRewardPool), + stakedPhantomToken + ); + + assertEq(adapter.creditManager(), address(creditManager), "Incorrect creditManager"); + assertEq(adapter.addressProvider(), address(addressProvider), "Incorrect addressProvider"); + assertEq(adapter.targetContract(), address(baseRewardPool), "Incorrect targetContract"); + assertEq(adapter.curveLPtoken(), curveLPToken, "Incorrect curveLPtoken"); + assertEq(adapter.stakingToken(), convexStakingToken, "Incorrect stakingToken"); + assertEq(adapter.stakedPhantomToken(), stakedPhantomToken, "Incorrect stakedPhantomToken"); + assertEq(adapter.extraReward1(), address(0), "Incorrect extraReward1"); + assertEq(adapter.extraReward2(), address(0), "Incorrect extraReward2"); + assertEq(adapter.curveLPTokenMask(), 1, "Incorrect curveLPTokenMask"); + assertEq(adapter.stakingTokenMask(), 2, "Incorrect stakingTokenMask"); + assertEq(adapter.stakedTokenMask(), 4, "Incorrect stakedTokenMask"); + assertEq(adapter.rewardTokensMask(), 8 + 16, "Incorrect rewardTokensMask"); + } + + /// @notice U:[CVX1R-2]: Extra rewards are handled correctly + function test_U_CVX1R_02_extra_rewards_are_handled_correctly() public { + baseRewardPool.setNumExtraRewards(1); + _readsTokenMask(tokens[5]); + adapter = new ConvexV1BaseRewardPoolAdapter( + address(creditManager), + address(baseRewardPool), + stakedPhantomToken + ); + assertEq(adapter.extraReward1(), tokens[5], "Incorrect extraReward1"); + assertEq(adapter.extraReward2(), address(0), "Incorrect extraReward1"); + assertEq(adapter.rewardTokensMask(), 8 + 16 + 32, "Incorrect rewardTokensMask"); + + baseRewardPool.setNumExtraRewards(2); + _readsTokenMask(tokens[5]); + _readsTokenMask(tokens[6]); + adapter = new ConvexV1BaseRewardPoolAdapter( + address(creditManager), + address(baseRewardPool), + stakedPhantomToken + ); + assertEq(adapter.extraReward1(), tokens[5], "Incorrect extraReward1"); + assertEq(adapter.extraReward2(), tokens[6], "Incorrect extraReward1"); + assertEq(adapter.rewardTokensMask(), 8 + 16 + 32 + 64, "Incorrect rewardTokensMask"); + } + + /// @notice U:[CVX1R-3]: Wrapper functions revert on wrong caller + function test_U_CVX1R_03_wrapper_functions_revert_on_wrong_caller() public { + _revertsOnNonFacadeCaller(); + adapter.stake(0); + + _revertsOnNonFacadeCaller(); + adapter.stakeAll(); + + _revertsOnNonFacadeCaller(); + adapter.getReward(); + + _revertsOnNonFacadeCaller(); + adapter.withdraw(0, false); + + _revertsOnNonFacadeCaller(); + adapter.withdrawAll(false); + + _revertsOnNonFacadeCaller(); + adapter.withdrawAndUnwrap(0, false); + + _revertsOnNonFacadeCaller(); + adapter.withdrawAllAndUnwrap(false); + } + + // ----- // + // STAKE // + // ----- // + + /// @notice U:[CVX1R-4]: `stake` works as expected + function test_U_CVX1R_04_stake_works_as_expected() public { + _executesSwap({ + tokenIn: convexStakingToken, + tokenOut: stakedPhantomToken, + callData: abi.encodeCall(adapter.stake, (1000)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.stake(1000); + assertEq(tokensToEnable, 4, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[CVX1R-5]: `stakeAll` works as expected + function test_U_CVX1R_05_stakeAll_works_as_expected() public { + _executesSwap({ + tokenIn: convexStakingToken, + tokenOut: stakedPhantomToken, + callData: abi.encodeCall(adapter.stakeAll, ()), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.stakeAll(); + assertEq(tokensToEnable, 4, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 2, "Incorrect tokensToDisable"); + } + + // ----- // + // CLAIM // + // ----- // + + /// @notice U:[CVX1R-6]: `getReward` works as expected + function test_U_CVX1R_06_getReward_works_as_expected() public { + _executesCall({ + tokensToApprove: new address[](0), + tokensToValidate: new address[](0), + callData: abi.encodeCall(adapter.getReward, ()) + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.getReward(); + assertEq(tokensToEnable, 8 + 16, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + // -------- // + // WITHDRAW // + // -------- // + + /// @notice U:[CVX1R-7]: `withdraw` works as expected + function test_U_CVX1R_07_withdraw_works_as_expected() public { + for (uint256 i; i < 2; ++i) { + bool claim = i == 1; + _executesSwap({ + tokenIn: stakedPhantomToken, + tokenOut: convexStakingToken, + callData: abi.encodeCall(adapter.withdraw, (1000, claim)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdraw(1000, claim); + assertEq(tokensToEnable, claim ? (2 + 8 + 16) : 2, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + } + + /// @notice U:[CVX1R-8]: `withdrawAll` works as expected + function test_U_CVX1R_08_withdrawAll_works_as_expected() public { + for (uint256 i; i < 2; ++i) { + bool claim = i == 1; + _executesSwap({ + tokenIn: stakedPhantomToken, + tokenOut: convexStakingToken, + callData: abi.encodeCall(adapter.withdrawAll, (claim)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdrawAll(claim); + assertEq(tokensToEnable, claim ? (2 + 8 + 16) : 2, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 4, "Incorrect tokensToDisable"); + } + } + + // ------ // + // UNWRAP // + // ------ // + + /// @notice U:[CVX1R-9]: `withdrawAndUnwrap` works as expected + function test_U_CVX1R_09_withdrawAndUnwrap_works_as_expected() public { + for (uint256 i; i < 2; ++i) { + bool claim = i == 1; + _executesSwap({ + tokenIn: stakedPhantomToken, + tokenOut: curveLPToken, + callData: abi.encodeCall(adapter.withdrawAndUnwrap, (1000, claim)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdrawAndUnwrap(1000, claim); + assertEq(tokensToEnable, claim ? (1 + 8 + 16) : 1, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + } + + /// @notice U:[CVX1R-10]: `withdrawAllAndUnwrap` works as expected + function test_U_CVX1R_10_withdrawAllAndUnwrap_works_as_expected() public { + for (uint256 i; i < 2; ++i) { + bool claim = i == 1; + _executesSwap({ + tokenIn: stakedPhantomToken, + tokenOut: curveLPToken, + callData: abi.encodeCall(adapter.withdrawAllAndUnwrap, (claim)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdrawAllAndUnwrap(claim); + assertEq(tokensToEnable, claim ? (1 + 8 + 16) : 1, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 4, "Incorrect tokensToDisable"); + } + } +} diff --git a/contracts/test/unit/adapters/convex/ConvexV1BoosterAdapter.harness.sol b/contracts/test/unit/adapters/convex/ConvexV1BoosterAdapter.harness.sol new file mode 100644 index 00000000..ea25b128 --- /dev/null +++ b/contracts/test/unit/adapters/convex/ConvexV1BoosterAdapter.harness.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {ConvexV1BoosterAdapter} from "../../../../adapters/convex/ConvexV1_Booster.sol"; + +contract ConvexV1BoosterAdapterHarness is ConvexV1BoosterAdapter { + constructor(address _creditManager, address _booster) ConvexV1BoosterAdapter(_creditManager, _booster) {} + + function hackPidToPhantokToken(uint256 pid, address token) external { + pidToPhantomToken[pid] = token; + } +} diff --git a/contracts/test/unit/adapters/convex/ConvexV1BoosterAdapter.unit.t.sol b/contracts/test/unit/adapters/convex/ConvexV1BoosterAdapter.unit.t.sol new file mode 100644 index 00000000..7d7725cb --- /dev/null +++ b/contracts/test/unit/adapters/convex/ConvexV1BoosterAdapter.unit.t.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {BoosterMock} from "../../../mocks/integrations/convex/BoosterMock.sol"; +import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; +import {ConvexV1BoosterAdapterHarness} from "./ConvexV1BoosterAdapter.harness.sol"; + +/// @title Convex v1 booster adapter unit test +/// @notice U:[CVX1B]: Unit tests for Convex v1 booster adapter +contract ConvexV1BoosterAdapterUnitTest is AdapterUnitTestHelper { + ConvexV1BoosterAdapterHarness adapter; + BoosterMock booster; + + function setUp() public { + _setUp(); + + booster = new BoosterMock(address(0)); + booster.setPoolInfo(42, tokens[0], tokens[1]); + + adapter = new ConvexV1BoosterAdapterHarness(address(creditManager), address(booster)); + adapter.hackPidToPhantokToken(42, tokens[2]); + } + + /// @notice U:[CVX1B-1]: Constructor works as expected + function test_U_CVX1B_01_constructor_works_as_expected() public { + assertEq(adapter.creditManager(), address(creditManager), "Incorrect creditManager"); + assertEq(adapter.addressProvider(), address(addressProvider), "Incorrect addressProvider"); + assertEq(adapter.targetContract(), address(booster), "Incorrect targetContract"); + } + + /// @notice U:[CVX1B-2]: Wrapper functions revert on wrong caller + function test_U_CVX1B_02_wrapper_functions_revert_on_wrong_caller() public { + _revertsOnNonFacadeCaller(); + adapter.deposit(0, 0, false); + + _revertsOnNonFacadeCaller(); + adapter.depositAll(0, false); + + _revertsOnNonFacadeCaller(); + adapter.withdraw(0, 0); + + _revertsOnNonFacadeCaller(); + adapter.withdrawAll(0); + } + + /// @notice U:[CVX1B-3]: `deposit` works as expected + function test_U_CVX1B_03_deposit_works_as_expected() public { + for (uint256 i; i < 2; ++i) { + bool stake = i == 1; + + _executesSwap({ + tokenIn: tokens[0], + tokenOut: stake ? tokens[2] : tokens[1], + callData: abi.encodeCall(adapter.deposit, (42, 1000, stake)), + requiresApproval: true, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.deposit(42, 1000, stake); + + assertEq(tokensToEnable, stake ? 4 : 2, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + } + + /// @notice U:[CVX1B-4]: `depositAll` works as expected + function test_U_CVX1B_04_depositAll_works_as_expected() public { + for (uint256 i; i < 2; ++i) { + bool stake = i == 1; + + _executesSwap({ + tokenIn: tokens[0], + tokenOut: stake ? tokens[2] : tokens[1], + callData: abi.encodeCall(adapter.depositAll, (42, stake)), + requiresApproval: true, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.depositAll(42, stake); + + assertEq(tokensToEnable, stake ? 4 : 2, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 1, "Incorrect tokensToDisable"); + } + } + + /// @notice U:[CVX1B-5]: `withdraw` works as expected + function test_U_CVX1B_05_withdraw_works_as_expected() public { + _executesSwap({ + tokenIn: tokens[1], + tokenOut: tokens[0], + callData: abi.encodeCall(adapter.withdraw, (42, 1000)), + requiresApproval: false, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdraw(42, 1000); + + assertEq(tokensToEnable, 1, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[CVX1B-6]: `withdrawAll` works as expected + function test_U_CVX1B_06_withdrawAll_works_as_expected() public { + _executesSwap({ + tokenIn: tokens[1], + tokenOut: tokens[0], + callData: abi.encodeCall(adapter.withdrawAll, (42)), + requiresApproval: false, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdrawAll(42); + + assertEq(tokensToEnable, 1, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 2, "Incorrect tokensToDisable"); + } + + /// @notice U:[CVX1B-7]: `updatedStakedPhantomTokensMap` reverts on wrong caller + function test_U_CVX1B_07_updateStakedPhantomTokensMap_reverts_on_wrong_caller() public { + _revertsOnNonConfiguratorCaller(); + adapter.updateStakedPhantomTokensMap(); + } +} diff --git a/contracts/test/unit/adapters/convex/ConvexV1_BaseRewardPool.t.sol b/contracts/test/unit/adapters/convex/ConvexV1_BaseRewardPool.t.sol deleted file mode 100644 index 2cf35bf0..00000000 --- a/contracts/test/unit/adapters/convex/ConvexV1_BaseRewardPool.t.sol +++ /dev/null @@ -1,382 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; -import "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol"; - -import { - ConvexAdapterHelper, - CURVE_LP_AMOUNT, - DAI_ACCOUNT_AMOUNT, - REWARD_AMOUNT, - REWARD_AMOUNT1, - REWARD_AMOUNT2 -} from "./ConvexAdapterHelper.sol"; -import {ERC20Mock} from "@gearbox-protocol/core-v3/contracts/test/mocks/token/ERC20Mock.sol"; - -import {USER, CONFIGURATOR, FRIEND} from "../../../lib/constants.sol"; - -import {Test} from "forge-std/Test.sol"; - -contract ConvexV1BaseRewardPoolAdapterTest is Test, ConvexAdapterHelper { - function setUp() public { - _setupConvexSuite(2); - } - - /// - /// TESTS - /// - - function _openTestCreditAccountAndDeposit() internal returns (address creditAccount) { - (creditAccount,) = _openTestCreditAccount(); - ERC20Mock(curveLPToken).mint(creditAccount, CURVE_LP_AMOUNT); - - executeOneLineMulticall( - creditAccount, address(boosterAdapter), abi.encodeCall(boosterAdapter.deposit, (0, CURVE_LP_AMOUNT, false)) - ); - } - - /// @dev [ACVX1_P-1]: constructor sets correct values - function test_ACVX1_P_01_constructor_sets_correct_values() public { - for (uint256 numExtras; numExtras <= 2; numExtras++) { - _setupConvexSuite(numExtras); - - assertEq(basePoolAdapter.stakingToken(), convexLPToken, "Incorrect Convex LP token"); - assertEq( - basePoolAdapter.stakingTokenMask(), - creditManager.getTokenMaskOrRevert(convexLPToken), - "Incorrect Convex LP token mask" - ); - - assertEq(basePoolAdapter.stakedPhantomToken(), phantomToken, "Incorrect staked token"); - assertEq( - basePoolAdapter.stakedTokenMask(), - creditManager.getTokenMaskOrRevert(phantomToken), - "Incorrect staked token mask" - ); - - assertEq(basePoolAdapter.curveLPtoken(), curveLPToken, "Incorrect Curve LP token"); - assertEq( - basePoolAdapter.curveLPTokenMask(), - creditManager.getTokenMaskOrRevert(curveLPToken), - "Incorrect Curve LP token mask" - ); - - assertEq( - basePoolAdapter.rewardTokensMask(), _makeRewardTokensMask(numExtras), "Incorrect reward tokens mask" - ); - } - } - - /// @dev [ACVX1_P-2]: constructor reverts when one of the tokens is not allowed - function test_ACVX1_P_02_constructor_reverts_on_token_not_allowed() public { - for (uint8 i = 0; i < 7; i++) { - _checkPoolAdapterConstructorRevert(i); - } - } - - /// @dev [ACVX1_P-3]: stake works correctly and emits events - function test_ACVX1_P_03_stake_works_correctly() public { - setUp(); - - address creditAccount = _openTestCreditAccountAndDeposit(); - - expectAllowance(convexLPToken, creditAccount, address(basePoolMock), 0); - - expectStakeStackCalls(USER, CURVE_LP_AMOUNT / 2, false); - - executeOneLineMulticall( - creditAccount, address(basePoolAdapter), abi.encodeCall(basePoolAdapter.stake, (CURVE_LP_AMOUNT / 2)) - ); - - expectBalance(convexLPToken, creditAccount, CURVE_LP_AMOUNT - CURVE_LP_AMOUNT / 2); - - expectBalance(phantomToken, creditAccount, CURVE_LP_AMOUNT / 2); - - expectAllowance(convexLPToken, creditAccount, address(basePoolMock), 1); - - expectTokenIsEnabled(creditAccount, phantomToken, true); - - expectTokenIsEnabled(creditAccount, address(basePoolMock), true); - } - - /// @dev [ACVX1_P-4]: stakeAll works correctly and emits events - function test_ACVX1_P_04_stakeAll_works_correctly() public { - setUp(); - - address creditAccount = _openTestCreditAccountAndDeposit(); - - expectAllowance(convexLPToken, creditAccount, address(basePoolMock), 0); - - expectStakeStackCalls(USER, CURVE_LP_AMOUNT, true); - - executeOneLineMulticall(creditAccount, address(basePoolAdapter), abi.encodeCall(basePoolAdapter.stakeAll, ())); - - expectBalance(convexLPToken, creditAccount, 0); - - expectBalance(phantomToken, creditAccount, CURVE_LP_AMOUNT); - - expectAllowance(convexLPToken, creditAccount, address(basePoolMock), 1); - - expectTokenIsEnabled(creditAccount, convexLPToken, false); - expectTokenIsEnabled(creditAccount, phantomToken, true); - - expectTokenIsEnabled(creditAccount, address(basePoolMock), true); - } - - /// @dev [ACVX1_P-5]: getReward works correctly and emits events - function test_ACVX1_P_05_getReward_works_correctly() public { - for (uint256 numExtras; numExtras <= 2; numExtras++) { - _setupConvexSuite(numExtras); - - address creditAccount = _openTestCreditAccountAndDeposit(); - - executeOneLineMulticall( - creditAccount, address(basePoolAdapter), abi.encodeCall(basePoolAdapter.stakeAll, ()) - ); - - basePoolMock.addRewardAmount(REWARD_AMOUNT); - - if (numExtras >= 1) { - extraPoolMock1.addRewardAmount(REWARD_AMOUNT1); - } - if (numExtras == 2) { - extraPoolMock2.addRewardAmount(REWARD_AMOUNT2); - } - - expectTokenIsEnabled(creditAccount, cvx, false); - expectTokenIsEnabled(creditAccount, crv, false); - expectTokenIsEnabled(creditAccount, extraRewardToken1, false); - expectTokenIsEnabled(creditAccount, extraRewardToken2, false); - - expectClaimStackCalls(creditAccount, USER, numExtras); - - executeOneLineMulticall( - creditAccount, address(basePoolAdapter), abi.encodeCall(basePoolAdapter.getReward, ()) - ); - - expectBalance(crv, creditAccount, REWARD_AMOUNT); - expectBalance(cvx, creditAccount, REWARD_AMOUNT); - - expectBalance(extraRewardToken1, creditAccount, numExtras >= 1 ? REWARD_AMOUNT1 : 0); - expectBalance(extraRewardToken2, creditAccount, numExtras == 2 ? REWARD_AMOUNT2 : 0); - - expectTokenIsEnabled(creditAccount, cvx, true); - expectTokenIsEnabled(creditAccount, crv, true); - expectTokenIsEnabled(creditAccount, extraRewardToken1, numExtras >= 1); - expectTokenIsEnabled(creditAccount, extraRewardToken2, numExtras == 2); - expectTokenIsEnabled(creditAccount, address(basePoolMock), true); - } - } - - /// @dev [ACVX1_P-6]: withdraw works correctly and emits events - function test_ACVX1_P_06_withdraw_works_correctly() public { - for (uint256 numExtras; numExtras <= 2; numExtras++) { - _setupConvexSuite(numExtras); - - address creditAccount = _openTestCreditAccountAndDeposit(); - - executeOneLineMulticall( - creditAccount, address(basePoolAdapter), abi.encodeCall(basePoolAdapter.stakeAll, ()) - ); - - basePoolMock.addRewardAmount(REWARD_AMOUNT); - - if (numExtras >= 1) { - extraPoolMock1.addRewardAmount(REWARD_AMOUNT1); - } - if (numExtras == 2) { - extraPoolMock2.addRewardAmount(REWARD_AMOUNT2); - } - - expectTokenIsEnabled(creditAccount, convexLPToken, false); - expectTokenIsEnabled(creditAccount, cvx, false); - expectTokenIsEnabled(creditAccount, crv, false); - expectTokenIsEnabled(creditAccount, extraRewardToken1, false); - expectTokenIsEnabled(creditAccount, extraRewardToken2, false); - - expectPoolWithdrawStackCalls(USER, CURVE_LP_AMOUNT / 2, false, false, numExtras); - - executeOneLineMulticall( - creditAccount, - address(basePoolAdapter), - abi.encodeCall(basePoolAdapter.withdraw, (CURVE_LP_AMOUNT / 2, true)) - ); - - expectBalance(crv, creditAccount, REWARD_AMOUNT); - expectBalance(cvx, creditAccount, REWARD_AMOUNT); - expectBalance(extraRewardToken1, creditAccount, numExtras >= 1 ? REWARD_AMOUNT1 : 0); - expectBalance(extraRewardToken2, creditAccount, numExtras == 2 ? REWARD_AMOUNT2 : 0); - expectBalance(convexLPToken, creditAccount, CURVE_LP_AMOUNT / 2); - - expectBalance(phantomToken, creditAccount, CURVE_LP_AMOUNT - CURVE_LP_AMOUNT / 2); - - expectTokenIsEnabled(creditAccount, convexLPToken, true); - expectTokenIsEnabled(creditAccount, cvx, true); - expectTokenIsEnabled(creditAccount, crv, true); - expectTokenIsEnabled(creditAccount, extraRewardToken1, numExtras >= 1); - expectTokenIsEnabled(creditAccount, extraRewardToken2, numExtras == 2); - - expectTokenIsEnabled(creditAccount, address(basePoolMock), true); - } - } - - /// @dev [ACVX1_P-7]: withdrawAll works correctly and emits events - function test_ACVX1_P_07_withdrawAll_works_correctly() public { - for (uint256 numExtras; numExtras <= 2; numExtras++) { - _setupConvexSuite(numExtras); - - address creditAccount = _openTestCreditAccountAndDeposit(); - - executeOneLineMulticall( - creditAccount, address(basePoolAdapter), abi.encodeCall(basePoolAdapter.stakeAll, ()) - ); - - basePoolMock.addRewardAmount(REWARD_AMOUNT); - if (numExtras >= 1) { - extraPoolMock1.addRewardAmount(REWARD_AMOUNT1); - } - if (numExtras == 2) { - extraPoolMock2.addRewardAmount(REWARD_AMOUNT2); - } - - expectTokenIsEnabled(creditAccount, phantomToken, true); - expectTokenIsEnabled(creditAccount, convexLPToken, false); - expectTokenIsEnabled(creditAccount, cvx, false); - expectTokenIsEnabled(creditAccount, crv, false); - expectTokenIsEnabled(creditAccount, extraRewardToken1, false); - expectTokenIsEnabled(creditAccount, extraRewardToken2, false); - - expectPoolWithdrawStackCalls(USER, CURVE_LP_AMOUNT, true, false, numExtras); - - executeOneLineMulticall( - creditAccount, address(basePoolAdapter), abi.encodeCall(basePoolAdapter.withdrawAll, (true)) - ); - - expectBalance(crv, creditAccount, REWARD_AMOUNT); - expectBalance(cvx, creditAccount, REWARD_AMOUNT); - expectBalance(extraRewardToken1, creditAccount, numExtras >= 1 ? REWARD_AMOUNT1 : 0); - expectBalance(extraRewardToken2, creditAccount, numExtras == 2 ? REWARD_AMOUNT2 : 0); - expectBalance(convexLPToken, creditAccount, CURVE_LP_AMOUNT); - expectBalance(phantomToken, creditAccount, 0); - - expectTokenIsEnabled(creditAccount, phantomToken, false); - - expectTokenIsEnabled(creditAccount, convexLPToken, true); - expectTokenIsEnabled(creditAccount, cvx, true); - expectTokenIsEnabled(creditAccount, crv, true); - expectTokenIsEnabled(creditAccount, extraRewardToken1, numExtras >= 1); - expectTokenIsEnabled(creditAccount, extraRewardToken2, numExtras == 2); - - expectTokenIsEnabled(creditAccount, address(basePoolMock), true); - } - } - - /// @dev [ACVX1_P-8]: withdrawAndUnwrap works correctly and emits events - function test_ACVX1_P_08_withdrawAndUnwrap_works_correctly() public { - for (uint256 numExtras; numExtras <= 2; numExtras++) { - _setupConvexSuite(numExtras); - - address creditAccount = _openTestCreditAccountAndDeposit(); - - executeOneLineMulticall( - creditAccount, address(basePoolAdapter), abi.encodeCall(basePoolAdapter.stakeAll, ()) - ); - - basePoolMock.addRewardAmount(REWARD_AMOUNT); - if (numExtras >= 1) { - extraPoolMock1.addRewardAmount(REWARD_AMOUNT1); - } - if (numExtras == 2) { - extraPoolMock2.addRewardAmount(REWARD_AMOUNT2); - } - - expectTokenIsEnabled(creditAccount, curveLPToken, false, "initial setup"); - expectTokenIsEnabled(creditAccount, cvx, false, "initial setup"); - expectTokenIsEnabled(creditAccount, crv, false, "initial setup"); - expectTokenIsEnabled(creditAccount, extraRewardToken1, false, "initial setup"); - expectTokenIsEnabled(creditAccount, extraRewardToken2, false, "initial setup"); - - expectPoolWithdrawStackCalls(USER, CURVE_LP_AMOUNT / 2, false, true, numExtras); - - executeOneLineMulticall( - creditAccount, - address(basePoolAdapter), - abi.encodeCall(basePoolAdapter.withdrawAndUnwrap, (CURVE_LP_AMOUNT / 2, true)) - ); - - expectBalance(crv, creditAccount, REWARD_AMOUNT); - expectBalance(cvx, creditAccount, REWARD_AMOUNT); - expectBalance(extraRewardToken1, creditAccount, numExtras >= 1 ? REWARD_AMOUNT1 : 0); - expectBalance(extraRewardToken2, creditAccount, numExtras == 2 ? REWARD_AMOUNT2 : 0); - - expectBalance(curveLPToken, creditAccount, CURVE_LP_AMOUNT / 2); - - expectBalance(phantomToken, creditAccount, CURVE_LP_AMOUNT - CURVE_LP_AMOUNT / 2); - - expectTokenIsEnabled(creditAccount, curveLPToken, true); - expectTokenIsEnabled(creditAccount, cvx, true); - expectTokenIsEnabled(creditAccount, crv, true); - expectTokenIsEnabled(creditAccount, extraRewardToken1, numExtras >= 1); - expectTokenIsEnabled(creditAccount, extraRewardToken2, numExtras == 2); - - expectTokenIsEnabled(creditAccount, address(basePoolMock), true); - } - } - - /// @dev [ACVX1_P-9]: withdrawAllAndUnwrap works correctly and emits events - function test_ACVX1_P_09_withdrawAllAndUnwrap_works_correctly() public { - for (uint256 numExtras; numExtras <= 2; numExtras++) { - _setupConvexSuite(numExtras); - - address creditAccount = _openTestCreditAccountAndDeposit(); - - executeOneLineMulticall( - creditAccount, address(basePoolAdapter), abi.encodeCall(basePoolAdapter.stakeAll, ()) - ); - - basePoolMock.addRewardAmount(REWARD_AMOUNT); - if (numExtras >= 1) { - extraPoolMock1.addRewardAmount(REWARD_AMOUNT1); - } - if (numExtras == 2) { - extraPoolMock2.addRewardAmount(REWARD_AMOUNT2); - } - - expectTokenIsEnabled(creditAccount, phantomToken, true); - expectTokenIsEnabled(creditAccount, curveLPToken, false); - expectTokenIsEnabled(creditAccount, cvx, false); - expectTokenIsEnabled(creditAccount, crv, false); - expectTokenIsEnabled(creditAccount, extraRewardToken1, false); - expectTokenIsEnabled(creditAccount, extraRewardToken2, false); - - expectPoolWithdrawStackCalls(USER, CURVE_LP_AMOUNT, true, true, numExtras); - - executeOneLineMulticall( - creditAccount, address(basePoolAdapter), abi.encodeCall(basePoolAdapter.withdrawAllAndUnwrap, (true)) - ); - - expectBalance(crv, creditAccount, REWARD_AMOUNT); - expectBalance(cvx, creditAccount, REWARD_AMOUNT); - - expectBalance(extraRewardToken1, creditAccount, numExtras >= 1 ? REWARD_AMOUNT1 : 0); - expectBalance(extraRewardToken2, creditAccount, numExtras == 2 ? REWARD_AMOUNT2 : 0); - - expectBalance(curveLPToken, creditAccount, CURVE_LP_AMOUNT); - - expectBalance(phantomToken, creditAccount, 0); - - expectTokenIsEnabled(creditAccount, phantomToken, false); - - expectTokenIsEnabled(creditAccount, curveLPToken, true); - expectTokenIsEnabled(creditAccount, cvx, true); - expectTokenIsEnabled(creditAccount, crv, true); - expectTokenIsEnabled(creditAccount, extraRewardToken1, numExtras >= 1); - expectTokenIsEnabled(creditAccount, extraRewardToken2, numExtras == 2); - - expectTokenIsEnabled(creditAccount, address(basePoolMock), true); - } - } -} diff --git a/contracts/test/unit/adapters/convex/ConvexV1_Booster.t.sol b/contracts/test/unit/adapters/convex/ConvexV1_Booster.t.sol deleted file mode 100644 index 60f76ddc..00000000 --- a/contracts/test/unit/adapters/convex/ConvexV1_Booster.t.sol +++ /dev/null @@ -1,163 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import { - ConvexAdapterHelper, - CURVE_LP_AMOUNT, - DAI_ACCOUNT_AMOUNT, - REWARD_AMOUNT, - REWARD_AMOUNT1, - REWARD_AMOUNT2 -} from "./ConvexAdapterHelper.sol"; -import {ERC20Mock} from "@gearbox-protocol/core-v3/contracts/test/mocks/token/ERC20Mock.sol"; - -import {USER, CONFIGURATOR} from "../../../lib/constants.sol"; - -import {CallerNotConfiguratorException} from "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol"; - -import {Test} from "forge-std/Test.sol"; - -contract ConvexV1BoosterAdapterTest is Test, ConvexAdapterHelper { - address creditAccount; - - function setUp() public { - _setupConvexSuite(2); - - (creditAccount,) = _openTestCreditAccount(); - } - - /// - /// TESTS - /// - - /// @dev [ACVX1_B-1]: updateStakedPhantomTokensMap reverts when called not by configurtator - function test_ACVX1_B_01_updateStakedPhantomTokensMap_access_restricted() public { - vm.prank(CONFIGURATOR); - boosterAdapter.updateStakedPhantomTokensMap(); - - vm.expectRevert(CallerNotConfiguratorException.selector); - vm.prank(USER); - boosterAdapter.updateStakedPhantomTokensMap(); - } - - /// @dev [ACVX1_B-2]: deposit function works correctly and emits events - function test_ACVX1_B_02_deposit_works_correctly() public { - for (uint256 st = 0; st < 2; st++) { - bool staking = st != 0; - - setUp(); - - ERC20Mock(curveLPToken).mint(creditAccount, CURVE_LP_AMOUNT); - - expectAllowance(curveLPToken, creditAccount, address(boosterMock), 0); - - expectDepositStackCalls(USER, CURVE_LP_AMOUNT / 2, staking, false); - - executeOneLineMulticall( - creditAccount, - address(boosterAdapter), - abi.encodeCall(boosterAdapter.deposit, (0, CURVE_LP_AMOUNT / 2, staking)) - ); - - expectBalance(curveLPToken, creditAccount, CURVE_LP_AMOUNT - CURVE_LP_AMOUNT / 2); - - expectBalance(staking ? phantomToken : convexLPToken, creditAccount, CURVE_LP_AMOUNT / 2); - expectAllowance(curveLPToken, creditAccount, address(boosterMock), 1); - - expectTokenIsEnabled(creditAccount, convexLPToken, !staking); - expectTokenIsEnabled(creditAccount, phantomToken, staking); - - expectTokenIsEnabled(creditAccount, address(boosterMock), true); - } - } - - /// @dev [ACVX1_B_03]: depositAll function works correctly and emits events - function test_ACVX1_B_03_depositAll_works_correctly() public { - for (uint256 st = 0; st < 2; st++) { - bool staking = st != 0; - - setUp(); - - ERC20Mock(curveLPToken).mint(creditAccount, CURVE_LP_AMOUNT); - - expectAllowance(curveLPToken, creditAccount, address(boosterMock), 0); - - expectDepositStackCalls(USER, CURVE_LP_AMOUNT, staking, true); - - executeOneLineMulticall( - creditAccount, address(boosterAdapter), abi.encodeCall(boosterAdapter.depositAll, (0, staking)) - ); - - expectBalance(curveLPToken, creditAccount, 0); - - expectBalance(staking ? phantomToken : convexLPToken, creditAccount, CURVE_LP_AMOUNT); - - expectAllowance(curveLPToken, creditAccount, address(boosterMock), 1); - - expectTokenIsEnabled(creditAccount, curveLPToken, false); - expectTokenIsEnabled(creditAccount, convexLPToken, !staking); - expectTokenIsEnabled(creditAccount, phantomToken, staking); - - expectTokenIsEnabled(creditAccount, address(boosterMock), true); - } - } - - /// @dev [ACVX1_B-4]: withdraw function works correctly and emits events - function test_ACVX1_B_04_withdraw_works_correctly() public { - setUp(); - - ERC20Mock(curveLPToken).mint(creditAccount, CURVE_LP_AMOUNT); - - executeOneLineMulticall( - creditAccount, address(boosterAdapter), abi.encodeCall(boosterAdapter.deposit, (0, CURVE_LP_AMOUNT, false)) - ); - - expectAllowance(convexLPToken, creditAccount, address(boosterMock), 0); - - expectWithdrawStackCalls(USER, CURVE_LP_AMOUNT / 2, false); - - executeOneLineMulticall( - creditAccount, address(boosterAdapter), abi.encodeCall(boosterAdapter.withdraw, (0, CURVE_LP_AMOUNT / 2)) - ); - - expectBalance(curveLPToken, creditAccount, CURVE_LP_AMOUNT / 2); - - expectBalance(convexLPToken, creditAccount, CURVE_LP_AMOUNT - CURVE_LP_AMOUNT / 2); - - expectAllowance(convexLPToken, creditAccount, address(boosterMock), 0); - - expectTokenIsEnabled(creditAccount, curveLPToken, true); - - expectTokenIsEnabled(creditAccount, address(boosterMock), true); - } - - /// @dev [ACVX1_B-5]: withdrawAll function works correctly and emits events - function test_ACVX1_B_05_withdrawAll_works_correctly() public { - setUp(); - - ERC20Mock(curveLPToken).mint(creditAccount, CURVE_LP_AMOUNT); - - executeOneLineMulticall( - creditAccount, address(boosterAdapter), abi.encodeCall(boosterAdapter.deposit, (0, CURVE_LP_AMOUNT, false)) - ); - expectAllowance(convexLPToken, creditAccount, address(boosterMock), 0); - - expectWithdrawStackCalls(USER, CURVE_LP_AMOUNT, true); - - executeOneLineMulticall(creditAccount, address(boosterAdapter), abi.encodeCall(boosterAdapter.withdrawAll, (0))); - - expectBalance(curveLPToken, creditAccount, CURVE_LP_AMOUNT); - - expectBalance(convexLPToken, creditAccount, 0); - - expectAllowance(convexLPToken, creditAccount, address(boosterMock), 0); - - expectTokenIsEnabled(creditAccount, convexLPToken, false); - - expectTokenIsEnabled(creditAccount, curveLPToken, true); - - expectTokenIsEnabled(creditAccount, address(boosterMock), true); - } -} diff --git a/contracts/test/unit/adapters/curve/CurveV1Adapter2Assets.unit.t.sol b/contracts/test/unit/adapters/curve/CurveV1Adapter2Assets.unit.t.sol new file mode 100644 index 00000000..ab184c47 --- /dev/null +++ b/contracts/test/unit/adapters/curve/CurveV1Adapter2Assets.unit.t.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {CurveV1Adapter2Assets} from "../../../../adapters/curve/CurveV1_2.sol"; +import {ICurvePool2Assets} from "../../../../integrations/curve/ICurvePool_2.sol"; +import {PoolMock, PoolType} from "../../../mocks/integrations/curve/PoolMock.sol"; +import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; + +/// @title Curve v1 adapter 2 assets unit test +/// @notice U:[CRV2]: Unit tests for Curve 2 coins pool adapter +contract CurveV1Adapter2AssetsUnitTest is AdapterUnitTestHelper { + CurveV1Adapter2Assets adapter; + PoolMock pool; + + address token0; + address token1; + address lpToken; + + uint256 token0Mask; + uint256 token1Mask; + uint256 lpTokenMask; + + function setUp() public { + _setUp(); + + (token0, token0Mask) = (tokens[0], 1); + (token1, token1Mask) = (tokens[1], 2); + (lpToken, lpTokenMask) = (tokens[2], 4); + + address[] memory coins = new address[](2); + coins[0] = token0; + coins[1] = token1; + pool = new PoolMock(PoolType.Stable, coins, new address[](0)); + + adapter = new CurveV1Adapter2Assets(address(creditManager), address(pool), lpToken, address(0)); + + assertEq(adapter.nCoins(), 2, "Incorrect nCoins"); + } + + /// @notice U:[CRV2-1]: Wrapper functions revert on wrong caller + function test_U_CRV2_01_wrapper_functions_revert_on_wrong_caller() public { + _revertsOnNonFacadeCaller(); + adapter.add_liquidity([uint256(0), 0], 0); + + _revertsOnNonFacadeCaller(); + adapter.remove_liquidity(0, [uint256(0), 0]); + + _revertsOnNonFacadeCaller(); + adapter.remove_liquidity_imbalance([uint256(0), 0], 0); + } + + /// @notice U:[CRV2-2]: `add_liquidity` works as expected + function test_U_CRV2_02_add_liquidity_works_as_expected() public { + address[] memory tokensToApprove = new address[](2); + tokensToApprove[0] = token0; + tokensToApprove[1] = token1; + _executesCall({ + tokensToApprove: tokensToApprove, + tokensToValidate: new address[](0), + callData: abi.encodeCall(ICurvePool2Assets.add_liquidity, ([uint256(750), 250], 500)) + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.add_liquidity([uint256(750), 250], 500); + + assertEq(tokensToEnable, lpTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[CRV2-3]: `remove_liquidity` works as expected + function test_U_CRV2_03_remove_liquidity_works_as_expected() public { + _executesCall({ + tokensToApprove: new address[](0), + tokensToValidate: new address[](0), + callData: abi.encodeCall(ICurvePool2Assets.remove_liquidity, (500, [uint256(750), 250])) + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.remove_liquidity(500, [uint256(750), 250]); + + assertEq(tokensToEnable, token0Mask | token1Mask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[CRV2-4]: `remove_liquidity_imbalance` works as expected + function test_U_CRV2_04_remove_liquidity_imbalance_works_as_expected() public { + _executesCall({ + tokensToApprove: new address[](0), + tokensToValidate: new address[](0), + callData: abi.encodeCall(ICurvePool2Assets.remove_liquidity_imbalance, ([uint256(0), 250], 500)) + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.remove_liquidity_imbalance([uint256(0), 250], 500); + + assertEq(tokensToEnable, token1Mask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } +} diff --git a/contracts/test/unit/adapters/curve/CurveV1Adapter3Assets.unit.t.sol b/contracts/test/unit/adapters/curve/CurveV1Adapter3Assets.unit.t.sol new file mode 100644 index 00000000..f27ff828 --- /dev/null +++ b/contracts/test/unit/adapters/curve/CurveV1Adapter3Assets.unit.t.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {CurveV1Adapter3Assets} from "../../../../adapters/curve/CurveV1_3.sol"; +import {ICurvePool3Assets} from "../../../../integrations/curve/ICurvePool_3.sol"; +import {PoolMock, PoolType} from "../../../mocks/integrations/curve/PoolMock.sol"; +import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; + +/// @title Curve v1 adapter 3 assets unit test +/// @notice U:[CRV3]: Unit tests for Curve 3 coins pool adapter +contract CurveV1Adapter3AssetsUnitTest is AdapterUnitTestHelper { + CurveV1Adapter3Assets adapter; + PoolMock pool; + + address token0; + address token1; + address token2; + address lpToken; + + uint256 token0Mask; + uint256 token1Mask; + uint256 token2Mask; + uint256 lpTokenMask; + + function setUp() public { + _setUp(); + + (token0, token0Mask) = (tokens[0], 1); + (token1, token1Mask) = (tokens[1], 2); + (token2, token2Mask) = (tokens[2], 4); + (lpToken, lpTokenMask) = (tokens[3], 8); + + address[] memory coins = new address[](3); + coins[0] = token0; + coins[1] = token1; + coins[2] = token2; + pool = new PoolMock(PoolType.Stable, coins, new address[](0)); + + adapter = new CurveV1Adapter3Assets(address(creditManager), address(pool), lpToken, address(0)); + + assertEq(adapter.nCoins(), 3, "Incorrect nCoins"); + } + + /// @notice U:[CRV3-1]: Wrapper functions revert on wrong caller + function test_U_CRV3_01_wrapper_functions_revert_on_wrong_caller() public { + _revertsOnNonFacadeCaller(); + adapter.add_liquidity([uint256(0), 0, 0], 0); + + _revertsOnNonFacadeCaller(); + adapter.remove_liquidity(0, [uint256(0), 0, 0]); + + _revertsOnNonFacadeCaller(); + adapter.remove_liquidity_imbalance([uint256(0), 0, 0], 0); + } + + /// @notice U:[CRV3-2]: `add_liquidity` works as expected + function test_U_CRV3_02_add_liquidity_works_as_expected() public { + address[] memory tokensToApprove = new address[](3); + tokensToApprove[0] = token0; + tokensToApprove[1] = token1; + tokensToApprove[2] = token2; + _executesCall({ + tokensToApprove: tokensToApprove, + tokensToValidate: new address[](0), + callData: abi.encodeCall(ICurvePool3Assets.add_liquidity, ([uint256(750), 500, 250], 500)) + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.add_liquidity([uint256(750), 500, 250], 500); + + assertEq(tokensToEnable, lpTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[CRV3-3]: `remove_liquidity` works as expected + function test_U_CRV3_03_remove_liquidity_works_as_expected() public { + _executesCall({ + tokensToApprove: new address[](0), + tokensToValidate: new address[](0), + callData: abi.encodeCall(ICurvePool3Assets.remove_liquidity, (500, [uint256(750), 500, 250])) + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.remove_liquidity(500, [uint256(750), 500, 250]); + + assertEq(tokensToEnable, token0Mask | token1Mask | token2Mask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[CRV3-4]: `remove_liquidity_imbalance` works as expected + function test_U_CRV3_04_remove_liquidity_imbalance_works_as_expected() public { + _executesCall({ + tokensToApprove: new address[](0), + tokensToValidate: new address[](0), + callData: abi.encodeCall(ICurvePool3Assets.remove_liquidity_imbalance, ([uint256(0), 500, 250], 500)) + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = + adapter.remove_liquidity_imbalance([uint256(0), 500, 250], 500); + + assertEq(tokensToEnable, token1Mask | token2Mask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } +} diff --git a/contracts/test/unit/adapters/curve/CurveV1Adapter4Assets.unit.t.sol b/contracts/test/unit/adapters/curve/CurveV1Adapter4Assets.unit.t.sol new file mode 100644 index 00000000..1cbbd013 --- /dev/null +++ b/contracts/test/unit/adapters/curve/CurveV1Adapter4Assets.unit.t.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {CurveV1Adapter4Assets} from "../../../../adapters/curve/CurveV1_4.sol"; +import {ICurvePool4Assets} from "../../../../integrations/curve/ICurvePool_4.sol"; +import {PoolMock, PoolType} from "../../../mocks/integrations/curve/PoolMock.sol"; +import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; + +/// @title Curve v1 adapter 4 assets unit test +/// @notice U:[CRV4]: Unit tests for Curve 4 coins pool adapter +contract CurveV1Adapter4AssetsUnitTest is AdapterUnitTestHelper { + CurveV1Adapter4Assets adapter; + PoolMock pool; + + address token0; + address token1; + address token2; + address token3; + address lpToken; + + uint256 token0Mask; + uint256 token1Mask; + uint256 token2Mask; + uint256 token3Mask; + uint256 lpTokenMask; + + function setUp() public { + _setUp(); + + (token0, token0Mask) = (tokens[0], 1); + (token1, token1Mask) = (tokens[1], 2); + (token2, token2Mask) = (tokens[2], 4); + (token3, token3Mask) = (tokens[3], 8); + (lpToken, lpTokenMask) = (tokens[4], 16); + + address[] memory coins = new address[](4); + coins[0] = token0; + coins[1] = token1; + coins[2] = token2; + coins[3] = token3; + pool = new PoolMock(PoolType.Stable, coins, new address[](0)); + + adapter = new CurveV1Adapter4Assets(address(creditManager), address(pool), lpToken, address(0)); + + assertEq(adapter.nCoins(), 4, "Incorrect nCoins"); + } + + /// @notice U:[CRV4-1]: Wrapper functions revert on wrong caller + function test_U_CRV4_01_wrapper_functions_revert_on_wrong_caller() public { + _revertsOnNonFacadeCaller(); + adapter.add_liquidity([uint256(0), 0, 0, 0], 0); + + _revertsOnNonFacadeCaller(); + adapter.remove_liquidity(0, [uint256(0), 0, 0, 0]); + + _revertsOnNonFacadeCaller(); + adapter.remove_liquidity_imbalance([uint256(0), 0, 0, 0], 0); + } + + /// @notice U:[CRV4-2]: `add_liquidity` works as expected + function test_U_CRV4_02_add_liquidity_works_as_expected() public { + address[] memory tokensToApprove = new address[](4); + tokensToApprove[0] = token0; + tokensToApprove[1] = token1; + tokensToApprove[2] = token2; + tokensToApprove[3] = token3; + _executesCall({ + tokensToApprove: tokensToApprove, + tokensToValidate: new address[](0), + callData: abi.encodeCall(ICurvePool4Assets.add_liquidity, ([uint256(750), 500, 500, 250], 500)) + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.add_liquidity([uint256(750), 500, 500, 250], 500); + + assertEq(tokensToEnable, lpTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[CRV4-3]: `remove_liquidity` works as expected + function test_U_CRV4_03_remove_liquidity_works_as_expected() public { + _executesCall({ + tokensToApprove: new address[](0), + tokensToValidate: new address[](0), + callData: abi.encodeCall(ICurvePool4Assets.remove_liquidity, (500, [uint256(750), 500, 500, 250])) + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.remove_liquidity(500, [uint256(750), 500, 500, 250]); + + assertEq(tokensToEnable, token0Mask | token1Mask | token2Mask | token3Mask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[CRV4-4]: `remove_liquidity_imbalance` works as expected + function test_U_CRV4_04_remove_liquidity_imbalance_works_as_expected() public { + _executesCall({ + tokensToApprove: new address[](0), + tokensToValidate: new address[](0), + callData: abi.encodeCall(ICurvePool4Assets.remove_liquidity_imbalance, ([uint256(0), 500, 0, 250], 500)) + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = + adapter.remove_liquidity_imbalance([uint256(0), 500, 0, 250], 500); + + assertEq(tokensToEnable, token1Mask | token3Mask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } +} diff --git a/contracts/test/unit/adapters/curve/CurveV1AdapterBase.harness.sol b/contracts/test/unit/adapters/curve/CurveV1AdapterBase.harness.sol new file mode 100644 index 00000000..f0a7dcc5 --- /dev/null +++ b/contracts/test/unit/adapters/curve/CurveV1AdapterBase.harness.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {AdapterType} from "@gearbox-protocol/sdk-gov/contracts/AdapterType.sol"; + +import {CurveV1AdapterBase} from "../../../../adapters/curve/CurveV1_Base.sol"; +import {ICurvePool2Assets} from "../../../../integrations/curve/ICurvePool_2.sol"; +import {ICurvePool3Assets} from "../../../../integrations/curve/ICurvePool_3.sol"; +import {ICurvePool4Assets} from "../../../../integrations/curve/ICurvePool_4.sol"; + +contract CurveV1AdapterBaseHarness is CurveV1AdapterBase { + constructor(address _creditManager, address _curvePool, address _lp_token, address _metapoolBase, uint256 _nCoins) + CurveV1AdapterBase(_creditManager, _curvePool, _lp_token, _metapoolBase, _nCoins) + {} + + function _gearboxAdapterType() external view override returns (AdapterType) { + return nCoins == 2 + ? AdapterType.CURVE_V1_2ASSETS + : (nCoins == 3 ? AdapterType.CURVE_V1_3ASSETS : AdapterType.CURVE_V1_4ASSETS); + } + + function _getAddLiquidityOneCoinCallData(uint256 i, uint256 amount, uint256 minAmount) + internal + view + override + returns (bytes memory callData) + { + if (nCoins == 2) { + uint256[2] memory amounts; + amounts[i] = amount; + return abi.encodeCall(ICurvePool2Assets.add_liquidity, (amounts, minAmount)); + } else if (nCoins == 3) { + uint256[3] memory amounts; + amounts[i] = amount; + return abi.encodeCall(ICurvePool3Assets.add_liquidity, (amounts, minAmount)); + } else { + uint256[4] memory amounts; + amounts[i] = amount; + return abi.encodeCall(ICurvePool4Assets.add_liquidity, (amounts, minAmount)); + } + } + + function _getCalcAddOneCoinCallData(uint256 i, uint256 amount) + internal + view + override + returns (bytes memory callData, bytes memory callDataAlt) + { + if (nCoins == 2) { + uint256[2] memory amounts; + amounts[i] = amount; + return ( + abi.encodeCall(ICurvePool2Assets.calc_token_amount, (amounts, true)), + abi.encodeWithSignature("calc_token_amount(uint256[2])", amounts) + ); + } else if (nCoins == 3) { + uint256[3] memory amounts; + amounts[i] = amount; + return ( + abi.encodeCall(ICurvePool3Assets.calc_token_amount, (amounts, true)), + abi.encodeWithSignature("calc_token_amount(uint256[3])", amounts) + ); + } else { + uint256[4] memory amounts; + amounts[i] = amount; + return ( + abi.encodeCall(ICurvePool4Assets.calc_token_amount, (amounts, true)), + abi.encodeWithSignature("calc_token_amount(uint256[4])", amounts) + ); + } + } +} diff --git a/contracts/test/unit/adapters/curve/CurveV1AdapterBase.unit.t.sol b/contracts/test/unit/adapters/curve/CurveV1AdapterBase.unit.t.sol new file mode 100644 index 00000000..fbe17475 --- /dev/null +++ b/contracts/test/unit/adapters/curve/CurveV1AdapterBase.unit.t.sol @@ -0,0 +1,444 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import { + IncorrectParameterException, + ZeroAddressException +} from "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol"; + +import {PoolMock, PoolType} from "../../../mocks/integrations/curve/PoolMock.sol"; +import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; +import {CurveV1AdapterBaseHarness} from "./CurveV1AdapterBase.harness.sol"; + +/// @title Curve v1 adapter base unit test +/// @notice U:[CRVB]: Unit tests for Curve pool adapter base +contract CurveV1AdapterBaseUnitTest is AdapterUnitTestHelper { + CurveV1AdapterBaseHarness adapter; + PoolMock basePool; + PoolMock pool; + + address token0; + address token1; + address underlying0; + address underlying1; + address lpToken; + + uint256 token0Mask; + uint256 token1Mask; + uint256 underlying0Mask; + uint256 underlying1Mask; + uint256 lpTokenMask; + + // ----- // + // SETUP // + // ----- // + + modifier onlyStablePools() { + _setupPoolAndAdapter(PoolType.Stable); + _; + } + + modifier onlyCryptoPools() { + _setupPoolAndAdapter(PoolType.Crypto); + _; + } + + modifier bothStableAndCryptoPools() { + uint256 snapshot = vm.snapshot(); + _setupPoolAndAdapter(PoolType.Stable); + _; + vm.revertTo(snapshot); + _setupPoolAndAdapter(PoolType.Crypto); + _; + } + + function setUp() public { + _setUp(); + + (token0, token0Mask) = (tokens[0], 1); + (token1, token1Mask) = (tokens[1], 2); + (underlying0, underlying0Mask) = (tokens[2], 4); + (underlying1, underlying1Mask) = (tokens[3], 8); + (lpToken, lpTokenMask) = (tokens[4], 16); + } + + function _setupPoolAndAdapter(PoolType poolType) internal { + address[] memory baseCoins = new address[](2); + baseCoins[0] = underlying0; + baseCoins[1] = underlying1; + basePool = new PoolMock(poolType, baseCoins, new address[](0)); + + address[] memory coins = new address[](2); + coins[0] = token0; + coins[1] = token1; + pool = new PoolMock(poolType, coins, new address[](0)); + + adapter = new CurveV1AdapterBaseHarness(address(creditManager), address(pool), lpToken, address(basePool), 2); + + assertEq(adapter.use256(), poolType == PoolType.Crypto, "Incorrect use256"); + } + + // ------- // + // GENERAL // + // ------- // + + /// @notice U:[CRVB-1]: Constructor works as expected + function test_U_CRVB_01_constructor_works_as_expected() public { + pool = new PoolMock(PoolType.Stable, new address[](0), new address[](0)); + + // reverts on zero LP token + vm.expectRevert(ZeroAddressException.selector); + new CurveV1AdapterBaseHarness(address(creditManager), address(pool), address(0), address(0), 2); + + // reverts when pool has fewer coins than needed + vm.expectRevert(IncorrectParameterException.selector); + new CurveV1AdapterBaseHarness(address(creditManager), address(pool), lpToken, address(0), 2); + + // plain pool + address[] memory coins = new address[](2); + coins[0] = token0; + coins[1] = token1; + pool = new PoolMock(PoolType.Stable, coins, new address[](0)); + + _readsTokenMask(token0); + _readsTokenMask(token1); + _readsTokenMask(lpToken); + adapter = new CurveV1AdapterBaseHarness(address(creditManager), address(pool), lpToken, address(0), 2); + + assertEq(adapter.creditManager(), address(creditManager), "Incorrect creditManager"); + assertEq(adapter.addressProvider(), address(addressProvider), "Incorrect addressProvider"); + assertEq(adapter.targetContract(), address(pool), "Incorrect targetContract"); + + assertEq(adapter.token(), lpToken, "Incorrect token"); + assertEq(adapter.lp_token(), lpToken, "Incorrect token"); + assertEq(adapter.lpTokenMask(), lpTokenMask, "Incorrect lpTokenMask"); + assertEq(adapter.metapoolBase(), address(0), "Incorrect metapoolBase"); + assertEq(adapter.nCoins(), 2, "Incorrect nCoins"); + assertEq(adapter.token0(), token0, "Incorrect token0"); + assertEq(adapter.token1(), token1, "Incorrect token1"); + assertEq(adapter.token0Mask(), token0Mask, "Incorrect token0Mask"); + assertEq(adapter.token1Mask(), token1Mask, "Incorrect token1Mask"); + + // metapool + address[] memory underlyings = new address[](2); + underlyings[0] = underlying0; + underlyings[1] = underlying1; + basePool = new PoolMock(PoolType.Stable, underlyings, new address[](0)); + + _readsTokenMask(token0); + _readsTokenMask(token1); + _readsTokenMask(underlying0); + _readsTokenMask(underlying1); + _readsTokenMask(lpToken); + adapter = new CurveV1AdapterBaseHarness(address(creditManager), address(pool), lpToken, address(basePool), 2); + + assertEq(adapter.metapoolBase(), address(basePool), "Incorrect metapoolBase"); + assertEq(adapter.underlying0(), token0, "Incorrect underlying0"); + assertEq(adapter.underlying1(), underlying0, "Incorrect underlying1"); + assertEq(adapter.underlying2(), underlying1, "Incorrect underlying2"); + assertEq(adapter.underlying0Mask(), token0Mask, "Incorrect underlying0Mask"); + assertEq(adapter.underlying1Mask(), underlying0Mask, "Incorrect underlying1Mask"); + assertEq(adapter.underlying2Mask(), underlying1Mask, "Incorrect underlying2Mask"); + + // lending pool + pool = new PoolMock(PoolType.Stable, coins, underlyings); + + _readsTokenMask(token0); + _readsTokenMask(token1); + _readsTokenMask(underlying0); + _readsTokenMask(underlying1); + _readsTokenMask(lpToken); + adapter = new CurveV1AdapterBaseHarness(address(creditManager), address(pool), lpToken, address(0), 2); + assertEq(adapter.metapoolBase(), address(0), "Incorrect metapoolBase"); + assertEq(adapter.underlying0(), underlying0, "Incorrect underlying0"); + assertEq(adapter.underlying1(), underlying1, "Incorrect underlying1"); + assertEq(adapter.underlying0Mask(), underlying0Mask, "Incorrect underlying0Mask"); + assertEq(adapter.underlying1Mask(), underlying1Mask, "Incorrect underlying1Mask"); + } + + /// @notice U:[CRVB-2]: Wrapper functions revert on wrong caller + function test_U_CRVB_02_wrapper_functions_revert_on_wrong_caller() public bothStableAndCryptoPools { + _revertsOnNonFacadeCaller(); + adapter.exchange(uint256(0), uint256(0), 0, 0); + + _revertsOnNonFacadeCaller(); + adapter.exchange(int128(0), int128(0), 0, 0); + + _revertsOnNonFacadeCaller(); + adapter.exchange_all(uint256(0), uint256(0), 0); + + _revertsOnNonFacadeCaller(); + adapter.exchange_all(int128(0), int128(0), 0); + + _revertsOnNonFacadeCaller(); + adapter.exchange_underlying(uint256(0), uint256(0), 0, 0); + + _revertsOnNonFacadeCaller(); + adapter.exchange_underlying(int128(0), int128(0), 0, 0); + + _revertsOnNonFacadeCaller(); + adapter.exchange_all_underlying(uint256(0), uint256(0), 0); + + _revertsOnNonFacadeCaller(); + adapter.exchange_all_underlying(int128(0), int128(0), 0); + + _revertsOnNonFacadeCaller(); + adapter.add_liquidity_one_coin(0, 0, 0); + + _revertsOnNonFacadeCaller(); + adapter.add_all_liquidity_one_coin(0, 0); + + _revertsOnNonFacadeCaller(); + adapter.remove_liquidity_one_coin(0, uint256(0), 0); + + _revertsOnNonFacadeCaller(); + adapter.remove_liquidity_one_coin(0, int128(0), 0); + + _revertsOnNonFacadeCaller(); + adapter.remove_all_liquidity_one_coin(uint256(0), 0); + + _revertsOnNonFacadeCaller(); + adapter.remove_all_liquidity_one_coin(int128(0), 0); + } + + // -------- // + // EXCHANGE // + // -------- // + + /// @notice U:[CRVB-3]: `exchange` works as expected + function test_U_CRVB_03_exchange_works_as_expected() public bothStableAndCryptoPools { + for (uint256 i; i < 2; ++i) { + bool use256 = i == 2; + + _executesSwap({ + tokenIn: token0, + tokenOut: token1, + callData: abi.encodeWithSignature( + pool.isCrypto() + ? "exchange(uint256,uint256,uint256,uint256)" + : "exchange(int128,int128,uint256,uint256)", + 0, + 1, + 1000, + 500 + ), + requiresApproval: true, + validatesTokens: false + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = use256 + ? adapter.exchange(uint256(0), uint256(1), 1000, 500) + : adapter.exchange(int128(0), int128(1), 1000, 500); + + assertEq(tokensToEnable, token1Mask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + } + + /// @notice U:[CRVB-4]: `exchange_all` works as expected + function test_U_CRVB_04_exchange_all_works_as_expected() public bothStableAndCryptoPools { + deal({token: token0, to: creditAccount, give: 1001}); + for (uint256 i; i < 2; ++i) { + bool use256 = i == 2; + + _readsActiveAccount(); + _executesSwap({ + tokenIn: token0, + tokenOut: token1, + callData: abi.encodeWithSignature( + pool.isCrypto() + ? "exchange(uint256,uint256,uint256,uint256)" + : "exchange(int128,int128,uint256,uint256)", + 0, + 1, + 1000, + 500 + ), + requiresApproval: true, + validatesTokens: false + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = use256 + ? adapter.exchange_all(uint256(0), uint256(1), 0.5e27) + : adapter.exchange_all(int128(0), int128(1), 0.5e27); + + assertEq(tokensToEnable, token1Mask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, token0Mask, "Incorrect tokensToDisable"); + } + } + + /// @notice U:[CRVB-5]: `exchange_underlying` works as expected + function test_U_CRVB_05_exchange_underlying_works_as_expected() public bothStableAndCryptoPools { + for (uint256 i; i < 2; ++i) { + bool use256 = i == 2; + + _executesSwap({ + tokenIn: token0, + tokenOut: underlying0, + callData: abi.encodeWithSignature( + pool.isCrypto() + ? "exchange_underlying(uint256,uint256,uint256,uint256)" + : "exchange_underlying(int128,int128,uint256,uint256)", + 0, + 1, + 1000, + 500 + ), + requiresApproval: true, + validatesTokens: false + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = use256 + ? adapter.exchange_underlying(uint256(0), uint256(1), 1000, 500) + : adapter.exchange_underlying(int128(0), int128(1), 1000, 500); + + assertEq(tokensToEnable, underlying0Mask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + } + + /// @notice U:[CRVB-6]: `exchange_all_underlying` works as expected + function test_U_CRVB_06_exchange_all_underlying_works_as_expected() public bothStableAndCryptoPools { + deal({token: token0, to: creditAccount, give: 1001}); + for (uint256 i; i < 2; ++i) { + bool use256 = i == 2; + + _readsActiveAccount(); + _executesSwap({ + tokenIn: token0, + tokenOut: underlying0, + callData: abi.encodeWithSignature( + pool.isCrypto() + ? "exchange_underlying(uint256,uint256,uint256,uint256)" + : "exchange_underlying(int128,int128,uint256,uint256)", + 0, + 1, + 1000, + 500 + ), + requiresApproval: true, + validatesTokens: false + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = use256 + ? adapter.exchange_all_underlying(uint256(0), uint256(1), 0.5e27) + : adapter.exchange_all_underlying(int128(0), int128(1), 0.5e27); + + assertEq(tokensToEnable, underlying0Mask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, token0Mask, "Incorrect tokensToDisable"); + } + } + + // ------------- // + // ADD LIQUIDITY // + // ------------- // + + /// @notice U:[CRVB-7]: `add_liquidity_one_coin` works as expected + function test_U_CRVB_07_add_liquidity_one_coin_works_as_expected() public onlyStablePools { + _executesSwap({ + tokenIn: token0, + tokenOut: lpToken, + callData: abi.encodeWithSignature("add_liquidity(uint256[2],uint256)", 1000, 0, 500), + requiresApproval: true, + validatesTokens: false + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.add_liquidity_one_coin(1000, 0, 500); + + assertEq(tokensToEnable, lpTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[CRVB-8]: `add_all_liquidity_one_coin` works as expected + function test_U_CRVB_08_add_all_liquidity_one_coin_works_as_expected() public onlyStablePools { + deal({token: token0, to: creditAccount, give: 1001}); + + _executesSwap({ + tokenIn: token0, + tokenOut: lpToken, + callData: abi.encodeWithSignature("add_liquidity(uint256[2],uint256)", 1000, 0, 500), + requiresApproval: true, + validatesTokens: false + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.add_all_liquidity_one_coin(0, 0.5e27); + + assertEq(tokensToEnable, lpTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, token0Mask, "Incorrect tokensToDisable"); + } + + // ---------------- // + // REMOVE LIQUIDITY // + // ---------------- // + + /// @notice U:[CRVB-9]: `remove_liquidity_one_coin` works as expected + function test_U_CRVB_09_remove_liquidity_one_coin_works_as_expected() public bothStableAndCryptoPools { + for (uint256 i; i < 2; ++i) { + bool use256 = i == 1; + + _executesSwap({ + tokenIn: lpToken, + tokenOut: token0, + callData: abi.encodeWithSignature( + pool.isCrypto() + ? "remove_liquidity_one_coin(uint256,uint256,uint256)" + : "remove_liquidity_one_coin(uint256,int128,uint256)", + 1000, + 0, + 500 + ), + requiresApproval: false, + validatesTokens: false + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = use256 + ? adapter.remove_liquidity_one_coin(1000, uint256(0), 500) + : adapter.remove_liquidity_one_coin(1000, int128(0), 500); + + assertEq(tokensToEnable, token0Mask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + } + + /// @notice U:[CRVB-10]: `remove_all_liquidity_one_coin` works as expected + function test_U_CRVB_10_remove_all_liquidity_one_coin_works_as_expected() public bothStableAndCryptoPools { + deal({token: lpToken, to: creditAccount, give: 1001}); + + for (uint256 i; i < 2; ++i) { + bool use256 = i == 1; + + _readsActiveAccount(); + _executesSwap({ + tokenIn: lpToken, + tokenOut: token0, + callData: abi.encodeWithSignature( + pool.isCrypto() + ? "remove_liquidity_one_coin(uint256,uint256,uint256)" + : "remove_liquidity_one_coin(uint256,int128,uint256)", + 1000, + 0, + 500 + ), + requiresApproval: false, + validatesTokens: false + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = use256 + ? adapter.remove_all_liquidity_one_coin(uint256(0), 0.5e27) + : adapter.remove_all_liquidity_one_coin(int128(0), 0.5e27); + + assertEq(tokensToEnable, token0Mask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, lpTokenMask, "Incorrect tokensToDisable"); + } + } +} diff --git a/contracts/test/unit/adapters/curve/CurveV1AdapterBaseMetapoolTest.t.sol b/contracts/test/unit/adapters/curve/CurveV1AdapterBaseMetapoolTest.t.sol deleted file mode 100644 index 0ce00e6f..00000000 --- a/contracts/test/unit/adapters/curve/CurveV1AdapterBaseMetapoolTest.t.sol +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {CurveV1AdapterBase} from "../../../../adapters/curve/CurveV1_Base.sol"; -import {ICurveV1Adapter} from "../../../../interfaces/curve/ICurveV1Adapter.sol"; - -import {CurveV1MetapoolMock} from "../../../mocks/integrations/CurveV1MetapoolMock.sol"; - -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; - -// TEST -import "../../../lib/constants.sol"; - -import {CurveV1AdapterHelper} from "./CurveV1AdapterHelper.sol"; - -// EXCEPTIONS - -/// @title CurveV1AdapterBaseMetaPoolTest -/// @notice Designed for unit test purposes only -contract CurveV1AdapterBaseMetaPoolTest is Test, CurveV1AdapterHelper { - ICurveV1Adapter public adapter; - CurveV1MetapoolMock public curveV1Mock; - - function setUp() public { - _setUpCurveMetapoolSuite(); - - tokenTestSuite.balanceOf(Tokens.DAI, _curveV1MockAddr); - - curveV1Mock = CurveV1MetapoolMock(_curveV1MockAddr); - - curveV1Mock.setRate(0, 1, RAY); // 3CRV / LINK = 1 - - curveV1Mock.setRateUnderlying(0, 1, RAY); // DAI / LINK = 1 USD - curveV1Mock.setRateUnderlying(0, 2, (99 * RAY) / 100); // USDC / LINK = .99 - curveV1Mock.setRateUnderlying(0, 3, (99 * RAY) / 100); // USDT / LINK = .99 - curveV1Mock.setRateUnderlying(2, 3, RAY); // USDC / USDT = 1 - - adapter = CurveV1AdapterBase(_adapterAddr); - } - - /// - /// - /// TESTS - /// - /// - /// @dev [ACV1-M-1]: constructor sets correct values - function test_ACV1_M_01_constructor_sets_correct_values() public { - assertEq(address(adapter.metapoolBase()), address(curveV1Mock.basePool()), "Incorrect base pool"); - } - - /// @dev [ACV1-M-2]: exchange_underlying works correctly - function test_ACV1_M_02_exchange_underlying_works_correctly() public { - address tokenIn = tokenTestSuite.addressOf(Tokens.cLINK); - address tokenOut = tokenTestSuite.addressOf(Tokens.cUSDT); - - (address creditAccount,) = _openTestCreditAccount(); - - addCollateral(Tokens.cLINK, LINK_ACCOUNT_AMOUNT); - - bytes memory callData = abi.encodeWithSignature( - "exchange_underlying(int128,int128,uint256,uint256)", - 0, - 3, - LINK_EXCHANGE_AMOUNT, - (LINK_EXCHANGE_AMOUNT * 99) / 100 - ); - - expectMulticallStackCalls(address(adapter), address(curveV1Mock), USER, callData, tokenIn, tokenOut, true); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.cLINK, creditAccount, LINK_ACCOUNT_AMOUNT - LINK_EXCHANGE_AMOUNT); - - expectBalance(Tokens.cUSDT, creditAccount, (LINK_EXCHANGE_AMOUNT * 99) / 100); - expectTokenIsEnabled(creditAccount, tokenOut, true); - } - - /// @dev [ACV1-M-3]: exchange_all_underlying works correctly - function test_ACV1_M_03_exchange_all_underlying_works_correctly() public { - address tokenIn = tokenTestSuite.addressOf(Tokens.cLINK); - address tokenOut = tokenTestSuite.addressOf(Tokens.cUSDT); - - (address creditAccount,) = _openTestCreditAccount(); - - addCollateral(Tokens.cLINK, LINK_ACCOUNT_AMOUNT); - - bytes memory callData = abi.encodeWithSignature( - "exchange_underlying(int128,int128,uint256,uint256)", - 0, - 3, - LINK_ACCOUNT_AMOUNT - 1, - ((LINK_ACCOUNT_AMOUNT - 1) * 99) / 100 - ); - - expectMulticallStackCalls(address(adapter), address(curveV1Mock), USER, callData, tokenIn, tokenOut, true); - - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeWithSignature("exchange_all_underlying(int128,int128,uint256)", 0, 3, (RAY * 99) / 100) - ); - - expectBalance(Tokens.cLINK, creditAccount, 1); - - expectBalance(Tokens.cUSDT, creditAccount, ((LINK_ACCOUNT_AMOUNT - 1) * 99) / 100); - - expectTokenIsEnabled(creditAccount, tokenIn, false); - expectTokenIsEnabled(creditAccount, tokenOut, true); - } -} diff --git a/contracts/test/unit/adapters/curve/CurveV1AdapterBaseTest.t.sol b/contracts/test/unit/adapters/curve/CurveV1AdapterBaseTest.t.sol deleted file mode 100644 index 9c19edd4..00000000 --- a/contracts/test/unit/adapters/curve/CurveV1AdapterBaseTest.t.sol +++ /dev/null @@ -1,616 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {AdapterType} from "@gearbox-protocol/sdk-gov/contracts/AdapterType.sol"; - -import {CurveV1AdapterBase} from "../../../../adapters/curve/CurveV1_Base.sol"; -import {ICurveV1Adapter} from "../../../../interfaces/curve/ICurveV1Adapter.sol"; - -import {CurveV1Mock} from "../../../mocks/integrations/CurveV1Mock.sol"; - -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; - -import {ICurvePool2Assets} from "../../../../integrations/curve/ICurvePool_2.sol"; -import {ICurvePool3Assets} from "../../../../integrations/curve/ICurvePool_3.sol"; -import {ICurvePool4Assets} from "../../../../integrations/curve/ICurvePool_4.sol"; - -// TEST -import "../../../lib/constants.sol"; - -import {CurveV1AdapterHelper} from "./CurveV1AdapterHelper.sol"; - -import "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol"; - -contract CurveV1AdapterBaseHarness is CurveV1AdapterBase { - AdapterType public constant override _gearboxAdapterType = AdapterType.CURVE_V1_EXCHANGE_ONLY; - - constructor(address _creditManager, address _curvePool, address _lp_token, address _metapoolBase, uint256 _nCoins) - CurveV1AdapterBase(_creditManager, _curvePool, _lp_token, _metapoolBase, _nCoins) - {} - - function _getAddLiquidityOneCoinCallData(uint256 i, uint256 amount, uint256 minAmount) - internal - view - override - returns (bytes memory) - {} - - function _getCalcAddOneCoinCallData(uint256 i, uint256 amount) - internal - view - override - returns (bytes memory, bytes memory) - {} -} - -/// @title CurveV1AdapterBaseTest -/// @notice Designed for unit test purposes only -contract CurveV1AdapterBaseTest is Test, CurveV1AdapterHelper { - ICurveV1Adapter public adapter; - CurveV1Mock public curveV1Mock; - - function setUp() public { - _setUp(4); - } - - function _setUp(uint256 nCoins) public { - _setupCurveSuite(nCoins); - - curveV1Mock = CurveV1Mock(_curveV1MockAddr); - - curveV1Mock.setRate(0, 1, RAY); // USDC / DAI = 1 USD - curveV1Mock.setRate(0, 2, (99 * RAY) / 100); // USDT / DAI = .99 - curveV1Mock.setRate(1, 2, (99 * RAY) / 100); // USDT / USDC = .99 - - curveV1Mock.setRateUnderlying(0, 1, RAY); // USDC / DAI = 1 USD - curveV1Mock.setRateUnderlying(0, 2, (99 * RAY) / 100); // USDT / DAI = .99 - curveV1Mock.setRateUnderlying(1, 2, (99 * RAY) / 100); // USDT / USDC = .99 - - adapter = CurveV1AdapterBase(_adapterAddr); - - tokenTestSuite.mint(Tokens.USDT, address(curveV1Mock), 2 * DAI_ACCOUNT_AMOUNT); - - tokenTestSuite.mint(Tokens.USDC, address(curveV1Mock), 2 * DAI_ACCOUNT_AMOUNT); - } - - /// - /// - /// TESTS - /// - /// - - /// @dev [ACV1-1]: constructor reverts for zero addresses and non allowed tokens - function test_ACV1_01_constructor_reverts_for_zero_addresses_and_non_allowed_tokens() public { - vm.expectRevert(abi.encodeWithSelector(ZeroAddressException.selector)); - new CurveV1AdapterBaseHarness( - address(creditManager), - address(0), - address(0), - address(0), - 2 - ); - - vm.expectRevert(abi.encodeWithSelector(ZeroAddressException.selector)); - new CurveV1AdapterBaseHarness( - address(creditManager), - address(curveV1Mock), - address(0), - address(0), - 2 - ); - - vm.expectRevert(TokenNotAllowedException.selector); - - new CurveV1AdapterBaseHarness( - address(creditManager), - address(curveV1Mock), - DUMB_ADDRESS, - address(0), - 2 - ); - - address mock; - - for (uint256 i = 0; i < 4; i++) { - uint256 nCoins = i <= 1 ? 2 : i + 1; - - address[] memory coins = new address[](nCoins); - address[] memory underlying_coins = new address[](nCoins); - - for (uint256 j = 0; j < nCoins; j++) { - coins[j] = tokenTestSuite.addressOf(Tokens.DAI); - underlying_coins[j] = tokenTestSuite.addressOf(Tokens.DAI); - } - - coins[i] = address(0); - - mock = address(new CurveV1Mock(coins, underlying_coins)); - vm.expectRevert(ZeroAddressException.selector); - new CurveV1AdapterBaseHarness( - address(creditManager), - address(mock), - lpToken, - address(0), - nCoins - ); - } - - for (uint256 i = 0; i < 4; i++) { - uint256 nCoins = i <= 1 ? 2 : i + 1; - - address[] memory coins = new address[](nCoins); - address[] memory underlying_coins = new address[](nCoins); - - for (uint256 j = 0; j < nCoins; j++) { - coins[j] = tokenTestSuite.addressOf(Tokens.DAI); - underlying_coins[j] = tokenTestSuite.addressOf(Tokens.DAI); - } - - coins[i] = DUMB_ADDRESS; - - mock = address(new CurveV1Mock(coins, underlying_coins)); - vm.expectRevert(TokenNotAllowedException.selector); - new CurveV1AdapterBaseHarness( - address(creditManager), - address(mock), - lpToken, - address(0), - nCoins - ); - - coins[i] = tokenTestSuite.addressOf(Tokens.DAI); - underlying_coins[i] = DUMB_ADDRESS; - - mock = address(new CurveV1Mock(coins, underlying_coins)); - vm.expectRevert(TokenNotAllowedException.selector); - new CurveV1AdapterBaseHarness( - address(creditManager), - address(mock), - lpToken, - address(0), - nCoins - ); - } - } - - /// @dev [ACV1-2]: constructor sets correct values - function test_ACV1_02_constructor_sets_correct_values() public { - assertEq(address(adapter.creditManager()), address(creditManager), "Incorrect CreditManagerV3"); - assertEq(address(adapter.targetContract()), address(curveV1Mock), "Incorrect router"); - assertEq(address(adapter.token()), address(curveV1Mock.token()), "Incorrect LP token"); - assertEq(address(adapter.lp_token()), address(curveV1Mock.token()), "Incorrect LP token"); - assertEq( - adapter.lpTokenMask(), - creditManager.getTokenMaskOrRevert(address(curveV1Mock.token())), - "Incorrect LP token mask" - ); - assertEq(adapter.nCoins(), 4, "Incorrect nCoins"); - assertTrue(!adapter.use256(), "Adapter incorrectly determines stableswap"); - - // tokens - assertEq(address(adapter.token0()), tokenTestSuite.addressOf(poolTkns[0]), "Incorrect token 0"); - assertEq(address(adapter.token1()), tokenTestSuite.addressOf(poolTkns[1]), "Incorrect token 1"); - assertEq(address(adapter.token2()), tokenTestSuite.addressOf(poolTkns[2]), "Incorrect token 2"); - assertEq(address(adapter.token3()), tokenTestSuite.addressOf(poolTkns[3]), "Incorrect token 3"); - - // tokens masks - assertEq( - adapter.token0Mask(), - creditManager.getTokenMaskOrRevert(tokenTestSuite.addressOf(poolTkns[0])), - "Incorrect token 0 mask" - ); - assertEq( - adapter.token1Mask(), - creditManager.getTokenMaskOrRevert(tokenTestSuite.addressOf(poolTkns[1])), - "Incorrect token 1 mask" - ); - assertEq( - adapter.token2Mask(), - creditManager.getTokenMaskOrRevert(tokenTestSuite.addressOf(poolTkns[2])), - "Incorrect token 2 mask" - ); - assertEq( - adapter.token3Mask(), - creditManager.getTokenMaskOrRevert(tokenTestSuite.addressOf(poolTkns[3])), - "Incorrect token 3 mask" - ); - - // underlying tokens - assertEq( - address(adapter.underlying0()), - tokenTestSuite.addressOf(underlyingPoolTkns[0]), - "Incorrect underlying token 0" - ); - assertEq( - address(adapter.underlying1()), - tokenTestSuite.addressOf(underlyingPoolTkns[1]), - "Incorrect underlying token 1" - ); - assertEq( - address(adapter.underlying2()), - tokenTestSuite.addressOf(underlyingPoolTkns[2]), - "Incorrect underlying token 2" - ); - assertEq( - address(adapter.underlying3()), - tokenTestSuite.addressOf(underlyingPoolTkns[3]), - "Incorrect underlying token 3" - ); - - // underlying tokens masks - assertEq( - adapter.underlying0Mask(), - creditManager.getTokenMaskOrRevert(tokenTestSuite.addressOf(underlyingPoolTkns[0])), - "Incorrect underlying token 0 mask" - ); - assertEq( - adapter.underlying1Mask(), - creditManager.getTokenMaskOrRevert(tokenTestSuite.addressOf(underlyingPoolTkns[1])), - "Incorrect underlying token 1 mask" - ); - assertEq( - adapter.underlying2Mask(), - creditManager.getTokenMaskOrRevert(tokenTestSuite.addressOf(underlyingPoolTkns[2])), - "Incorrect underlying token 2 mask" - ); - assertEq( - adapter.underlying3Mask(), - creditManager.getTokenMaskOrRevert(tokenTestSuite.addressOf(underlyingPoolTkns[3])), - "Incorrect underlying token 3 mask" - ); - } - - // /// @dev [ACV1-3]: exchange reverts if user has no account - // function test_ACV1_03_swap_reverts_if_user_has_no_account() public { - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall( - // creditAccount, - // address(adapter), - // abi.encodeWithSignature("exchange(int128,int128,uint256,uint256)", 0, 1, 1, 1) - // ); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall( - // creditAccount, address(adapter), abi.encodeWithSignature("exchange_all(int128,int128,uint256)", 0, 0, 1) - // ); - // } - - /// @dev [ACV1-4]: exchange works for user as expected - function test_ACV1_04_exchange_works_for_user_as_expected() public { - setUp(); - - address tokenIn = tokenTestSuite.addressOf(Tokens.cDAI); - address tokenOut = tokenTestSuite.addressOf(Tokens.cUSDT); - - (address creditAccount,) = _openTestCreditAccount(); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 0); - - addCollateral(Tokens.cDAI, DAI_ACCOUNT_AMOUNT); - - bytes memory callData = abi.encodeWithSignature( - "exchange(int128,int128,uint256,uint256)", 0, 2, DAI_EXCHANGE_AMOUNT, (DAI_EXCHANGE_AMOUNT * 99) / 100 - ); - - expectMulticallStackCalls(address(adapter), address(curveV1Mock), USER, callData, tokenIn, tokenOut, true); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.cDAI, creditAccount, DAI_ACCOUNT_AMOUNT - DAI_EXCHANGE_AMOUNT); - - expectBalance(Tokens.cUSDT, creditAccount, (DAI_EXCHANGE_AMOUNT * 99) / 100); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 1); - - expectTokenIsEnabled(creditAccount, tokenOut, true); - } - - /// @dev [ACV1-5]: exchnage_all works for user as expected - function test_ACV1_05_exchnage_all_works_for_user_as_expected() public { - setUp(); - - address tokenIn = tokenTestSuite.addressOf(Tokens.cDAI); - address tokenOut = tokenTestSuite.addressOf(Tokens.cUSDT); - - (address creditAccount,) = _openTestCreditAccount(); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 0); - - addCollateral(Tokens.cDAI, DAI_ACCOUNT_AMOUNT); - - bytes memory callData = abi.encodeWithSignature( - "exchange(int128,int128,uint256,uint256)", - 0, - 2, - DAI_ACCOUNT_AMOUNT - 1, - ((DAI_ACCOUNT_AMOUNT - 1) * 99) / 100 - ); - - expectMulticallStackCalls(address(adapter), address(curveV1Mock), USER, callData, tokenIn, tokenOut, true); - - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeWithSignature("exchange_all(int128,int128,uint256)", 0, 2, (99 * RAY) / 100) - ); - - expectBalance(Tokens.cDAI, creditAccount, 1); - - expectBalance(Tokens.cUSDT, creditAccount, ((DAI_ACCOUNT_AMOUNT - 1) * 99) / 100); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 1); - - expectTokenIsEnabled(creditAccount, tokenIn, false); - expectTokenIsEnabled(creditAccount, tokenOut, true); - } - - /// @dev [ACV1-6]: exchange_underlying works for user as expected - function test_ACV1_06_exchange_underlying_works_for_user_as_expected() public { - setUp(); - address tokenIn = tokenTestSuite.addressOf(Tokens.DAI); - address tokenOut = tokenTestSuite.addressOf(Tokens.USDT); - - (address creditAccount, uint256 initialDAIbalance) = _openTestCreditAccount(); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 0); - - bytes memory callData = abi.encodeWithSignature( - "exchange_underlying(int128,int128,uint256,uint256)", - 0, - 2, - DAI_EXCHANGE_AMOUNT, - (DAI_EXCHANGE_AMOUNT * 99) / 100 - ); - - expectMulticallStackCalls(address(adapter), address(curveV1Mock), USER, callData, tokenIn, tokenOut, true); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.DAI, creditAccount, initialDAIbalance - DAI_EXCHANGE_AMOUNT); - - expectBalance(Tokens.USDT, creditAccount, (DAI_EXCHANGE_AMOUNT * 99) / 100); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 1); - - expectTokenIsEnabled(creditAccount, tokenOut, true); - } - - /// @dev [ACV1-7]: exchange_all_underlying works for user as expected - function test_ACV1_07_exchange_all_underlying_works_for_user_as_expected() public { - setUp(); - - address tokenIn = tokenTestSuite.addressOf(Tokens.DAI); - address tokenOut = tokenTestSuite.addressOf(Tokens.USDT); - - (address creditAccount, uint256 initialDAIbalance) = _openTestCreditAccount(); - - bytes memory callData = abi.encodeWithSignature( - "exchange_underlying(int128,int128,uint256,uint256)", - 0, - 2, - initialDAIbalance - 1, - ((initialDAIbalance - 1) * 99) / 100 - ); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 0); - - expectMulticallStackCalls(address(adapter), address(curveV1Mock), USER, callData, tokenIn, tokenOut, true); - - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeWithSignature("exchange_all_underlying(int128,int128,uint256)", 0, 2, (99 * RAY) / 100) - ); - - expectBalance(Tokens.DAI, creditAccount, 1); - - expectBalance(Tokens.USDT, creditAccount, ((initialDAIbalance - 1) * 99) / 100); - - expectTokenIsEnabled(creditAccount, tokenIn, false); - expectTokenIsEnabled(creditAccount, tokenOut, true); - } - - /// @dev [ACV1-8]: add_liquidity_one_coin works for user as expected - function test_ACV1_08_add_liquidity_one_coin_works_for_user_as_expected() public { - for (uint256 nCoins = 2; nCoins <= 4; nCoins++) { - for (uint256 i = 0; i < nCoins; i++) { - _setUp(nCoins); - - address tokenIn = curveV1Mock.coins(i); - address tokenOut = lpToken; - - // tokenTestSuite.mint(Tokens.DAI, USER, DAI_ACCOUNT_AMOUNT); - (address creditAccount,) = _openTestCreditAccount(); - - tokenTestSuite.mint(tokenIn, USER, DAI_ACCOUNT_AMOUNT); - // addCollateral(tokenIn, DAI_ACCOUNT_AMOUNT); - - bytes memory callData = abi.encodeWithSignature( - "add_liquidity_one_coin(uint256,int128,uint256)", - DAI_ACCOUNT_AMOUNT / 2, - int128(int256(i)), - DAI_ACCOUNT_AMOUNT / 4 - ); - - bytes memory expectedCallData; - - if (nCoins == 2) { - uint256[2] memory amounts; - amounts[i] = DAI_ACCOUNT_AMOUNT / 2; - expectedCallData = - abi.encodeCall(ICurvePool2Assets.add_liquidity, (amounts, DAI_ACCOUNT_AMOUNT / 4)); - } else if (nCoins == 3) { - uint256[3] memory amounts; - amounts[i] = DAI_ACCOUNT_AMOUNT / 2; - expectedCallData = - abi.encodeCall(ICurvePool3Assets.add_liquidity, (amounts, DAI_ACCOUNT_AMOUNT / 4)); - } else if (nCoins == 4) { - uint256[4] memory amounts; - amounts[i] = DAI_ACCOUNT_AMOUNT / 2; - expectedCallData = - abi.encodeCall(ICurvePool4Assets.add_liquidity, (amounts, DAI_ACCOUNT_AMOUNT / 4)); - } - - expectMulticallStackCalls( - address(adapter), address(curveV1Mock), USER, expectedCallData, tokenIn, tokenOut, true - ); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(tokenIn, creditAccount, DAI_ACCOUNT_AMOUNT / 2); - - expectBalance(curveV1Mock.token(), creditAccount, DAI_ACCOUNT_AMOUNT / 4); - - expectTokenIsEnabled(creditAccount, curveV1Mock.token(), true); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 1); - - // _closeTestCreditAccount(); - } - } - } - - /// @dev [ACV1-9]: add_all_liquidity_one_coin works for user as expected - function test_ACV1_09_add_all_liquidity_one_coin_works_for_user_as_expected() public { - for (uint256 nCoins = 2; nCoins <= 4; nCoins++) { - for (uint256 i = 0; i < nCoins; i++) { - _setUp(nCoins); - - address tokenIn = curveV1Mock.coins(i); - address tokenOut = lpToken; - - // tokenTestSuite.mint(Tokens.DAI, USER, DAI_ACCOUNT_AMOUNT); - (address creditAccount,) = _openTestCreditAccount(); - - tokenTestSuite.mint(tokenIn, USER, DAI_ACCOUNT_AMOUNT); - // addCollateral(tokenIn, DAI_ACCOUNT_AMOUNT); - - bytes memory callData = - abi.encodeWithSignature("add_all_liquidity_one_coin(int128,uint256)", int128(int256(i)), RAY / 2); - - bytes memory expectedCallData; - - if (nCoins == 2) { - uint256[2] memory amounts; - amounts[i] = DAI_ACCOUNT_AMOUNT - 1; - expectedCallData = - abi.encodeCall(ICurvePool2Assets.add_liquidity, (amounts, (DAI_ACCOUNT_AMOUNT - 1) / 2)); - } else if (nCoins == 3) { - uint256[3] memory amounts; - amounts[i] = DAI_ACCOUNT_AMOUNT - 1; - expectedCallData = - abi.encodeCall(ICurvePool3Assets.add_liquidity, (amounts, (DAI_ACCOUNT_AMOUNT - 1) / 2)); - } else if (nCoins == 4) { - uint256[4] memory amounts; - amounts[i] = DAI_ACCOUNT_AMOUNT - 1; - expectedCallData = - abi.encodeCall(ICurvePool4Assets.add_liquidity, (amounts, (DAI_ACCOUNT_AMOUNT - 1) / 2)); - } - - expectMulticallStackCalls( - address(adapter), address(curveV1Mock), USER, expectedCallData, tokenIn, tokenOut, true - ); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(tokenIn, creditAccount, 1); - - expectBalance(curveV1Mock.token(), creditAccount, (DAI_ACCOUNT_AMOUNT - 1) / 2); - - expectTokenIsEnabled(creditAccount, tokenIn, false); - expectTokenIsEnabled(creditAccount, curveV1Mock.token(), true); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 1); - - // _closeTestCreditAccount(); - } - } - } - - /// @dev [ACV1-10]: remove_liquidity_one_coin works as expected - function test_ACV1_10_remove_liquidity_one_coin_works_correctly() public { - for (uint256 nCoins = 2; nCoins <= 4; nCoins++) { - for (uint256 i = 0; i < nCoins; i++) { - _setUp(nCoins); - - address tokenIn = lpToken; - address tokenOut = curveV1Mock.coins(i); - - (address creditAccount,) = _openTestCreditAccount(); - - // provide LP token to creditAccount - addCRVCollateral(curveV1Mock, CURVE_LP_ACCOUNT_AMOUNT); - - bytes memory expectedCallData = abi.encodeWithSignature( - "remove_liquidity_one_coin(uint256,int128,uint256)", - CURVE_LP_OPERATION_AMOUNT, - int128(int256(i)), - USDT_ACCOUNT_AMOUNT / 2 - ); - - tokenTestSuite.mint(tokenOut, address(curveV1Mock), USDT_ACCOUNT_AMOUNT); - - expectMulticallStackCalls( - address(adapter), address(curveV1Mock), USER, expectedCallData, tokenIn, tokenOut, false - ); - - executeOneLineMulticall(creditAccount, address(adapter), expectedCallData); - - expectBalance(tokenIn, creditAccount, CURVE_LP_ACCOUNT_AMOUNT - CURVE_LP_OPERATION_AMOUNT); - expectBalance(tokenOut, creditAccount, USDT_ACCOUNT_AMOUNT / 2); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 0); - - expectTokenIsEnabled(creditAccount, tokenOut, true); - } - } - } - - /// @dev [ACV1-11]: remove_all_liquidity_one_coin works as expected - function test_ACV1_11_remove_all_liquidity_one_coin_works_correctly() public { - for (uint256 nCoins = 2; nCoins <= 4; nCoins++) { - for (uint256 i = 0; i < nCoins; i++) { - _setUp(nCoins); - - address tokenIn = lpToken; - address tokenOut = curveV1Mock.coins(i); - - uint256 rateRAY = RAY / 2; - - (address creditAccount,) = _openTestCreditAccount(); - - // provide LP token to creditAccount - addCRVCollateral(curveV1Mock, CURVE_LP_ACCOUNT_AMOUNT); - - tokenTestSuite.mint(tokenOut, address(curveV1Mock), USDT_ACCOUNT_AMOUNT); - - bytes memory expectedCallData = abi.encodeWithSignature( - "remove_liquidity_one_coin(uint256,int128,uint256)", - CURVE_LP_ACCOUNT_AMOUNT - 1, - int128(int256(i)), - (CURVE_LP_ACCOUNT_AMOUNT - 1) / 2 - ); - - expectMulticallStackCalls( - address(adapter), address(curveV1Mock), USER, expectedCallData, tokenIn, tokenOut, false - ); - - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeWithSignature("remove_all_liquidity_one_coin(int128,uint256)", int128(int256(i)), rateRAY) - ); - - expectBalance(tokenIn, creditAccount, 1); - expectBalance(tokenOut, creditAccount, (CURVE_LP_ACCOUNT_AMOUNT - 1) / 2); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 0); - - expectTokenIsEnabled(creditAccount, tokenIn, false); - expectTokenIsEnabled(creditAccount, tokenOut, true); - } - } - } -} diff --git a/contracts/test/unit/adapters/curve/CurveV1AdapterCryptoTest.t.sol b/contracts/test/unit/adapters/curve/CurveV1AdapterCryptoTest.t.sol deleted file mode 100644 index c1fb1b71..00000000 --- a/contracts/test/unit/adapters/curve/CurveV1AdapterCryptoTest.t.sol +++ /dev/null @@ -1,362 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {CurveV1AdapterBase} from "../../../../adapters/curve/CurveV1_Base.sol"; -import {ICurveV1Adapter} from "../../../../interfaces/curve/ICurveV1Adapter.sol"; - -import {CurveV1Mock} from "../../../mocks/integrations/CurveV1Mock.sol"; - -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; - -import {ICurvePool2Assets} from "../../../../integrations/curve/ICurvePool_2.sol"; - -// TEST -import "../../../lib/constants.sol"; - -import {CurveV1AdapterHelper} from "./CurveV1AdapterHelper.sol"; - -// EXCEPTIONS - -/// @title CurveV1AdapterCryptoTest -/// @notice Designed for unit test purposes only -contract CurveV1AdapterCryptoTest is Test, CurveV1AdapterHelper { - ICurveV1Adapter public adapter; - CurveV1Mock public curveV1Mock; - - function setUp() public { - _setUpCurveCryptoSuite(); - - curveV1Mock = CurveV1Mock(_curveV1MockAddr); - - curveV1Mock.setRate(0, 1, 100 * RAY); - - curveV1Mock.setRateUnderlying(0, 1, 100 * RAY); - curveV1Mock.setRateUnderlying(0, 2, 99 * RAY); - curveV1Mock.setRateUnderlying(0, 3, 99 * RAY); - - adapter = CurveV1AdapterBase(_adapterAddr); - } - - /// - /// - /// TESTS - /// - /// - - /// @dev [ACC-1]: constructor sets correct values - function test_ACC_01_constructor_sets_correct_values() public { - assertTrue(adapter.use256(), "Adapter incorrectly determines cryptoswap"); - } - - /// @dev [ACC-2]: exchange works for user as expected - function test_ACC_02_exchange_works_for_user_as_expected() public { - setUp(); - - address tokenIn = tokenTestSuite.addressOf(Tokens.cLINK); - address tokenOut = curveV1Mock.coins(uint256(1)); - - (address creditAccount,) = _openTestCreditAccount(); - - addCollateral(Tokens.cLINK, LINK_ACCOUNT_AMOUNT); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 0); - - bytes memory callData = abi.encodeWithSignature( - "exchange(uint256,uint256,uint256,uint256)", 0, 1, LINK_EXCHANGE_AMOUNT, LINK_EXCHANGE_AMOUNT * 100 - ); - - expectMulticallStackCalls(address(adapter), address(curveV1Mock), USER, callData, tokenIn, tokenOut, false); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.cLINK, creditAccount, LINK_ACCOUNT_AMOUNT - LINK_EXCHANGE_AMOUNT); - - expectBalance(tokenOut, creditAccount, LINK_EXCHANGE_AMOUNT * 100); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 1); - - expectTokenIsEnabled(creditAccount, tokenOut, true); - } - - /// @dev [ACC-3]: exchange_all works for user as expected - function test_ACC_03_exchange_all_works_for_user_as_expected() public { - setUp(); - - address tokenIn = tokenTestSuite.addressOf(Tokens.cLINK); - address tokenOut = curveV1Mock.coins(uint256(1)); - - (address creditAccount,) = _openTestCreditAccount(); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 0); - - addCollateral(Tokens.cLINK, LINK_ACCOUNT_AMOUNT); - - bytes memory callData = abi.encodeWithSignature( - "exchange(uint256,uint256,uint256,uint256)", 0, 1, LINK_ACCOUNT_AMOUNT - 1, (LINK_ACCOUNT_AMOUNT - 1) * 100 - ); - - expectMulticallStackCalls(address(adapter), address(curveV1Mock), USER, callData, tokenIn, tokenOut, false); - - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeWithSignature("exchange_all(uint256,uint256,uint256)", 0, 1, RAY * 100) - ); - - expectBalance(tokenIn, creditAccount, 1); - - expectBalance(tokenOut, creditAccount, (LINK_ACCOUNT_AMOUNT - 1) * 100); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 1); - - expectTokenIsEnabled(creditAccount, tokenIn, false); - expectTokenIsEnabled(creditAccount, tokenOut, true); - } - - /// @dev [ACC-4]: exchange_underlying works for user as expected - function test_ACC_04_exchange_underlying_works_for_user_as_expected() public { - setUp(); - address tokenIn = tokenTestSuite.addressOf(Tokens.cLINK); - address tokenOut = tokenTestSuite.addressOf(Tokens.cUSDT); - - (address creditAccount,) = _openTestCreditAccount(); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 0); - - addCollateral(Tokens.cLINK, LINK_ACCOUNT_AMOUNT); - - bytes memory callData = abi.encodeWithSignature( - "exchange_underlying(uint256,uint256,uint256,uint256)", - 0, - 3, - LINK_EXCHANGE_AMOUNT, - LINK_EXCHANGE_AMOUNT * 99 - ); - - expectMulticallStackCalls(address(adapter), address(curveV1Mock), USER, callData, tokenIn, tokenOut, false); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.cLINK, creditAccount, LINK_ACCOUNT_AMOUNT - LINK_EXCHANGE_AMOUNT); - - expectBalance(Tokens.cUSDT, creditAccount, LINK_EXCHANGE_AMOUNT * 99); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 1); - - expectTokenIsEnabled(creditAccount, tokenOut, true); - } - - /// @dev [ACC-5]: exchange_all_underlying works for user as expected - function test_ACC_05_exchange_all_underlying_works_for_user_as_expected() public { - address tokenIn = tokenTestSuite.addressOf(Tokens.cLINK); - address tokenOut = tokenTestSuite.addressOf(Tokens.cUSDT); - - (address creditAccount,) = _openTestCreditAccount(); - - addCollateral(Tokens.cLINK, LINK_ACCOUNT_AMOUNT); - - bytes memory callData = abi.encodeWithSignature( - "exchange_underlying(uint256,uint256,uint256,uint256)", - 0, - 3, - LINK_ACCOUNT_AMOUNT - 1, - (LINK_ACCOUNT_AMOUNT - 1) * 99 - ); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 0); - - expectMulticallStackCalls(address(adapter), address(curveV1Mock), USER, callData, tokenIn, tokenOut, false); - - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeWithSignature("exchange_all_underlying(uint256,uint256,uint256)", 0, 3, RAY * 99) - ); - - expectBalance(Tokens.cLINK, creditAccount, 1); - - expectBalance(Tokens.cUSDT, creditAccount, (LINK_ACCOUNT_AMOUNT - 1) * 99); - - expectTokenIsEnabled(creditAccount, tokenIn, false); - expectTokenIsEnabled(creditAccount, tokenOut, true); - } - - /// @dev [ACC-6]: add_all_liquidity_one_coin works for user as expected - function test_ACC_06_add_all_liquidity_one_coin_works_for_user_as_expected() public { - for (uint256 i = 0; i < 2; i++) { - setUp(); - - address tokenIn = curveV1Mock.coins(i); - address tokenOut = lpToken; - - // tokenTestSuite.mint(Tokens.DAI, USER, DAI_ACCOUNT_AMOUNT); - (address creditAccount,) = _openTestCreditAccount(); - - tokenTestSuite.mint(tokenIn, USER, LINK_ACCOUNT_AMOUNT); - // addCollateral(tokenIn, LINK_ACCOUNT_AMOUNT); - - bytes memory callData = abi.encodeWithSignature("add_all_liquidity_one_coin(uint256,uint256)", i, RAY / 2); - - bytes memory expectedCallData; - - uint256[2] memory amounts; - amounts[i] = LINK_ACCOUNT_AMOUNT - 1; - expectedCallData = abi.encodeCall(ICurvePool2Assets.add_liquidity, (amounts, (LINK_ACCOUNT_AMOUNT - 1) / 2)); - - expectMulticallStackCalls( - address(adapter), address(curveV1Mock), USER, expectedCallData, tokenIn, tokenOut, true - ); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(tokenIn, creditAccount, 1); - - expectBalance(curveV1Mock.token(), creditAccount, (LINK_ACCOUNT_AMOUNT - 1) / 2); - - expectTokenIsEnabled(creditAccount, tokenIn, false); - expectTokenIsEnabled(creditAccount, curveV1Mock.token(), true); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 1); - - // _closeTestCreditAccount(); - } - } - - /// @dev [ACС-7]: add_liquidity_one_coin works for user as expected - function test_ACC_07_add_liquidity_one_coin_works_for_user_as_expected() public { - for (uint256 i = 0; i < 2; i++) { - setUp(); - - address tokenIn = curveV1Mock.coins(i); - address tokenOut = lpToken; - - // tokenTestSuite.mint(Tokens.DAI, USER, DAI_ACCOUNT_AMOUNT); - (address creditAccount,) = _openTestCreditAccount(); - - tokenTestSuite.mint(tokenIn, USER, LINK_ACCOUNT_AMOUNT); - // addCollateral(tokenIn, LINK_ACCOUNT_AMOUNT); - - bytes memory callData = abi.encodeWithSignature( - "add_liquidity_one_coin(uint256,uint256,uint256)", LINK_ACCOUNT_AMOUNT / 2, i, LINK_ACCOUNT_AMOUNT / 4 - ); - - bytes memory expectedCallData; - - uint256[2] memory amounts; - amounts[i] = LINK_ACCOUNT_AMOUNT / 2; - expectedCallData = abi.encodeCall(ICurvePool2Assets.add_liquidity, (amounts, LINK_ACCOUNT_AMOUNT / 4)); - - expectMulticallStackCalls( - address(adapter), address(curveV1Mock), USER, expectedCallData, tokenIn, tokenOut, true - ); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(tokenIn, creditAccount, LINK_ACCOUNT_AMOUNT / 2); - - expectBalance(curveV1Mock.token(), creditAccount, LINK_ACCOUNT_AMOUNT / 4); - - expectTokenIsEnabled(creditAccount, curveV1Mock.token(), true); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 1); - - // _closeTestCreditAccount(); - } - } - - /// @dev [ACC-8]: remove_liquidity_one_coin works as expected - function test_ACC_08_remove_liquidity_one_coin_works_correctly() public { - for (uint256 i = 0; i < 2; i++) { - setUp(); - - address tokenIn = lpToken; - address tokenOut = curveV1Mock.coins(i); - - (address creditAccount,) = _openTestCreditAccount(); - - // provide LP token to creditAccount - addCRVCollateral(curveV1Mock, CURVE_LP_ACCOUNT_AMOUNT); - - bytes memory expectedCallData = abi.encodeWithSignature( - "remove_liquidity_one_coin(uint256,uint256,uint256)", - CURVE_LP_OPERATION_AMOUNT, - i, - CURVE_LP_OPERATION_AMOUNT / 2 - ); - - tokenTestSuite.mint(tokenOut, address(curveV1Mock), USDT_ACCOUNT_AMOUNT); - - expectMulticallStackCalls( - address(adapter), address(curveV1Mock), USER, expectedCallData, tokenIn, tokenOut, false - ); - - executeOneLineMulticall(creditAccount, address(adapter), expectedCallData); - - expectBalance(tokenIn, creditAccount, CURVE_LP_ACCOUNT_AMOUNT - CURVE_LP_OPERATION_AMOUNT); - expectBalance(tokenOut, creditAccount, CURVE_LP_OPERATION_AMOUNT / 2); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 0); - - expectTokenIsEnabled(creditAccount, tokenOut, true); - } - } - - /// @dev [ACC-9]: remove_all_liquidity_one_coin works as expected - function test_ACC_09_remove_all_liquidity_one_coin_works_correctly() public { - for (uint256 i = 0; i < 2; i++) { - setUp(); - - address tokenIn = lpToken; - address tokenOut = curveV1Mock.coins(i); - - uint256 rateRAY = RAY / 2; - - (address creditAccount,) = _openTestCreditAccount(); - - // provide LP token to creditAccount - addCRVCollateral(curveV1Mock, CURVE_LP_ACCOUNT_AMOUNT); - - tokenTestSuite.mint(tokenOut, address(curveV1Mock), USDT_ACCOUNT_AMOUNT); - - bytes memory expectedCallData = abi.encodeWithSignature( - "remove_liquidity_one_coin(uint256,uint256,uint256)", - CURVE_LP_ACCOUNT_AMOUNT - 1, - int128(int256(i)), - (CURVE_LP_ACCOUNT_AMOUNT - 1) / 2 - ); - - expectMulticallStackCalls( - address(adapter), address(curveV1Mock), USER, expectedCallData, tokenIn, tokenOut, false - ); - - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeWithSignature("remove_all_liquidity_one_coin(uint256,uint256)", i, rateRAY) - ); - - expectBalance(tokenIn, creditAccount, 1); - expectBalance(tokenOut, creditAccount, (CURVE_LP_ACCOUNT_AMOUNT - 1) / 2); - - expectAllowance(tokenIn, creditAccount, address(curveV1Mock), 0); - - expectTokenIsEnabled(creditAccount, tokenIn, false); - expectTokenIsEnabled(creditAccount, tokenOut, true); - } - } - - /// @dev [ACV1-15]: Adapter calc_add_one_coin works correctly - function test_ACV1_15_calc_add_one_coin_works_correctly(uint256 amount) public { - vm.assume(amount < 10 ** 27); - _setUp(); - for (uint256 i = 0; i < 2; i++) { - int128 i128 = (int128(uint128(i))); - - curveV1Mock.setDepositRate(i128, RAY * i); - - assertEq(adapter.calc_add_one_coin(amount, uint256(i)), amount * i, "Incorrect ammount"); - } - } -} diff --git a/contracts/test/unit/adapters/curve/CurveV1AdapterHelper.sol b/contracts/test/unit/adapters/curve/CurveV1AdapterHelper.sol deleted file mode 100644 index 5506b231..00000000 --- a/contracts/test/unit/adapters/curve/CurveV1AdapterHelper.sol +++ /dev/null @@ -1,696 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -import {CurveV1Adapter2Assets} from "../../../../adapters/curve/CurveV1_2.sol"; -import {CurveV1Adapter3Assets} from "../../../../adapters/curve/CurveV1_3.sol"; -import {CurveV1Adapter4Assets} from "../../../../adapters/curve/CurveV1_4.sol"; -import {CurveV1StETHPoolGateway} from "../../../../helpers/curve/CurveV1_stETHGateway.sol"; -import {CurveV1AdapterStETH} from "../../../../adapters/curve/CurveV1_stETH.sol"; -import {ICurveV1Adapter} from "../../../../interfaces/curve/ICurveV1Adapter.sol"; -import {ICurvePoolStETH} from "../../../../integrations/curve/ICurvePoolStETH.sol"; -import {ICurvePool} from "../../../../integrations/curve/ICurvePool.sol"; -import {ICRVToken} from "../../../../integrations/curve/ICRVToken.sol"; -import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; -import {CurveV1StETHMock} from "../../../mocks/integrations/CurveV1StETHMock.sol"; - -import {CurveV1Mock} from "../../../mocks/integrations/CurveV1Mock.sol"; -import {CurveV1MetapoolMock} from "../../../mocks/integrations/CurveV1MetapoolMock.sol"; -import {CurveV1Mock_2Assets} from "../../../mocks/integrations/CurveV1Mock_2Assets.sol"; -import {CurveV1Mock_3Assets} from "../../../mocks/integrations/CurveV1Mock_3Assets.sol"; -import {CurveV1Mock_4Assets} from "../../../mocks/integrations/CurveV1Mock_4Assets.sol"; - -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; - -import {PriceFeedParams} from "@gearbox-protocol/oracles-v3/contracts/oracles/PriceFeedParams.sol"; -import {CurveStableLPPriceFeed} from "@gearbox-protocol/oracles-v3/contracts/oracles/curve/CurveStableLPPriceFeed.sol"; -import {CurveCryptoLPPriceFeed} from "@gearbox-protocol/oracles-v3/contracts/oracles/curve/CurveCryptoLPPriceFeed.sol"; - -// TEST -import "../../../lib/constants.sol"; - -import {AdapterTestHelper} from "../AdapterTestHelper.sol"; - -uint256 constant DAI_TO_LP = DAI_ACCOUNT_AMOUNT / 4; -uint256 constant USDC_TO_LP = USDC_ACCOUNT_AMOUNT / 3; -uint256 constant USDT_TO_LP = USDT_ACCOUNT_AMOUNT / 5; -uint256 constant LINK_TO_LP = LINK_ACCOUNT_AMOUNT / 5; - -/// @title CurveV1AdapterHelper -/// @notice Designed for unit test purposes only -contract CurveV1AdapterHelper is Test, AdapterTestHelper { - address internal _curveV1MockAddr; - address internal _adapterAddr; - - address internal _curveV1stETHMockAddr; - address internal _curveV1stETHPoolGateway; - address internal _adapterStETHAddr; - - address internal _basePoolAddr; - - Tokens[4] internal poolTkns; - Tokens[4] internal underlyingPoolTkns; - address internal lpToken; - - function _setupCurveSuite(uint256 nCoins) internal { - _setUp(); - - poolTkns = [Tokens.cDAI, Tokens.cUSDC, Tokens.cUSDT, Tokens.cLINK]; - underlyingPoolTkns = [Tokens.DAI, Tokens.USDC, Tokens.USDT, Tokens.LINK]; - - address[] memory curvePoolTokens = getPoolTokens(nCoins); - address[] memory curvePoolUnderlyings = getUnderlyingPoolTokens(nCoins); - - address _priceFeed; - - if (nCoins == 2) { - _curveV1MockAddr = address(new CurveV1Mock_2Assets(curvePoolTokens, curvePoolUnderlyings)); - - _priceFeed = address( - new CurveStableLPPriceFeed( - address(addressProvider), - _curveV1MockAddr, - _curveV1MockAddr, - [ PriceFeedParams({ priceFeed: priceOracle.priceFeeds(curvePoolTokens[0]), stalenessPeriod: 48 hours}), - PriceFeedParams({ priceFeed: priceOracle.priceFeeds(curvePoolTokens[1]), stalenessPeriod: 48 hours}), - PriceFeedParams({ priceFeed: address(0), stalenessPeriod: 0}), - PriceFeedParams({ priceFeed: address(0), stalenessPeriod: 0})] - - ) - ); - } else if (nCoins == 3) { - _curveV1MockAddr = address(new CurveV1Mock_3Assets(curvePoolTokens, curvePoolUnderlyings)); - - _priceFeed = address( - new CurveStableLPPriceFeed( - address(addressProvider), - _curveV1MockAddr, - _curveV1MockAddr, - [ PriceFeedParams({ priceFeed: priceOracle.priceFeeds(curvePoolTokens[0]), stalenessPeriod: 48 hours}), - PriceFeedParams({ priceFeed: priceOracle.priceFeeds(curvePoolTokens[1]), stalenessPeriod: 48 hours}), - PriceFeedParams({ priceFeed: priceOracle.priceFeeds(curvePoolTokens[2]), stalenessPeriod: 48 hours}), - PriceFeedParams({ priceFeed: address(0), stalenessPeriod: 0})] - - ) - ); - } else if (nCoins == 4) { - _curveV1MockAddr = address(new CurveV1Mock_4Assets(curvePoolTokens, curvePoolUnderlyings)); - - _priceFeed = address( - new CurveStableLPPriceFeed( - address(addressProvider), - _curveV1MockAddr, - _curveV1MockAddr, - [ PriceFeedParams({ priceFeed: priceOracle.priceFeeds(curvePoolTokens[0]), stalenessPeriod: 48 hours}), - PriceFeedParams({ priceFeed: priceOracle.priceFeeds(curvePoolTokens[1]), stalenessPeriod: 48 hours}), - PriceFeedParams({ priceFeed: priceOracle.priceFeeds(curvePoolTokens[2]), stalenessPeriod: 48 hours}), - PriceFeedParams({ priceFeed: priceOracle.priceFeeds(curvePoolTokens[3]), stalenessPeriod: 48 hours})] - - ) - ); - } else { - revert("costructor: Incorrect nCoins parameter"); - } - - lpToken = address(ICurvePool(_curveV1MockAddr).token()); - - vm.startPrank(CONFIGURATOR); - - priceOracle.setPriceFeed(lpToken, _priceFeed, 0); - creditConfigurator.addCollateralToken(lpToken, 9300); - - vm.stopPrank(); - - if (nCoins == 2) { - _adapterAddr = address( - new CurveV1Adapter2Assets( - address(creditManager), - _curveV1MockAddr, - lpToken, - address(0) - ) - ); - } else if (nCoins == 3) { - _adapterAddr = address( - new CurveV1Adapter3Assets( - address(creditManager), - _curveV1MockAddr, - lpToken, - address(0) - ) - ); - } else if (nCoins == 4) { - _adapterAddr = address( - new CurveV1Adapter4Assets( - address(creditManager), - _curveV1MockAddr, - lpToken, - address(0) - ) - ); - } else { - revert("costructor: Incorrect nCoins parameter"); - } - - vm.prank(CONFIGURATOR); - creditConfigurator.allowAdapter(_adapterAddr); - - tokenTestSuite.mint(Tokens.cDAI, USER, DAI_ACCOUNT_AMOUNT); - tokenTestSuite.mint(Tokens.DAI, USER, DAI_ACCOUNT_AMOUNT); - - // - // Provide liquidity to the pool - // - tokenTestSuite.mint(Tokens.cDAI, _curveV1MockAddr, DAI_ACCOUNT_AMOUNT); - tokenTestSuite.mint(Tokens.cUSDC, _curveV1MockAddr, USDC_ACCOUNT_AMOUNT); - - if (nCoins >= 3) { - tokenTestSuite.mint(Tokens.cUSDT, _curveV1MockAddr, USDT_ACCOUNT_AMOUNT); - } - - if (nCoins >= 4) { - tokenTestSuite.mint(Tokens.cLINK, _curveV1MockAddr, LINK_ACCOUNT_AMOUNT); - } - - vm.label(_adapterAddr, "ADAPTER"); - vm.label(_curveV1MockAddr, "CURVE_MOCK"); - vm.label(lpToken, "CURVE_LP_TOKEN"); - } - - function _setUpCurveStETHSuite() internal { - _setUp(Tokens.WETH); - - address eth = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; - address steth = tokenTestSuite.addressOf(Tokens.STETH); - address weth = tokenTestSuite.addressOf(Tokens.WETH); - - // creditManager.addToken(tokenTestSuite.addressOf(Tokens.STETH)); - - tokenTestSuite.topUpWETH{value: 1e24}(); - - address[] memory coins = new address[](2); - coins[0] = eth; - coins[1] = steth; - - _curveV1stETHMockAddr = address(new CurveV1StETHMock(coins)); - - lpToken = address(ICurvePoolStETH(_curveV1stETHMockAddr).lp_token()); - - _curveV1stETHPoolGateway = address(new CurveV1StETHPoolGateway(weth, steth, _curveV1stETHMockAddr)); - - address _priceFeed = address( - new CurveStableLPPriceFeed( - address(addressProvider), - _curveV1stETHMockAddr, - _curveV1stETHMockAddr, - - [ PriceFeedParams({ priceFeed: priceOracle.priceFeeds(tokenTestSuite.addressOf(Tokens.WETH)), stalenessPeriod: 48 hours}), - PriceFeedParams({ priceFeed: priceOracle.priceFeeds(tokenTestSuite.addressOf(Tokens.STETH)), stalenessPeriod: 48 hours}), - PriceFeedParams({ priceFeed: address(0), stalenessPeriod: 0}), - PriceFeedParams({ priceFeed: address(0), stalenessPeriod: 0})] - - - ) - ); - - vm.startPrank(CONFIGURATOR); - // creditConfigurator.addCollateralToken(steth, 8300); - - priceOracle.setPriceFeed(lpToken, _priceFeed, 0); - creditConfigurator.addCollateralToken(lpToken, 8800); - - _adapterStETHAddr = address( - new CurveV1AdapterStETH( - address(creditManager), - _curveV1stETHPoolGateway, - lpToken - ) - ); - - creditConfigurator.allowAdapter(_adapterStETHAddr); - - vm.stopPrank(); - - vm.label(_adapterStETHAddr, "ADAPTER_STETH"); - vm.label(_curveV1stETHPoolGateway, "CURVE_STETH_GATEWAY"); - vm.label(_curveV1stETHMockAddr, "CURVE_STETH_POOL_MOCK"); - vm.label(lpToken, "CURVE_LP_STECRV_TOKEN"); - } - - function _setUpCurveMetapoolSuite() internal { - _setUp(Tokens.DAI); - - poolTkns = [Tokens.cDAI, Tokens.cUSDC, Tokens.cUSDT, Tokens.cLINK]; - underlyingPoolTkns = [Tokens.DAI, Tokens.USDC, Tokens.USDT, Tokens.LINK]; - - address[] memory curvePoolTokens = getPoolTokens(3); - address[] memory curvePoolUnderlyings = getUnderlyingPoolTokens(3); - - _basePoolAddr = address(new CurveV1Mock_3Assets(curvePoolTokens, curvePoolUnderlyings)); - - address _priceFeed = address( - new CurveStableLPPriceFeed( - address(addressProvider), - _basePoolAddr, - _basePoolAddr, - [PriceFeedParams({ priceFeed: priceOracle.priceFeeds(curvePoolTokens[0]), - stalenessPeriod: 48 hours}), - PriceFeedParams({ priceFeed: priceOracle.priceFeeds(curvePoolTokens[1]), stalenessPeriod: 48 hours}), - PriceFeedParams({ priceFeed: priceOracle.priceFeeds(curvePoolTokens[2]), stalenessPeriod: 48 hours}), - PriceFeedParams({ priceFeed: address(0), stalenessPeriod: 0})] - - ) - ); - - address baseLpToken = address(ICurvePool(_basePoolAddr).token()); - - vm.startPrank(CONFIGURATOR); - - priceOracle.setPriceFeed(baseLpToken, _priceFeed, 0); - creditConfigurator.addCollateralToken(baseLpToken, 9300); - - vm.stopPrank(); - - tokenTestSuite.mint(Tokens.cDAI, _basePoolAddr, DAI_ACCOUNT_AMOUNT); - tokenTestSuite.mint(Tokens.cUSDC, _basePoolAddr, USDC_ACCOUNT_AMOUNT); - tokenTestSuite.mint(Tokens.cUSDT, _basePoolAddr, USDT_ACCOUNT_AMOUNT); - - _curveV1MockAddr = address( - new CurveV1MetapoolMock( - tokenTestSuite.addressOf(Tokens.cLINK), - _basePoolAddr - ) - ); - - curvePoolTokens = getPoolTokens(4); - - _priceFeed = address( - new CurveStableLPPriceFeed( - address(addressProvider), - _curveV1MockAddr, - _curveV1MockAddr, - [PriceFeedParams({ priceFeed: priceOracle.priceFeeds(curvePoolTokens[0]), stalenessPeriod: 48 hours }), - PriceFeedParams({ priceFeed:priceOracle.priceFeeds(curvePoolTokens[1]),stalenessPeriod: 48 hours }), - PriceFeedParams({ priceFeed:priceOracle.priceFeeds(curvePoolTokens[2]),stalenessPeriod: 48 hours }), - PriceFeedParams({ priceFeed:priceOracle.priceFeeds(curvePoolTokens[3]),stalenessPeriod: 48 hours })] - - ) - ); - - lpToken = address(ICurvePool(_curveV1MockAddr).token()); - - vm.startPrank(CONFIGURATOR); - - priceOracle.setPriceFeed(lpToken, _priceFeed, 0); - creditConfigurator.addCollateralToken(lpToken, 9300); - - vm.stopPrank(); - - _adapterAddr = address( - new CurveV1Adapter2Assets( - address(creditManager), - _curveV1MockAddr, - lpToken, - _basePoolAddr - ) - ); - - vm.prank(CONFIGURATOR); - creditConfigurator.allowAdapter(_adapterAddr); - - tokenTestSuite.mint(Tokens.cLINK, _curveV1MockAddr, LINK_ACCOUNT_AMOUNT); - CurveV1Mock(_basePoolAddr).mintLP(_curveV1MockAddr, DAI_ACCOUNT_AMOUNT); - - vm.label(_adapterAddr, "ADAPTER"); - vm.label(_curveV1MockAddr, "CURVE_MOCK"); - vm.label(lpToken, "CURVE_LP_TOKEN"); - } - - function _setUpCurveCryptoSuite() internal { - _setUp(Tokens.DAI); - - poolTkns = [Tokens.cDAI, Tokens.cUSDC, Tokens.cUSDT, Tokens.cLINK]; - underlyingPoolTkns = [Tokens.DAI, Tokens.USDC, Tokens.USDT, Tokens.LINK]; - - address[] memory curvePoolTokens = getPoolTokens(3); - address[] memory curvePoolUnderlyings = getUnderlyingPoolTokens(3); - - _basePoolAddr = address(new CurveV1Mock_3Assets(curvePoolTokens, curvePoolUnderlyings)); - - address _basePriceFeed = address( - new CurveStableLPPriceFeed( - address(addressProvider), - _basePoolAddr, - _basePoolAddr, - [PriceFeedParams({ priceFeed: priceOracle.priceFeeds(curvePoolTokens[0]), stalenessPeriod: 48 hours}), - PriceFeedParams({ priceFeed: priceOracle.priceFeeds(curvePoolTokens[1]),stalenessPeriod: 48 hours}), - PriceFeedParams({priceFeed: priceOracle.priceFeeds(curvePoolTokens[2]),stalenessPeriod: 48 hours}), - PriceFeedParams({priceFeed: address(0),stalenessPeriod:0})] - - ) - ); - - address baseLpToken = address(ICurvePool(_basePoolAddr).token()); - - vm.startPrank(CONFIGURATOR); - - priceOracle.setPriceFeed(baseLpToken, _basePriceFeed, 0); - creditConfigurator.addCollateralToken(baseLpToken, 9300); - - vm.stopPrank(); - - tokenTestSuite.mint(Tokens.cDAI, _basePoolAddr, LINK_ACCOUNT_AMOUNT * 100); - tokenTestSuite.mint(Tokens.cUSDC, _basePoolAddr, LINK_ACCOUNT_AMOUNT * 100); - tokenTestSuite.mint(Tokens.cUSDT, _basePoolAddr, LINK_ACCOUNT_AMOUNT * 100); - - _curveV1MockAddr = address( - new CurveV1MetapoolMock( - tokenTestSuite.addressOf(Tokens.cLINK), - _basePoolAddr - ) - ); - - CurveV1MetapoolMock(_curveV1MockAddr).setIsCryptoPool(true); - - curvePoolTokens = getPoolTokens(4); - - address _priceFeed = address( - new CurveCryptoLPPriceFeed( - address(addressProvider), - _curveV1MockAddr, - _curveV1MockAddr, - [PriceFeedParams({priceFeed: priceOracle.priceFeeds(curvePoolTokens[3]), stalenessPeriod: 48 hours}), - PriceFeedParams({priceFeed: _basePriceFeed,stalenessPeriod: 0}), - PriceFeedParams({priceFeed: address(0), stalenessPeriod: 0})] - - ) - ); - - lpToken = address(ICurvePool(_curveV1MockAddr).token()); - - vm.startPrank(CONFIGURATOR); - - priceOracle.setPriceFeed(lpToken, _priceFeed, 0); - creditConfigurator.addCollateralToken(lpToken, 9300); - - vm.stopPrank(); - - _adapterAddr = address( - new CurveV1Adapter2Assets( - address(creditManager), - _curveV1MockAddr, - lpToken, - _basePoolAddr - ) - ); - - vm.prank(CONFIGURATOR); - creditConfigurator.allowAdapter(_adapterAddr); - - tokenTestSuite.mint(Tokens.cLINK, _curveV1MockAddr, LINK_ACCOUNT_AMOUNT); - CurveV1Mock(_basePoolAddr).mintLP(_curveV1MockAddr, DAI_ACCOUNT_AMOUNT * 100); - - vm.label(_adapterAddr, "ADAPTER"); - vm.label(_curveV1MockAddr, "CURVE_MOCK"); - vm.label(lpToken, "CURVE_LP_TOKEN"); - } - - // - // HELPERS - // - function getPoolTokens(uint256 nCoins) internal returns (address[] memory poolTokens) { - require(nCoins <= poolTkns.length, "getPoolTokens: Incorrect nCoins parameter"); - - poolTokens = new address[](nCoins); - - for (uint256 i = 0; i < nCoins; i++) { - poolTokens[i] = tokenTestSuite.addressOf(poolTkns[i]); - if (creditManager.getTokenMaskOrRevert(poolTokens[i]) == 0) { - vm.startPrank(CONFIGURATOR); - priceOracle.setPriceFeed( - poolTokens[i], priceOracle.priceFeeds(tokenTestSuite.addressOf(poolTkns[i])), 0 - ); - creditConfigurator.addCollateralToken(poolTokens[i], 9300); - vm.stopPrank(); - } - } - } - - function getUnderlyingPoolTokens(uint256 nCoins) internal returns (address[] memory underlyingPoolTokens) { - require(nCoins <= underlyingPoolTkns.length, "getUnderlyingPoolTokens: Incorrect nCoins parameter"); - - underlyingPoolTokens = new address[](nCoins); - - for (uint256 i = 0; i < nCoins; i++) { - underlyingPoolTokens[i] = tokenTestSuite.addressOf(underlyingPoolTkns[i]); - if (creditManager.getTokenMaskOrRevert(underlyingPoolTokens[i]) == 0) { - vm.startPrank(CONFIGURATOR); - priceOracle.setPriceFeed( - underlyingPoolTokens[i], - priceOracle.priceFeeds(tokenTestSuite.addressOf(underlyingPoolTkns[i])), - 48 hours - ); - creditConfigurator.addCollateralToken(underlyingPoolTokens[i], 9300); - vm.stopPrank(); - } - } - } - - function _castToDynamic(uint256[2] memory arr) internal pure returns (uint256[] memory res) { - res = new uint256[](2); - res[0] = arr[0]; - res[1] = arr[1]; - } - - function _castToDynamic(uint256[3] memory arr) internal pure returns (uint256[] memory res) { - res = new uint256[](3); - res[0] = arr[0]; - res[1] = arr[1]; - res[2] = arr[2]; - } - - function _castToDynamic(uint256[4] memory arr) internal pure returns (uint256[] memory res) { - res = new uint256[](4); - res[0] = arr[0]; - res[1] = arr[1]; - res[2] = arr[2]; - res[3] = arr[3]; - } - - function addCRVCollateral(CurveV1Mock curveV1Mock, uint256 amount) internal { - // provide LP token to creditAccount - ICRVToken crv = ICRVToken(curveV1Mock.token()); - crv.set_minter(address(this)); - - crv.mint(USER, amount); - crv.set_minter(address(curveV1Mock)); - - vm.startPrank(USER); - IERC20(address(crv)).approve(address(creditManager), type(uint256).max); - - // creditFacade.addCollateral(USER, address(crv), amount); - - vm.stopPrank(); - } - - // - // CALL AND EVENT CHECKS - // - - // - // ADD LIQUIDITY - // - function expectAddLiquidityCalls(address creditAccount, address borrower, bytes memory callData, uint256 nCoins) - internal - { - address[] memory curvePoolTokens = getPoolTokens(nCoins); - - _expectAddLiquidityCalls(creditAccount, borrower, callData, _curveV1MockAddr, curvePoolTokens); - } - - function expectStETHAddLiquidityCalls(address creditAccount, address borrower, bytes memory callData) internal { - address[] memory curvePoolTokens = new address[](2); - curvePoolTokens[0] = tokenTestSuite.addressOf(Tokens.WETH); - curvePoolTokens[1] = tokenTestSuite.addressOf(Tokens.STETH); - - _expectAddLiquidityCalls(creditAccount, borrower, callData, _curveV1stETHPoolGateway, curvePoolTokens); - } - - function _expectAddLiquidityCalls( - address creditAccount, - address borrower, - bytes memory callData, - address pool, - address[] memory curvePoolTokens - ) internal { - uint256 nCoins = curvePoolTokens.length; - - vm.expectEmit(true, false, false, false); - emit StartMultiCall(creditAccount, borrower); - - for (uint256 i = 0; i < nCoins; i++) { - vm.expectCall( - address(creditManager), - abi.encodeCall(ICreditManagerV3.approveCreditAccount, (curvePoolTokens[i], type(uint256).max)) - ); - } - - uint256 lpTokenMask = creditManager.getTokenMaskOrRevert(lpToken); - - vm.expectCall(address(creditManager), abi.encodeCall(ICreditManagerV3.execute, (callData))); - - vm.expectEmit(true, false, false, false); - emit Execute(creditAccount, pool); - - for (uint256 i = 0; i < nCoins; i++) { - vm.expectCall( - address(creditManager), abi.encodeCall(ICreditManagerV3.approveCreditAccount, (curvePoolTokens[i], 1)) - ); - } - - vm.expectEmit(false, false, false, false); - emit FinishMultiCall(); - } - - // - // REMOVE LIQUIDITY - // - function expectRemoveLiquidityCalls(address creditAccount, address borrower, bytes memory callData, uint256 nCoins) - internal - { - address[] memory curvePoolTokens = getPoolTokens(nCoins); - - _expectRemoveLiquidityCalls(creditAccount, borrower, callData, _curveV1MockAddr, curvePoolTokens); - } - - function expectStETHRemoveLiquidityCalls(address creditAccount, address borrower, bytes memory callData) internal { - address[] memory curvePoolTokens = new address[](2); - curvePoolTokens[0] = tokenTestSuite.addressOf(Tokens.WETH); - curvePoolTokens[1] = tokenTestSuite.addressOf(Tokens.STETH); - - _expectRemoveLiquidityCalls(creditAccount, borrower, callData, _curveV1stETHPoolGateway, curvePoolTokens); - } - - function _expectRemoveLiquidityCalls( - address creditAccount, - address borrower, - bytes memory callData, - address pool, - address[] memory curvePoolTokens - ) internal { - uint256 nCoins = curvePoolTokens.length; - - vm.expectEmit(true, false, false, false); - emit StartMultiCall(creditAccount, borrower); - - uint256 tokensMask; - for (uint256 i = 0; i < nCoins; i++) { - tokensMask |= creditManager.getTokenMaskOrRevert(curvePoolTokens[i]); - } - - vm.expectCall(address(creditManager), abi.encodeCall(ICreditManagerV3.execute, (callData))); - - vm.expectEmit(true, false, false, false); - emit Execute(creditAccount, pool); - - vm.expectEmit(false, false, false, false); - emit FinishMultiCall(); - } - - // - // REMOVE LIQUIDITY IMBALANCE - // - function expectRemoveLiquidityImbalanceCalls( - address creditAccount, - address borrower, - bytes memory callData, - uint256 nCoins, - uint256[] memory amounts - ) internal { - address[] memory curvePoolTokens = getPoolTokens(nCoins); - - _expectRemoveLiquidityImbalanceCalls( - creditAccount, borrower, callData, amounts, _curveV1MockAddr, curvePoolTokens - ); - } - - function expectStETHRemoveLiquidityImbalanceCalls( - address creditAccount, - address borrower, - bytes memory callData, - uint256[2] memory amounts - ) internal { - address[] memory curvePoolTokens = new address[](2); - curvePoolTokens[0] = tokenTestSuite.addressOf(Tokens.WETH); - curvePoolTokens[1] = tokenTestSuite.addressOf(Tokens.STETH); - - _expectRemoveLiquidityImbalanceCalls( - creditAccount, borrower, callData, _castToDynamic(amounts), _curveV1stETHPoolGateway, curvePoolTokens - ); - } - - function _expectRemoveLiquidityImbalanceCalls( - address creditAccount, - address borrower, - bytes memory callData, - uint256[] memory amounts, - address pool, - address[] memory curvePoolTokens - ) internal { - uint256 nCoins = curvePoolTokens.length; - - vm.expectEmit(true, false, false, false); - emit StartMultiCall(creditAccount, borrower); - - uint256 tokensMask; - for (uint256 i = 0; i < nCoins; i++) { - if (amounts[i] > 0) { - tokensMask |= creditManager.getTokenMaskOrRevert(curvePoolTokens[i]); - } - } - - vm.expectCall(address(creditManager), abi.encodeCall(ICreditManagerV3.execute, (callData))); - - vm.expectEmit(true, false, false, false); - emit Execute(creditAccount, pool); - - vm.expectEmit(false, false, false, false); - emit FinishMultiCall(); - } - - function expectRemoveLiquidityImbalanceCalls( - address creditAccount, - address borrower, - bytes memory callData, - uint256 nCoins, - uint256[2] memory amounts - ) internal { - uint256[] memory amts = _castToDynamic(amounts); - expectRemoveLiquidityImbalanceCalls(creditAccount, borrower, callData, nCoins, amts); - } - - function expectRemoveLiquidityImbalanceCalls( - address creditAccount, - address borrower, - bytes memory callData, - uint256 nCoins, - uint256[3] memory amounts - ) internal { - uint256[] memory amts = _castToDynamic(amounts); - expectRemoveLiquidityImbalanceCalls(creditAccount, borrower, callData, nCoins, amts); - } - - function expectRemoveLiquidityImbalanceCalls( - address creditAccount, - address borrower, - bytes memory callData, - uint256 nCoins, - uint256[4] memory amounts - ) internal { - uint256[] memory amts = _castToDynamic(amounts); - expectRemoveLiquidityImbalanceCalls(creditAccount, borrower, callData, nCoins, amts); - } -} diff --git a/contracts/test/unit/adapters/curve/CurveV1Adapter_2AssetsTest.t.sol b/contracts/test/unit/adapters/curve/CurveV1Adapter_2AssetsTest.t.sol deleted file mode 100644 index cee01ec7..00000000 --- a/contracts/test/unit/adapters/curve/CurveV1Adapter_2AssetsTest.t.sol +++ /dev/null @@ -1,199 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {N_COINS, CurveV1Adapter2Assets} from "../../../../adapters/curve/CurveV1_2.sol"; - -import {ICurvePool2Assets} from "../../../../integrations/curve/ICurvePool_2.sol"; - -import {CurveV1Mock_2Assets} from "../../../mocks/integrations/CurveV1Mock_2Assets.sol"; - -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; - -// TEST -import "../../../lib/constants.sol"; - -import {CurveV1AdapterHelper, DAI_TO_LP, USDC_TO_LP} from "./CurveV1AdapterHelper.sol"; - -// EXCEPTIONS -import "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol"; - -/// @title CurveV1Adapter2AssetsTest -/// @notice Designed for unit test purposes only -contract CurveV1Adapter2AssetsTest is Test, CurveV1AdapterHelper { - CurveV1Adapter2Assets public adapter; - CurveV1Mock_2Assets public curveV1Mock; - - function setUp() public { - _setupCurveSuite(N_COINS); - - curveV1Mock = CurveV1Mock_2Assets(_curveV1MockAddr); - adapter = CurveV1Adapter2Assets(_adapterAddr); - } - - /// - /// - /// TESTS - /// - /// - - /// @dev [ACV1_2-1]: constructor sets correct values - function test_ACV1_2_01_constructor_sets_correct_values() public { - assertEq(address(adapter.token0()), tokenTestSuite.addressOf(Tokens.cDAI), "Incorrect token0"); - assertEq(address(adapter.token1()), tokenTestSuite.addressOf(Tokens.cUSDC), "Incorrect token1"); - } - - /// @dev [ACV1_2-2]: constructor reverts for zero addresses - function test_ACV1_2_02_constructor_reverts_for_zero_addresses() public { - for (uint256 i = 0; i < N_COINS; i++) { - address[] memory poolTokens = getPoolTokens(N_COINS); - poolTokens[i] = address(0); - - curveV1Mock = new CurveV1Mock_2Assets(poolTokens, poolTokens); - - address lp_token = curveV1Mock.lp_token(); - addMockPriceFeed(lp_token, 1e8); - - vm.prank(CONFIGURATOR); - creditConfigurator.addCollateralToken(lp_token, 8800); - - vm.expectRevert(abi.encodeWithSelector(ZeroAddressException.selector)); - adapter = new CurveV1Adapter2Assets( - address(creditManager), - address(curveV1Mock), - lp_token, - address(0) - ); - } - } - - /// @dev [ACV1_2-2]: constructor reverts for zero addresses - function test_ACV1_2_02A_constructor_reverts_for_unknown_addresses() public { - for (uint256 i = 0; i < N_COINS; i++) { - address[] memory poolTokens = getPoolTokens(N_COINS); - poolTokens[i] = tokenTestSuite.addressOf(Tokens.LUNA); - - curveV1Mock = new CurveV1Mock_2Assets(poolTokens, poolTokens); - - address lp_token = curveV1Mock.lp_token(); - addMockPriceFeed(lp_token, 1e8); - - vm.prank(CONFIGURATOR); - creditConfigurator.addCollateralToken(lp_token, 8800); - - vm.expectRevert(TokenNotAllowedException.selector); - adapter = new CurveV1Adapter2Assets( - address(creditManager), - address(curveV1Mock), - lp_token, - address(0) - ); - } - } - - // /// @dev [ACV1_2-3]: liquidity functions revert if user has no account - // function test_ACV1_2_03_liquidity_functions_revert_if_user_has_no_account() public { - // uint256[N_COINS] memory data = [uint256(1), uint256(2)]; - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall(creditAccount, address(adapter), abi.encodeCall(adapter.add_liquidity, (data, 0))); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall(creditAccount, address(adapter), abi.encodeCall(adapter.remove_liquidity, (0, data))); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall( - // creditAccount, address(adapter), abi.encodeCall(adapter.remove_liquidity_imbalance, (data, 1)) - // ); - // } - - /// @dev [ACV1_2-4]: add_liquidity works as expected( - function test_ACV1_2_04_add_liquidity_works_as_expected() public { - setUp(); - (address creditAccount,) = _openTestCreditAccount(); - - addCollateral(Tokens.cDAI, DAI_ACCOUNT_AMOUNT); - addCollateral(Tokens.cUSDC, USDC_ACCOUNT_AMOUNT); - - uint256[N_COINS] memory amounts = [DAI_TO_LP, USDC_TO_LP]; - - bytes memory callData = abi.encodeCall(ICurvePool2Assets.add_liquidity, (amounts, CURVE_LP_OPERATION_AMOUNT)); - - expectAllowance(Tokens.cDAI, creditAccount, address(curveV1Mock), 0); - - expectAllowance(Tokens.cUSDC, creditAccount, address(curveV1Mock), 0); - - expectAddLiquidityCalls(creditAccount, USER, callData, N_COINS); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.cDAI, creditAccount, DAI_ACCOUNT_AMOUNT - DAI_TO_LP); - - expectBalance(Tokens.cUSDC, creditAccount, USDC_ACCOUNT_AMOUNT - USDC_TO_LP); - - expectBalance(curveV1Mock.token(), creditAccount, CURVE_LP_OPERATION_AMOUNT); - - expectTokenIsEnabled(creditAccount, curveV1Mock.token(), true); - - expectAllowance(Tokens.cDAI, creditAccount, address(curveV1Mock), 1); - - expectAllowance(Tokens.cUSDC, creditAccount, address(curveV1Mock), 1); - } - - /// @dev [ACV1_2-5]: remove_liquidity works as expected( - function test_ACV1_2_05_remove_liquidity_works_as_expected() public { - setUp(); - (address creditAccount,) = _openTestCreditAccount(); - - // provide LP token to creditAccount - addCRVCollateral(curveV1Mock, CURVE_LP_ACCOUNT_AMOUNT); - - uint256[N_COINS] memory amounts = [DAI_TO_LP, USDC_TO_LP]; - - bytes memory callData = abi.encodeCall(ICurvePool2Assets.remove_liquidity, (CURVE_LP_OPERATION_AMOUNT, amounts)); - - expectRemoveLiquidityCalls(creditAccount, USER, callData, N_COINS); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.cDAI, creditAccount, DAI_TO_LP); - - expectBalance(Tokens.cUSDC, creditAccount, USDC_TO_LP); - - expectBalance(curveV1Mock.token(), creditAccount, CURVE_LP_ACCOUNT_AMOUNT - CURVE_LP_OPERATION_AMOUNT); - - expectAllowance(curveV1Mock.token(), creditAccount, address(curveV1Mock), 0); - - expectTokenIsEnabled(creditAccount, Tokens.cDAI, true); - expectTokenIsEnabled(creditAccount, Tokens.cUSDC, true); - } - - /// @dev [ACV1_2-6]: remove_liquidity_imbalance works as expected( - function test_ACV1_2_06_remove_liquidity_imbalance_works_as_expected() public { - setUp(); - (address creditAccount,) = _openTestCreditAccount(); - - addCRVCollateral(curveV1Mock, CURVE_LP_ACCOUNT_AMOUNT); - - uint256[N_COINS] memory expectedAmounts = [DAI_TO_LP, USDC_TO_LP]; - - bytes memory callData = - abi.encodeCall(ICurvePool2Assets.remove_liquidity_imbalance, (expectedAmounts, CURVE_LP_OPERATION_AMOUNT)); - - expectRemoveLiquidityImbalanceCalls(creditAccount, USER, callData, N_COINS, expectedAmounts); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.cDAI, creditAccount, DAI_TO_LP); - - expectBalance(Tokens.cUSDC, creditAccount, USDC_TO_LP); - - expectBalance(curveV1Mock.token(), creditAccount, CURVE_LP_ACCOUNT_AMOUNT - CURVE_LP_OPERATION_AMOUNT); - - expectAllowance(curveV1Mock.token(), creditAccount, address(curveV1Mock), 0); - - expectTokenIsEnabled(creditAccount, Tokens.cDAI, true); - expectTokenIsEnabled(creditAccount, Tokens.cUSDC, true); - } -} diff --git a/contracts/test/unit/adapters/curve/CurveV1Adapter_3AssetsTest.t.sol b/contracts/test/unit/adapters/curve/CurveV1Adapter_3AssetsTest.t.sol deleted file mode 100644 index e28683ea..00000000 --- a/contracts/test/unit/adapters/curve/CurveV1Adapter_3AssetsTest.t.sol +++ /dev/null @@ -1,209 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {N_COINS, CurveV1Adapter3Assets} from "../../../../adapters/curve/CurveV1_3.sol"; -import {CurveV1Mock_3Assets} from "../../../mocks/integrations/CurveV1Mock_3Assets.sol"; -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; - -// TEST -import "../../../lib/constants.sol"; -import {CurveV1AdapterHelper, DAI_TO_LP, USDC_TO_LP, USDT_TO_LP} from "./CurveV1AdapterHelper.sol"; - -// EXCEPTIONS - -import "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol"; - -/// @title CurveV1Adapter3AssetsTest -/// @notice Designed for unit test purposes only -contract CurveV1Adapter3AssetsTest is Test, CurveV1AdapterHelper { - CurveV1Adapter3Assets public adapter; - CurveV1Mock_3Assets public curveV1Mock; - - function setUp() public { - _setupCurveSuite(N_COINS); - curveV1Mock = CurveV1Mock_3Assets(_curveV1MockAddr); - adapter = CurveV1Adapter3Assets(_adapterAddr); - } - - /// - /// - /// TESTS - /// - /// - - /// @dev [ACV1_3-1]: constructor sets correct values - function test_ACV1_3_01_constructor_sets_correct_values() public { - assertEq(address(adapter.token0()), tokenTestSuite.addressOf(Tokens.cDAI), "Incorrect token0"); - assertEq(address(adapter.token1()), tokenTestSuite.addressOf(Tokens.cUSDC), "Incorrect token1"); - assertEq(address(adapter.token2()), tokenTestSuite.addressOf(Tokens.cUSDT), "Incorrect token2"); - } - - /// @dev [ACV1_3-2]: constructor reverts for zero addresses - function test_ACV1_3_02_constructor_reverts_for_zero_addresses() public { - for (uint256 i = 0; i < N_COINS; i++) { - address[] memory poolTokens = getPoolTokens(N_COINS); - poolTokens[i] = address(0); - - curveV1Mock = new CurveV1Mock_3Assets(poolTokens, poolTokens); - - address lp_token = curveV1Mock.lp_token(); - addMockPriceFeed(lp_token, 1e8); - - vm.prank(CONFIGURATOR); - creditConfigurator.addCollateralToken(lp_token, 8800); - - vm.expectRevert(abi.encodeWithSelector(ZeroAddressException.selector)); - adapter = new CurveV1Adapter3Assets( - address(creditManager), - address(curveV1Mock), - lp_token, - address(0) - ); - } - } - - /// @dev [ACV1_3-2A]: constructor reverts for zero addresses - function test_ACV1_3_02A_constructor_reverts_for_unknown_addresses() public { - for (uint256 i = 0; i < N_COINS; i++) { - address[] memory poolTokens = getPoolTokens(N_COINS); - poolTokens[i] = tokenTestSuite.addressOf(Tokens.LUNA); - - curveV1Mock = new CurveV1Mock_3Assets(poolTokens, poolTokens); - - address lp_token = curveV1Mock.lp_token(); - addMockPriceFeed(lp_token, 1e8); - - vm.prank(CONFIGURATOR); - creditConfigurator.addCollateralToken(lp_token, 8800); - - vm.expectRevert(TokenNotAllowedException.selector); - adapter = new CurveV1Adapter3Assets( - address(creditManager), - address(curveV1Mock), - lp_token, - address(0) - ); - } - } - - // /// @dev [ACV1_3-3]: liquidity functions revert if user has no account - // function test_ACV1_3_03_liquidity_functions_revert_if_user_has_no_account() public { - // uint256[N_COINS] memory data = [uint256(1), uint256(2), uint256(3)]; - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall(creditAccount, address(adapter), abi.encodeCall(adapter.add_liquidity, (data, 0))); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall(creditAccount, address(adapter), abi.encodeCall(adapter.remove_liquidity, (0, data))); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall( - // creditAccount, address(adapter), abi.encodeCall(adapter.remove_liquidity_imbalance, (data, 1)) - // ); - // } - - /// @dev [ACV1_3-4]: add_liquidity works as expected - function test_ACV1_3_04_add_liquidity_works_as_expected() public { - setUp(); - - (address creditAccount,) = _openTestCreditAccount(); - - addCollateral(Tokens.cDAI, DAI_ACCOUNT_AMOUNT); - addCollateral(Tokens.cUSDC, USDC_ACCOUNT_AMOUNT); - addCollateral(Tokens.cUSDT, USDT_ACCOUNT_AMOUNT); - - expectAllowance(Tokens.cDAI, creditAccount, address(curveV1Mock), 0); - - expectAllowance(Tokens.cUSDC, creditAccount, address(curveV1Mock), 0); - - expectAllowance(Tokens.cUSDT, creditAccount, address(curveV1Mock), 0); - - uint256[N_COINS] memory amounts = [DAI_TO_LP, USDC_TO_LP, USDT_TO_LP]; - - bytes memory callData = - abi.encodeCall(CurveV1Adapter3Assets.add_liquidity, (amounts, CURVE_LP_OPERATION_AMOUNT)); - - expectAddLiquidityCalls(creditAccount, USER, callData, N_COINS); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.cDAI, creditAccount, DAI_ACCOUNT_AMOUNT - DAI_TO_LP); - - expectBalance(Tokens.cUSDC, creditAccount, USDC_ACCOUNT_AMOUNT - USDC_TO_LP); - - expectBalance(Tokens.cUSDT, creditAccount, USDT_ACCOUNT_AMOUNT - USDT_TO_LP); - - expectBalance(curveV1Mock.token(), creditAccount, CURVE_LP_OPERATION_AMOUNT); - - expectTokenIsEnabled(creditAccount, curveV1Mock.token(), true); - - expectAllowance(Tokens.cDAI, creditAccount, address(curveV1Mock), 1); - - expectAllowance(Tokens.cUSDC, creditAccount, address(curveV1Mock), 1); - - expectAllowance(Tokens.cUSDT, creditAccount, address(curveV1Mock), 1); - } - - /// @dev [ACV1_3-5]: remove_liquidity works as expected( - function test_ACV1_3_05_remove_liquidity_works_as_expected() public { - setUp(); - (address creditAccount,) = _openTestCreditAccount(); - - // provide LP token to creditAccount - addCRVCollateral(curveV1Mock, CURVE_LP_ACCOUNT_AMOUNT); - - uint256[N_COINS] memory amounts = [DAI_TO_LP, USDC_TO_LP, USDT_TO_LP]; - - bytes memory callData = - abi.encodeCall(CurveV1Adapter3Assets.remove_liquidity, (CURVE_LP_OPERATION_AMOUNT, amounts)); - - expectRemoveLiquidityCalls(creditAccount, USER, callData, N_COINS); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.cDAI, creditAccount, DAI_TO_LP); - expectBalance(Tokens.cUSDC, creditAccount, USDC_TO_LP); - expectBalance(Tokens.cUSDT, creditAccount, USDT_TO_LP); - - expectBalance(curveV1Mock.token(), creditAccount, CURVE_LP_ACCOUNT_AMOUNT - CURVE_LP_OPERATION_AMOUNT); - - expectAllowance(curveV1Mock.token(), creditAccount, address(curveV1Mock), 0); - - expectTokenIsEnabled(creditAccount, Tokens.cDAI, true); - expectTokenIsEnabled(creditAccount, Tokens.cUSDC, true); - expectTokenIsEnabled(creditAccount, Tokens.cUSDT, true); - } - - /// @dev [ACV1_3-6]: remove_liquidity_imbalance works as expected( - function test_ACV1_3_06_remove_liquidity_imbalance_works_as_expected() public { - setUp(); - (address creditAccount,) = _openTestCreditAccount(); - - addCRVCollateral(curveV1Mock, CURVE_LP_ACCOUNT_AMOUNT); - - uint256[N_COINS] memory expectedAmounts = [0, 0, USDT_TO_LP]; - - bytes memory callData = abi.encodeCall( - CurveV1Adapter3Assets.remove_liquidity_imbalance, (expectedAmounts, CURVE_LP_OPERATION_AMOUNT) - ); - - expectRemoveLiquidityImbalanceCalls(creditAccount, USER, callData, N_COINS, expectedAmounts); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.cDAI, creditAccount, 0); - - expectBalance(Tokens.cUSDC, creditAccount, 0); - - expectBalance(Tokens.cUSDT, creditAccount, USDT_TO_LP); - - expectBalance(curveV1Mock.token(), creditAccount, CURVE_LP_ACCOUNT_AMOUNT - CURVE_LP_OPERATION_AMOUNT); - - expectAllowance(curveV1Mock.token(), creditAccount, address(curveV1Mock), 0); - - expectTokenIsEnabled(creditAccount, Tokens.cUSDC, false); - expectTokenIsEnabled(creditAccount, Tokens.cUSDT, true); - } -} diff --git a/contracts/test/unit/adapters/curve/CurveV1Adapter_4AssetsTest.t.sol b/contracts/test/unit/adapters/curve/CurveV1Adapter_4AssetsTest.t.sol deleted file mode 100644 index c9bcf482..00000000 --- a/contracts/test/unit/adapters/curve/CurveV1Adapter_4AssetsTest.t.sol +++ /dev/null @@ -1,230 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {N_COINS, CurveV1Adapter4Assets} from "../../../../adapters/curve/CurveV1_4.sol"; - -import {CurveV1Mock_4Assets} from "../../../mocks/integrations/CurveV1Mock_4Assets.sol"; - -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; - -// TEST -import "../../../lib/constants.sol"; - -import {CurveV1AdapterHelper, DAI_TO_LP, USDC_TO_LP, USDT_TO_LP, LINK_TO_LP} from "./CurveV1AdapterHelper.sol"; - -// EXCEPTIONS - -import "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol"; - -/// @title CurveV1Adapter4AssetsTest -/// @notice Designed for unit test purposes only -contract CurveV1Adapter4AssetsTest is Test, CurveV1AdapterHelper { - CurveV1Adapter4Assets public adapter; - CurveV1Mock_4Assets public curveV1Mock; - - function setUp() public { - _setupCurveSuite(N_COINS); - curveV1Mock = CurveV1Mock_4Assets(_curveV1MockAddr); - adapter = CurveV1Adapter4Assets(_adapterAddr); - } - - /// - /// - /// TESTS - /// - /// - - /// @dev [ACV1_4-1]: constructor sets correct values - function test_ACV1_4_01_constructor_sets_correct_values() public { - assertEq(address(adapter.token0()), tokenTestSuite.addressOf(Tokens.cDAI), "Incorrect token0"); - assertEq(address(adapter.token1()), tokenTestSuite.addressOf(Tokens.cUSDC), "Incorrect token1"); - assertEq(address(adapter.token2()), tokenTestSuite.addressOf(Tokens.cUSDT), "Incorrect token2"); - assertEq(address(adapter.token3()), tokenTestSuite.addressOf(Tokens.cLINK), "Incorrect token3"); - } - - /// @dev [ACV1_3-2]: constructor reverts for zero addresses - function test_ACV1_4_02_constructor_reverts_for_zero_addresses() public { - for (uint256 i = 0; i < N_COINS; i++) { - address[] memory poolTokens = getPoolTokens(N_COINS); - poolTokens[i] = address(0); - - curveV1Mock = new CurveV1Mock_4Assets(poolTokens, poolTokens); - - address lp_token = curveV1Mock.lp_token(); - addMockPriceFeed(lp_token, 1e8); - - vm.prank(CONFIGURATOR); - creditConfigurator.addCollateralToken(lp_token, 8800); - - vm.expectRevert(abi.encodeWithSelector(ZeroAddressException.selector)); - adapter = new CurveV1Adapter4Assets( - address(creditManager), - address(curveV1Mock), - lp_token, - address(0) - ); - } - } - - /// @dev [ACV1_4-2A]: constructor reverts for zero addresses - function test_ACV1_4_02A_constructor_reverts_for_unknown_addresses() public { - for (uint256 i = 0; i < N_COINS; i++) { - address[] memory poolTokens = getPoolTokens(N_COINS); - poolTokens[i] = tokenTestSuite.addressOf(Tokens.LUNA); - - curveV1Mock = new CurveV1Mock_4Assets(poolTokens, poolTokens); - - address lp_token = curveV1Mock.lp_token(); - addMockPriceFeed(lp_token, 1e8); - - vm.prank(CONFIGURATOR); - creditConfigurator.addCollateralToken(lp_token, 8800); - - vm.expectRevert(TokenNotAllowedException.selector); - adapter = new CurveV1Adapter4Assets( - address(creditManager), - address(curveV1Mock), - lp_token, - address(0) - ); - } - } - - // /// @dev [ACV1_4-3]: liquidity functions revert if user has no account - // function test_ACV1_4_03_liquidity_functions_revert_if_user_has_no_account() public { - // uint256[N_COINS] memory data = [uint256(1), uint256(2), uint256(3), uint256(4)]; - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall(creditAccount, address(adapter), abi.encodeCall(adapter.add_liquidity, (data, 0))); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall(creditAccount, address(adapter), abi.encodeCall(adapter.remove_liquidity, (0, data))); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall( - // creditAccount, address(adapter), abi.encodeCall(adapter.remove_liquidity_imbalance, (data, 1)) - // ); - // } - - /// @dev [ACV1_4-4]: add_liquidity works as expected( - function test_ACV1_4_04_add_liquidity_works_as_expected() public { - setUp(); - - (address creditAccount,) = _openTestCreditAccount(); - - addCollateral(Tokens.cDAI, DAI_ACCOUNT_AMOUNT); - addCollateral(Tokens.cUSDC, USDC_ACCOUNT_AMOUNT); - addCollateral(Tokens.cUSDT, USDT_ACCOUNT_AMOUNT); - addCollateral(Tokens.cLINK, LINK_ACCOUNT_AMOUNT); - - expectAllowance(Tokens.cDAI, creditAccount, address(curveV1Mock), 0); - - expectAllowance(Tokens.cUSDC, creditAccount, address(curveV1Mock), 0); - - expectAllowance(Tokens.cUSDT, creditAccount, address(curveV1Mock), 0); - - expectAllowance(Tokens.cLINK, creditAccount, address(curveV1Mock), 0); - - uint256[N_COINS] memory amounts = [DAI_TO_LP, USDC_TO_LP, USDT_TO_LP, LINK_TO_LP]; - - bytes memory callData = - abi.encodeCall(CurveV1Adapter4Assets.add_liquidity, (amounts, CURVE_LP_OPERATION_AMOUNT)); - - expectAddLiquidityCalls(creditAccount, USER, callData, N_COINS); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.cDAI, creditAccount, DAI_ACCOUNT_AMOUNT - DAI_TO_LP); - - expectBalance(Tokens.cUSDC, creditAccount, USDC_ACCOUNT_AMOUNT - USDC_TO_LP); - - expectBalance(Tokens.cUSDT, creditAccount, USDT_ACCOUNT_AMOUNT - USDT_TO_LP); - - expectBalance(Tokens.cLINK, creditAccount, LINK_ACCOUNT_AMOUNT - LINK_TO_LP); - - expectBalance(curveV1Mock.token(), creditAccount, CURVE_LP_OPERATION_AMOUNT); - - expectTokenIsEnabled(creditAccount, curveV1Mock.token(), true); - - expectAllowance(Tokens.cDAI, creditAccount, address(curveV1Mock), 1); - - expectAllowance(Tokens.cUSDC, creditAccount, address(curveV1Mock), 1); - - expectAllowance(Tokens.cUSDT, creditAccount, address(curveV1Mock), 1); - - expectAllowance(Tokens.cLINK, creditAccount, address(curveV1Mock), 1); - } - - /// @dev [ACV1_4-5]: remove_liquidity works as expected( - function test_ACV1_4_05_remove_liquidity_works_as_expected() public { - setUp(); - - (address creditAccount,) = _openTestCreditAccount(); - - // provide LP token to creditAccount - addCRVCollateral(curveV1Mock, CURVE_LP_ACCOUNT_AMOUNT); - - uint256[N_COINS] memory amounts = [DAI_TO_LP, USDC_TO_LP, USDT_TO_LP, LINK_TO_LP]; - - bytes memory callData = - abi.encodeCall(CurveV1Adapter4Assets.remove_liquidity, (CURVE_LP_OPERATION_AMOUNT, amounts)); - - expectRemoveLiquidityCalls(creditAccount, USER, callData, N_COINS); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.cDAI, creditAccount, DAI_TO_LP); - - expectBalance(Tokens.cUSDC, creditAccount, USDC_TO_LP); - - expectBalance(Tokens.cUSDT, creditAccount, USDT_TO_LP); - - expectBalance(Tokens.cLINK, creditAccount, LINK_TO_LP); - - expectBalance(curveV1Mock.token(), creditAccount, CURVE_LP_ACCOUNT_AMOUNT - CURVE_LP_OPERATION_AMOUNT); - - expectAllowance(curveV1Mock.token(), creditAccount, address(curveV1Mock), 0); - - expectTokenIsEnabled(creditAccount, Tokens.cDAI, true); - expectTokenIsEnabled(creditAccount, Tokens.cUSDC, true); - expectTokenIsEnabled(creditAccount, Tokens.cUSDT, true); - expectTokenIsEnabled(creditAccount, Tokens.cLINK, true); - } - - /// @dev [ACV1_4-6]: remove_liquidity_imbalance works as expected( - function test_ACV1_4_06_remove_liquidity_imbalance_works_as_expected() public { - setUp(); - - (address creditAccount,) = _openTestCreditAccount(); - - addCRVCollateral(curveV1Mock, CURVE_LP_ACCOUNT_AMOUNT); - uint256[N_COINS] memory expectedAmounts = [0, 0, USDT_TO_LP, LINK_TO_LP]; - - bytes memory callData = abi.encodeCall( - CurveV1Adapter4Assets.remove_liquidity_imbalance, (expectedAmounts, CURVE_LP_OPERATION_AMOUNT) - ); - - expectRemoveLiquidityImbalanceCalls(creditAccount, USER, callData, N_COINS, expectedAmounts); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.cDAI, creditAccount, 0); - - expectBalance(Tokens.cUSDC, creditAccount, 0); - - expectBalance(Tokens.cUSDT, creditAccount, USDT_TO_LP); - - expectBalance(Tokens.cLINK, creditAccount, LINK_TO_LP); - - expectBalance(curveV1Mock.token(), creditAccount, CURVE_LP_ACCOUNT_AMOUNT - CURVE_LP_OPERATION_AMOUNT); - - expectAllowance(curveV1Mock.token(), creditAccount, address(curveV1Mock), 0); - - expectTokenIsEnabled(creditAccount, Tokens.cDAI, false); - expectTokenIsEnabled(creditAccount, Tokens.cUSDC, false); - expectTokenIsEnabled(creditAccount, Tokens.cUSDT, true); - expectTokenIsEnabled(creditAccount, Tokens.cLINK, true); - } -} diff --git a/contracts/test/unit/adapters/curve/CurveV1StETHTest.t.sol b/contracts/test/unit/adapters/curve/CurveV1StETHTest.t.sol deleted file mode 100644 index eb6e380c..00000000 --- a/contracts/test/unit/adapters/curve/CurveV1StETHTest.t.sol +++ /dev/null @@ -1,318 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {N_COINS} from "../../../../integrations/curve/ICurvePoolStETH.sol"; - -import {CurveV1AdapterStETH} from "../../../../adapters/curve/CurveV1_stETH.sol"; - -import {ICurvePool2Assets} from "../../../../integrations/curve/ICurvePool_2.sol"; -import {CurveV1Mock} from "../../../mocks/integrations/CurveV1Mock.sol"; - -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; - -// TEST -import "../../../lib/constants.sol"; - -import {CurveV1AdapterHelper} from "./CurveV1AdapterHelper.sol"; - -// EXCEPTIONS - -uint256 constant STETH_ADD_LIQUIDITY_AMOUNT = STETH_ACCOUNT_AMOUNT / 10; -uint256 constant WETH_ADD_LIQUIDITY_AMOUNT = WETH_ACCOUNT_AMOUNT / 5; -uint256 constant WETH_REMOVE_LIQUIDITY_AMOUNT = WETH_ACCOUNT_AMOUNT / 5; -uint256 constant RATE = 2; - -/// @title CurveV1StEthAdapterTest -/// @notice Designed for unit test purposes only -contract CurveV1StEthAdapterTest is Test, CurveV1AdapterHelper { - CurveV1AdapterStETH public adapter; - CurveV1Mock public curveV1Mock; - address public lp_token; - - function setUp() public { - _setUpCurveStETHSuite(); - - curveV1Mock = CurveV1Mock(_curveV1stETHMockAddr); - curveV1Mock.setRate(0, 1, RATE * RAY); // WETH / STETH = 2 - - adapter = CurveV1AdapterStETH(_adapterStETHAddr); - - tokenTestSuite.mint(Tokens.STETH, address(curveV1Mock), STETH_ACCOUNT_AMOUNT); - - vm.deal(address(curveV1Mock), WETH_ACCOUNT_AMOUNT); - - lp_token = curveV1Mock.lp_token(); - } - - /// - /// - /// TESTS - /// - /// - - /// @dev [ACV1S-1]: add_liquidity works correctly - function test_ACV1S_01_add_liquidity_works_correctly() public { - setUp(); - - (address creditAccount, uint256 initialWethBalance) = _openTestCreditAccount(); - - vm.prank(USER); - addCollateral(Tokens.STETH, STETH_ACCOUNT_AMOUNT); - - expectAllowance(Tokens.WETH, creditAccount, _curveV1stETHPoolGateway, 0); - - expectAllowance(Tokens.STETH, creditAccount, _curveV1stETHPoolGateway, 0); - - uint256 ethalanceBefore = address(curveV1Mock).balance; - uint256 stethBalanceBefore = tokenTestSuite.balanceOf(Tokens.STETH, address(curveV1Mock)); - - // Initial Gateway LP balance should be equal 0 - // Gateway LP balance should be equal 1 - expectBalance(lp_token, _curveV1stETHPoolGateway, 0, "setGateway lp_token != 0"); - - uint256[N_COINS] memory amounts = [WETH_ADD_LIQUIDITY_AMOUNT, STETH_ADD_LIQUIDITY_AMOUNT]; - - bytes memory callData = abi.encodeCall(ICurvePool2Assets.add_liquidity, (amounts, CURVE_LP_OPERATION_AMOUNT)); - - expectStETHAddLiquidityCalls(creditAccount, USER, callData); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.WETH, creditAccount, initialWethBalance - WETH_ADD_LIQUIDITY_AMOUNT); - - expectBalance(Tokens.STETH, creditAccount, STETH_ACCOUNT_AMOUNT - STETH_ADD_LIQUIDITY_AMOUNT); - - expectBalance(lp_token, creditAccount, CURVE_LP_OPERATION_AMOUNT - 1); - - // Gateway LP balance should be equal 1 - expectBalance(lp_token, _curveV1stETHPoolGateway, 1, "setGateway lp_token != 1"); - - expectEthBalance(address(curveV1Mock), ethalanceBefore + WETH_ADD_LIQUIDITY_AMOUNT); - - expectBalance(Tokens.STETH, address(curveV1Mock), stethBalanceBefore + STETH_ADD_LIQUIDITY_AMOUNT); - - expectTokenIsEnabled(creditAccount, lp_token, true); - - expectAllowance(Tokens.WETH, creditAccount, _curveV1stETHPoolGateway, 1); - - expectAllowance(Tokens.STETH, creditAccount, _curveV1stETHPoolGateway, 1); - } - - /// @dev [ACV1S-2]: remove_liquidity works correctly - function test_ACV1S_02_remove_liquidity_works_correctly() public { - setUp(); - - (address creditAccount, uint256 initialWethBalance) = _openTestCreditAccount(); - - vm.prank(USER); - addCollateral(Tokens.STETH, STETH_ACCOUNT_AMOUNT); - - uint256 ethalanceBefore = address(curveV1Mock).balance; - - uint256[N_COINS] memory amounts = [WETH_ADD_LIQUIDITY_AMOUNT, STETH_ADD_LIQUIDITY_AMOUNT]; - - uint256 STETH_REMOVE_LIQUIDITY_AMOUNT = STETH_ADD_LIQUIDITY_AMOUNT / 2; - // uint256 WETH_REMOVE_LIQUIDITY_AMOUNT = WETH_ADD_LIQUIDITY_AMOUNT / 4; - - // Initial Gateway LP balance should be equal 0 - expectBalance(lp_token, _curveV1stETHPoolGateway, 0, "setGateway lp_token != 0"); - - executeOneLineMulticall( - creditAccount, address(adapter), abi.encodeCall(adapter.add_liquidity, (amounts, CURVE_LP_OPERATION_AMOUNT)) - ); - - // Initial Gateway LP balance should be equal 1 - expectBalance(lp_token, _curveV1stETHPoolGateway, 1, "setGateway lp_token != 1"); - - expectAllowance(lp_token, creditAccount, _curveV1stETHPoolGateway, 0); - - bytes memory callData = abi.encodeCall( - ICurvePool2Assets.remove_liquidity, - (CURVE_LP_OPERATION_AMOUNT / 2, [WETH_REMOVE_LIQUIDITY_AMOUNT, STETH_REMOVE_LIQUIDITY_AMOUNT]) - ); - - expectStETHRemoveLiquidityCalls(creditAccount, USER, callData); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - // balance -1 cause gateway takes it for gas efficience - expectBalance( - Tokens.WETH, - creditAccount, - initialWethBalance - WETH_ADD_LIQUIDITY_AMOUNT + WETH_REMOVE_LIQUIDITY_AMOUNT - 1 - ); - - // balance -1 cause gateway takes it for gas efficience - expectBalance( - Tokens.STETH, - creditAccount, - STETH_ACCOUNT_AMOUNT - STETH_ADD_LIQUIDITY_AMOUNT + STETH_REMOVE_LIQUIDITY_AMOUNT - 1 - ); - - // balance -1 cause gateway takes it for gas efficience - expectBalance(lp_token, creditAccount, (CURVE_LP_OPERATION_AMOUNT - 1) - CURVE_LP_OPERATION_AMOUNT / 2); - - // Gateway balance check - expectBalance(Tokens.STETH, _curveV1stETHPoolGateway, 1, "stETH != 1"); - expectBalance(Tokens.WETH, _curveV1stETHPoolGateway, 1, "WETH != 1"); - expectBalance(lp_token, _curveV1stETHPoolGateway, 1, "steCRV != 1"); - - expectEthBalance( - address(curveV1Mock), ethalanceBefore + WETH_ADD_LIQUIDITY_AMOUNT - WETH_REMOVE_LIQUIDITY_AMOUNT - ); - - expectAllowance(lp_token, creditAccount, _curveV1stETHPoolGateway, 1); - - expectTokenIsEnabled(creditAccount, Tokens.WETH, true); - expectTokenIsEnabled(creditAccount, Tokens.STETH, true); - } - - /// @dev [ACV1S-3]: exchange works correctly - function test_ACV1S_03_exchange_works_correctly() public { - setUp(); - - address tokenIn = tokenTestSuite.addressOf(Tokens.WETH); - address tokenOut = tokenTestSuite.addressOf(Tokens.STETH); - - (address creditAccount, uint256 initialWethBalance) = _openTestCreditAccount(); - - uint256 ethalanceBefore = address(curveV1Mock).balance; - - // uint256 WETH_EXCHANGE_AMOUNT = WETH_ACCOUNT_AMOUNT / 5; - - expectAllowance(Tokens.WETH, creditAccount, _curveV1stETHPoolGateway, 0); - - bytes memory callData = - abi.encodeWithSignature("exchange(int128,int128,uint256,uint256)", 0, 1, WETH_EXCHANGE_AMOUNT, 0); - - expectMulticallStackCalls(address(adapter), _curveV1stETHPoolGateway, USER, callData, tokenIn, tokenOut, true); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.WETH, creditAccount, initialWethBalance - WETH_EXCHANGE_AMOUNT); - - // balance would be -1 because of rayMul - expectBalance(Tokens.STETH, creditAccount, WETH_EXCHANGE_AMOUNT * RATE - 1); - - expectEthBalance(address(curveV1Mock), ethalanceBefore + WETH_EXCHANGE_AMOUNT); - - expectAllowance(Tokens.WETH, creditAccount, _curveV1stETHPoolGateway, 1); - - expectTokenIsEnabled(creditAccount, Tokens.STETH, true); - } - - /// @dev [ACV1S-4]: remove_liquidity_one_coin works correctly - function test_ACV1S_04_remove_liquidity_one_coin_works_correctly() public { - setUp(); - - address tokenIn = curveV1Mock.token(); - address tokenOut = tokenTestSuite.addressOf(Tokens.WETH); - - (address creditAccount, uint256 initialWethBalance) = _openTestCreditAccount(); - - uint256 ethBalanceBefore = address(curveV1Mock).balance; - - addCRVCollateral(curveV1Mock, CURVE_LP_ACCOUNT_AMOUNT); - - expectAllowance(tokenIn, creditAccount, _curveV1stETHPoolGateway, 0); - - bytes memory callData = abi.encodeWithSignature( - "remove_liquidity_one_coin(uint256,int128,uint256)", CURVE_LP_OPERATION_AMOUNT, 0, WETH_EXCHANGE_AMOUNT - ); - - expectMulticallStackCalls(address(adapter), _curveV1stETHPoolGateway, USER, callData, tokenIn, tokenOut, true); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(tokenIn, creditAccount, CURVE_LP_ACCOUNT_AMOUNT - CURVE_LP_OPERATION_AMOUNT); - - expectBalance(tokenOut, creditAccount, initialWethBalance + WETH_EXCHANGE_AMOUNT - 1); - - expectEthBalance(address(curveV1Mock), ethBalanceBefore - WETH_EXCHANGE_AMOUNT); - - expectAllowance(tokenIn, creditAccount, _curveV1stETHPoolGateway, 1); - - expectTokenIsEnabled(creditAccount, Tokens.WETH, true); - } - - /// @dev [ACV1S-5]: remove_all_liquidity_one_coin works correctly - function test_ACV1S_05_remove_all_liquidity_one_coin_works_correctly() public { - setUp(); - - address tokenIn = curveV1Mock.token(); - address tokenOut = tokenTestSuite.addressOf(Tokens.WETH); - - (address creditAccount, uint256 initialWethBalance) = _openTestCreditAccount(); - - uint256 ethBalanceBefore = address(curveV1Mock).balance; - uint256 rateRAY = RAY / 2; - - addCRVCollateral(curveV1Mock, CURVE_LP_ACCOUNT_AMOUNT); - - expectAllowance(tokenIn, creditAccount, _curveV1stETHPoolGateway, 0); - - bytes memory expectedCallData = abi.encodeWithSignature( - "remove_liquidity_one_coin(uint256,int128,uint256)", - CURVE_LP_ACCOUNT_AMOUNT - 1, - 0, - (CURVE_LP_ACCOUNT_AMOUNT - 1) / 2 - ); - - expectMulticallStackCalls( - address(adapter), _curveV1stETHPoolGateway, USER, expectedCallData, tokenIn, tokenOut, true - ); - - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeWithSignature("remove_all_liquidity_one_coin(int128,uint256)", 0, rateRAY) - ); - - expectBalance(tokenIn, creditAccount, 1); - - expectBalance(tokenOut, creditAccount, initialWethBalance + ((CURVE_LP_ACCOUNT_AMOUNT - 1) / 2) - 1); - - expectEthBalance(address(curveV1Mock), ethBalanceBefore - ((CURVE_LP_ACCOUNT_AMOUNT - 1) / 2)); - - expectAllowance(tokenIn, creditAccount, _curveV1stETHPoolGateway, 1); - - expectTokenIsEnabled(creditAccount, Tokens.WETH, true); - } - - /// @dev [ACV1S-6]: remove_liquidity_imbalance works correctly - function test_ACV1S_06_remove_liquidity_imbalance_works_correctly() public { - setUp(); - - address tokenIn = curveV1Mock.token(); - - (address creditAccount, uint256 initialWethBalance) = _openTestCreditAccount(); - - uint256 ethBalanceBefore = address(curveV1Mock).balance; - - uint256[N_COINS] memory expectedAmounts = [WETH_REMOVE_LIQUIDITY_AMOUNT, 0]; - - addCRVCollateral(curveV1Mock, CURVE_LP_ACCOUNT_AMOUNT); - - expectAllowance(tokenIn, creditAccount, _curveV1stETHPoolGateway, 0); - - bytes memory callData = - abi.encodeCall(ICurvePool2Assets.remove_liquidity_imbalance, (expectedAmounts, CURVE_LP_OPERATION_AMOUNT)); - - expectStETHRemoveLiquidityImbalanceCalls(creditAccount, USER, callData, expectedAmounts); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(curveV1Mock.token(), creditAccount, CURVE_LP_ACCOUNT_AMOUNT - CURVE_LP_OPERATION_AMOUNT); - - expectBalance(Tokens.WETH, creditAccount, initialWethBalance + WETH_REMOVE_LIQUIDITY_AMOUNT - 1); - - expectEthBalance(address(curveV1Mock), ethBalanceBefore - WETH_REMOVE_LIQUIDITY_AMOUNT); - - expectAllowance(tokenIn, creditAccount, _curveV1stETHPoolGateway, 1); - - expectTokenIsEnabled(creditAccount, Tokens.WETH, true); - expectTokenIsEnabled(creditAccount, Tokens.STETH, false); - } -} diff --git a/contracts/test/unit/adapters/erc4626/ERC4626Adapter.unit.t.sol b/contracts/test/unit/adapters/erc4626/ERC4626Adapter.unit.t.sol new file mode 100644 index 00000000..eb5d8b2d --- /dev/null +++ b/contracts/test/unit/adapters/erc4626/ERC4626Adapter.unit.t.sol @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; +import {ERC4626Adapter} from "../../../../adapters/erc4626/ERC4626Adapter.sol"; +import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; + +/// @title ERC-4626 adapter unit test +/// @notice U:[TV]: Unit tests for ERC-4626 tokenized vault adapter +contract ERC4626AdapterUnitTest is AdapterUnitTestHelper { + ERC4626Adapter adapter; + + address asset; + address vault; + + uint256 assetMask; + uint256 sharesMask; + + function setUp() public { + _setUp(); + + (asset, assetMask) = (tokens[0], 1); + (vault, sharesMask) = (tokens[1], 2); + vm.mockCall(vault, abi.encodeCall(IERC4626.asset, ()), abi.encode(asset)); + + adapter = new ERC4626Adapter(address(creditManager), vault); + } + + /// @notice U:[TV-1]: Constructor works as expected + function test_U_TV_01_constructor_works_as_expected() public { + _readsTokenMask(asset); + _readsTokenMask(vault); + adapter = new ERC4626Adapter(address(creditManager), vault); + + assertEq(adapter.creditManager(), address(creditManager), "Incorrect creditManager"); + assertEq(adapter.addressProvider(), address(addressProvider), "Incorrect addressProvider"); + assertEq(adapter.targetContract(), vault, "Incorrect targetContract"); + assertEq(adapter.asset(), asset, "Incorrect asset"); + assertEq(adapter.assetMask(), assetMask, "Incorrect assetMask"); + assertEq(adapter.sharesMask(), sharesMask, "Incorrect sharesMask"); + } + + /// @notice U:[TV-2]: Wrapper functions revert on wrong caller + function test_U_TV_02_wrapper_functions_revert_on_wrong_caller() public { + _revertsOnNonFacadeCaller(); + adapter.deposit(0, address(0)); + + _revertsOnNonFacadeCaller(); + adapter.depositAll(); + + _revertsOnNonFacadeCaller(); + adapter.mint(0, address(0)); + + _revertsOnNonFacadeCaller(); + adapter.withdraw(0, address(0), address(0)); + + _revertsOnNonFacadeCaller(); + adapter.redeem(0, address(0), address(0)); + + _revertsOnNonFacadeCaller(); + adapter.redeemAll(); + } + + /// @notice U:[TV-3]: `deposit` works as expected + function test_U_TV_03_deposit_works_as_expected() public { + _readsActiveAccount(); + _executesSwap({ + tokenIn: asset, + tokenOut: vault, + callData: abi.encodeCall(IERC4626.deposit, (1000, creditAccount)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.deposit(1000, address(0)); + + assertEq(tokensToEnable, sharesMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[TV-4]: `depositAll` works as expected + function test_U_TV_04_depositAll_works_as_expected() public { + deal({token: asset, to: creditAccount, give: 1000}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: asset, + tokenOut: vault, + callData: abi.encodeCall(IERC4626.deposit, (999, creditAccount)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.depositAll(); + + assertEq(tokensToEnable, sharesMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, assetMask, "Incorrect tokensToDisable"); + } + + /// @notice U:[TV-5]: `mint` works as expected + function test_U_TV_05_mint_works_as_expected() public { + _readsActiveAccount(); + _executesSwap({ + tokenIn: asset, + tokenOut: vault, + callData: abi.encodeCall(IERC4626.mint, (1000, creditAccount)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.mint(1000, address(0)); + + assertEq(tokensToEnable, sharesMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[TV-6]: `withdraw` works as expected + function test_U_TV_06_withdraw_works_as_expected() public { + _readsActiveAccount(); + _executesSwap({ + tokenIn: vault, + tokenOut: asset, + callData: abi.encodeCall(IERC4626.withdraw, (1000, creditAccount, creditAccount)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdraw(1000, address(0), address(0)); + + assertEq(tokensToEnable, assetMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[TV-7]: `redeem` works as expected + function test_U_TV_07_redeem_works_as_expected() public { + _readsActiveAccount(); + _executesSwap({ + tokenIn: vault, + tokenOut: asset, + callData: abi.encodeCall(IERC4626.redeem, (1000, creditAccount, creditAccount)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.redeem(1000, address(0), address(0)); + + assertEq(tokensToEnable, assetMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[TV-8]: `redeemAll` works as expected + function test_U_TV_08_redeemAll_works_as_expected() public { + deal({token: vault, to: creditAccount, give: 1000}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: vault, + tokenOut: asset, + callData: abi.encodeCall(IERC4626.redeem, (999, creditAccount, creditAccount)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.redeemAll(); + + assertEq(tokensToEnable, assetMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, sharesMask, "Incorrect tokensToDisable"); + } +} diff --git a/contracts/test/unit/adapters/lido/LidoV1Adapter.t.sol b/contracts/test/unit/adapters/lido/LidoV1Adapter.t.sol deleted file mode 100644 index 305b301b..00000000 --- a/contracts/test/unit/adapters/lido/LidoV1Adapter.t.sol +++ /dev/null @@ -1,176 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import "@gearbox-protocol/core-v3/contracts/interfaces/IAddressProviderV3.sol"; -import {LidoV1Adapter} from "../../../../adapters/lido/LidoV1.sol"; -import {LidoV1Gateway} from "../../../../helpers/lido/LidoV1_WETHGateway.sol"; -import {LidoMock, ILidoMockEvents} from "../../../mocks/integrations/LidoMock.sol"; - -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; - -// TEST -import "../../../lib/constants.sol"; - -import {AdapterTestHelper} from "../AdapterTestHelper.sol"; - -// EXCEPTIONS - -import "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol"; - -uint256 constant STETH_POOLED_ETH = 2 * WAD; -uint256 constant STETH_TOTAL_SHARES = WAD; - -/// @title LidoV1AdapterTest -/// @notice Designed for unit test purposes only -contract LidoV1AdapterTest is Test, AdapterTestHelper, ILidoMockEvents { - LidoMock public lidoV1Mock; - LidoV1Gateway public lidoV1Gateway; - LidoV1Adapter public lidoV1Adapter; - - address treasury; - - function setUp() public { - _setUp(Tokens.WETH); - - lidoV1Mock = LidoMock(payable(tokenTestSuite.addressOf(Tokens.STETH))); - - lidoV1Gateway = new LidoV1Gateway( - tokenTestSuite.addressOf(Tokens.WETH), - address(lidoV1Mock) - ); - - lidoV1Adapter = new LidoV1Adapter( - address(creditManager), - address(lidoV1Gateway) - ); - - treasury = lidoV1Adapter.treasury(); - - vm.prank(CONFIGURATOR); - creditConfigurator.allowAdapter(address(lidoV1Adapter)); - - treasury = addressProvider.getAddressOrRevert(AP_TREASURY, NO_VERSION_CONTROL); - - lidoV1Mock.syncExchangeRate(STETH_POOLED_ETH, STETH_TOTAL_SHARES); - - tokenTestSuite.approve(Tokens.WETH, USER, address(creditManager)); - - vm.label(address(lidoV1Adapter), "ADAPTER_LIDO"); - vm.label(address(lidoV1Gateway), "GATEWAY_LIDO"); - vm.label(address(lidoV1Mock), "LIDO_MOCK"); - } - - /// - /// - /// TESTS - /// - /// - - /// @dev [LDOV1-1]: Constructor sets correct parameters - function test_LDOV1_01_constructor_sets_correct_params() public { - assertEq(lidoV1Adapter.stETH(), address(lidoV1Mock), "stETH address incorrect"); - assertEq( - lidoV1Adapter.stETHTokenMask(), - creditManager.getTokenMaskOrRevert(address(lidoV1Mock)), - "stETH token mask incorrect" - ); - - assertEq(lidoV1Adapter.weth(), tokenTestSuite.addressOf(Tokens.WETH), "WETH address incorrect"); - assertEq( - lidoV1Adapter.wethTokenMask(), - creditManager.getTokenMaskOrRevert(tokenTestSuite.addressOf(Tokens.WETH)), - "WETH token mask incorrect" - ); - - assertEq( - lidoV1Adapter.treasury(), - addressProvider.getAddressOrRevert(AP_TREASURY, NO_VERSION_CONTROL), - "Treasury address incorrect" - ); - } - - // /// @dev [LDOV1-2]: submit and submitAll reverts if user has no account - // function test_LDOV1_02_submit_and_submitAll_reverts_if_user_has_no_account() public { - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall(creditAccount, address(lidoV1Adapter), abi.encodeCall(lidoV1Adapter.submit, (2 * WAD))); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall(creditAccount, address(lidoV1Adapter), abi.encodeCall(lidoV1Adapter.submitAll, ())); - // } - - /// @dev [LDOV1-3]: Submit works correctly and fires events - function test_LDOV1_03_submit_works_correctly() public { - setUp(); - (address creditAccount, uint256 initialWETHamount) = _openTestCreditAccount(); - - expectAllowance(Tokens.WETH, creditAccount, address(lidoV1Gateway), 0); - - bytes memory expectedCallData = abi.encodeCall(LidoV1Gateway.submit, (2 * WAD, DUMB_ADDRESS)); - - expectMulticallStackCalls( - address(lidoV1Adapter), - address(lidoV1Gateway), - USER, - expectedCallData, - tokenTestSuite.addressOf(Tokens.WETH), - tokenTestSuite.addressOf(Tokens.STETH), - true - ); - - executeOneLineMulticall(creditAccount, address(lidoV1Adapter), abi.encodeCall(LidoV1Adapter.submit, (2 * WAD))); - - expectBalance(Tokens.WETH, creditAccount, initialWETHamount - 2 * WAD); - - uint256 stETHExpectedSharesGateway = (2 * WAD * STETH_TOTAL_SHARES) / STETH_POOLED_ETH; - uint256 stETHExpectedBalanceGateway = lidoV1Mock.getPooledEthByShares(stETHExpectedSharesGateway); - - uint256 stETHExpectedSharesAfterGatewayTransfer = lidoV1Mock.getSharesByPooledEth(stETHExpectedBalanceGateway); - uint256 stETHExpectedBalance = lidoV1Mock.getPooledEthByShares(stETHExpectedSharesAfterGatewayTransfer); - - expectBalance(Tokens.STETH, creditAccount, stETHExpectedBalance); - expectEthBalance(address(lidoV1Mock), 2 * WAD); - expectAllowance(Tokens.WETH, creditAccount, address(lidoV1Gateway), 1); - expectTokenIsEnabled(creditAccount, Tokens.STETH, true); - } - - /// @dev [LDOV1-4]: submitAll works correctly and fires events - function test_LDOV1_04_submitAll_works_correctly() public { - setUp(); - - (address creditAccount, uint256 initialWETHamount) = _openTestCreditAccount(); - - expectAllowance(Tokens.WETH, creditAccount, address(lidoV1Gateway), 0); - - bytes memory expectedCallData = abi.encodeCall(LidoV1Gateway.submit, (initialWETHamount - 1, DUMB_ADDRESS)); - - expectMulticallStackCalls( - address(lidoV1Adapter), - address(lidoV1Gateway), - USER, - expectedCallData, - tokenTestSuite.addressOf(Tokens.WETH), - tokenTestSuite.addressOf(Tokens.STETH), - true - ); - - executeOneLineMulticall(creditAccount, address(lidoV1Adapter), abi.encodeCall(LidoV1Adapter.submitAll, ())); - - expectBalance(Tokens.WETH, creditAccount, 1); - - // Have to account for stETH precision errors - - uint256 stETHExpectedSharesGateway = ((initialWETHamount - 1) * STETH_TOTAL_SHARES) / STETH_POOLED_ETH; - uint256 stETHExpectedBalanceGateway = lidoV1Mock.getPooledEthByShares(stETHExpectedSharesGateway); - - uint256 stETHExpectedSharesAfterGatewayTransfer = lidoV1Mock.getSharesByPooledEth(stETHExpectedBalanceGateway); - uint256 stETHExpectedBalance = lidoV1Mock.getPooledEthByShares(stETHExpectedSharesAfterGatewayTransfer); - - expectBalance(Tokens.STETH, creditAccount, stETHExpectedBalance); - expectEthBalance(address(lidoV1Mock), initialWETHamount - 1); - expectAllowance(Tokens.WETH, creditAccount, address(lidoV1Gateway), 1); - expectTokenIsEnabled(creditAccount, Tokens.WETH, false); - expectTokenIsEnabled(creditAccount, Tokens.STETH, true); - } -} diff --git a/contracts/test/unit/adapters/lido/LidoV1Adapter.unit.t.sol b/contracts/test/unit/adapters/lido/LidoV1Adapter.unit.t.sol new file mode 100644 index 00000000..5943e736 --- /dev/null +++ b/contracts/test/unit/adapters/lido/LidoV1Adapter.unit.t.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {AP_TREASURY} from "@gearbox-protocol/core-v3/contracts/interfaces/IAddressProviderV3.sol"; +import {LidoV1Adapter} from "../../../../adapters/lido/LidoV1.sol"; +import {LidoV1Gateway} from "../../../../helpers/lido/LidoV1_WETHGateway.sol"; +import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; + +/// @title Lido v1 adapter unit test +/// @notice U:[LDO1]: Unit tests for Lido v1 adapter +contract LidoV1AdapterUnitTest is AdapterUnitTestHelper { + LidoV1Adapter adapter; + + address gateway; + address treasury; + + address weth; + address stETH; + + uint256 wethMask; + uint256 stETHMask; + + function setUp() public { + _setUp(); + + gateway = makeAddr("LIDO_GATEWAY"); + treasury = makeAddr("TREASURY"); + + vm.prank(configurator); + addressProvider.setAddress(AP_TREASURY, treasury, false); + + (weth, wethMask) = (tokens[0], 1); + (stETH, stETHMask) = (tokens[1], 2); + + vm.mockCall(gateway, abi.encodeWithSignature("weth()"), abi.encode(weth)); + vm.mockCall(gateway, abi.encodeWithSignature("stETH()"), abi.encode(stETH)); + + adapter = new LidoV1Adapter(address(creditManager), gateway); + } + + /// @notice U:[LDO1-1]: Constructor works as expected + function test_U_LDO1_01_constructor_works_as_expected() public { + _readsTokenMask(weth); + _readsTokenMask(stETH); + adapter = new LidoV1Adapter(address(creditManager), gateway); + + assertEq(adapter.creditManager(), address(creditManager), "Incorrect creditManager"); + assertEq(adapter.addressProvider(), address(addressProvider), "Incorrect addressProvider"); + assertEq(adapter.targetContract(), gateway, "Incorrect targetContract"); + assertEq(adapter.weth(), weth, "Incorrect weth"); + assertEq(adapter.stETH(), stETH, "Incorrect stETH"); + assertEq(adapter.wethTokenMask(), wethMask, "Incorrect wethMask"); + assertEq(adapter.stETHTokenMask(), stETHMask, "Incorrect stETHMask"); + assertEq(adapter.treasury(), treasury, "Incorrect treasury"); + } + + /// @notice U:[LDO1-2]: Wrapper functions revert on wrong caller + function test_U_LDO1_02_wrapper_functions_revert_on_wrong_caller() public { + _revertsOnNonFacadeCaller(); + adapter.submit(0); + + _revertsOnNonFacadeCaller(); + adapter.submitAll(); + } + + /// @notice U:[LDO1-3]: `submit` works as expected + function test_U_LDO1_03_submit_works_as_expected() public { + _executesSwap({ + tokenIn: weth, + tokenOut: stETH, + callData: abi.encodeCall(LidoV1Gateway.submit, (1000, treasury)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.submit(1000); + + assertEq(tokensToEnable, stETHMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[LDO1-4]: `submitAll` works as expected + function test_U_LDO1_04_submitAll_works_as_expected() public { + deal({token: weth, to: creditAccount, give: 1000}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: weth, + tokenOut: stETH, + callData: abi.encodeCall(LidoV1Gateway.submit, (999, treasury)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.submitAll(); + + assertEq(tokensToEnable, stETHMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, wethMask, "Incorrect tokensToDisable"); + } +} diff --git a/contracts/test/unit/adapters/lido/WstETHV1Adapter.t.sol b/contracts/test/unit/adapters/lido/WstETHV1Adapter.t.sol deleted file mode 100644 index d596fdb5..00000000 --- a/contracts/test/unit/adapters/lido/WstETHV1Adapter.t.sol +++ /dev/null @@ -1,273 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {WstETHV1Adapter} from "../../../../adapters/lido/WstETHV1.sol"; -import {WstETHV1Mock} from "../../../mocks/integrations/WstETHV1Mock.sol"; -import {WstETHPriceFeed} from "@gearbox-protocol/oracles-v3/contracts/oracles/lido/WstETHPriceFeed.sol"; -import {IwstETH} from "../../../../integrations/lido/IwstETH.sol"; - -// TEST -import "../../../lib/constants.sol"; -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; - -import {AdapterTestHelper} from "../AdapterTestHelper.sol"; -import {ERC20Mock} from "@gearbox-protocol/core-v3/contracts/test/mocks/token/ERC20Mock.sol"; - -import "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol"; - -uint256 constant STETH_PER_TOKEN = (110 * WAD) / 100; - -/// @title WstETHV1AdapterTest -/// @notice Designed for unit test purposes only -contract WstETHV1AdapterTest is Test, AdapterTestHelper { - // using StringUtils for string; - - WstETHV1Adapter public adapter; - WstETHV1Mock public wstETHMock; - address public stETH; - - function setUp() public { - _setUp(Tokens.STETH); - - stETH = tokenTestSuite.addressOf(Tokens.STETH); - - wstETHMock = new WstETHV1Mock(stETH); - wstETHMock.setStEthPerToken(STETH_PER_TOKEN); - - vm.startPrank(CONFIGURATOR); - - priceOracle.setPriceFeed( - address(wstETHMock), - address( - new WstETHPriceFeed( - address(addressProvider), - address(wstETHMock), - priceOracle.priceFeeds(stETH), - 48 hours - ) - ), - 0 - ); - - creditConfigurator.addCollateralToken(address(wstETHMock), 8300); - - adapter = new WstETHV1Adapter( - address(creditManager), - address(wstETHMock) - ); - - creditConfigurator.allowAdapter(address(adapter)); - - vm.stopPrank(); - tokenTestSuite.mint(Tokens.WETH, USER, 10 * WETH_ACCOUNT_AMOUNT); - - vm.label(address(adapter), "ADAPTER"); - vm.label(address(stETH), "STETH"); - vm.label(address(wstETHMock), "WSTETH_MOCK"); - } - - // - // HELPERS - // - function _openStETHTestCreditAccount(bool wrap) internal returns (address creditAccount, uint256 amount) { - uint256 initialStETHbalance; - (creditAccount, initialStETHbalance) = _openTestCreditAccount(); - - if (wrap) { - executeOneLineMulticall(creditAccount, address(adapter), abi.encodeCall(adapter.wrapAll, ())); - - amount = (((initialStETHbalance - 1) * WAD) / STETH_PER_TOKEN); - expectBalance(Tokens.STETH, creditAccount, 1); - expectBalance(address(wstETHMock), creditAccount, amount); - - expectTokenIsEnabled(creditAccount, Tokens.STETH, false); - expectTokenIsEnabled(creditAccount, address(wstETHMock), true); - } else { - amount = initialStETHbalance; - - expectBalance(Tokens.STETH, creditAccount, initialStETHbalance); - expectBalance(address(wstETHMock), creditAccount, 0); - - expectTokenIsEnabled(creditAccount, Tokens.STETH, true); - expectTokenIsEnabled(creditAccount, address(wstETHMock), false); - } - } - - /// - /// - /// TESTS - /// - /// - - /// @dev [AWSTV1-1]: constructor sets correct values - function test_AWSTV1_01_constructor_sets_correct_values() public { - assertEq(address(adapter.stETH()), tokenTestSuite.addressOf(Tokens.STETH), "Incorrect token"); - assertEq( - adapter.stETHTokenMask(), - creditManager.getTokenMaskOrRevert(tokenTestSuite.addressOf(Tokens.STETH)), - "Incorrect stETH mask" - ); - assertEq( - adapter.wstETHTokenMask(), creditManager.getTokenMaskOrRevert(address(wstETHMock)), "Incorrect wstETH mask" - ); - } - - /// @dev [AWSTV1-2]: constructor reverts if token is not allowed - function test_AWSTV1_02_constructor_reverts_if_token_is_not_allowed() public { - ERC20Mock forbiddenToken = new ERC20Mock("Forbid", "FBD", 18); - - WstETHV1Mock forbidWstETHV1Mock = new WstETHV1Mock( - address(forbiddenToken) - ); - - vm.expectRevert(TokenNotAllowedException.selector); - new WstETHV1Adapter( - address(creditManager), - address(forbidWstETHV1Mock) - ); - - WstETHV1Mock notAllowedWstETHV1Mock = new WstETHV1Mock( - tokenTestSuite.addressOf(Tokens.STETH) - ); - vm.expectRevert(TokenNotAllowedException.selector); - new WstETHV1Adapter( - address(creditManager), - address(notAllowedWstETHV1Mock) - ); - } - - // /// @dev [AWSTV1-3]: wrap and unwrap reverts if user has no account - // function test_AWSTV1_03_wrap_and_unwrap_if_user_has_no_account() public { - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall(creditAccount, address(adapter), abi.encodeCall(adapter.wrapAll, ())); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall(creditAccount, address(adapter), abi.encodeCall(adapter.wrap, (1000))); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall(creditAccount, address(adapter), abi.encodeCall(adapter.unwrapAll, ())); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall(creditAccount, address(adapter), abi.encodeCall(adapter.unwrap, (1000))); - // } - - // - // WRAP - // - - /// @dev [AWSTV1-4]: wrapAll works for user as expected - function test_AWSTV1_04_wrapAll_works_for_user_as_expected() public { - setUp(); - (address creditAccount, uint256 initialStETHbalance) = _openStETHTestCreditAccount(false); - - expectAllowance(Tokens.STETH, creditAccount, address(wstETHMock), 0); - - bytes memory expectedCallData = abi.encodeCall(IwstETH.wrap, (initialStETHbalance - 1)); - - expectMulticallStackCalls( - address(adapter), address(wstETHMock), USER, expectedCallData, stETH, address(wstETHMock), true - ); - - executeOneLineMulticall(creditAccount, address(adapter), abi.encodeCall(WstETHV1Adapter.wrapAll, ())); - - expectBalance(Tokens.STETH, creditAccount, 1); - - expectBalance(address(wstETHMock), creditAccount, ((initialStETHbalance - 1) * WAD) / STETH_PER_TOKEN); - - expectAllowance(Tokens.STETH, creditAccount, address(wstETHMock), 1); - - expectTokenIsEnabled(creditAccount, Tokens.STETH, false); - expectTokenIsEnabled(creditAccount, address(wstETHMock), true); - - expectTokenIsEnabled(creditAccount, address(wstETHMock), true); - } - - /// @dev [AWSTV1-5]: wrap works for user as expected - function test_AWSTV1_05_wrap_uint256_works_for_user_as_expected() public { - setUp(); - (address creditAccount, uint256 initialStETHbalance) = _openStETHTestCreditAccount(false); - - expectAllowance(Tokens.STETH, creditAccount, address(wstETHMock), 0); - - bytes memory expectedCallData = abi.encodeCall(IwstETH.wrap, (WETH_EXCHANGE_AMOUNT)); - - expectMulticallStackCalls( - address(adapter), address(wstETHMock), USER, expectedCallData, stETH, address(wstETHMock), true - ); - - executeOneLineMulticall(creditAccount, address(adapter), expectedCallData); - - expectBalance(Tokens.STETH, creditAccount, initialStETHbalance - WETH_EXCHANGE_AMOUNT); - - expectBalance(address(wstETHMock), creditAccount, (WETH_EXCHANGE_AMOUNT * WAD) / STETH_PER_TOKEN); - - expectAllowance(Tokens.STETH, creditAccount, address(wstETHMock), 1); - - expectTokenIsEnabled(creditAccount, address(wstETHMock), true); - - expectTokenIsEnabled(creditAccount, address(wstETHMock), true); - } - - // - // UNWRAP - // - - /// @dev [AWSTV1-6]: unwrapAll works for user as expected - function test_AWSTV1_06_unwrapAll_works_for_user_as_expected() public { - setUp(); - - (address creditAccount, uint256 initialWstETHamount) = _openStETHTestCreditAccount(true); - - bytes memory expectedCallData = abi.encodeCall(IwstETH.unwrap, (initialWstETHamount - 1)); - - expectMulticallStackCalls( - address(adapter), address(wstETHMock), USER, expectedCallData, stETH, address(wstETHMock), false - ); - - executeOneLineMulticall(creditAccount, address(adapter), abi.encodeCall(WstETHV1Adapter.unwrapAll, ())); - - expectBalance(Tokens.STETH, creditAccount, ((initialWstETHamount - 1) * STETH_PER_TOKEN) / WAD + 1); - - expectBalance(address(wstETHMock), creditAccount, 1); - - // There is not need to approve wstETH to itself, so nothing in terms of allowance should be done - expectAllowance(address(wstETHMock), creditAccount, address(wstETHMock), 0); - - expectTokenIsEnabled(creditAccount, address(wstETHMock), false); - expectTokenIsEnabled(creditAccount, Tokens.STETH, true); - - expectTokenIsEnabled(creditAccount, address(wstETHMock), true); - } - - /// @dev [AWSTV1-7]: unwrap works for user as expected - function test_AWSTV1_07_unwrap_uint256_works_for_user_as_expected() public { - setUp(); - (address creditAccount, uint256 wstETHamount) = _openStETHTestCreditAccount(true); - - expectAllowance(address(wstETHMock), creditAccount, address(wstETHMock), 0); - - uint256 amount = wstETHamount / 2; - - bytes memory expectedCallData = abi.encodeCall(IwstETH.unwrap, (amount)); - - expectMulticallStackCalls( - address(adapter), address(wstETHMock), USER, expectedCallData, stETH, address(wstETHMock), false - ); - - executeOneLineMulticall(creditAccount, address(adapter), expectedCallData); - - expectBalance(Tokens.STETH, creditAccount, ((amount) * STETH_PER_TOKEN) / WAD + 1); - - // +1 cause it keeps from deposit there - expectBalance(address(wstETHMock), creditAccount, wstETHamount - amount); - - // There is not need to approve wstETH to itself, so nothing in terms of allowance should be done - expectAllowance(address(wstETHMock), creditAccount, address(wstETHMock), 0); - - expectTokenIsEnabled(creditAccount, Tokens.STETH, true); - - expectTokenIsEnabled(creditAccount, address(wstETHMock), true); - } -} diff --git a/contracts/test/unit/adapters/lido/WstETHV1Adapter.unit.t.sol b/contracts/test/unit/adapters/lido/WstETHV1Adapter.unit.t.sol new file mode 100644 index 00000000..86fe4d20 --- /dev/null +++ b/contracts/test/unit/adapters/lido/WstETHV1Adapter.unit.t.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {WstETHV1Adapter} from "../../../../adapters/lido/WstETHV1.sol"; +import {IwstETH, IwstETHGetters} from "../../../../integrations/lido/IwstETH.sol"; +import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; + +/// @title wstETH v1 adapter unit test +/// @notice U:[LDO1W]: Unit tests for wstETH v1 adapter +contract WstETHV1AdapterUnitTest is AdapterUnitTestHelper { + WstETHV1Adapter adapter; + + address stETH; + address wstETH; + + uint256 stETHMask; + uint256 wstETHMask; + + function setUp() public { + _setUp(); + + (stETH, stETHMask) = (tokens[0], 1); + (wstETH, wstETHMask) = (tokens[1], 2); + vm.mockCall(wstETH, abi.encodeCall(IwstETHGetters.stETH, ()), abi.encode(stETH)); + + adapter = new WstETHV1Adapter(address(creditManager), wstETH); + } + + /// @notice U:[LDO1W-1]: Constructor works as expected + function test_U_LDO1W_01_constructor_works_as_expected() public { + _readsTokenMask(stETH); + _readsTokenMask(wstETH); + adapter = new WstETHV1Adapter(address(creditManager), wstETH); + + assertEq(adapter.creditManager(), address(creditManager), "Incorrect creditManager"); + assertEq(adapter.addressProvider(), address(addressProvider), "Incorrect addressProvider"); + assertEq(adapter.targetContract(), wstETH, "Incorrect targetContract"); + assertEq(adapter.stETH(), stETH, "Incorrect stETH"); + assertEq(adapter.stETHTokenMask(), stETHMask, "Incorrect stETHMask"); + assertEq(adapter.wstETHTokenMask(), wstETHMask, "Incorrect wstETHMask"); + } + + /// @notice U:[LDO1W-2]: Wrapper functions revert on wrong caller + function test_U_LDO1W_02_wrapper_functions_revert_on_wrong_caller() public { + _revertsOnNonFacadeCaller(); + adapter.wrap(0); + + _revertsOnNonFacadeCaller(); + adapter.wrapAll(); + + _revertsOnNonFacadeCaller(); + adapter.unwrap(0); + + _revertsOnNonFacadeCaller(); + adapter.unwrapAll(); + } + + /// @notice U:[LDO1W-3]: `wrap` works as expected + function test_U_LDO1W_03_wrap_works_as_expected() public { + _executesSwap({ + tokenIn: stETH, + tokenOut: wstETH, + callData: abi.encodeCall(IwstETH.wrap, (1000)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.wrap(1000); + + assertEq(tokensToEnable, wstETHMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[LDO1W-4]: `wrapAll` works as expected + function test_U_LDO1W_04_wrapAll_works_as_expected() public { + deal({token: stETH, to: creditAccount, give: 1000}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: stETH, + tokenOut: wstETH, + callData: abi.encodeCall(IwstETH.wrap, (999)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.wrapAll(); + + assertEq(tokensToEnable, wstETHMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, stETHMask, "Incorrect tokensToDisable"); + } + + /// @notice U:[LDO1W-5]: `unwrap` works as expected + function test_U_LDO1W_05_unwrap_works_as_expected() public { + _executesSwap({ + tokenIn: wstETH, + tokenOut: stETH, + callData: abi.encodeCall(IwstETH.unwrap, (1000)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.unwrap(1000); + + assertEq(tokensToEnable, stETHMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[LDO1W-6]: `unwrapAll` works as expected + function test_U_LDO1W_06_unwrapAll_works_as_expected() public { + deal({token: wstETH, to: creditAccount, give: 1000}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: wstETH, + tokenOut: stETH, + callData: abi.encodeCall(IwstETH.unwrap, (999)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.unwrapAll(); + + assertEq(tokensToEnable, stETHMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, wstETHMask, "Incorrect tokensToDisable"); + } +} diff --git a/contracts/test/unit/adapters/uniswap/UniswapV2Adapter.harness.sol b/contracts/test/unit/adapters/uniswap/UniswapV2Adapter.harness.sol new file mode 100644 index 00000000..e186dd10 --- /dev/null +++ b/contracts/test/unit/adapters/uniswap/UniswapV2Adapter.harness.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {UniswapV2Adapter} from "../../../../adapters/uniswap/UniswapV2.sol"; + +contract UniswapV2AdapterHarness is UniswapV2Adapter { + constructor(address creditManager, address router) UniswapV2Adapter(creditManager, router) {} + + function validatePath(address[] memory path) + external + view + returns (bool valid, address tokenIn, address tokenOut) + { + (valid, tokenIn, tokenOut) = _validatePath(path); + } +} diff --git a/contracts/test/unit/adapters/uniswap/UniswapV2Adapter.t.sol b/contracts/test/unit/adapters/uniswap/UniswapV2Adapter.t.sol deleted file mode 100644 index 9ec404ae..00000000 --- a/contracts/test/unit/adapters/uniswap/UniswapV2Adapter.t.sol +++ /dev/null @@ -1,315 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {IUniswapV2Router02} from "../../../../integrations/uniswap/IUniswapV2Router02.sol"; -import {UniswapV2Adapter} from "../../../../adapters/uniswap/UniswapV2.sol"; -import {IUniswapV2Adapter, IUniswapV2AdapterExceptions} from "../../../../interfaces/uniswap/IUniswapV2Adapter.sol"; -import {UniswapV2Mock} from "../../../mocks/integrations/UniswapV2Mock.sol"; - -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; - -// TEST -import "../../../lib/constants.sol"; -import {AdapterTestHelper} from "../AdapterTestHelper.sol"; - -/// @title UniswapV2AdapterTest -/// @notice Designed for unit test purposes only -contract UniswapV2AdapterTest is AdapterTestHelper, IUniswapV2AdapterExceptions { - IUniswapV2Adapter public adapter; - UniswapV2Mock public uniswapMock; - uint256 public deadline; - - function setUp() public { - _setUp(); - - uniswapMock = new UniswapV2Mock(); - - uniswapMock.setRate( - tokenTestSuite.addressOf(Tokens.DAI), tokenTestSuite.addressOf(Tokens.WETH), RAY / DAI_WETH_RATE - ); - - uniswapMock.setRate(tokenTestSuite.addressOf(Tokens.DAI), tokenTestSuite.addressOf(Tokens.USDC), RAY); - - uniswapMock.setRate(tokenTestSuite.addressOf(Tokens.USDC), tokenTestSuite.addressOf(Tokens.USDT), RAY); - - uniswapMock.setRate( - tokenTestSuite.addressOf(Tokens.USDT), tokenTestSuite.addressOf(Tokens.WETH), RAY / DAI_WETH_RATE - ); - - tokenTestSuite.mint(Tokens.WETH, address(uniswapMock), (2 * DAI_ACCOUNT_AMOUNT) / DAI_WETH_RATE); - - adapter = new UniswapV2Adapter(address(creditManager), address(uniswapMock)); - - vm.prank(CONFIGURATOR); - creditConfigurator.allowAdapter(address(adapter)); - - vm.label(address(adapter), "ADAPTER"); - vm.label(address(uniswapMock), "UNISWAP_MOCK"); - - deadline = _getUniswapDeadline(); - } - - /// - /// - /// TESTS - /// - /// - - // /// @dev [AUV2-1]: swap reverts if uses has no account - // function test_AUV2_01_swap_reverts_if_user_has_no_account() public { - // address[] memory dumbPath; - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall( - // creditAccount, - // address(adapter), - // abi.encodeCall(adapter.swapTokensForExactTokens, (0, 0, dumbPath, address(0), 0)) - // ); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall( - // creditAccount, - // address(adapter), - // abi.encodeCall(adapter.swapExactTokensForTokens, (0, 0, dumbPath, address(0), 0)) - // ); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall( - // creditAccount, address(adapter), abi.encodeCall(adapter.swapAllTokensForTokens, (0, dumbPath, 0)) - // ); - // } - - /// @dev [AUV2-2]: swapTokensForExactTokens works for user as expected - function test_AUV2_02_swapTokensForExactTokens_works_for_user_as_expected() public { - setUp(); - - address[] memory path = new address[](2); - path[0] = creditManager.underlying(); - path[1] = tokenTestSuite.addressOf(Tokens.WETH); - - (address creditAccount, uint256 initialDAIbalance) = _openTestCreditAccount(); - - expectAllowance(path[0], creditAccount, address(uniswapMock), 0); - - bytes memory expectedCallData = abi.encodeCall( - IUniswapV2Router02.swapTokensForExactTokens, - ( - DAI_EXCHANGE_AMOUNT / DAI_WETH_RATE / 2, - DAI_EXCHANGE_AMOUNT, - path, - address(creditAccount), // adapter should change recepient from address(0) to creditAccount - deadline - ) - ); - - expectMulticallStackCalls( - address(adapter), address(uniswapMock), USER, expectedCallData, path[0], path[1], true - ); - - // MULTICALL - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeCall( - adapter.swapTokensForExactTokens, - ( - DAI_EXCHANGE_AMOUNT / DAI_WETH_RATE / 2, - DAI_EXCHANGE_AMOUNT, - path, - address(0), // adapter should change recepient from address(0) to creditAccount - deadline - ) - ) - ); - - expectBalance(Tokens.DAI, creditAccount, initialDAIbalance - ((DAI_EXCHANGE_AMOUNT / 2) * 1000) / 997); - - expectBalance(Tokens.WETH, creditAccount, DAI_EXCHANGE_AMOUNT / DAI_WETH_RATE / 2); - - expectAllowance(creditManager.underlying(), creditAccount, address(uniswapMock), 1); - - expectTokenIsEnabled(creditAccount, Tokens.WETH, true); - } - - /// @dev [AUV2-3]: swapExactTokensForTokens works for user as expected - function test_AUV2_03_swapExactTokensForTokens_works_for_user_as_expected() public { - setUp(); - address[] memory path = new address[](2); - path[0] = creditManager.underlying(); - path[1] = tokenTestSuite.addressOf(Tokens.WETH); - - (address creditAccount, uint256 initialDAIbalance) = _openTestCreditAccount(); - - expectAllowance(path[0], creditAccount, address(uniswapMock), 0); - - bytes memory expectedCallData = abi.encodeCall( - IUniswapV2Router02.swapExactTokensForTokens, - ( - DAI_EXCHANGE_AMOUNT, - 0, - path, - address(creditAccount), // adapter should change recepient from address(0) to creditAccount - deadline - ) - ); - - expectMulticallStackCalls( - address(adapter), address(uniswapMock), USER, expectedCallData, path[0], path[1], true - ); - - // MULTICALL - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeCall( - adapter.swapExactTokensForTokens, - ( - DAI_EXCHANGE_AMOUNT, - 0, - path, - address(0), // adapter should change recepient from address(0) to creditAccount - deadline - ) - ) - ); - - expectBalance(Tokens.DAI, creditAccount, initialDAIbalance - DAI_EXCHANGE_AMOUNT); - - expectBalance(Tokens.WETH, creditAccount, ((DAI_EXCHANGE_AMOUNT / DAI_WETH_RATE) * 997) / 1000); - - expectAllowance(path[0], creditAccount, address(uniswapMock), 1); - - expectTokenIsEnabled(creditAccount, path[1], true); - } - - /// @dev [AUV2-4]: swapAllTokensForTokens works for user as expected - function test_AUV2_04_swapAllTokensForTokens_works_for_user_as_expected() public { - setUp(); - address[] memory path = new address[](2); - path[0] = creditManager.underlying(); - path[1] = tokenTestSuite.addressOf(Tokens.WETH); - - (address creditAccount, uint256 initialDAIbalance) = _openTestCreditAccount(); - - expectAllowance(path[0], creditAccount, address(uniswapMock), 0); - - bytes memory expectedCallData = abi.encodeCall( - IUniswapV2Router02.swapExactTokensForTokens, - ( - initialDAIbalance - 1, - (((initialDAIbalance - 1) / DAI_WETH_RATE) * 997) / 1000, - path, - address(creditAccount), - deadline - ) - ); - - expectMulticallStackCalls( - address(adapter), address(uniswapMock), USER, expectedCallData, path[0], path[1], true - ); - - // MULTICALL - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeCall( - IUniswapV2Adapter.swapAllTokensForTokens, - (((RAY / DAI_WETH_RATE) * 997) / 1000, path, _getUniswapDeadline()) - ) - ); - - expectBalance(Tokens.DAI, creditAccount, 1); - - expectBalance(Tokens.WETH, creditAccount, (((initialDAIbalance - 1) / DAI_WETH_RATE) * 997) / 1000); - - expectAllowance(path[0], creditAccount, address(uniswapMock), 1); - - expectTokenIsEnabled(creditAccount, path[0], false); - expectTokenIsEnabled(creditAccount, path[1], true); - } - - /// @dev [AUV2-5]: Path validity checks are correct - function test_AUV2_05_path_validity_checks_are_correct() public { - (address creditAccount,) = _openTestCreditAccount(); - - address[] memory path = new address[](5); - path[0] = creditManager.underlying(); - path[1] = tokenTestSuite.addressOf(Tokens.USDC); - path[2] = tokenTestSuite.addressOf(Tokens.USDT); - path[3] = tokenTestSuite.addressOf(Tokens.LINK); - path[4] = tokenTestSuite.addressOf(Tokens.WETH); - - vm.expectRevert(InvalidPathException.selector); - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeCall(adapter.swapExactTokensForTokens, (DAI_EXCHANGE_AMOUNT, 0, path, address(0), deadline)) - ); - - vm.expectRevert(InvalidPathException.selector); - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeCall( - adapter.swapTokensForExactTokens, - (DAI_EXCHANGE_AMOUNT / DAI_WETH_RATE / 2, DAI_EXCHANGE_AMOUNT, path, address(0), deadline) - ) - ); - - vm.expectRevert(InvalidPathException.selector); - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeCall( - adapter.swapAllTokensForTokens, (((RAY / DAI_WETH_RATE) * 997) / 1000, path, _getUniswapDeadline()) - ) - ); - - path = new address[](4); - path[0] = creditManager.underlying(); - path[1] = tokenTestSuite.addressOf(Tokens.USDC); - path[2] = tokenTestSuite.addressOf(Tokens.LINK); - path[3] = tokenTestSuite.addressOf(Tokens.WETH); - - vm.expectRevert(InvalidPathException.selector); - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeCall(adapter.swapExactTokensForTokens, (DAI_EXCHANGE_AMOUNT, 0, path, address(0), deadline)) - ); - - vm.expectRevert(InvalidPathException.selector); - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeCall(adapter.swapExactTokensForTokens, (DAI_EXCHANGE_AMOUNT, 0, path, address(0), deadline)) - ); - - path = new address[](4); - path[0] = creditManager.underlying(); - path[1] = tokenTestSuite.addressOf(Tokens.LINK); - path[2] = tokenTestSuite.addressOf(Tokens.USDT); - path[3] = tokenTestSuite.addressOf(Tokens.WETH); - - vm.expectRevert(InvalidPathException.selector); - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeCall(adapter.swapExactTokensForTokens, (DAI_EXCHANGE_AMOUNT, 0, path, address(0), deadline)) - ); - - path = new address[](4); - path[0] = creditManager.underlying(); - path[1] = tokenTestSuite.addressOf(Tokens.USDC); - path[2] = tokenTestSuite.addressOf(Tokens.USDT); - path[3] = tokenTestSuite.addressOf(Tokens.WETH); - - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeCall(adapter.swapExactTokensForTokens, (DAI_EXCHANGE_AMOUNT, 0, path, address(0), deadline)) - ); - } -} diff --git a/contracts/test/unit/adapters/uniswap/UniswapV2Adapter.unit.t.sol b/contracts/test/unit/adapters/uniswap/UniswapV2Adapter.unit.t.sol new file mode 100644 index 00000000..2ca79f35 --- /dev/null +++ b/contracts/test/unit/adapters/uniswap/UniswapV2Adapter.unit.t.sol @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {IUniswapV2Router01} from "../../../../integrations/uniswap/IUniswapV2Router01.sol"; +import { + IUniswapV2AdapterEvents, + IUniswapV2AdapterExceptions, + UniswapV2PairStatus +} from "../../../../interfaces/uniswap/IUniswapV2Adapter.sol"; +import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; +import {UniswapV2AdapterHarness} from "./UniswapV2Adapter.harness.sol"; + +/// @title Uniswap v2 adapter unit test +/// @notice U:[UNI2]: Unit tests for Uniswap v2 swap router adapter +contract UniswapV2AdapterUnitTest is AdapterUnitTestHelper, IUniswapV2AdapterEvents, IUniswapV2AdapterExceptions { + UniswapV2AdapterHarness adapter; + + address router; + + function setUp() public { + _setUp(); + + router = makeAddr("ROUTER"); + adapter = new UniswapV2AdapterHarness(address(creditManager), router); + + _setPairsStatus(3, 7); + } + + /// @notice U:[UNI2-1]: Constructor works as expected + function test_U_UNI2_01_constructor_works_as_expected() public { + assertEq(adapter.creditManager(), address(creditManager), "Incorrect creditManager"); + assertEq(adapter.addressProvider(), address(addressProvider), "Incorrect addressProvider"); + assertEq(adapter.targetContract(), router, "Incorrect targetContract"); + } + + /// @notice U:[UNI2-2]: Wrapper functions revert on wrong caller + function test_U_UNI2_02_wrapper_functions_revert_on_wrong_caller() public { + address[] memory emptyPath; + + _revertsOnNonFacadeCaller(); + adapter.swapTokensForExactTokens(0, 0, emptyPath, address(0), 0); + + _revertsOnNonFacadeCaller(); + adapter.swapExactTokensForTokens(0, 0, emptyPath, address(0), 0); + + _revertsOnNonFacadeCaller(); + adapter.swapAllTokensForTokens(0, emptyPath, 0); + } + + /// @notice U:[UNI2-3]: `swapTokensForExactTokens` works as expected + function test_U_UNI2_03_swapTokensForExactTokens_works_as_expected() public { + address[] memory path = _makePath(0); + vm.expectRevert(InvalidPathException.selector); + vm.prank(creditFacade); + adapter.swapExactTokensForTokens(123, 456, path, address(0), 789); + + path = _makePath(2); + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[0], + tokenOut: tokens[1], + callData: abi.encodeCall(IUniswapV2Router01.swapExactTokensForTokens, (123, 456, path, creditAccount, 789)), + requiresApproval: true, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = + adapter.swapExactTokensForTokens(123, 456, path, address(0), 789); + + assertEq(tokensToEnable, 2, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[UNI2-4]: `swapExactTokensForTokens` works as expected + function test_U_UNI2_04_swapExactTokensForTokens_works_as_expected() public { + address[] memory path = _makePath(0); + vm.expectRevert(InvalidPathException.selector); + vm.prank(creditFacade); + adapter.swapExactTokensForTokens(123, 456, path, address(0), 789); + + path = _makePath(2); + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[0], + tokenOut: tokens[1], + callData: abi.encodeCall(IUniswapV2Router01.swapExactTokensForTokens, (123, 456, path, creditAccount, 789)), + requiresApproval: true, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = + adapter.swapExactTokensForTokens(123, 456, path, address(0), 789); + + assertEq(tokensToEnable, 2, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[UNI2-5]: `swapAllTokensForTokens` works as expected + function test_U_UNI2_05_swapAllTokensForTokens_works_as_expected() public { + deal({token: tokens[0], to: creditAccount, give: 1001}); + + address[] memory path = _makePath(0); + vm.expectRevert(InvalidPathException.selector); + vm.prank(creditFacade); + adapter.swapAllTokensForTokens(0.5e27, path, 789); + + path = _makePath(2); + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[0], + tokenOut: tokens[1], + callData: abi.encodeCall(IUniswapV2Router01.swapExactTokensForTokens, (1000, 500, path, creditAccount, 789)), + requiresApproval: true, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.swapAllTokensForTokens(0.5e27, path, 789); + + assertEq(tokensToEnable, 2, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 1, "Incorrect tokensToDisable"); + } + + /// @notice U:[UNI2-6]: `setPairStatusBatch` works as expected + function test_U_UNI2_06_setPairStatusBatch_works_as_expected() public { + UniswapV2PairStatus[] memory pairs; + + _revertsOnNonConfiguratorCaller(); + adapter.setPairStatusBatch(pairs); + + pairs = new UniswapV2PairStatus[](2); + pairs[0] = UniswapV2PairStatus(tokens[0], tokens[1], false); + pairs[1] = UniswapV2PairStatus(tokens[1], tokens[2], true); + + vm.expectEmit(true, true, false, true); + emit SetPairStatus(_min(tokens[0], tokens[1]), _max(tokens[0], tokens[1]), false); + + vm.expectEmit(true, true, false, true); + emit SetPairStatus(_min(tokens[1], tokens[2]), _max(tokens[1], tokens[2]), true); + + vm.prank(configurator); + adapter.setPairStatusBatch(pairs); + + assertFalse(adapter.isPairAllowed(tokens[0], tokens[1]), "First pair incorrectly allowed"); + assertTrue(adapter.isPairAllowed(tokens[1], tokens[2]), "Second pair incorrectly not allowed"); + } + + /// @notice U:[UNI2-7]: `_validatePath` works as expected + function test_U_UNI2_07_validatePath_works_as_expected() public { + bool isValid; + address tokenIn; + address tokenOut; + address[] memory path; + + // insane paths + (isValid,,) = adapter.validatePath(new address[](0)); + assertFalse(isValid, "Empty path incorrectly valid"); + + (isValid,,) = adapter.validatePath(new address[](1)); + assertFalse(isValid, "Short path incorrectly valid"); + + (isValid,,) = adapter.validatePath(new address[](5)); + assertFalse(isValid, "Long path incorrectly valid"); + + // exhaustive search + for (uint256 pathLen = 2; pathLen <= 4; ++pathLen) { + path = _makePath(pathLen); + + uint256 numCases = 1 << (pathLen - 1); + for (uint256 mask; mask < numCases; ++mask) { + _setPairsStatus(pathLen - 1, mask); + (isValid, tokenIn, tokenOut) = adapter.validatePath(path); + + if (mask == numCases - 1) { + assertTrue(isValid, "Path incorrectly invalid"); + assertEq(tokenIn, tokens[0], "Incorrect tokenIn"); + assertEq(tokenOut, tokens[pathLen - 1], "Incorrect tokenOut"); + } else { + assertFalse(isValid, "Path incorrectly valid"); + } + } + } + } + + // ------- // + // HELPERS // + // ------- // + + /// @dev Returns swap path of `len` consecutive `tokens` + function _makePath(uint256 len) internal view returns (address[] memory path) { + path = new address[](len); + for (uint256 i; i < len; ++i) { + path[i] = tokens[i]; + } + } + + /// @dev Sets statuses for `len` consecutive pairs of `tokens` based on `allowedPairsMask` + function _setPairsStatus(uint256 len, uint256 allowedPairsMask) internal { + UniswapV2PairStatus[] memory pairs = new UniswapV2PairStatus[](len); + for (uint256 i; i < len; ++i) { + uint256 mask = 1 << i; + pairs[i] = UniswapV2PairStatus(tokens[i], tokens[i + 1], allowedPairsMask & mask != 0); + } + vm.prank(configurator); + adapter.setPairStatusBatch(pairs); + } + + /// @dev Returns smaller of two addresses + function _min(address token0, address token1) internal pure returns (address) { + return token0 < token1 ? token0 : token1; + } + + /// @dev Returns larger of two addresses + function _max(address token0, address token1) internal pure returns (address) { + return token0 < token1 ? token1 : token0; + } +} diff --git a/contracts/test/unit/adapters/uniswap/UniswapV3Adapter.harness.sol b/contracts/test/unit/adapters/uniswap/UniswapV3Adapter.harness.sol new file mode 100644 index 00000000..8672d144 --- /dev/null +++ b/contracts/test/unit/adapters/uniswap/UniswapV3Adapter.harness.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {UniswapV3Adapter} from "../../../../adapters/uniswap/UniswapV3.sol"; + +contract UniswapV3AdapterHarness is UniswapV3Adapter { + constructor(address creditManager, address router) UniswapV3Adapter(creditManager, router) {} + + function validatePath(bytes memory path) external view returns (bool valid, address tokenIn, address tokenOut) { + (valid, tokenIn, tokenOut) = _validatePath(path); + } +} diff --git a/contracts/test/unit/adapters/uniswap/UniswapV3Adapter.t.sol b/contracts/test/unit/adapters/uniswap/UniswapV3Adapter.t.sol deleted file mode 100644 index 96b1aa7c..00000000 --- a/contracts/test/unit/adapters/uniswap/UniswapV3Adapter.t.sol +++ /dev/null @@ -1,543 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {BytesLib} from "../../../../integrations/uniswap/BytesLib.sol"; - -import {ISwapRouter} from "../../../../integrations/uniswap/IUniswapV3.sol"; -import {UniswapV3Adapter} from "../../../../adapters/uniswap/UniswapV3.sol"; -import { - IUniswapV3Adapter, - IUniswapV3AdapterExceptions, - IUniswapV3AdapterTypes -} from "../../../../interfaces/uniswap/IUniswapV3Adapter.sol"; -import {UniswapV3Mock} from "../../../mocks/integrations/UniswapV3Mock.sol"; - -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; - -// TEST -import "../../../lib/constants.sol"; - -import {AdapterTestHelper} from "../AdapterTestHelper.sol"; - -/// @title UniswapV3AdapterTest -/// @notice Designed for unit test purposes only -contract UniswapV3AdapterTest is Test, AdapterTestHelper, IUniswapV3AdapterTypes, IUniswapV3AdapterExceptions { - using BytesLib for bytes; - - IUniswapV3Adapter public adapter; - UniswapV3Mock public uniswapMock; - uint256 public deadline; - - function setUp() public { - _setUp(); - uniswapMock = new UniswapV3Mock(); - - uniswapMock.setRate( - tokenTestSuite.addressOf(Tokens.WETH), tokenTestSuite.addressOf(Tokens.DAI), DAI_WETH_RATE * RAY - ); - - uniswapMock.setRate(tokenTestSuite.addressOf(Tokens.DAI), tokenTestSuite.addressOf(Tokens.USDC), RAY); - - uniswapMock.setRate( - tokenTestSuite.addressOf(Tokens.WETH), tokenTestSuite.addressOf(Tokens.USDC), DAI_WETH_RATE * RAY - ); - - uniswapMock.setRate(tokenTestSuite.addressOf(Tokens.USDC), tokenTestSuite.addressOf(Tokens.USDT), RAY); - - uniswapMock.setRate( - tokenTestSuite.addressOf(Tokens.USDT), tokenTestSuite.addressOf(Tokens.WETH), RAY / DAI_WETH_RATE - ); - - tokenTestSuite.mint(Tokens.WETH, address(uniswapMock), (2 * DAI_ACCOUNT_AMOUNT) / DAI_WETH_RATE); - - adapter = new UniswapV3Adapter(address(creditManager), address(uniswapMock)); - - vm.prank(CONFIGURATOR); - creditConfigurator.allowAdapter(address(adapter)); - - vm.label(address(adapter), "ADAPTER"); - vm.label(address(uniswapMock), "UNISWAP_V3_MOCK"); - - deadline = _getUniswapDeadline(); - } - - /// - /// HELPERS - /// - - function _getExactInputSingleParams() internal view returns (ISwapRouter.ExactInputSingleParams memory params) { - params = ISwapRouter.ExactInputSingleParams({ - tokenIn: tokenTestSuite.addressOf(Tokens.DAI), - tokenOut: tokenTestSuite.addressOf(Tokens.WETH), - fee: 3000, - recipient: USER, - deadline: deadline, - amountIn: DAI_EXCHANGE_AMOUNT, - amountOutMinimum: ((DAI_EXCHANGE_AMOUNT / DAI_WETH_RATE) * 997) / 1000, - sqrtPriceLimitX96: 0 - }); - } - - function _getExactInputParams() internal view returns (ISwapRouter.ExactInputParams memory params) { - address tokenIn = tokenTestSuite.addressOf(Tokens.DAI); - address tokenOut = tokenTestSuite.addressOf(Tokens.WETH); - - params = ISwapRouter.ExactInputParams({ - path: bytes(abi.encodePacked(tokenIn)).concat(bytes(abi.encodePacked(uint24(3000)))).concat( - bytes(abi.encodePacked(tokenOut)) - ), - recipient: USER, - deadline: deadline, - amountIn: DAI_EXCHANGE_AMOUNT, - amountOutMinimum: ((DAI_EXCHANGE_AMOUNT / DAI_WETH_RATE) * 997) / 1000 - }); - } - - function _getAllInputSingleParams() - internal - view - returns (IUniswapV3Adapter.ExactAllInputSingleParams memory params) - { - params = ExactAllInputSingleParams({ - tokenIn: tokenTestSuite.addressOf(Tokens.DAI), - tokenOut: tokenTestSuite.addressOf(Tokens.WETH), - fee: 3000, - deadline: deadline, - rateMinRAY: ((RAY / DAI_WETH_RATE) * 997) / 1000, - sqrtPriceLimitX96: 0 - }); - } - - function _getAllInputParams() internal view returns (IUniswapV3Adapter.ExactAllInputParams memory params) { - address tokenIn = tokenTestSuite.addressOf(Tokens.DAI); - address tokenOut = tokenTestSuite.addressOf(Tokens.WETH); - - params = ExactAllInputParams({ - path: bytes(abi.encodePacked(tokenIn)).concat(bytes(abi.encodePacked(uint24(3000)))).concat( - bytes(abi.encodePacked(tokenOut)) - ), - deadline: deadline, - rateMinRAY: ((RAY / DAI_WETH_RATE) * 997) / 1000 - }); - } - - function _getExactOutputSingleParams() internal view returns (ISwapRouter.ExactOutputSingleParams memory params) { - params = ISwapRouter.ExactOutputSingleParams({ - tokenIn: tokenTestSuite.addressOf(Tokens.DAI), - tokenOut: tokenTestSuite.addressOf(Tokens.WETH), - fee: 3000, - recipient: USER, - deadline: deadline, - amountOut: DAI_EXCHANGE_AMOUNT / DAI_WETH_RATE / 2, - amountInMaximum: DAI_EXCHANGE_AMOUNT, - sqrtPriceLimitX96: 0 - }); - } - - function _getExactOutputParams() internal view returns (ISwapRouter.ExactOutputParams memory params) { - address tokenIn = tokenTestSuite.addressOf(Tokens.DAI); - address tokenOut = tokenTestSuite.addressOf(Tokens.WETH); - - params = ISwapRouter.ExactOutputParams({ - path: bytes(abi.encodePacked(tokenOut)).concat(bytes(abi.encodePacked(uint24(3000)))).concat( - bytes(abi.encodePacked(tokenIn)) - ), - recipient: USER, - deadline: deadline, - amountOut: DAI_EXCHANGE_AMOUNT / DAI_WETH_RATE / 2, - amountInMaximum: DAI_EXCHANGE_AMOUNT - }); - } - - /// - /// - /// TESTS - /// - /// - - // /// @dev [AUV3-1]: swap reverts if user has no account - // function test_AUV3_01_swap_reverts_if_user_has_no_account() public { - // ISwapRouter.ExactInputSingleParams memory exactInputSingleParams = _getExactInputSingleParams(); - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall( - // creditAccount, address(adapter), abi.encodeCall(adapter.exactInputSingle, (exactInputSingleParams)) - // ); - - // ISwapRouter.ExactInputParams memory exactInputParams = _getExactInputParams(); - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall(creditAccount, address(adapter), abi.encodeCall(adapter.exactInput, (exactInputParams))); - - // IUniswapV3Adapter.ExactAllInputSingleParams memory exactAllInputSingleParams = _getAllInputSingleParams(); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall( - // creditAccount, address(adapter), abi.encodeCall(adapter.exactAllInputSingle, (exactAllInputSingleParams)) - // ); - - // IUniswapV3Adapter.ExactAllInputParams memory exactAllInputParams = _getAllInputParams(); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall( - // creditAccount, address(adapter), abi.encodeCall(adapter.exactAllInput, (exactAllInputParams)) - // ); - - // ISwapRouter.ExactOutputSingleParams memory exactOutputSingleParams = _getExactOutputSingleParams(); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall( - // creditAccount, address(adapter), abi.encodeCall(adapter.exactOutputSingle, (exactOutputSingleParams)) - // ); - - // ISwapRouter.ExactOutputParams memory exactOutputParams = _getExactOutputParams(); - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall( - // creditAccount, address(adapter), abi.encodeCall(adapter.exactOutput, (exactOutputParams)) - // ); - // } - - // - // USER INTERACTION - // - - /// @dev [AUV3-2]: exactInputSingle works for user as expected - function test_AUV3_02_exactInputSingle_works_for_user_as_expected() public { - setUp(); - - ISwapRouter.ExactInputSingleParams memory exactInputSingleParams = _getExactInputSingleParams(); - - (address creditAccount, uint256 initialDAIbalance) = _openTestCreditAccount(); - - expectAllowance(Tokens.DAI, creditAccount, address(uniswapMock), 0); - - exactInputSingleParams.recipient = creditAccount; - - bytes memory expectedCallData = abi.encodeCall(ISwapRouter.exactInputSingle, (exactInputSingleParams)); - - exactInputSingleParams.recipient = address(0); - - bytes memory callData = abi.encodeCall(adapter.exactInputSingle, (exactInputSingleParams)); - - expectMulticallStackCalls( - address(adapter), - address(uniswapMock), - USER, - expectedCallData, - exactInputSingleParams.tokenIn, - exactInputSingleParams.tokenOut, - true - ); - - // MULTICALL - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.DAI, creditAccount, initialDAIbalance - DAI_EXCHANGE_AMOUNT); - - expectBalance(Tokens.WETH, creditAccount, ((DAI_EXCHANGE_AMOUNT / DAI_WETH_RATE) * 997) / 1000); - - expectAllowance(Tokens.DAI, creditAccount, address(uniswapMock), 1); - - expectTokenIsEnabled(creditAccount, Tokens.WETH, true); - } - - /// @dev [AUV3-3]: exactAllInputSingle works for user as expected - function test_AUV3_03_exactAllInputSingle_works_for_user_as_expected() public { - setUp(); - - IUniswapV3Adapter.ExactAllInputSingleParams memory exactAllInputSingleParams = _getAllInputSingleParams(); - - (address creditAccount, uint256 initialDAIbalance) = _openTestCreditAccount(); - - expectAllowance(Tokens.DAI, creditAccount, address(uniswapMock), 0); - - uint256 amountIn = initialDAIbalance - 1; - - bytes memory expectedCallData = abi.encodeCall( - ISwapRouter.exactInputSingle, - ( - ISwapRouter.ExactInputSingleParams({ - tokenIn: tokenTestSuite.addressOf(Tokens.DAI), - tokenOut: tokenTestSuite.addressOf(Tokens.WETH), - fee: 3000, - recipient: creditAccount, - deadline: exactAllInputSingleParams.deadline, - amountIn: amountIn, - amountOutMinimum: ((amountIn / DAI_WETH_RATE) * 997) / 1000, - sqrtPriceLimitX96: 0 - }) - ) - ); - - bytes memory callData = abi.encodeCall(adapter.exactAllInputSingle, (exactAllInputSingleParams)); - - expectMulticallStackCalls( - address(adapter), - address(uniswapMock), - USER, - expectedCallData, - exactAllInputSingleParams.tokenIn, - exactAllInputSingleParams.tokenOut, - true - ); - - // MULTICALL - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.DAI, creditAccount, 1); - - expectBalance( - Tokens.WETH, creditAccount, (((initialDAIbalance - 1) / DAI_WETH_RATE) * (1_000_000 - 3000)) / 1_000_000 - ); - - expectAllowance(Tokens.DAI, creditAccount, address(uniswapMock), 1); - - expectTokenIsEnabled(creditAccount, Tokens.DAI, false); - expectTokenIsEnabled(creditAccount, Tokens.WETH, true); - } - - /// @dev [AUV3-4]: exactInput works for user as expected - function test_AUV3_04_exactInput_works_for_user_as_expected() public { - setUp(); - - ISwapRouter.ExactInputParams memory exactInputParams = _getExactInputParams(); - - (address creditAccount, uint256 initialDAIbalance) = _openTestCreditAccount(); - - expectAllowance(Tokens.DAI, creditAccount, address(uniswapMock), 0); - - bytes memory callData = abi.encodeCall(adapter.exactInput, (exactInputParams)); - - exactInputParams.recipient = creditAccount; - - bytes memory expectedCallData = abi.encodeCall(ISwapRouter.exactInput, (exactInputParams)); - - address tokenIn = tokenTestSuite.addressOf(Tokens.DAI); - address tokenOut = tokenTestSuite.addressOf(Tokens.WETH); - - expectMulticallStackCalls( - address(adapter), address(uniswapMock), USER, expectedCallData, tokenIn, tokenOut, true - ); - - // MULTICALL - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.DAI, creditAccount, initialDAIbalance - DAI_EXCHANGE_AMOUNT); - - expectBalance(Tokens.WETH, creditAccount, ((DAI_EXCHANGE_AMOUNT / DAI_WETH_RATE) * 997) / 1000); - - expectAllowance(Tokens.DAI, creditAccount, address(uniswapMock), 1); - - expectTokenIsEnabled(creditAccount, Tokens.WETH, true); - } - - /// @dev [AUV3-5]: exactAllInput works for user as expected - function test_AUV3_05_exactAllInput_works_for_user_as_expected() public { - setUp(); - - IUniswapV3Adapter.ExactAllInputParams memory exactAllInputParams = _getAllInputParams(); - - (address creditAccount, uint256 initialDAIbalance) = _openTestCreditAccount(); - - expectAllowance(Tokens.DAI, creditAccount, address(uniswapMock), 0); - - address tokenIn = tokenTestSuite.addressOf(Tokens.DAI); - address tokenOut = tokenTestSuite.addressOf(Tokens.WETH); - - uint256 amountIn = initialDAIbalance - 1; - - bytes memory expectedCallData = abi.encodeCall( - ISwapRouter.exactInput, - ( - ISwapRouter.ExactInputParams({ - path: bytes(abi.encodePacked(tokenIn)).concat(bytes(abi.encodePacked(uint24(3000)))).concat( - bytes(abi.encodePacked(tokenOut)) - ), - recipient: creditAccount, - deadline: exactAllInputParams.deadline, - amountIn: amountIn, - amountOutMinimum: ((amountIn / DAI_WETH_RATE) * 997) / 1000 - }) - ) - ); - - bytes memory callData = abi.encodeCall(adapter.exactAllInput, (exactAllInputParams)); - - expectMulticallStackCalls( - address(adapter), address(uniswapMock), USER, expectedCallData, tokenIn, tokenOut, true - ); - - // MULTICALL - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.DAI, creditAccount, 1); - - expectBalance( - Tokens.WETH, creditAccount, (((initialDAIbalance - 1) / DAI_WETH_RATE) * (1_000_000 - 3000)) / 1_000_000 - ); - - expectAllowance(Tokens.DAI, creditAccount, address(uniswapMock), 1); - - expectTokenIsEnabled(creditAccount, Tokens.DAI, false); - expectTokenIsEnabled(creditAccount, Tokens.WETH, true); - } - - /// @dev [AUV3-6]: exactOutputSingle works for user as expected - function test_AUV3_06_exactOutputSingle_works_for_user_as_expected() public { - setUp(); - - ISwapRouter.ExactOutputSingleParams memory exactOutputSingleParams = _getExactOutputSingleParams(); - - (address creditAccount, uint256 initialDAIbalance) = _openTestCreditAccount(); - - expectAllowance(Tokens.DAI, creditAccount, address(uniswapMock), 0); - - bytes memory callData = abi.encodeCall(adapter.exactOutputSingle, (exactOutputSingleParams)); - - exactOutputSingleParams.recipient = creditAccount; - - bytes memory expectedCallData = abi.encodeCall(ISwapRouter.exactOutputSingle, (exactOutputSingleParams)); - - expectMulticallStackCalls( - address(adapter), - address(uniswapMock), - USER, - expectedCallData, - exactOutputSingleParams.tokenIn, - exactOutputSingleParams.tokenOut, - true - ); - - // MULTICALL - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.DAI, creditAccount, initialDAIbalance - ((DAI_EXCHANGE_AMOUNT / 2) * 1000) / 997); - - expectBalance(Tokens.WETH, creditAccount, DAI_EXCHANGE_AMOUNT / DAI_WETH_RATE / 2); - - expectAllowance(Tokens.DAI, creditAccount, address(uniswapMock), 1); - - expectTokenIsEnabled(creditAccount, Tokens.WETH, true); - } - - /// @dev [AUV3-7]: exactOutput works for user as expected - function test_AUV3_07_exactOutput_works_for_user_as_expected() public { - setUp(); - - ISwapRouter.ExactOutputParams memory exactOutputParams = _getExactOutputParams(); - - (address creditAccount, uint256 initialDAIbalance) = _openTestCreditAccount(); - - expectAllowance(Tokens.DAI, creditAccount, address(uniswapMock), 0); - - bytes memory callData = abi.encodeCall(adapter.exactOutput, (exactOutputParams)); - - exactOutputParams.recipient = creditAccount; - - bytes memory expectedCallData = abi.encodeCall(ISwapRouter.exactOutput, (exactOutputParams)); - - address tokenIn = tokenTestSuite.addressOf(Tokens.DAI); - address tokenOut = tokenTestSuite.addressOf(Tokens.WETH); - - expectMulticallStackCalls( - address(adapter), address(uniswapMock), USER, expectedCallData, tokenIn, tokenOut, true - ); - - // MULTICALL - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.DAI, creditAccount, initialDAIbalance - ((DAI_EXCHANGE_AMOUNT / 2) * 1000) / 997); - - expectBalance(Tokens.WETH, creditAccount, DAI_EXCHANGE_AMOUNT / DAI_WETH_RATE / 2); - - expectAllowance(Tokens.DAI, creditAccount, address(uniswapMock), 1); - - expectTokenIsEnabled(creditAccount, Tokens.WETH, true); - } - - /// @dev [AUV3-8]: UniswapV3 adapter can't be exploited with an incorrectly-formed path - function test_AUV3_08_exactOutput_cannot_be_exploited_with_tailored_path_parameter() public { - (address creditAccount,) = _openTestCreditAccount(); - - ISwapRouter.ExactOutputParams memory exactOutputParams = _getExactOutputParams(); - exactOutputParams.path = exactOutputParams.path.concat(abi.encodePacked(tokenTestSuite.addressOf(Tokens.USDC))); - - vm.expectRevert(InvalidPathException.selector); - executeOneLineMulticall( - creditAccount, address(adapter), abi.encodeCall(adapter.exactOutput, (exactOutputParams)) - ); - } - - /// @dev [AUV3-9]: Path validity checks are correct - function test_AUV3_09_path_validity_checks_are_correct() public { - (address creditAccount,) = _openTestCreditAccount(); - - ISwapRouter.ExactInputParams memory exactInputParams = _getExactInputParams(); - - exactInputParams.path = bytes(abi.encodePacked(creditManager.underlying())).concat( - bytes(abi.encodePacked(uint24(3000))) - ).concat(bytes(abi.encodePacked(tokenTestSuite.addressOf(Tokens.USDC)))).concat( - bytes(abi.encodePacked(uint24(3000))) - ).concat(bytes(abi.encodePacked(tokenTestSuite.addressOf(Tokens.USDT)))).concat( - bytes(abi.encodePacked(uint24(3000))) - ).concat(bytes(abi.encodePacked(tokenTestSuite.addressOf(Tokens.USDC)))).concat( - bytes(abi.encodePacked(uint24(3000))) - ).concat(bytes(abi.encodePacked(tokenTestSuite.addressOf(Tokens.WETH)))); - - vm.expectRevert(InvalidPathException.selector); - executeOneLineMulticall(creditAccount, address(adapter), abi.encodeCall(adapter.exactInput, (exactInputParams))); - - IUniswapV3Adapter.ExactAllInputParams memory exactAllInputParams = _getAllInputParams(); - - exactAllInputParams.path = bytes(abi.encodePacked(creditManager.underlying())).concat( - bytes(abi.encodePacked(uint24(3000))) - ).concat(bytes(abi.encodePacked(tokenTestSuite.addressOf(Tokens.USDC)))).concat( - bytes(abi.encodePacked(uint24(3000))) - ).concat(bytes(abi.encodePacked(tokenTestSuite.addressOf(Tokens.USDT)))).concat( - bytes(abi.encodePacked(uint24(3000))) - ).concat(bytes(abi.encodePacked(tokenTestSuite.addressOf(Tokens.USDC)))).concat( - bytes(abi.encodePacked(uint24(3000))) - ).concat(bytes(abi.encodePacked(tokenTestSuite.addressOf(Tokens.WETH)))); - - vm.expectRevert(InvalidPathException.selector); - executeOneLineMulticall( - creditAccount, address(adapter), abi.encodeCall(adapter.exactAllInput, (exactAllInputParams)) - ); - - ISwapRouter.ExactOutputParams memory exactOutputParams = _getExactOutputParams(); - - exactOutputParams.path = bytes(abi.encodePacked(creditManager.underlying())).concat( - bytes(abi.encodePacked(uint24(3000))) - ).concat(bytes(abi.encodePacked(tokenTestSuite.addressOf(Tokens.USDC)))).concat( - bytes(abi.encodePacked(uint24(3000))) - ).concat(bytes(abi.encodePacked(tokenTestSuite.addressOf(Tokens.USDT)))).concat( - bytes(abi.encodePacked(uint24(3000))) - ).concat(bytes(abi.encodePacked(tokenTestSuite.addressOf(Tokens.USDC)))).concat( - bytes(abi.encodePacked(uint24(3000))) - ).concat(bytes(abi.encodePacked(tokenTestSuite.addressOf(Tokens.WETH)))); - - vm.expectRevert(InvalidPathException.selector); - executeOneLineMulticall( - creditAccount, address(adapter), abi.encodeCall(adapter.exactOutput, (exactOutputParams)) - ); - - exactInputParams.path = bytes(abi.encodePacked(creditManager.underlying())).concat( - bytes(abi.encodePacked(uint24(3000))) - ).concat(bytes(abi.encodePacked(tokenTestSuite.addressOf(Tokens.LINK)))).concat( - bytes(abi.encodePacked(uint24(3000))) - ).concat(bytes(abi.encodePacked(tokenTestSuite.addressOf(Tokens.WETH)))); - - vm.expectRevert(InvalidPathException.selector); - executeOneLineMulticall(creditAccount, address(adapter), abi.encodeCall(adapter.exactInput, (exactInputParams))); - - exactInputParams.path = bytes(abi.encodePacked(creditManager.underlying())).concat( - bytes(abi.encodePacked(uint24(3000))) - ).concat(bytes(abi.encodePacked(tokenTestSuite.addressOf(Tokens.USDC)))).concat( - bytes(abi.encodePacked(uint24(3000))) - ).concat(bytes(abi.encodePacked(tokenTestSuite.addressOf(Tokens.WETH)))); - - exactInputParams.amountOutMinimum = 0; - - executeOneLineMulticall(creditAccount, address(adapter), abi.encodeCall(adapter.exactInput, (exactInputParams))); - } -} diff --git a/contracts/test/unit/adapters/uniswap/UniswapV3Adapter.unit.t.sol b/contracts/test/unit/adapters/uniswap/UniswapV3Adapter.unit.t.sol new file mode 100644 index 00000000..10f2e3cb --- /dev/null +++ b/contracts/test/unit/adapters/uniswap/UniswapV3Adapter.unit.t.sol @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {ISwapRouter} from "../../../../integrations/uniswap/IUniswapV3.sol"; +import { + IUniswapV3AdapterEvents, + IUniswapV3AdapterExceptions, + IUniswapV3AdapterTypes, + UniswapV3PoolStatus +} from "../../../../interfaces/uniswap/IUniswapV3Adapter.sol"; +import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; +import {UniswapV3AdapterHarness} from "./UniswapV3Adapter.harness.sol"; + +/// @title Uniswap v3 adapter unit test +/// @notice U:[UNI3]: Unit tests for Uniswap v3 swap router adapter +contract UniswapV3AdapterUnitTest is + AdapterUnitTestHelper, + IUniswapV3AdapterEvents, + IUniswapV3AdapterExceptions, + IUniswapV3AdapterTypes +{ + UniswapV3AdapterHarness adapter; + + address router; + + function setUp() public { + _setUp(); + + router = makeAddr("ROUTER"); + adapter = new UniswapV3AdapterHarness(address(creditManager), router); + + _setPoolsStatus(3, 7); + } + + /// @notice U:[UNI3-1]: Constructor works as expected + function test_U_UNI3_01_constructor_works_as_expected() public { + assertEq(adapter.creditManager(), address(creditManager), "Incorrect creditManager"); + assertEq(adapter.addressProvider(), address(addressProvider), "Incorrect addressProvider"); + assertEq(adapter.targetContract(), router, "Incorrect targetContract"); + } + + /// @notice U:[UNI3-2]: Wrapper functions revert on wrong caller + function test_U_UNI3_02_wrapper_functions_revert_on_wrong_caller() public { + ISwapRouter.ExactInputSingleParams memory p1; + _revertsOnNonFacadeCaller(); + adapter.exactInputSingle(p1); + + ExactAllInputSingleParams memory p2; + _revertsOnNonFacadeCaller(); + adapter.exactAllInputSingle(p2); + + ISwapRouter.ExactInputParams memory p3; + _revertsOnNonFacadeCaller(); + adapter.exactInput(p3); + + ExactAllInputParams memory p4; + _revertsOnNonFacadeCaller(); + adapter.exactAllInput(p4); + + ISwapRouter.ExactOutputSingleParams memory p5; + _revertsOnNonFacadeCaller(); + adapter.exactOutputSingle(p5); + + ISwapRouter.ExactOutputParams memory p6; + _revertsOnNonFacadeCaller(); + adapter.exactOutput(p6); + } + + /// @notice U:[UNI3-3]: `exactInputSingle` works as expected + function test_U_UNI3_03_exactInputSingle_works_as_expected() public { + ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({ + tokenIn: tokens[0], + tokenOut: tokens[1], + fee: 500, + amountIn: 123, + amountOutMinimum: 456, + deadline: 789, + recipient: creditAccount, + sqrtPriceLimitX96: 0 + }); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[0], + tokenOut: tokens[1], + callData: abi.encodeCall(ISwapRouter.exactInputSingle, (params)), + requiresApproval: true, + validatesTokens: true + }); + + params.recipient = address(0); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.exactInputSingle(params); + + assertEq(tokensToEnable, 2, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[UNI3-4]: `exactAllInputSingle` works as expected + function test_U_UNI3_04_exactAllInputSingle_works_as_expected() public { + deal({token: tokens[0], to: creditAccount, give: 1001}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[0], + tokenOut: tokens[1], + callData: abi.encodeCall( + ISwapRouter.exactInputSingle, + ( + ISwapRouter.ExactInputSingleParams({ + tokenIn: tokens[0], + tokenOut: tokens[1], + fee: 500, + amountIn: 1000, + amountOutMinimum: 500, + deadline: 789, + recipient: creditAccount, + sqrtPriceLimitX96: 0 + }) + ) + ), + requiresApproval: true, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.exactAllInputSingle( + ExactAllInputSingleParams({ + tokenIn: tokens[0], + tokenOut: tokens[1], + fee: 500, + deadline: 789, + rateMinRAY: 0.5e27, + sqrtPriceLimitX96: 0 + }) + ); + + assertEq(tokensToEnable, 2, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 1, "Incorrect tokensToDisable"); + } + + /// @notice U:[UNI3-5]: `exactInput` works as expected + function test_U_UNI3_05_exactInput_works_as_expected() public { + ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({ + path: _makePath(0), + amountIn: 123, + amountOutMinimum: 456, + deadline: 789, + recipient: creditAccount + }); + vm.expectRevert(InvalidPathException.selector); + vm.prank(creditFacade); + adapter.exactInput(params); + + params.path = _makePath(3); + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[0], + tokenOut: tokens[2], + callData: abi.encodeCall(ISwapRouter.exactInput, (params)), + requiresApproval: true, + validatesTokens: true + }); + + params.recipient = address(0); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.exactInput(params); + + assertEq(tokensToEnable, 4, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[UNI3-6]: `exactAllInput` works as expected + function test_U_UNI3_06_exactAllInput_works_as_expected() public { + deal({token: tokens[0], to: creditAccount, give: 1001}); + + ExactAllInputParams memory params = ExactAllInputParams({path: _makePath(0), deadline: 789, rateMinRAY: 0.5e27}); + vm.expectRevert(InvalidPathException.selector); + vm.prank(creditFacade); + adapter.exactAllInput(params); + + params.path = _makePath(3); + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[0], + tokenOut: tokens[2], + callData: abi.encodeCall( + ISwapRouter.exactInput, + ( + ISwapRouter.ExactInputParams({ + path: params.path, + amountIn: 1000, + amountOutMinimum: 500, + deadline: 789, + recipient: creditAccount + }) + ) + ), + requiresApproval: true, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.exactAllInput(params); + + assertEq(tokensToEnable, 4, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 1, "Incorrect tokensToDisable"); + } + + /// @notice U:[UNI3-7]: `exactOutputSingle` works as expected + function test_U_UNI3_07_exactOutputSingle_works_as_expected() public { + ISwapRouter.ExactOutputSingleParams memory params = ISwapRouter.ExactOutputSingleParams({ + tokenIn: tokens[0], + tokenOut: tokens[1], + fee: 500, + amountOut: 123, + amountInMaximum: 456, + deadline: 789, + recipient: creditAccount, + sqrtPriceLimitX96: 0 + }); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[0], + tokenOut: tokens[1], + callData: abi.encodeCall(ISwapRouter.exactOutputSingle, (params)), + requiresApproval: true, + validatesTokens: true + }); + + params.recipient = address(0); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.exactOutputSingle(params); + + assertEq(tokensToEnable, 2, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[UNI3-8]: `exactOutput` works as expected + function test_U_UNI3_08_exactOutput_works_as_expected() public { + ISwapRouter.ExactOutputParams memory params = ISwapRouter.ExactOutputParams({ + path: _makePath(0), + amountOut: 123, + amountInMaximum: 456, + deadline: 789, + recipient: creditAccount + }); + vm.expectRevert(InvalidPathException.selector); + vm.prank(creditFacade); + adapter.exactOutput(params); + + params.path = _makePath(3); + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[2], // path is reversed for exactOutput + tokenOut: tokens[0], + callData: abi.encodeCall(ISwapRouter.exactOutput, (params)), + requiresApproval: true, + validatesTokens: true + }); + + params.recipient = address(0); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.exactOutput(params); + + assertEq(tokensToEnable, 1, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[UNI3-9]: `setPoolStatusBatch` works as expected + function test_U_UNI3_09_setPoolStatusBatch_works_as_expected() public { + UniswapV3PoolStatus[] memory pairs; + + _revertsOnNonConfiguratorCaller(); + adapter.setPoolStatusBatch(pairs); + + pairs = new UniswapV3PoolStatus[](2); + pairs[0] = UniswapV3PoolStatus(tokens[0], tokens[1], 500, false); + pairs[1] = UniswapV3PoolStatus(tokens[1], tokens[2], 3000, true); + + vm.expectEmit(true, true, true, true); + emit SetPoolStatus(_min(tokens[0], tokens[1]), _max(tokens[0], tokens[1]), 500, false); + + vm.expectEmit(true, true, true, true); + emit SetPoolStatus(_min(tokens[1], tokens[2]), _max(tokens[1], tokens[2]), 3000, true); + + vm.prank(configurator); + adapter.setPoolStatusBatch(pairs); + + assertFalse(adapter.isPoolAllowed(tokens[0], tokens[1], 500), "First pool incorrectly allowed"); + assertTrue(adapter.isPoolAllowed(tokens[1], tokens[2], 3000), "Second pool incorrectly not allowed"); + } + + /// @notice U:[UNI3-10]: `_validatePath` works as expected + function test_U_UNI3_10_validatePath_works_as_expected() public { + bool isValid; + address tokenIn; + address tokenOut; + bytes memory path; + + // insane path + (isValid,,) = adapter.validatePath(bytes("")); + assertFalse(isValid, "Empty path incorrectly valid"); + + (isValid,,) = adapter.validatePath(bytes("some random string that does not represent a valid path")); + assertFalse(isValid, "Arbitrary path incorrectly valid"); + + // valid paths over recognized tokens but through pools with wrong fees + path = abi.encodePacked(tokens[0], uint24(3000), tokens[1]); + (isValid,,) = adapter.validatePath(path); + assertFalse(isValid, "2-hop path through pool with wrong fee is incorrectly valid"); + + path = abi.encodePacked(tokens[0], uint24(500), tokens[1], uint24(3000), tokens[2]); + (isValid,,) = adapter.validatePath(path); + assertFalse(isValid, "3-hop path through pool with wrong fee is incorrectly valid"); + + path = abi.encodePacked(tokens[0], uint24(500), tokens[1], uint24(500), tokens[2], uint24(3000), tokens[3]); + (isValid,,) = adapter.validatePath(path); + assertFalse(isValid, "4-hop path through pool with wrong fee is incorrectly valid"); + + // exhaustive search + for (uint256 pathLen = 2; pathLen <= 4; ++pathLen) { + path = _makePath(pathLen); + + uint256 numCases = 1 << (pathLen - 1); + for (uint256 mask; mask < numCases; ++mask) { + _setPoolsStatus(pathLen - 1, mask); + (isValid, tokenIn, tokenOut) = adapter.validatePath(path); + + if (mask == numCases - 1) { + assertTrue(isValid, "Path incorrectly invalid"); + assertEq(tokenIn, tokens[0], "Incorrect tokenIn"); + assertEq(tokenOut, tokens[pathLen - 1], "Incorrect tokenOut"); + } else { + assertFalse(isValid, "Path incorrectly valid"); + } + } + } + } + + // ------- // + // HELPERS // + // ------- // + + /// @dev Returns swap path of `len` consecutive `tokens` + function _makePath(uint256 len) internal view returns (bytes memory path) { + uint24 fee = 500; + if (len == 2) path = abi.encodePacked(tokens[0], fee, tokens[1]); + if (len == 3) path = abi.encodePacked(tokens[0], fee, tokens[1], fee, tokens[2]); + if (len == 4) path = abi.encodePacked(tokens[0], fee, tokens[1], fee, tokens[2], fee, tokens[3]); + } + + /// @dev Sets statuses for `len` consecutive pools of `tokens` based on `allowedPoolsMask` + function _setPoolsStatus(uint256 len, uint256 allowedPairsMask) internal { + UniswapV3PoolStatus[] memory pairs = new UniswapV3PoolStatus[](len); + for (uint256 i; i < len; ++i) { + uint256 mask = 1 << i; + pairs[i] = UniswapV3PoolStatus(tokens[i], tokens[i + 1], 500, allowedPairsMask & mask != 0); + } + vm.prank(configurator); + adapter.setPoolStatusBatch(pairs); + } + + /// @dev Returns smaller of two addresses + function _min(address token0, address token1) internal pure returns (address) { + return token0 < token1 ? token0 : token1; + } + + /// @dev Returns larger of two addresses + function _max(address token0, address token1) internal pure returns (address) { + return token0 < token1 ? token1 : token0; + } +} diff --git a/contracts/test/unit/adapters/yearn/YearnV2Adapter.t.sol b/contracts/test/unit/adapters/yearn/YearnV2Adapter.t.sol deleted file mode 100644 index 955b7515..00000000 --- a/contracts/test/unit/adapters/yearn/YearnV2Adapter.t.sol +++ /dev/null @@ -1,384 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2023. -pragma solidity ^0.8.17; - -import {YearnV2Adapter} from "../../../../adapters/yearn/YearnV2.sol"; - -import {YearnV2Mock} from "../../../mocks/integrations/YearnV2Mock.sol"; - -import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; -import {YearnPriceFeed} from "@gearbox-protocol/oracles-v3/contracts/oracles/yearn/YearnPriceFeed.sol"; - -// TEST -import "../../../lib/constants.sol"; - -import {AdapterTestHelper} from "../AdapterTestHelper.sol"; -import {ERC20Mock} from "@gearbox-protocol/core-v3/contracts/test/mocks/token/ERC20Mock.sol"; -import "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol"; - -uint256 constant PRICE_PER_SHARE = (110 * WAD) / 100; - -/// @title YearnV2AdapterTest -/// @notice Designed for unit test purposes only -contract YearnV2AdapterTest is Test, AdapterTestHelper { - YearnV2Adapter public adapter; - YearnV2Mock public yearnV2Mock; - address public token; - address public yvDAI; - - function setUp() public { - _setUp(); - - token = tokenTestSuite.addressOf(Tokens.DAI); - - yearnV2Mock = new YearnV2Mock(token); - yearnV2Mock.setPricePerShare(PRICE_PER_SHARE); - - yvDAI = address(yearnV2Mock); - - vm.startPrank(CONFIGURATOR); - - priceOracle.setPriceFeed( - yvDAI, - address( - new YearnPriceFeed( - address(addressProvider), - yvDAI, - priceOracle.priceFeeds(token), - 48 hours - ) - ), - 0 - ); - - creditConfigurator.addCollateralToken(yvDAI, 8300); - - adapter = new YearnV2Adapter( - address(creditManager), - address(yearnV2Mock) - ); - - creditConfigurator.allowAdapter(address(adapter)); - - vm.stopPrank(); - tokenTestSuite.mint(Tokens.DAI, USER, 10 * DAI_ACCOUNT_AMOUNT); - - vm.label(address(adapter), "ADAPTER"); - vm.label(address(yearnV2Mock), "YEARN_MOCK"); - } - - // - // HELPERS - // - function _openYVDaiTestCreditAccount() internal returns (address creditAccount, uint256 yAmount) { - uint256 initialDAIbalance; - (creditAccount, initialDAIbalance) = _openTestCreditAccount(); - - yAmount = (((initialDAIbalance - 1) * WAD) / PRICE_PER_SHARE); - - executeOneLineMulticall(creditAccount, address(adapter), abi.encodeWithSignature("deposit()")); - - expectBalance(Tokens.DAI, creditAccount, 1); - expectBalance(yvDAI, creditAccount, yAmount); - - expectTokenIsEnabled(creditAccount, Tokens.DAI, false); - } - - /// - /// - /// TESTS - /// - /// - - /// @dev [AYV2-1]: constructor sets correct values - function test_AYV2_01_constructor_sets_correct_values() public { - assertEq(address(adapter.token()), tokenTestSuite.addressOf(Tokens.DAI), "Incorrect token"); - assertEq( - adapter.tokenMask(), - creditManager.getTokenMaskOrRevert(tokenTestSuite.addressOf(Tokens.DAI)), - "Incorrect underlying token mask" - ); - assertEq(adapter.yTokenMask(), creditManager.getTokenMaskOrRevert(yvDAI), "Incorrect vault token mask"); - } - - /// @dev [AYV2-2]: constructor reverts if token is not allowed - function test_AYV2_02_constructor_reverts_if_token_is_not_allowed() public { - ERC20Mock forbiddenToken = new ERC20Mock("Forbid", "FBD", 18); - - YearnV2Mock forbidYearnV2Mock = new YearnV2Mock( - address(forbiddenToken) - ); - - vm.expectRevert(TokenNotAllowedException.selector); - new YearnV2Adapter(address(creditManager), address(forbidYearnV2Mock)); - - YearnV2Mock notAllowedYearnV2Mock = new YearnV2Mock( - tokenTestSuite.addressOf(Tokens.DAI) - ); - - vm.expectRevert(TokenNotAllowedException.selector); - - new YearnV2Adapter( - address(creditManager), - address(notAllowedYearnV2Mock) - ); - } - - // /// @dev [AYV2-3]: depost(*) and witdraw(*) revert if user has no account - // function test_AYV2_03_deposit_and_withdraw_revert_if_uses_has_no_account() public { - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall(creditAccount, address(adapter), abi.encodeWithSignature("deposit()")); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall(creditAccount, address(adapter), abi.encodeWithSignature("deposit(uint256)", 1000)); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall( - // creditAccount, address(adapter), abi.encodeWithSignature("deposit(uint256,address)", 1000, address(0)) - // ); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall(creditAccount, address(adapter), abi.encodeWithSignature("withdraw()")); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall(creditAccount, address(adapter), abi.encodeWithSignature("withdraw(uint256)", 1000)); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall( - // creditAccount, address(adapter), abi.encodeWithSignature("withdraw(uint256,address)", 1000, address(0)) - // ); - - // vm.expectRevert(ICreditManagerV3Exceptions.HasNoOpenedAccountException.selector); - // executeOneLineMulticall( - // creditAccount, - // address(adapter), - // abi.encodeWithSignature("withdraw(uint256,address,uint256)", 1000, address(0), 2) - // ); - // } - - /// @dev [AYV2-4]: deposit works for user as expected - function test_AYV2_04_deposit_works_for_user_as_expected() public { - setUp(); - (address creditAccount, uint256 initialDAIbalance) = _openTestCreditAccount(); - - expectAllowance(Tokens.DAI, creditAccount, address(yearnV2Mock), 0); - - bytes memory expectedCallData = abi.encodeWithSignature("deposit(uint256)", initialDAIbalance - 1); - - expectMulticallStackCalls( - address(adapter), address(yearnV2Mock), USER, expectedCallData, token, address(yearnV2Mock), true - ); - - executeOneLineMulticall(creditAccount, address(adapter), abi.encodeWithSignature("deposit()")); - - expectBalance(Tokens.DAI, creditAccount, 1); - - expectBalance(yvDAI, creditAccount, ((initialDAIbalance - 1) * WAD) / PRICE_PER_SHARE); - - expectAllowance(Tokens.DAI, creditAccount, address(yearnV2Mock), 1); - - expectTokenIsEnabled(creditAccount, Tokens.DAI, false); - expectTokenIsEnabled(creditAccount, yvDAI, true); - - expectTokenIsEnabled(creditAccount, address(yearnV2Mock), true); - } - - /// @dev [AYV2-5]: deposit(uint256) works for user as expected - function test_AYV2_05_deposit_uint256_works_for_user_as_expected() public { - setUp(); - (address creditAccount, uint256 initialDAIbalance) = _openTestCreditAccount(); - - expectAllowance(Tokens.DAI, creditAccount, address(yearnV2Mock), 0); - - bytes memory expectedCallData = abi.encodeWithSignature("deposit(uint256)", DAI_EXCHANGE_AMOUNT); - - expectMulticallStackCalls( - address(adapter), address(yearnV2Mock), USER, expectedCallData, token, address(yearnV2Mock), true - ); - - executeOneLineMulticall( - creditAccount, address(adapter), abi.encodeWithSignature("deposit(uint256)", DAI_EXCHANGE_AMOUNT) - ); - - expectBalance(Tokens.DAI, creditAccount, initialDAIbalance - DAI_EXCHANGE_AMOUNT); - - expectBalance(yvDAI, creditAccount, (DAI_EXCHANGE_AMOUNT * WAD) / PRICE_PER_SHARE); - - expectAllowance(Tokens.DAI, creditAccount, address(yearnV2Mock), 1); - - expectTokenIsEnabled(creditAccount, yvDAI, true); - - expectTokenIsEnabled(creditAccount, address(yearnV2Mock), true); - } - - /// @dev [AYV2-6]: deposit(uint256, address) works for user as expected - function test_AYV2_06_deposit_uint256_address_works_for_user_as_expected() public { - setUp(); - (address creditAccount, uint256 initialDAIbalance) = _openTestCreditAccount(); - - expectAllowance(Tokens.DAI, creditAccount, address(yearnV2Mock), 0); - - bytes memory expectedCallData = abi.encodeWithSignature("deposit(uint256)", DAI_EXCHANGE_AMOUNT); - - bytes memory callData = abi.encodeWithSignature("deposit(uint256,address)", DAI_EXCHANGE_AMOUNT, address(0)); - - expectMulticallStackCalls( - address(adapter), address(yearnV2Mock), USER, expectedCallData, token, address(yearnV2Mock), true - ); - - executeOneLineMulticall(creditAccount, address(adapter), callData); - - expectBalance(Tokens.DAI, creditAccount, initialDAIbalance - DAI_EXCHANGE_AMOUNT); - - expectBalance(yvDAI, creditAccount, (DAI_EXCHANGE_AMOUNT * WAD) / PRICE_PER_SHARE); - - expectAllowance(Tokens.DAI, creditAccount, address(yearnV2Mock), 1); - - expectTokenIsEnabled(creditAccount, yvDAI, true); - - expectTokenIsEnabled(creditAccount, address(yearnV2Mock), true); - } - - // - // WITHDRAW - // - - /// @dev [AYV2-7]: withdraw works for user as expected - function test_AYV2_07_withdraw_works_for_user_as_expected() public { - setUp(); - - (address creditAccount, uint256 yAmount) = _openYVDaiTestCreditAccount(); - - bytes memory expectedCallData = abi.encodeWithSignature("withdraw(uint256)", yAmount - 1); - - expectMulticallStackCalls( - address(adapter), address(yearnV2Mock), USER, expectedCallData, token, address(yearnV2Mock), false - ); - - executeOneLineMulticall(creditAccount, address(adapter), abi.encodeWithSignature("withdraw()")); - - expectBalance(Tokens.DAI, creditAccount, ((yAmount - 1) * PRICE_PER_SHARE) / WAD + 1); - - expectBalance(yvDAI, creditAccount, 1); - - // There is not need to approve yVault to itself, so nothing in terms of allowance should be done - expectAllowance(yvDAI, creditAccount, address(yearnV2Mock), 0); - - expectTokenIsEnabled(creditAccount, yvDAI, false); - expectTokenIsEnabled(creditAccount, Tokens.DAI, true); - - expectTokenIsEnabled(creditAccount, address(yearnV2Mock), true); - } - - /// @dev [AYV2-8]: withdraw(uint256) works for user as expected - function test_AYV2_08_withdraw_uint256_works_for_user_as_expected() public { - setUp(); - (address creditAccount, uint256 yAmount) = _openYVDaiTestCreditAccount(); - - expectAllowance(yvDAI, creditAccount, address(yearnV2Mock), 0); - - uint256 amount = yAmount / 2; - - bytes memory expectedCallData = abi.encodeWithSignature("withdraw(uint256)", amount); - - expectMulticallStackCalls( - address(adapter), address(yearnV2Mock), USER, expectedCallData, token, address(yearnV2Mock), false - ); - - executeOneLineMulticall(creditAccount, address(adapter), abi.encodeWithSignature("withdraw(uint256)", amount)); - - expectBalance(Tokens.DAI, creditAccount, ((amount) * PRICE_PER_SHARE) / WAD + 1); - - // +1 cause it keeps from deposit there - expectBalance(yvDAI, creditAccount, yAmount - amount); - - // There is not need to approve yVault to itself, so nothing in terms of allowance should be done - expectAllowance(yvDAI, creditAccount, address(yearnV2Mock), 0); - - expectTokenIsEnabled(creditAccount, Tokens.DAI, true); - - expectTokenIsEnabled(creditAccount, address(yearnV2Mock), true); - } - - /// @dev [AYV2-9]: withdraw(uint256,address) works for user as expected - function test_AYV2_09_withdraw_uint256_address_works_for_user_as_expected() public { - setUp(); - (address creditAccount, uint256 yAmount) = _openYVDaiTestCreditAccount(); - - expectAllowance(yvDAI, creditAccount, address(yearnV2Mock), 0); - - uint256 amount = yAmount / 2; - - bytes memory expectedCallData = abi.encodeWithSignature("withdraw(uint256)", amount); - - expectMulticallStackCalls( - address(adapter), address(yearnV2Mock), USER, expectedCallData, token, address(yearnV2Mock), false - ); - - executeOneLineMulticall( - creditAccount, address(adapter), abi.encodeWithSignature("withdraw(uint256,address)", amount, address(0)) - ); - - expectBalance(Tokens.DAI, creditAccount, ((amount) * PRICE_PER_SHARE) / WAD + 1); - - // +1 cause it keeps from deposit there - expectBalance(yvDAI, creditAccount, yAmount - amount); - - // There is not need to approve yVault to itself, so nothing in terms of allowance should be done - expectAllowance(yvDAI, creditAccount, address(yearnV2Mock), 0); - - expectTokenIsEnabled(creditAccount, Tokens.DAI, true); - - expectTokenIsEnabled(creditAccount, address(yearnV2Mock), true); - } - - /// @dev [AYV2-10]: withdraw(uint256,address,uint256) works for user as expected - function test_AYV2_10_withdraw_uint256_address_uint256_works_for_user_as_expected() public { - setUp(); - (address creditAccount, uint256 yAmount) = _openYVDaiTestCreditAccount(); - - expectAllowance(yvDAI, creditAccount, address(yearnV2Mock), 0); - - uint256 amount = yAmount / 2; - - bytes memory expectedCallData = - abi.encodeWithSignature("withdraw(uint256,address,uint256)", amount, creditAccount, 1); - - expectMulticallStackCalls( - address(adapter), address(yearnV2Mock), USER, expectedCallData, token, address(yearnV2Mock), false - ); - - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeWithSignature("withdraw(uint256,address,uint256)", amount, address(0), 1) - ); - - expectBalance(Tokens.DAI, creditAccount, ((amount) * PRICE_PER_SHARE) / WAD + 1); - - // +1 cause it keeps from deposit there - expectBalance(yvDAI, creditAccount, yAmount - amount); - - // There is not need to approve yVault to itself, so nothing in terms of allowance should be done - expectAllowance(yvDAI, creditAccount, address(yearnV2Mock), 0); - - expectTokenIsEnabled(creditAccount, Tokens.DAI, true); - - expectTokenIsEnabled(creditAccount, address(yearnV2Mock), true); - } - - /// @dev [AYV2-11]: withdraw(uint256, address, uin256) passes maxLoss to target - function test_AYV2_11_withdraw_correctly_passes_maxLoss() public { - (address creditAccount, uint256 yAmount) = _openYVDaiTestCreditAccount(); - - uint256 amount = yAmount / 2; - - vm.expectRevert(bytes("Loss too big")); - executeOneLineMulticall( - creditAccount, - address(adapter), - abi.encodeWithSignature("withdraw(uint256,address,uint256)", amount, address(0), 2) - ); - } -} diff --git a/contracts/test/unit/adapters/yearn/YearnV2Adapter.unit.t.sol b/contracts/test/unit/adapters/yearn/YearnV2Adapter.unit.t.sol new file mode 100644 index 00000000..9422587d --- /dev/null +++ b/contracts/test/unit/adapters/yearn/YearnV2Adapter.unit.t.sol @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {YearnV2Adapter} from "../../../../adapters/yearn/YearnV2.sol"; +import {IYVault} from "../../../../integrations/yearn/IYVault.sol"; +import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; + +/// @title Yearn v2 adapter unit test +/// @notice U:[YFI2]: Unit tests for Yearn v2 yToken adapter +contract YearnV2AdapterUnitTest is AdapterUnitTestHelper { + YearnV2Adapter adapter; + + address token; + address yToken; + + uint256 tokenMask; + uint256 yTokenMask; + + function setUp() public { + _setUp(); + + (token, tokenMask) = (tokens[0], 1); + (yToken, yTokenMask) = (tokens[1], 2); + vm.mockCall(yToken, abi.encodeCall(IYVault.token, ()), abi.encode(token)); + + adapter = new YearnV2Adapter(address(creditManager), yToken); + } + + /// @notice U:[YFI2-1]: Constructor works as expected + function test_U_YFI2_01_constructor_works_as_expected() public { + _readsTokenMask(token); + _readsTokenMask(yToken); + adapter = new YearnV2Adapter(address(creditManager), yToken); + + assertEq(adapter.creditManager(), address(creditManager), "Incorrect creditManager"); + assertEq(adapter.addressProvider(), address(addressProvider), "Incorrect addressProvider"); + assertEq(adapter.targetContract(), yToken, "Incorrect targetContract"); + assertEq(adapter.token(), token, "Incorrect token"); + assertEq(adapter.tokenMask(), tokenMask, "Incorrect tokenMask"); + assertEq(adapter.yTokenMask(), yTokenMask, "Incorrect yTokenMask"); + } + + /// @notice U:[YFI2-2]: Wrapper functions revert on wrong caller + function test_U_YFI2_02_wrapper_functions_revert_on_wrong_caller() public { + _revertsOnNonFacadeCaller(); + adapter.deposit(); + + _revertsOnNonFacadeCaller(); + adapter.deposit(0); + + _revertsOnNonFacadeCaller(); + adapter.deposit(0, address(0)); + + _revertsOnNonFacadeCaller(); + adapter.withdraw(); + + _revertsOnNonFacadeCaller(); + adapter.withdraw(0); + + _revertsOnNonFacadeCaller(); + adapter.withdraw(0, address(0)); + + _revertsOnNonFacadeCaller(); + adapter.withdraw(0, address(0), 0); + } + + /// @notice U:[YFI2-3]: `deposit()` works as expected + function test_U_YFI2_03_deposit_works_as_expected() public { + deal({token: token, to: creditAccount, give: 1000}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: token, + tokenOut: yToken, + callData: abi.encodeWithSignature("deposit(uint256)", 999), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.deposit(); + + assertEq(tokensToEnable, yTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, tokenMask, "Incorrect tokensToDisable"); + } + + /// @notice U:[YFI2-4]: `deposit(uint256)` works as expected + function test_U_YFI2_04_deposit_uint256_works_as_expected() public { + _executesSwap({ + tokenIn: token, + tokenOut: yToken, + callData: abi.encodeWithSignature("deposit(uint256)", 1000), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.deposit(1000); + + assertEq(tokensToEnable, yTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[YFI2-5]: `deposit(uint256,address)` works as expected + function test_U_YFI2_05_deposit_uint256_address_works_as_expected() public { + _executesSwap({ + tokenIn: token, + tokenOut: yToken, + callData: abi.encodeWithSignature("deposit(uint256)", 1000), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.deposit(1000, address(0)); + + assertEq(tokensToEnable, yTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[YFI2-6]: `withdraw()` works as expected + function test_U_YFI2_06_withdraw_works_as_expected() public { + deal({token: yToken, to: creditAccount, give: 1000}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: yToken, + tokenOut: token, + callData: abi.encodeWithSignature("withdraw(uint256)", 999), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdraw(); + + assertEq(tokensToEnable, tokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, yTokenMask, "Incorrect tokensToDisable"); + } + + /// @notice U:[YFI2-7]: `withdraw(uint256)` works as expected + function test_U_YFI2_07_withdraw_uint256_works_as_expected() public { + _executesSwap({ + tokenIn: yToken, + tokenOut: token, + callData: abi.encodeWithSignature("withdraw(uint256)", 1000), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdraw(1000); + + assertEq(tokensToEnable, tokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[YFI2-8]: `withdraw(uint256,address)` works as expected + function test_U_YFI2_08_withdraw_uint256_address_works_as_expected() public { + _executesSwap({ + tokenIn: yToken, + tokenOut: token, + callData: abi.encodeWithSignature("withdraw(uint256)", 1000), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdraw(1000, address(0)); + + assertEq(tokensToEnable, tokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } + + /// @notice U:[YFI2-9]: `withdraw(uint256,address,uint256)` works as expected + function test_U_YFI2_09_withdraw_uint256_address_uint256_works_as_expected() public { + _readsActiveAccount(); + _executesSwap({ + tokenIn: yToken, + tokenOut: token, + callData: abi.encodeWithSignature("withdraw(uint256,address,uint256)", 1000, creditAccount, 10), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdraw(1000, address(0), 10); + + assertEq(tokensToEnable, tokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); + } +} diff --git a/contracts/test/unit/helpers/compound/CompoundV2_CEtherGateway.unit.t.sol b/contracts/test/unit/helpers/compound/CompoundV2_CEtherGateway.unit.t.sol new file mode 100644 index 00000000..eb915b67 --- /dev/null +++ b/contracts/test/unit/helpers/compound/CompoundV2_CEtherGateway.unit.t.sol @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {Test} from "forge-std/Test.sol"; + +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +import {IWETH} from "@gearbox-protocol/core-v2/contracts/interfaces/external/IWETH.sol"; +import {ZeroAddressException} from "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol"; +import {TokensTestSuite, Tokens} from "@gearbox-protocol/core-v3/contracts/test/suites/TokensTestSuite.sol"; + +import {CEtherGateway} from "../../../../helpers/compound/CompoundV2_CEtherGateway.sol"; +import {ICompoundV2_Exceptions} from "../../../../interfaces/compound/ICompoundV2_CTokenAdapter.sol"; + +import {CEtherMock, REDEEM_ERROR, REDEEM_UNDERLYING_ERROR} from "../../../mocks/integrations/compound/CEtherMock.sol"; + +/// @title CEther gateway unit test +/// @notice U:[CEG]: Unit tests for Compound v2 CEther gateway +contract CEtherGatewayUnitTest is Test, ICompoundV2_Exceptions { + using Address for address payable; + + IWETH weth; + CEtherMock ceth; + CEtherGateway gateway; + + address user; + + TokensTestSuite tokensTestSuite; + uint256 constant WETH_AMOUNT = 10 ether; + + function setUp() public { + tokensTestSuite = new TokensTestSuite(); + + weth = IWETH(tokensTestSuite.addressOf(Tokens.WETH)); + + // initial exchange rate 0.02 cETH per ETH, 5% yearly interest + ceth = new CEtherMock(0.02 ether, 0.05 ether); + vm.deal(address(ceth), 100 ether); + skip(365 days); + + gateway = new CEtherGateway(address(weth), address(ceth)); + + vm.label(address(weth), "WETH"); + vm.label(address(ceth), "cETH"); + vm.label(address(gateway), "cETH_GATEWAY"); + + user = makeAddr("user"); + } + + /// @notice U:[CEG-1]: Constructor works as expected + function test_U_CEG_01_constructor_works_as_expected() public { + vm.expectRevert(ZeroAddressException.selector); + new CEtherGateway(address(0), address(ceth)); + + vm.expectRevert(ZeroAddressException.selector); + new CEtherGateway(address(weth), address(0)); + + assertEq(gateway.weth(), address(weth), "Incorrect WETH address"); + assertEq(gateway.ceth(), address(ceth), "Incorrect cETH address"); + } + + /// @notice U:[CEG-2]: Gateway can receive ETH + function test_U_CEG_02_gateway_can_receive_eth() public { + assertEq(address(gateway).balance, 0); + payable(gateway).sendValue(1 ether); + assertEq(address(gateway).balance, 1 ether); + } + + /// @notice U:[CEG-3]: `mint` works as expected + function test_U_CEG_03_mint_works_as_expected() public { + uint256 mintAmount = 10 ether; + tokensTestSuite.mint(Tokens.WETH, user, mintAmount); + tokensTestSuite.approve(Tokens.WETH, user, address(gateway), mintAmount); + + uint256 cethBalanceExpected = mintAmount * 1 ether / ceth.exchangeRateCurrent(); + + vm.expectCall(address(weth), abi.encodeCall(IWETH.withdraw, (mintAmount))); + vm.expectCall(address(ceth), mintAmount, abi.encodeCall(CEtherMock.mint, ())); + + vm.prank(user); + uint256 error = gateway.mint(mintAmount); + + assertEq(error, 0, "Non-zero error code"); + assertEq(tokensTestSuite.balanceOf(address(weth), user), 0, "Incorrect WETH balance"); + assertEq(tokensTestSuite.balanceOf(address(ceth), user), cethBalanceExpected, "Incorrect cETH balance"); + } + + /// @notice U:[CEG-4]: `redeem` works as expected + function test_U_CEG_04_redeem_works_as_expected() public { + uint256 cethBalance = _mintCEther(10 ether); + + uint256 redeemTokens = cethBalance / 2; + tokensTestSuite.approve(address(ceth), user, address(gateway), redeemTokens); + + uint256 redeemAmountExpected = redeemTokens * ceth.exchangeRateCurrent() / 1 ether; + + vm.expectCall(address(ceth), abi.encodeCall(CEtherMock.redeem, (redeemTokens))); + vm.expectCall(address(weth), redeemAmountExpected, abi.encodeCall(IWETH.deposit, ())); + + vm.prank(user); + uint256 error = gateway.redeem(redeemTokens); + + assertEq(error, 0, "Non-zero error code"); + assertEq(tokensTestSuite.balanceOf(address(weth), user), redeemAmountExpected, "Incorrect WETH balance"); + assertEq(tokensTestSuite.balanceOf(address(ceth), user), cethBalance - redeemTokens, "Incorrect cETH balance"); + } + + /// @notice U:[CEG-5]: `redeemUnderlying` works as expected + function test_U_CEG_05_redeemUnderlying_works_as_expected() public { + uint256 cethBalance = _mintCEther(10 ether); + + uint256 redeemAmount = 5 ether; + tokensTestSuite.approve(address(ceth), user, address(gateway), cethBalance); + + uint256 redeemTokensExpected = redeemAmount * 1 ether / ceth.exchangeRateCurrent(); + + vm.expectCall(address(ceth), abi.encodeCall(CEtherMock.redeemUnderlying, (redeemAmount))); + vm.expectCall(address(weth), redeemAmount, abi.encodeCall(IWETH.deposit, ())); + + vm.prank(user); + uint256 error = gateway.redeemUnderlying(redeemAmount); + + assertEq(error, 0, "Non-zero error code"); + assertEq(tokensTestSuite.balanceOf(address(weth), user), redeemAmount, "Incorrect WETH balance"); + assertEq( + tokensTestSuite.balanceOf(address(ceth), user), cethBalance - redeemTokensExpected, "Incorrect cETH balance" + ); + } + + /// @notice U:[CEG-6]: redeem functions revert on non-zero CEther error code + function test_U_CEG_06_redeem_functions_revert_on_non_zero_error_code() public { + uint256 cethBalance = _mintCEther(10 ether); + tokensTestSuite.approve(address(ceth), user, address(gateway), cethBalance); + + ceth.setFailing(true); + + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, REDEEM_ERROR)); + vm.prank(user); + gateway.redeem(1 ether); + + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, REDEEM_UNDERLYING_ERROR)); + vm.prank(user); + gateway.redeemUnderlying(1 ether); + } + + /// @dev Deposits given amount of WETH to cETH through gateway for user + function _mintCEther(uint256 mintAmount) internal returns (uint256 cethBalance) { + tokensTestSuite.mint(Tokens.WETH, user, mintAmount); + tokensTestSuite.approve(Tokens.WETH, user, address(gateway), mintAmount); + vm.prank(user); + gateway.mint(WETH_AMOUNT); + + cethBalance = tokensTestSuite.balanceOf(address(ceth), user); + skip(365 days); + } +} diff --git a/contracts/test/unit/helpers/lido/LidoV1_WETHGateway.unit.t.sol b/contracts/test/unit/helpers/lido/LidoV1_WETHGateway.unit.t.sol new file mode 100644 index 00000000..a09a90cc --- /dev/null +++ b/contracts/test/unit/helpers/lido/LidoV1_WETHGateway.unit.t.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {Test} from "forge-std/Test.sol"; + +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +import {IWETH} from "@gearbox-protocol/core-v2/contracts/interfaces/external/IWETH.sol"; +import { + ReceiveIsNotAllowedException, + ZeroAddressException +} from "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol"; +import {ERC20Mock} from "@gearbox-protocol/core-v3/contracts/test/mocks/token/ERC20Mock.sol"; +import {TokensTestSuite} from "@gearbox-protocol/core-v3/contracts/test/suites/TokensTestSuite.sol"; + +import {LidoV1Gateway} from "../../../../helpers/lido/LidoV1_WETHGateway.sol"; +import {IstETH} from "../../../../integrations/lido/IstETH.sol"; + +/// @title Lido v1 gateway unit test +/// @notice U:[LWG]: Unit tests for Lido v1 WETH gateway +contract LidoV1GatewayUnitTest is Test { + using Address for address payable; + + LidoV1Gateway gateway; + + TokensTestSuite tokensTestSuite; + address weth; + address stETH; + + address user = makeAddr("USER"); + uint256 wethAmountIn = 1 ether; + uint256 stETHAmountOut = 0.8 ether; + address referral = makeAddr("REFERRAL"); + + function setUp() public { + tokensTestSuite = new TokensTestSuite(); + + weth = tokensTestSuite.wethToken(); + deal(weth, 100 ether); + + stETH = address(new ERC20Mock("staked ETH", "stETH", 18)); + vm.mockCall(stETH, wethAmountIn, abi.encodeCall(IstETH.submit, (referral)), abi.encode(stETHAmountOut)); + + gateway = new LidoV1Gateway(weth, stETH); + } + + /// @notice U:[LWG-1]: Constructor works as expected + function test_U_LWG_01_constructor_works_as_expected() public { + vm.expectRevert(ZeroAddressException.selector); + new LidoV1Gateway(address(0), stETH); + + vm.expectRevert(ZeroAddressException.selector); + new LidoV1Gateway(weth, address(0)); + + assertEq(gateway.weth(), weth, "Incorrect weth"); + assertEq(gateway.stETH(), stETH, "Incorrect stETH"); + } + + /// @notice U:[LWG-2]: `receive` works as expected + function test_U_LWG_02_receive_works_as_expected() public { + vm.expectRevert(ReceiveIsNotAllowedException.selector); + payable(gateway).sendValue(1 ether); + + deal(weth, 1 ether); + vm.prank(weth); + payable(gateway).sendValue(1 ether); + + assertEq(address(gateway).balance, 1 ether); + } + + /// @notice U:[LWG-3]: `submit` works as expected + function test_U_LWG_03_submit_works_as_expected() public { + deal({token: weth, to: user, give: wethAmountIn}); + deal({token: stETH, to: address(gateway), give: stETHAmountOut}); + tokensTestSuite.approve(weth, user, address(gateway), wethAmountIn); + + vm.expectCall(weth, abi.encodeCall(IWETH.withdraw, (wethAmountIn))); + vm.expectCall(stETH, wethAmountIn, abi.encodeCall(IstETH.submit, (referral))); + + vm.prank(user); + uint256 value = gateway.submit(wethAmountIn, referral); + assertEq(value, stETHAmountOut, "Incorrect stETH amount"); + assertEq(tokensTestSuite.balanceOf(weth, user), 0, "Incorrect WETH balance"); + assertEq(tokensTestSuite.balanceOf(stETH, user), stETHAmountOut, "Incorrect stETH balance"); + } +} diff --git a/foundry.toml b/foundry.toml index a963eccd..0808fa22 100644 --- a/foundry.toml +++ b/foundry.toml @@ -11,4 +11,4 @@ block_timestamp = 1640000000 gas_limit = 9223372036854775807 # the gas limit in tests block_base_fee_per_gas = 100 fs_permissions = [{ access = "read-write", path = "./"}] -evm_version="shanghai" \ No newline at end of file +evm_version = "shanghai"