From 9413574ec61b3bff0949c1b42c57321509dfeea5 Mon Sep 17 00:00:00 2001 From: Dima Lekhovitsky Date: Sat, 14 Oct 2023 18:09:56 +0300 Subject: [PATCH] test: adapters unit tests vol. 2 Includes unit tests for Aave, Compound and some files under `helpers/`. Also contains some test fixes and naming standardization. --- .github/workflows/pr.yml | 2 +- contracts/adapters/AbstractAdapter.sol | 36 +-- .../aave/AaveV2_LendingPoolAdapter.sol | 44 ++-- .../aave/AaveV2_WrappedATokenAdapter.sol | 82 +++---- .../compound/CompoundV2_CErc20Adapter.sol | 42 ++-- .../compound/CompoundV2_CEtherAdapter.sol | 54 ++--- .../compound/CompoundV2_CTokenAdapter.sol | 42 ++-- contracts/adapters/lido/LidoV1.sol | 30 +-- contracts/adapters/lido/WstETHV1.sol | 40 ++-- contracts/adapters/yearn/YearnV2.sol | 56 ++--- .../compound/CompoundV2_CEtherGateway.sol | 29 +-- contracts/helpers/lido/LidoV1_WETHGateway.sol | 15 +- ...anagerMock.sol => CreditManagerV3Mock.sol} | 4 +- .../adapters-new/AdapterUnitTestHelper.sol | 8 +- .../aave/AaveV2_LendingPoolAdapter.unit.t.sol | 145 ++++++++++++ .../AaveV2_WrappedATokenAdapter.unit.t.sol | 220 ++++++++++++++++++ .../CompoundV2_CErc20Adapter.unit.t.sol | 179 ++++++++++++++ .../CompoundV2_CEtherAdapter.unit.t.sol | 184 +++++++++++++++ .../lido/LidoV1Adapter.unit.t.sol | 18 +- .../lido/WstETHV1Adapter.unit.t.sol | 26 +-- .../uniswap/UniswapV3Adapter.unit.t.sol | 2 +- .../yearn/YearnV2Adapter.unit.t.sol | 38 +-- .../unit/adapters/AbstractAdapter.unit.t.sol | 171 -------------- .../unit/adapters/AbstractAdapterHarness.sol | 48 ---- .../CompoundV2_CEtherGateway.unit.t.sol | 158 +++++++++++++ .../lido/LidoV1_WETHGateway.unit.t.sol | 88 +++++++ 26 files changed, 1265 insertions(+), 496 deletions(-) rename contracts/test/mocks/credit/{CreditManagerMock.sol => CreditManagerV3Mock.sol} (92%) create mode 100644 contracts/test/unit/adapters-new/aave/AaveV2_LendingPoolAdapter.unit.t.sol create mode 100644 contracts/test/unit/adapters-new/aave/AaveV2_WrappedATokenAdapter.unit.t.sol create mode 100644 contracts/test/unit/adapters-new/compound/CompoundV2_CErc20Adapter.unit.t.sol create mode 100644 contracts/test/unit/adapters-new/compound/CompoundV2_CEtherAdapter.unit.t.sol delete mode 100644 contracts/test/unit/adapters/AbstractAdapter.unit.t.sol delete mode 100644 contracts/test/unit/adapters/AbstractAdapterHarness.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..043deb3e 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -34,7 +34,7 @@ jobs: version: nightly - 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 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/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/lido/LidoV1.sol b/contracts/adapters/lido/LidoV1.sol index a8c26c2d..5ada93fc 100644 --- a/contracts/adapters/lido/LidoV1.sol +++ b/contracts/adapters/lido/LidoV1.sol @@ -43,15 +43,15 @@ contract LidoV1Adapter is AbstractAdapter, ILidoV1Adapter { /// @param _creditManager Credit manager address /// @param _lidoGateway Lido gateway address constructor(address _creditManager, address _lidoGateway) - AbstractAdapter(_creditManager, _lidoGateway) // U:[LDO-1] + AbstractAdapter(_creditManager, _lidoGateway) // U:[LDO1-1] { - stETH = LidoV1Gateway(payable(_lidoGateway)).stETH(); // U:[LDO-1] - stETHTokenMask = _getMaskOrRevert(stETH); // U:[LDO-1] + stETH = LidoV1Gateway(payable(_lidoGateway)).stETH(); // U:[LDO1-1] + stETHTokenMask = _getMaskOrRevert(stETH); // U:[LDO1-1] - weth = LidoV1Gateway(payable(_lidoGateway)).weth(); // U:[LDO-1] - wethTokenMask = _getMaskOrRevert(weth); // U:[LDO-1] + weth = LidoV1Gateway(payable(_lidoGateway)).weth(); // U:[LDO1-1] + wethTokenMask = _getMaskOrRevert(weth); // U:[LDO1-1] - treasury = IAddressProviderV3(addressProvider).getAddressOrRevert(AP_TREASURY, NO_VERSION_CONTROL); // U:[LDO-1] + treasury = IAddressProviderV3(addressProvider).getAddressOrRevert(AP_TREASURY, NO_VERSION_CONTROL); // U:[LDO1-1] } /// @notice Stakes given amount of WETH in Lido via Gateway @@ -60,10 +60,10 @@ contract LidoV1Adapter is AbstractAdapter, ILidoV1Adapter { function submit(uint256 amount) external override - creditFacadeOnly // U:[LDO-2] + creditFacadeOnly // U:[LDO1-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _submit(amount, false); // U:[LDO-3] + (tokensToEnable, tokensToDisable) = _submit(amount, false); // U:[LDO1-3] } /// @notice Stakes the entire balance of WETH in Lido via Gateway, disables WETH @@ -71,15 +71,15 @@ contract LidoV1Adapter is AbstractAdapter, ILidoV1Adapter { function submitAll() external override - creditFacadeOnly // U:[LDO-2] + creditFacadeOnly // U:[LDO1-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // U:[LDO-4] + address creditAccount = _creditAccount(); // U:[LDO1-4] - uint256 balance = IERC20(weth).balanceOf(creditAccount); // U:[LDO-4] + uint256 balance = IERC20(weth).balanceOf(creditAccount); // U:[LDO1-4] if (balance > 1) { unchecked { - (tokensToEnable, tokensToDisable) = _submit(balance - 1, true); // U:[LDO-4] + (tokensToEnable, tokensToDisable) = _submit(balance - 1, true); // U:[LDO1-4] } } } @@ -92,9 +92,9 @@ contract LidoV1Adapter is AbstractAdapter, ILidoV1Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _approveToken(weth, type(uint256).max); // U:[LDO-3,4] - _execute(abi.encodeCall(LidoV1Gateway.submit, (amount, treasury))); // U:[LDO-3,4] - _approveToken(weth, 1); // U:[LDO-3,4] + _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 34b808df..da4dab0d 100644 --- a/contracts/adapters/lido/WstETHV1.sol +++ b/contracts/adapters/lido/WstETHV1.sol @@ -30,11 +30,11 @@ contract WstETHV1Adapter is AbstractAdapter, IwstETHV1Adapter { /// @param _creditManager Credit manager address /// @param _wstETH wstETH token address constructor(address _creditManager, address _wstETH) - AbstractAdapter(_creditManager, _wstETH) // U:[WST-1] + AbstractAdapter(_creditManager, _wstETH) // U:[LDO1W-1] { - stETH = IwstETH(_wstETH).stETH(); // U:[WST-1] - wstETHTokenMask = _getMaskOrRevert(_wstETH); // U:[WST-1] - stETHTokenMask = _getMaskOrRevert(stETH); // U:[WST-1] + stETH = IwstETH(_wstETH).stETH(); // U:[LDO1W-1] + wstETHTokenMask = _getMaskOrRevert(_wstETH); // U:[LDO1W-1] + stETHTokenMask = _getMaskOrRevert(stETH); // U:[LDO1W-1] } // ---- // @@ -46,25 +46,25 @@ contract WstETHV1Adapter is AbstractAdapter, IwstETHV1Adapter { function wrap(uint256 amount) external override - creditFacadeOnly // U:[WST-2] + creditFacadeOnly // U:[LDO1W-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _wrap(amount, false); // U:[WST-3] + (tokensToEnable, tokensToDisable) = _wrap(amount, false); // U:[LDO1W-3] } /// @notice Wraps the entire balance of stETH into wstETH, disables stETH function wrapAll() external override - creditFacadeOnly // U:[WST-2] + creditFacadeOnly // U:[LDO1W-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // U:[WST-4] + address creditAccount = _creditAccount(); // U:[LDO1W-4] - uint256 balance = IERC20(stETH).balanceOf(creditAccount); // U:[WST-4] + uint256 balance = IERC20(stETH).balanceOf(creditAccount); // U:[LDO1W-4] if (balance > 1) { unchecked { - (tokensToEnable, tokensToDisable) = _wrap(balance - 1, true); // U:[WST-4] + (tokensToEnable, tokensToDisable) = _wrap(balance - 1, true); // U:[LDO1W-4] } } } @@ -77,9 +77,9 @@ contract WstETHV1Adapter is AbstractAdapter, IwstETHV1Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _approveToken(stETH, type(uint256).max); // U:[WST-3,4] - _execute(abi.encodeCall(IwstETH.wrap, (amount))); // U:[WST-3,4] - _approveToken(stETH, 1); // U:[WST-3,4] + _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); } @@ -92,25 +92,25 @@ contract WstETHV1Adapter is AbstractAdapter, IwstETHV1Adapter { function unwrap(uint256 amount) external override - creditFacadeOnly // U:[WST-2] + creditFacadeOnly // U:[LDO1W-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _unwrap(amount, false); // U:[WST-5] + (tokensToEnable, tokensToDisable) = _unwrap(amount, false); // U:[LDO1W-5] } /// @notice Unwraps the entire balance of wstETH to stETH, disables wstETH function unwrapAll() external override - creditFacadeOnly // U:[WST-2] + creditFacadeOnly // U:[LDO1W-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // U:[WST-6] + address creditAccount = _creditAccount(); // U:[LDO1W-6] - uint256 balance = IERC20(targetContract).balanceOf(creditAccount); // U:[WST-6] + uint256 balance = IERC20(targetContract).balanceOf(creditAccount); // U:[LDO1W-6] if (balance > 1) { unchecked { - (tokensToEnable, tokensToDisable) = _unwrap(balance - 1, true); // U:[WST-6] + (tokensToEnable, tokensToDisable) = _unwrap(balance - 1, true); // U:[LDO1W-6] } } } @@ -123,7 +123,7 @@ contract WstETHV1Adapter is AbstractAdapter, IwstETHV1Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _execute(abi.encodeCall(IwstETH.unwrap, (amount))); // U:[WST-5,6] + _execute(abi.encodeCall(IwstETH.unwrap, (amount))); // U:[LDO1W-5,6] (tokensToEnable, tokensToDisable) = (stETHTokenMask, disableWstETH ? wstETHTokenMask : 0); } } diff --git a/contracts/adapters/yearn/YearnV2.sol b/contracts/adapters/yearn/YearnV2.sol index 979902d7..05a55416 100644 --- a/contracts/adapters/yearn/YearnV2.sol +++ b/contracts/adapters/yearn/YearnV2.sol @@ -30,11 +30,11 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { /// @param _creditManager Credit manager address /// @param _vault Yearn vault address constructor(address _creditManager, address _vault) - AbstractAdapter(_creditManager, _vault) // U:[YFI-1] + AbstractAdapter(_creditManager, _vault) // U:[YFI2-1] { - token = IYVault(targetContract).token(); // U:[YFI-1] - tokenMask = _getMaskOrRevert(token); // U:[YFI-1] - yTokenMask = _getMaskOrRevert(_vault); // U:[YFI-1] + token = IYVault(targetContract).token(); // U:[YFI2-1] + tokenMask = _getMaskOrRevert(token); // U:[YFI2-1] + yTokenMask = _getMaskOrRevert(_vault); // U:[YFI2-1] } // -------- // @@ -45,15 +45,15 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { function deposit() external override - creditFacadeOnly // U:[YFI-2] + creditFacadeOnly // U:[YFI2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // U:[YFI-3] + address creditAccount = _creditAccount(); // U:[YFI2-3] - uint256 balance = IERC20(token).balanceOf(creditAccount); // U:[YFI-3] + uint256 balance = IERC20(token).balanceOf(creditAccount); // U:[YFI2-3] if (balance > 1) { unchecked { - (tokensToEnable, tokensToDisable) = _deposit(balance - 1, true); // U:[YFI-3] + (tokensToEnable, tokensToDisable) = _deposit(balance - 1, true); // U:[YFI2-3] } } } @@ -63,10 +63,10 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { function deposit(uint256 amount) external override - creditFacadeOnly // U:[YFI-2] + creditFacadeOnly // U:[YFI2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _deposit(amount, false); // U:[YFI-4] + (tokensToEnable, tokensToDisable) = _deposit(amount, false); // U:[YFI2-4] } /// @notice Deposit given amount of underlying tokens into the vault @@ -75,10 +75,10 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { function deposit(uint256 amount, address) external override - creditFacadeOnly // U:[YFI-2] + creditFacadeOnly // U:[YFI2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _deposit(amount, false); // U:[YFI-5] + (tokensToEnable, tokensToDisable) = _deposit(amount, false); // U:[YFI2-5] } /// @dev Internal implementation of `deposit` functions @@ -89,9 +89,9 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _approveToken(token, type(uint256).max); // U:[YFI-3,4,5] - _execute(abi.encodeWithSignature("deposit(uint256)", amount)); // U:[YFI-3,4,5] - _approveToken(token, 1); // U:[YFI-3,4,5] + _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); } @@ -103,16 +103,16 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { function withdraw() external override - creditFacadeOnly // U:[YFI-2] + creditFacadeOnly // U:[YFI2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // U:[YFI-6] + address creditAccount = _creditAccount(); // U:[YFI2-6] - uint256 balance = IERC20(targetContract).balanceOf(creditAccount); // U:[YFI-6] + uint256 balance = IERC20(targetContract).balanceOf(creditAccount); // U:[YFI2-6] if (balance > 1) { unchecked { - (tokensToEnable, tokensToDisable) = _withdraw(balance - 1, true); // U:[YFI-6] + (tokensToEnable, tokensToDisable) = _withdraw(balance - 1, true); // U:[YFI2-6] } } } @@ -122,10 +122,10 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { function withdraw(uint256 maxShares) external override - creditFacadeOnly // U:[YFI-2] + creditFacadeOnly // U:[YFI2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _withdraw(maxShares, false); // U:[YFI-7] + (tokensToEnable, tokensToDisable) = _withdraw(maxShares, false); // U:[YFI2-7] } /// @notice Burn given amount of yTokens to withdraw corresponding amount of underlying from the vault @@ -134,10 +134,10 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { function withdraw(uint256 maxShares, address) external override - creditFacadeOnly // U:[YFI-2] + creditFacadeOnly // U:[YFI2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - (tokensToEnable, tokensToDisable) = _withdraw(maxShares, false); // U:[YFI-8] + (tokensToEnable, tokensToDisable) = _withdraw(maxShares, false); // U:[YFI2-8] } /// @notice Burn given amount of yTokens to withdraw corresponding amount of underlying from the vault @@ -147,11 +147,11 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { function withdraw(uint256 maxShares, address, uint256 maxLoss) external override - creditFacadeOnly // U:[YFI-2] + creditFacadeOnly // U:[YFI2-2] returns (uint256 tokensToEnable, uint256 tokensToDisable) { - address creditAccount = _creditAccount(); // U:[YFI-9] - (tokensToEnable, tokensToDisable) = _withdraw(maxShares, creditAccount, maxLoss); // U:[YFI-9] + address creditAccount = _creditAccount(); // U:[YFI2-9] + (tokensToEnable, tokensToDisable) = _withdraw(maxShares, creditAccount, maxLoss); // U:[YFI2-9] } /// @dev Internal implementation of `withdraw` functions @@ -162,7 +162,7 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _execute(abi.encodeWithSignature("withdraw(uint256)", maxShares)); // U:[YFI-6,7,8] + _execute(abi.encodeWithSignature("withdraw(uint256)", maxShares)); // U:[YFI2-6,7,8] (tokensToEnable, tokensToDisable) = (tokenMask, disableTokenIn ? yTokenMask : 0); } @@ -174,7 +174,7 @@ contract YearnV2Adapter is AbstractAdapter, IYearnV2Adapter { internal returns (uint256 tokensToEnable, uint256 tokensToDisable) { - _execute(abi.encodeWithSignature("withdraw(uint256,address,uint256)", maxShares, creditAccount, maxLoss)); // U:[YFI-9] + _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 92% rename from contracts/test/mocks/credit/CreditManagerMock.sol rename to contracts/test/mocks/credit/CreditManagerV3Mock.sol index 8f603204..984fd460 100644 --- a/contracts/test/mocks/credit/CreditManagerMock.sol +++ b/contracts/test/mocks/credit/CreditManagerV3Mock.sol @@ -3,12 +3,12 @@ // (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; diff --git a/contracts/test/unit/adapters-new/AdapterUnitTestHelper.sol b/contracts/test/unit/adapters-new/AdapterUnitTestHelper.sol index e63a7496..1eae5734 100644 --- a/contracts/test/unit/adapters-new/AdapterUnitTestHelper.sol +++ b/contracts/test/unit/adapters-new/AdapterUnitTestHelper.sol @@ -14,13 +14,13 @@ 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 {CreditManagerMock, CreditManagerMockEvents} from "../../mocks/credit/CreditManagerMock.sol"; +import {CreditManagerV3Mock, CreditManagerV3MockEvents} from "../../mocks/credit/CreditManagerV3Mock.sol"; -contract AdapterUnitTestHelper is Test, CreditManagerMockEvents { +contract AdapterUnitTestHelper is Test, CreditManagerV3MockEvents { address configurator; address creditFacade; address creditAccount; - CreditManagerMock creditManager; + CreditManagerV3Mock creditManager; AddressProviderV3ACLMock addressProvider; address[8] tokens; @@ -33,7 +33,7 @@ contract AdapterUnitTestHelper is Test, CreditManagerMockEvents { vm.prank(configurator); addressProvider = new AddressProviderV3ACLMock(); - creditManager = new CreditManagerMock(address(addressProvider), creditFacade); + creditManager = new CreditManagerV3Mock(address(addressProvider), creditFacade); for (uint256 i; i < tokens.length; ++i) { string memory name = string.concat("Test Token ", vm.toString(i)); diff --git a/contracts/test/unit/adapters-new/aave/AaveV2_LendingPoolAdapter.unit.t.sol b/contracts/test/unit/adapters-new/aave/AaveV2_LendingPoolAdapter.unit.t.sol new file mode 100644 index 00000000..f5f61b17 --- /dev/null +++ b/contracts/test/unit/adapters-new/aave/AaveV2_LendingPoolAdapter.unit.t.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: UNLICENSED +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {AaveV2_LendingPoolAdapter} from "../../../../adapters/aave/AaveV2_LendingPoolAdapter.sol"; +import {ILendingPool, DataTypes} from "../../../../integrations/aave/ILendingPool.sol"; +import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; + +/// @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; + + address lendingPool; + + function setUp() public { + _setUp(); + + lendingPool = makeAddr("LENDING_POOL"); + + DataTypes.ReserveData memory data; + data.aTokenAddress = tokens[1]; + vm.mockCall(lendingPool, abi.encodeCall(ILendingPool.getReserveData, (tokens[0])), abi.encode(data)); + + adapter = new AaveV2_LendingPoolAdapter(address(creditManager), lendingPool); + } + + /// @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 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); + + _revertsOnNonFacadeCaller(); + adapter.depositAll(address(0)); + + _revertsOnNonFacadeCaller(); + adapter.withdraw(address(0), 0, address(0)); + + _revertsOnNonFacadeCaller(); + adapter.withdrawAll(address(0)); + } + + /// @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 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}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[0], + tokenOut: tokens[1], + callData: abi.encodeCall(ILendingPool.deposit, (tokens[0], 1000, creditAccount, 0)), + requiresApproval: true, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.depositAll(tokens[0]); + + assertEq(tokensToEnable, 2, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 1, "Incorrect tokensToDisable"); + } + + /// @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 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"); + } + + /// @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}); + + _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.withdrawAll(tokens[0]); + + assertEq(tokensToEnable, 1, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, 2, "Incorrect tokensToDisable"); + } +} diff --git a/contracts/test/unit/adapters-new/aave/AaveV2_WrappedATokenAdapter.unit.t.sol b/contracts/test/unit/adapters-new/aave/AaveV2_WrappedATokenAdapter.unit.t.sol new file mode 100644 index 00000000..a175c966 --- /dev/null +++ b/contracts/test/unit/adapters-new/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-new/compound/CompoundV2_CErc20Adapter.unit.t.sol b/contracts/test/unit/adapters-new/compound/CompoundV2_CErc20Adapter.unit.t.sol new file mode 100644 index 00000000..127a49e6 --- /dev/null +++ b/contracts/test/unit/adapters-new/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-new/compound/CompoundV2_CEtherAdapter.unit.t.sol b/contracts/test/unit/adapters-new/compound/CompoundV2_CEtherAdapter.unit.t.sol new file mode 100644 index 00000000..f9c0b418 --- /dev/null +++ b/contracts/test/unit/adapters-new/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-new/lido/LidoV1Adapter.unit.t.sol b/contracts/test/unit/adapters-new/lido/LidoV1Adapter.unit.t.sol index 6328dd30..5943e736 100644 --- a/contracts/test/unit/adapters-new/lido/LidoV1Adapter.unit.t.sol +++ b/contracts/test/unit/adapters-new/lido/LidoV1Adapter.unit.t.sol @@ -9,7 +9,7 @@ import {LidoV1Gateway} from "../../../../helpers/lido/LidoV1_WETHGateway.sol"; import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; /// @title Lido v1 adapter unit test -/// @notice U:[LDO]: Unit tests for Lido v1 adapter +/// @notice U:[LDO1]: Unit tests for Lido v1 adapter contract LidoV1AdapterUnitTest is AdapterUnitTestHelper { LidoV1Adapter adapter; @@ -40,8 +40,8 @@ contract LidoV1AdapterUnitTest is AdapterUnitTestHelper { adapter = new LidoV1Adapter(address(creditManager), gateway); } - /// @notice U:[LDO-1]: Constructor works as expected - function test_U_LDO_01_constructor_works_as_expected() public { + /// @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); @@ -56,8 +56,8 @@ contract LidoV1AdapterUnitTest is AdapterUnitTestHelper { assertEq(adapter.treasury(), treasury, "Incorrect treasury"); } - /// @notice U:[LDO-2]: Wrapper functions revert on wrong caller - function test_U_LDO_02_wrapper_functions_revert_on_wrong_caller() public { + /// @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); @@ -65,8 +65,8 @@ contract LidoV1AdapterUnitTest is AdapterUnitTestHelper { adapter.submitAll(); } - /// @notice U:[LDO-3]: `submit` works as expected - function test_U_LDO_03_submit_works_as_expected() public { + /// @notice U:[LDO1-3]: `submit` works as expected + function test_U_LDO1_03_submit_works_as_expected() public { _executesSwap({ tokenIn: weth, tokenOut: stETH, @@ -81,8 +81,8 @@ contract LidoV1AdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); } - /// @notice U:[LDO-4]: `submitAll` works as expected - function test_U_LDO_04_submitAll_works_as_expected() public { + /// @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(); diff --git a/contracts/test/unit/adapters-new/lido/WstETHV1Adapter.unit.t.sol b/contracts/test/unit/adapters-new/lido/WstETHV1Adapter.unit.t.sol index ac8bb896..86fe4d20 100644 --- a/contracts/test/unit/adapters-new/lido/WstETHV1Adapter.unit.t.sol +++ b/contracts/test/unit/adapters-new/lido/WstETHV1Adapter.unit.t.sol @@ -8,7 +8,7 @@ import {IwstETH, IwstETHGetters} from "../../../../integrations/lido/IwstETH.sol import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; /// @title wstETH v1 adapter unit test -/// @notice U:[WST]: Unit tests for wstETH v1 adapter +/// @notice U:[LDO1W]: Unit tests for wstETH v1 adapter contract WstETHV1AdapterUnitTest is AdapterUnitTestHelper { WstETHV1Adapter adapter; @@ -28,8 +28,8 @@ contract WstETHV1AdapterUnitTest is AdapterUnitTestHelper { adapter = new WstETHV1Adapter(address(creditManager), wstETH); } - /// @notice U:[WST-1]: Constructor works as expected - function test_U_WST_01_constructor_works_as_expected() public { + /// @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); @@ -42,8 +42,8 @@ contract WstETHV1AdapterUnitTest is AdapterUnitTestHelper { assertEq(adapter.wstETHTokenMask(), wstETHMask, "Incorrect wstETHMask"); } - /// @notice U:[WST-2]: Wrapper functions revert on wrong caller - function test_U_WST_02_wrapper_functions_revert_on_wrong_caller() public { + /// @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); @@ -57,8 +57,8 @@ contract WstETHV1AdapterUnitTest is AdapterUnitTestHelper { adapter.unwrapAll(); } - /// @notice U:[WST-3]: `wrap` works as expected - function test_U_WST_03_wrap_works_as_expected() public { + /// @notice U:[LDO1W-3]: `wrap` works as expected + function test_U_LDO1W_03_wrap_works_as_expected() public { _executesSwap({ tokenIn: stETH, tokenOut: wstETH, @@ -73,8 +73,8 @@ contract WstETHV1AdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); } - /// @notice U:[WST-4]: `wrapAll` works as expected - function test_U_WST_04_wrapAll_works_as_expected() public { + /// @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(); @@ -92,8 +92,8 @@ contract WstETHV1AdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, stETHMask, "Incorrect tokensToDisable"); } - /// @notice U:[WST-5]: `unwrap` works as expected - function test_U_WST_05_unwrap_works_as_expected() public { + /// @notice U:[LDO1W-5]: `unwrap` works as expected + function test_U_LDO1W_05_unwrap_works_as_expected() public { _executesSwap({ tokenIn: wstETH, tokenOut: stETH, @@ -108,8 +108,8 @@ contract WstETHV1AdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); } - /// @notice U:[WST-6]: `unwrapAll` works as expected - function test_U_WST_06_unwrapAll_works_as_expected() public { + /// @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(); diff --git a/contracts/test/unit/adapters-new/uniswap/UniswapV3Adapter.unit.t.sol b/contracts/test/unit/adapters-new/uniswap/UniswapV3Adapter.unit.t.sol index 3b742a0a..10f2e3cb 100644 --- a/contracts/test/unit/adapters-new/uniswap/UniswapV3Adapter.unit.t.sol +++ b/contracts/test/unit/adapters-new/uniswap/UniswapV3Adapter.unit.t.sol @@ -15,7 +15,7 @@ import {UniswapV3AdapterHarness} from "./UniswapV3Adapter.harness.sol"; /// @title Uniswap v3 adapter unit test /// @notice U:[UNI3]: Unit tests for Uniswap v3 swap router adapter -contract UniswapV2AdapterUnitTest is +contract UniswapV3AdapterUnitTest is AdapterUnitTestHelper, IUniswapV3AdapterEvents, IUniswapV3AdapterExceptions, diff --git a/contracts/test/unit/adapters-new/yearn/YearnV2Adapter.unit.t.sol b/contracts/test/unit/adapters-new/yearn/YearnV2Adapter.unit.t.sol index 75681f30..9422587d 100644 --- a/contracts/test/unit/adapters-new/yearn/YearnV2Adapter.unit.t.sol +++ b/contracts/test/unit/adapters-new/yearn/YearnV2Adapter.unit.t.sol @@ -8,7 +8,7 @@ import {IYVault} from "../../../../integrations/yearn/IYVault.sol"; import {AdapterUnitTestHelper} from "../AdapterUnitTestHelper.sol"; /// @title Yearn v2 adapter unit test -/// @notice U:[YFI]: Unit tests for Yearn v2 yToken adapter +/// @notice U:[YFI2]: Unit tests for Yearn v2 yToken adapter contract YearnV2AdapterUnitTest is AdapterUnitTestHelper { YearnV2Adapter adapter; @@ -28,8 +28,8 @@ contract YearnV2AdapterUnitTest is AdapterUnitTestHelper { adapter = new YearnV2Adapter(address(creditManager), yToken); } - /// @notice U:[YFI-1]: Constructor works as expected - function test_U_YFI_01_constructor_works_as_expected() public { + /// @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); @@ -42,8 +42,8 @@ contract YearnV2AdapterUnitTest is AdapterUnitTestHelper { assertEq(adapter.yTokenMask(), yTokenMask, "Incorrect yTokenMask"); } - /// @notice U:[YFI-2]: Wrapper functions revert on wrong caller - function test_U_YFI_02_wrapper_functions_revert_on_wrong_caller() public { + /// @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(); @@ -66,8 +66,8 @@ contract YearnV2AdapterUnitTest is AdapterUnitTestHelper { adapter.withdraw(0, address(0), 0); } - /// @notice U:[YFI-3]: `deposit()` works as expected - function test_U_YFI_03_deposit_works_as_expected() public { + /// @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(); @@ -85,8 +85,8 @@ contract YearnV2AdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, tokenMask, "Incorrect tokensToDisable"); } - /// @notice U:[YFI-4]: `deposit(uint256)` works as expected - function test_U_YFI_04_deposit_uint256_works_as_expected() public { + /// @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, @@ -101,8 +101,8 @@ contract YearnV2AdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); } - /// @notice U:[YFI-5]: `deposit(uint256,address)` works as expected - function test_U_YFI_05_deposit_uint256_address_works_as_expected() public { + /// @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, @@ -117,8 +117,8 @@ contract YearnV2AdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); } - /// @notice U:[YFI-6]: `withdraw()` works as expected - function test_U_YFI_06_withdraw_works_as_expected() public { + /// @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(); @@ -136,8 +136,8 @@ contract YearnV2AdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, yTokenMask, "Incorrect tokensToDisable"); } - /// @notice U:[TV-7]: `withdraw(uint256)` works as expected - function test_U_YFI_07_withdraw_uint256_works_as_expected() public { + /// @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, @@ -152,8 +152,8 @@ contract YearnV2AdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); } - /// @notice U:[YFI-8]: `withdraw(uint256,address)` works as expected - function test_U_YFI_08_withdraw_uint256_address_works_as_expected() public { + /// @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, @@ -168,8 +168,8 @@ contract YearnV2AdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); } - /// @notice U:[YFI-9]: `withdraw(uint256,address,uint256)` works as expected - function test_U_YFI_09_withdraw_uint256_address_uint256_works_as_expected() public { + /// @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, 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/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"); + } +}