diff --git a/contracts/adapters/convex/ConvexV1_Booster.sol b/contracts/adapters/convex/ConvexV1_Booster.sol index fbe53178..91902171 100644 --- a/contracts/adapters/convex/ConvexV1_Booster.sol +++ b/contracts/adapters/convex/ConvexV1_Booster.sol @@ -51,10 +51,10 @@ contract ConvexV1BoosterAdapter is AbstractAdapter, IConvexV1BoosterAdapter { } /// @notice Deposits the entire balance of Curve LP tokens into Booster, except the specified amount - /// @param leftoverAmount Amount of Curve LP to keep on the account /// @param _pid ID of the pool to deposit to + /// @param leftoverAmount Amount of Curve LP to keep on the account /// @param _stake Whether to stake Convex LP tokens in the rewards pool - function depositDiff(uint256 leftoverAmount, uint256 _pid, bool _stake) + function depositDiff(uint256 _pid, uint256 leftoverAmount, bool _stake) external override creditFacadeOnly @@ -135,9 +135,9 @@ contract ConvexV1BoosterAdapter is AbstractAdapter, IConvexV1BoosterAdapter { } /// @notice Withdraws all Curve LP tokens from Booster, except the specified amount - /// @param leftoverAmount Amount of Convex LP to keep on the account /// @param _pid ID of the pool to withdraw from - function withdrawDiff(uint256 leftoverAmount, uint256 _pid) + /// @param leftoverAmount Amount of Convex LP to keep on the account + function withdrawDiff(uint256 _pid, uint256 leftoverAmount) external override creditFacadeOnly @@ -173,7 +173,7 @@ contract ConvexV1BoosterAdapter is AbstractAdapter, IConvexV1BoosterAdapter { if (balance > leftoverAmount) { unchecked { - (tokensToEnable, tokensToDisable,) = _executeSwapSafeApprove( + (tokensToEnable, tokensToDisable,) = _executeSwapNoApprove( tokenIn, tokenOut, abi.encodeCall(IBooster.withdraw, (_pid, balance - leftoverAmount)), diff --git a/contracts/test/unit/adapters/AdapterUnitTestHelper.sol b/contracts/test/unit/adapters/AdapterUnitTestHelper.sol index 21015bda..ebe58a29 100644 --- a/contracts/test/unit/adapters/AdapterUnitTestHelper.sol +++ b/contracts/test/unit/adapters/AdapterUnitTestHelper.sol @@ -26,6 +26,11 @@ contract AdapterUnitTestHelper is Test, CreditManagerV3MockEvents { address[8] tokens; + uint256 diffMintedAmount = 1001; + uint256 diffLeftoverAmount; + uint256 diffInputAmount; + bool diffDisableTokenIn; + function _setUp() internal { configurator = makeAddr("CONFIGURATOR"); creditFacade = makeAddr("CREDIT_FACADE"); @@ -47,6 +52,22 @@ contract AdapterUnitTestHelper is Test, CreditManagerV3MockEvents { creditManager.setActiveCreditAccount(creditAccount); } + modifier diffTestCases() { + uint256 snapshot = vm.snapshot(); + + diffLeftoverAmount = 501; + diffInputAmount = 500; + diffDisableTokenIn = false; + _; + + vm.revertTo(snapshot); + + diffLeftoverAmount = 1; + diffInputAmount = 1000; + diffDisableTokenIn = true; + _; + } + function _revertsOnNonConfiguratorCaller() internal { vm.expectRevert(CallerNotConfiguratorException.selector); } diff --git a/contracts/test/unit/adapters/aave/AaveV2_LendingPoolAdapter.unit.t.sol b/contracts/test/unit/adapters/aave/AaveV2_LendingPoolAdapter.unit.t.sol index f5f61b17..efa850b7 100644 --- a/contracts/test/unit/adapters/aave/AaveV2_LendingPoolAdapter.unit.t.sol +++ b/contracts/test/unit/adapters/aave/AaveV2_LendingPoolAdapter.unit.t.sol @@ -38,12 +38,18 @@ contract AaveV2_LendingPoolAdapterUnitTest is AdapterUnitTestHelper { _revertsOnNonFacadeCaller(); adapter.deposit(address(0), 0, address(0), 0); + _revertsOnNonFacadeCaller(); + adapter.depositDiff(address(0), 0); + _revertsOnNonFacadeCaller(); adapter.depositAll(address(0)); _revertsOnNonFacadeCaller(); adapter.withdraw(address(0), 0, address(0)); + _revertsOnNonFacadeCaller(); + adapter.withdrawDiff(address(0), 0); + _revertsOnNonFacadeCaller(); adapter.withdrawAll(address(0)); } @@ -86,6 +92,26 @@ contract AaveV2_LendingPoolAdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, 1, "Incorrect tokensToDisable"); } + /// @notice U:[AAVE2-4A]: `depositDiff` works as expected + function test_U_AAVE2_04A_depositDiff_works_as_expected() public diffTestCases { + deal({token: tokens[0], to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[0], + tokenOut: tokens[1], + callData: abi.encodeCall(ILendingPool.deposit, (tokens[0], diffInputAmount, creditAccount, 0)), + requiresApproval: true, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.depositDiff(tokens[0], diffLeftoverAmount); + + assertEq(tokensToEnable, 2, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? 1 : 0, "Incorrect tokensToDisable"); + } + /// @notice U:[AAVE2-5A]: `withdraw` works as expected function test_U_AAVE2_05A_withdraw_works_as_expected() public { _readsActiveAccount(); @@ -142,4 +168,24 @@ contract AaveV2_LendingPoolAdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToEnable, 1, "Incorrect tokensToEnable"); assertEq(tokensToDisable, 2, "Incorrect tokensToDisable"); } + + /// @notice U:[AAVE2-6A]: `withdrawDiff` works as expected + function test_U_AAVE2_06A_withdrawDiff_works_as_expected() public diffTestCases { + deal({token: tokens[1], to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[1], + tokenOut: tokens[0], + callData: abi.encodeCall(ILendingPool.withdraw, (tokens[0], diffInputAmount, creditAccount)), + requiresApproval: false, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdrawDiff(tokens[0], diffLeftoverAmount); + + assertEq(tokensToEnable, 1, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? 2 : 0, "Incorrect tokensToDisable"); + } } diff --git a/contracts/test/unit/adapters/aave/AaveV2_WrappedATokenAdapter.unit.t.sol b/contracts/test/unit/adapters/aave/AaveV2_WrappedATokenAdapter.unit.t.sol index a175c966..c77f88a2 100644 --- a/contracts/test/unit/adapters/aave/AaveV2_WrappedATokenAdapter.unit.t.sol +++ b/contracts/test/unit/adapters/aave/AaveV2_WrappedATokenAdapter.unit.t.sol @@ -56,24 +56,36 @@ contract AaveV2_WrappedATokenAdapterUnitTest is AdapterUnitTestHelper { _revertsOnNonFacadeCaller(); adapter.deposit(0); + _revertsOnNonFacadeCaller(); + adapter.depositDiff(0); + _revertsOnNonFacadeCaller(); adapter.depositAll(); _revertsOnNonFacadeCaller(); adapter.depositUnderlying(0); + _revertsOnNonFacadeCaller(); + adapter.depositDiffUnderlying(0); + _revertsOnNonFacadeCaller(); adapter.depositAllUnderlying(); _revertsOnNonFacadeCaller(); adapter.withdraw(0); + _revertsOnNonFacadeCaller(); + adapter.withdrawDiff(0); + _revertsOnNonFacadeCaller(); adapter.withdrawAll(); _revertsOnNonFacadeCaller(); adapter.withdrawUnderlying(0); + _revertsOnNonFacadeCaller(); + adapter.withdrawDiffUnderlying(0); + _revertsOnNonFacadeCaller(); adapter.withdrawAllUnderlying(); } @@ -113,6 +125,26 @@ contract AaveV2_WrappedATokenAdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, aTokenMask, "Incorrect tokensToDisable"); } + /// @notice U:[AAVE2-4A]: `depositDiff` works as expected + function test_U_AAVE2_04A_depositDiff_works_as_expected() public diffTestCases { + deal({token: aToken, to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: aToken, + tokenOut: waToken, + callData: abi.encodeCall(WrappedAToken.deposit, (diffInputAmount)), + requiresApproval: true, + validatesTokens: false + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.depositDiff(diffLeftoverAmount); + + assertEq(tokensToEnable, waTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? aTokenMask : 0, "Incorrect tokensToDisable"); + } + /// @notice U:[AAVE2W-5]: `depositUnderlying` works as expected function test_U_AAVE2W_05_depositUnderlying_works_as_expected() public { _executesSwap({ @@ -148,6 +180,26 @@ contract AaveV2_WrappedATokenAdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, tokenMask, "Incorrect tokensToDisable"); } + /// @notice U:[AAVE2-6A]: `depositDiffUnderlying` works as expected + function test_U_AAVE2_06A_depositDiffUnderlying_works_as_expected() public diffTestCases { + deal({token: token, to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: token, + tokenOut: waToken, + callData: abi.encodeCall(WrappedAToken.depositUnderlying, (diffInputAmount)), + requiresApproval: true, + validatesTokens: false + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.depositDiffUnderlying(diffLeftoverAmount); + + assertEq(tokensToEnable, waTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? tokenMask : 0, "Incorrect tokensToDisable"); + } + /// @notice U:[AAVE2W-7]: `withdraw` works as expected function test_U_AAVE2W_07_withdraw_works_as_expected() public { _executesSwap({ @@ -183,6 +235,26 @@ contract AaveV2_WrappedATokenAdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, waTokenMask, "Incorrect tokensToDisable"); } + /// @notice U:[AAVE2-8A]: `withdrawDiff` works as expected + function test_U_AAVE2_08A_withdrawDiff_works_as_expected() public diffTestCases { + deal({token: waToken, to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: waToken, + tokenOut: aToken, + callData: abi.encodeCall(WrappedAToken.withdraw, (diffInputAmount)), + requiresApproval: false, + validatesTokens: false + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdrawDiff(diffLeftoverAmount); + + assertEq(tokensToEnable, aTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? waTokenMask : 0, "Incorrect tokensToDisable"); + } + /// @notice U:[AAVE2W-9]: `withdrawUnderlying` works as expected function test_U_AAVE2W_09_withdrawUnderlying_works_as_expected() public { _executesSwap({ @@ -217,4 +289,24 @@ contract AaveV2_WrappedATokenAdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToEnable, tokenMask, "Incorrect tokensToEnable"); assertEq(tokensToDisable, waTokenMask, "Incorrect tokensToDisable"); } + + /// @notice U:[AAVE2-10A]: `withdrawDiffUnderlying` works as expected + function test_U_AAVE2_10A_withdrawDiffUnderlying_works_as_expected() public diffTestCases { + deal({token: waToken, to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: waToken, + tokenOut: token, + callData: abi.encodeCall(WrappedAToken.withdrawUnderlying, (diffInputAmount)), + requiresApproval: false, + validatesTokens: false + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdrawDiffUnderlying(diffLeftoverAmount); + + assertEq(tokensToEnable, tokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? waTokenMask : 0, "Incorrect tokensToDisable"); + } } diff --git a/contracts/test/unit/adapters/balancer/BalancerV2VaultAdapter.unit.t.sol b/contracts/test/unit/adapters/balancer/BalancerV2VaultAdapter.unit.t.sol index dcd71d7a..aa60e7ee 100644 --- a/contracts/test/unit/adapters/balancer/BalancerV2VaultAdapter.unit.t.sol +++ b/contracts/test/unit/adapters/balancer/BalancerV2VaultAdapter.unit.t.sol @@ -18,6 +18,7 @@ import { IBalancerV2VaultAdapterEvents, IBalancerV2VaultAdapterExceptions, PoolStatus, + SingleSwapDiff, SingleSwapAll } from "../../../../interfaces/balancer/IBalancerV2VaultAdapter.sol"; @@ -64,6 +65,7 @@ contract BalancerV2VaultAdapterUnitTest is int256[] memory limits; SingleSwap memory singleSwap; SingleSwapAll memory singleSwapAll; + SingleSwapDiff memory singleSwapDiff; FundManagement memory fundManagement; BatchSwapStep[] memory batchSwapSteps; JoinPoolRequest memory joinPoolRequest; @@ -72,6 +74,9 @@ contract BalancerV2VaultAdapterUnitTest is _revertsOnNonFacadeCaller(); adapter.swap(singleSwap, fundManagement, 0, 0); + _revertsOnNonFacadeCaller(); + adapter.swapDiff(singleSwapDiff, 0, 0); + _revertsOnNonFacadeCaller(); adapter.swapAll(singleSwapAll, 0, 0); @@ -84,6 +89,9 @@ contract BalancerV2VaultAdapterUnitTest is _revertsOnNonFacadeCaller(); adapter.joinPoolSingleAsset(bytes32(0), asset, 0, 0); + _revertsOnNonFacadeCaller(); + adapter.joinPoolSingleAssetDiff(bytes32(0), asset, 0, 0); + _revertsOnNonFacadeCaller(); adapter.joinPoolSingleAssetAll(bytes32(0), asset, 0); @@ -93,6 +101,9 @@ contract BalancerV2VaultAdapterUnitTest is _revertsOnNonFacadeCaller(); adapter.exitPoolSingleAsset(bytes32(0), asset, 0, 0); + _revertsOnNonFacadeCaller(); + adapter.exitPoolSingleAssetDiff(bytes32(0), asset, 0, 0); + _revertsOnNonFacadeCaller(); adapter.exitPoolSingleAssetAll(bytes32(0), asset, 0); } @@ -173,6 +184,51 @@ contract BalancerV2VaultAdapterUnitTest is assertEq(tokensToDisable, 2, "Incorrect tokensToDisable"); } + /// @notice U:[BAL2-4A]: `swapDiff` works as expected + function test_U_BAL2_04A_swapDiff_works_as_expected() public diffTestCases { + deal({token: tokens[1], to: creditAccount, give: diffMintedAmount}); + + SingleSwapDiff memory singleSwapDiff = SingleSwapDiff({ + poolId: poolId, + assetIn: _asset(1), + assetOut: _asset(2), + leftoverAmount: diffLeftoverAmount, + userData: "DUMMY DATA" + }); + + SingleSwap memory singleSwap = SingleSwap({ + poolId: poolId, + kind: SwapKind.GIVEN_IN, + assetIn: _asset(1), + assetOut: _asset(2), + amount: diffInputAmount, + userData: "DUMMY DATA" + }); + + vm.expectRevert(PoolNotSupportedException.selector); + vm.prank(creditFacade); + adapter.swapDiff(singleSwapDiff, 0, 0); + + vm.prank(configurator); + adapter.setPoolStatus(poolId, PoolStatus.ALLOWED); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[1], + tokenOut: tokens[2], + callData: abi.encodeCall( + IBalancerV2Vault.swap, (singleSwap, _getFundManagement(creditAccount), diffInputAmount / 2, 456) + ), + requiresApproval: true, + validatesTokens: true + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.swapDiff(singleSwapDiff, 0.5e27, 456); + + assertEq(tokensToEnable, 4, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? 2 : 0, "Incorrect tokensToDisable"); + } + /// @notice U:[BAL2-5]: `batchSwap` works as expected function test_U_BAL2_05_batchSwap_works_as_expected() public { BatchSwapStep[] memory swaps = new BatchSwapStep[](2); @@ -347,6 +403,48 @@ contract BalancerV2VaultAdapterUnitTest is assertEq(tokensToDisable, 4, "Incorrect tokensToDisable"); } + /// @notice U:[BAL2-8A]: `joinPoolSingleAssetDiff` works as expected + function test_U_BAL2_08A_joinPoolSingleAssetDiff_works_as_expected() public diffTestCases { + deal({token: tokens[2], to: creditAccount, give: diffMintedAmount}); + + vm.expectRevert(PoolNotSupportedException.selector); + vm.prank(creditFacade); + adapter.joinPoolSingleAssetDiff(poolId, _asset(2), diffLeftoverAmount, 0.5e27); + + vm.prank(configurator); + adapter.setPoolStatus(poolId, PoolStatus.SWAP_ONLY); + + vm.expectRevert(PoolNotSupportedException.selector); + vm.prank(creditFacade); + adapter.joinPoolSingleAssetDiff(poolId, _asset(2), diffLeftoverAmount, 0.5e27); + + vm.prank(configurator); + adapter.setPoolStatus(poolId, PoolStatus.ALLOWED); + + JoinPoolRequest memory request; + request.assets = _assets(0, 1, 2, 3); + request.maxAmountsIn = new uint256[](4); + request.maxAmountsIn[2] = diffInputAmount; + + uint256[] memory maxAmountsInWithoutBPT = new uint256[](3); + maxAmountsInWithoutBPT[1] = diffInputAmount; + request.userData = abi.encode(uint256(1), maxAmountsInWithoutBPT, diffInputAmount / 2); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[2], + tokenOut: tokens[0], + callData: abi.encodeCall(IBalancerV2Vault.joinPool, (poolId, creditAccount, creditAccount, request)), + requiresApproval: true, + validatesTokens: true + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = + adapter.joinPoolSingleAssetDiff(poolId, _asset(2), diffLeftoverAmount, 0.5e27); + assertEq(tokensToEnable, 1, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? 4 : 0, "Incorrect tokensToDisable"); + } + // ----- // // EXITS // // ----- // @@ -426,6 +524,33 @@ contract BalancerV2VaultAdapterUnitTest is assertEq(tokensToDisable, 1, "Incorrect tokensToDisable"); } + /// @notice U:[BAL2-11A]: `exitPoolSingleAssetDiff` works as expected + function test_U_BAL2_11A_exitPoolSingleAssetDiff_works_as_expected() public diffTestCases { + deal({token: tokens[0], to: creditAccount, give: diffMintedAmount}); + + ExitPoolRequest memory request; + request.assets = _assets(0, 1, 2, 3); + request.minAmountsOut = new uint256[](4); + request.minAmountsOut[2] = diffInputAmount / 2; + request.userData = abi.encode(uint256(0), diffInputAmount, 1); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[0], + tokenOut: tokens[2], + callData: abi.encodeCall(IBalancerV2Vault.exitPool, (poolId, creditAccount, payable(creditAccount), request)), + requiresApproval: false, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = + adapter.exitPoolSingleAssetDiff(poolId, _asset(2), diffLeftoverAmount, 0.5e27); + + assertEq(tokensToEnable, 4, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? 1 : 0, "Incorrect tokensToDisable"); + } + // ------- // // CONFIG // // ------ // diff --git a/contracts/test/unit/adapters/compound/CompoundV2_CErc20Adapter.unit.t.sol b/contracts/test/unit/adapters/compound/CompoundV2_CErc20Adapter.unit.t.sol index 127a49e6..41ebd16b 100644 --- a/contracts/test/unit/adapters/compound/CompoundV2_CErc20Adapter.unit.t.sol +++ b/contracts/test/unit/adapters/compound/CompoundV2_CErc20Adapter.unit.t.sol @@ -51,12 +51,18 @@ contract CompoundV2_CErc20AdapterUnitTest is AdapterUnitTestHelper, ICompoundV2_ _revertsOnNonFacadeCaller(); adapter.mint(0); + _revertsOnNonFacadeCaller(); + adapter.mintDiff(0); + _revertsOnNonFacadeCaller(); adapter.mintAll(); _revertsOnNonFacadeCaller(); adapter.redeem(0); + _revertsOnNonFacadeCaller(); + adapter.redeemDiff(0); + _revertsOnNonFacadeCaller(); adapter.redeemAll(); @@ -74,6 +80,10 @@ contract CompoundV2_CErc20AdapterUnitTest is AdapterUnitTestHelper, ICompoundV2_ vm.prank(creditFacade); adapter.mint(1); + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, 1)); + vm.prank(creditFacade); + adapter.mintDiff(1); + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, 1)); vm.prank(creditFacade); adapter.mintAll(); @@ -82,6 +92,10 @@ contract CompoundV2_CErc20AdapterUnitTest is AdapterUnitTestHelper, ICompoundV2_ vm.prank(creditFacade); adapter.redeem(1); + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, 1)); + vm.prank(creditFacade); + adapter.redeemDiff(1); + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, 1)); vm.prank(creditFacade); adapter.redeemAll(); @@ -126,6 +140,25 @@ contract CompoundV2_CErc20AdapterUnitTest is AdapterUnitTestHelper, ICompoundV2_ assertEq(tokensToDisable, tokenMask, "Incorrect tokensToDisable"); } + /// @notice U:[COMP2T-5A]: `mintDiff` works as expected + function test_U_COMP2T_05A_mintDiff_works_as_expected() public diffTestCases { + deal({token: token, to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: token, + tokenOut: cToken, + callData: abi.encodeCall(ICErc20Actions.mint, (diffInputAmount)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.mintDiff(diffLeftoverAmount); + + assertEq(tokensToEnable, cTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? tokenMask : 0, "Incorrect tokensToDisable"); + } + /// @notice U:[COMP2T-6]: `redeem` works as expected function test_U_COMP2T_06_redeem_works_as_expected() public { _executesSwap({ @@ -161,6 +194,25 @@ contract CompoundV2_CErc20AdapterUnitTest is AdapterUnitTestHelper, ICompoundV2_ assertEq(tokensToDisable, cTokenMask, "Incorrect tokensToDisable"); } + /// @notice U:[COMP2T-7A]: `redeemDiff` works as expected + function test_U_COMP2T_07A_redeemDiff_works_as_expected() public diffTestCases { + deal({token: cToken, to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: cToken, + tokenOut: token, + callData: abi.encodeCall(ICErc20Actions.redeem, (diffInputAmount)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.redeemDiff(diffLeftoverAmount); + + assertEq(tokensToEnable, tokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? cTokenMask : 0, "Incorrect tokensToDisable"); + } + /// @notice U:[COMP2T-8]: `redeemUnderlying` works as expected function test_U_COMP2T_08_redeemUnderlying_works_as_expected() public { _executesSwap({ diff --git a/contracts/test/unit/adapters/compound/CompoundV2_CEtherAdapter.unit.t.sol b/contracts/test/unit/adapters/compound/CompoundV2_CEtherAdapter.unit.t.sol index f9c0b418..3973f988 100644 --- a/contracts/test/unit/adapters/compound/CompoundV2_CEtherAdapter.unit.t.sol +++ b/contracts/test/unit/adapters/compound/CompoundV2_CEtherAdapter.unit.t.sol @@ -56,12 +56,18 @@ contract CompoundV2_CEtherAdapterUnitTest is AdapterUnitTestHelper, ICompoundV2_ _revertsOnNonFacadeCaller(); adapter.mint(0); + _revertsOnNonFacadeCaller(); + adapter.mintDiff(0); + _revertsOnNonFacadeCaller(); adapter.mintAll(); _revertsOnNonFacadeCaller(); adapter.redeem(0); + _revertsOnNonFacadeCaller(); + adapter.redeemDiff(0); + _revertsOnNonFacadeCaller(); adapter.redeemAll(); @@ -79,6 +85,10 @@ contract CompoundV2_CEtherAdapterUnitTest is AdapterUnitTestHelper, ICompoundV2_ vm.prank(creditFacade); adapter.mint(1); + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, 1)); + vm.prank(creditFacade); + adapter.mintDiff(1); + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, 1)); vm.prank(creditFacade); adapter.mintAll(); @@ -87,6 +97,10 @@ contract CompoundV2_CEtherAdapterUnitTest is AdapterUnitTestHelper, ICompoundV2_ vm.prank(creditFacade); adapter.redeem(1); + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, 1)); + vm.prank(creditFacade); + adapter.redeemDiff(1); + vm.expectRevert(abi.encodeWithSelector(CTokenError.selector, 1)); vm.prank(creditFacade); adapter.redeemAll(); @@ -131,6 +145,25 @@ contract CompoundV2_CEtherAdapterUnitTest is AdapterUnitTestHelper, ICompoundV2_ assertEq(tokensToDisable, tokenMask, "Incorrect tokensToDisable"); } + /// @notice U:[COMP2T-5A]: `mintDiff` works as expected + function test_U_COMP2E_05A_mintDiff_works_as_expected() public diffTestCases { + deal({token: token, to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: token, + tokenOut: cToken, + callData: abi.encodeCall(ICErc20Actions.mint, (diffInputAmount)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.mintDiff(diffLeftoverAmount); + + assertEq(tokensToEnable, cTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? tokenMask : 0, "Incorrect tokensToDisable"); + } + /// @notice U:[COMP2E-6]: `redeem` works as expected function test_U_COMP2E_06_redeem_works_as_expected() public { _executesSwap({ @@ -166,6 +199,25 @@ contract CompoundV2_CEtherAdapterUnitTest is AdapterUnitTestHelper, ICompoundV2_ assertEq(tokensToDisable, cTokenMask, "Incorrect tokensToDisable"); } + /// @notice U:[COMP2T-7A]: `redeemDiff` works as expected + function test_U_COMP2T_07A_redeemDiff_works_as_expected() public diffTestCases { + deal({token: cToken, to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: cToken, + tokenOut: token, + callData: abi.encodeCall(ICErc20Actions.redeem, (diffInputAmount)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.redeemDiff(diffLeftoverAmount); + + assertEq(tokensToEnable, tokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? cTokenMask : 0, "Incorrect tokensToDisable"); + } + /// @notice U:[COMP2E-8]: `redeemUnderlying` works as expected function test_U_COMP2E_08_redeemUnderlying_works_as_expected() public { _executesSwap({ diff --git a/contracts/test/unit/adapters/convex/ConvexV1BaseRewardPoolAdapter.unit.t.sol b/contracts/test/unit/adapters/convex/ConvexV1BaseRewardPoolAdapter.unit.t.sol index b22fa385..a4cbaf4f 100644 --- a/contracts/test/unit/adapters/convex/ConvexV1BaseRewardPoolAdapter.unit.t.sol +++ b/contracts/test/unit/adapters/convex/ConvexV1BaseRewardPoolAdapter.unit.t.sol @@ -111,6 +111,9 @@ contract ConvexV1BaseRewardPoolAdapterUnitTest is AdapterUnitTestHelper { _revertsOnNonFacadeCaller(); adapter.stake(0); + _revertsOnNonFacadeCaller(); + adapter.stakeDiff(0); + _revertsOnNonFacadeCaller(); adapter.stakeAll(); @@ -120,12 +123,18 @@ contract ConvexV1BaseRewardPoolAdapterUnitTest is AdapterUnitTestHelper { _revertsOnNonFacadeCaller(); adapter.withdraw(0, false); + _revertsOnNonFacadeCaller(); + adapter.withdrawDiff(0, false); + _revertsOnNonFacadeCaller(); adapter.withdrawAll(false); _revertsOnNonFacadeCaller(); adapter.withdrawAndUnwrap(0, false); + _revertsOnNonFacadeCaller(); + adapter.withdrawDiffAndUnwrap(0, false); + _revertsOnNonFacadeCaller(); adapter.withdrawAllAndUnwrap(false); } @@ -151,10 +160,12 @@ contract ConvexV1BaseRewardPoolAdapterUnitTest is AdapterUnitTestHelper { /// @notice U:[CVX1R-5]: `stakeAll` works as expected function test_U_CVX1R_05_stakeAll_works_as_expected() public { + deal({token: convexStakingToken, to: creditAccount, give: 1000}); + _readsActiveAccount(); _executesSwap({ tokenIn: convexStakingToken, tokenOut: stakedPhantomToken, - callData: abi.encodeCall(adapter.stakeAll, ()), + callData: abi.encodeCall(adapter.stake, (999)), requiresApproval: true, validatesTokens: false }); @@ -164,6 +175,23 @@ contract ConvexV1BaseRewardPoolAdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, 2, "Incorrect tokensToDisable"); } + /// @notice U:[CVX1R-5A]: `stakeDiff` works as expected + function test_U_CVX1R_05A_stakeDiff_works_as_expected() public diffTestCases { + deal({token: convexStakingToken, to: creditAccount, give: diffMintedAmount}); + _readsActiveAccount(); + _executesSwap({ + tokenIn: convexStakingToken, + tokenOut: stakedPhantomToken, + callData: abi.encodeCall(adapter.stake, (diffInputAmount)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.stakeDiff(diffLeftoverAmount); + assertEq(tokensToEnable, 4, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? 2 : 0, "Incorrect tokensToDisable"); + } + // ----- // // CLAIM // // ----- // @@ -205,12 +233,13 @@ contract ConvexV1BaseRewardPoolAdapterUnitTest is AdapterUnitTestHelper { /// @notice U:[CVX1R-8]: `withdrawAll` works as expected function test_U_CVX1R_08_withdrawAll_works_as_expected() public { + deal({token: stakedPhantomToken, to: creditAccount, give: 1000}); for (uint256 i; i < 2; ++i) { bool claim = i == 1; _executesSwap({ tokenIn: stakedPhantomToken, tokenOut: convexStakingToken, - callData: abi.encodeCall(adapter.withdrawAll, (claim)), + callData: abi.encodeCall(adapter.withdraw, (999, claim)), requiresApproval: false, validatesTokens: false }); @@ -221,6 +250,26 @@ contract ConvexV1BaseRewardPoolAdapterUnitTest is AdapterUnitTestHelper { } } + /// @notice U:[CVX1R-8A]: `withdrawDiff` works as expected + function test_U_CVX1R_08A_withdrawAll_works_as_expected() public diffTestCases { + deal({token: stakedPhantomToken, to: creditAccount, give: diffMintedAmount}); + for (uint256 i; i < 2; ++i) { + bool claim = i == 1; + _readsActiveAccount(); + _executesSwap({ + tokenIn: stakedPhantomToken, + tokenOut: convexStakingToken, + callData: abi.encodeCall(adapter.withdraw, (diffInputAmount, claim)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdrawDiff(diffLeftoverAmount, claim); + assertEq(tokensToEnable, claim ? (2 + 8 + 16) : 2, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? 4 : 0, "Incorrect tokensToDisable"); + } + } + // ------ // // UNWRAP // // ------ // @@ -245,12 +294,13 @@ contract ConvexV1BaseRewardPoolAdapterUnitTest is AdapterUnitTestHelper { /// @notice U:[CVX1R-10]: `withdrawAllAndUnwrap` works as expected function test_U_CVX1R_10_withdrawAllAndUnwrap_works_as_expected() public { + deal({token: stakedPhantomToken, to: creditAccount, give: 1000}); for (uint256 i; i < 2; ++i) { bool claim = i == 1; _executesSwap({ tokenIn: stakedPhantomToken, tokenOut: curveLPToken, - callData: abi.encodeCall(adapter.withdrawAllAndUnwrap, (claim)), + callData: abi.encodeCall(adapter.withdrawAndUnwrap, (999, claim)), requiresApproval: false, validatesTokens: false }); @@ -260,4 +310,24 @@ contract ConvexV1BaseRewardPoolAdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, 4, "Incorrect tokensToDisable"); } } + + /// @notice U:[CVX1R-10]: `withdrawDiffAndUnwrap` works as expected + function test_U_CVX1R_10_withdrawDiffAndUnwrap_works_as_expected() public diffTestCases { + deal({token: stakedPhantomToken, to: creditAccount, give: diffMintedAmount}); + for (uint256 i; i < 2; ++i) { + bool claim = i == 1; + _readsActiveAccount(); + _executesSwap({ + tokenIn: stakedPhantomToken, + tokenOut: curveLPToken, + callData: abi.encodeCall(adapter.withdrawAndUnwrap, (diffInputAmount, claim)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdrawDiffAndUnwrap(diffLeftoverAmount, claim); + assertEq(tokensToEnable, claim ? (1 + 8 + 16) : 1, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? 4 : 0, "Incorrect tokensToDisable"); + } + } } diff --git a/contracts/test/unit/adapters/convex/ConvexV1BoosterAdapter.unit.t.sol b/contracts/test/unit/adapters/convex/ConvexV1BoosterAdapter.unit.t.sol index 7d7725cb..cf9a11b2 100644 --- a/contracts/test/unit/adapters/convex/ConvexV1BoosterAdapter.unit.t.sol +++ b/contracts/test/unit/adapters/convex/ConvexV1BoosterAdapter.unit.t.sol @@ -35,12 +35,18 @@ contract ConvexV1BoosterAdapterUnitTest is AdapterUnitTestHelper { _revertsOnNonFacadeCaller(); adapter.deposit(0, 0, false); + _revertsOnNonFacadeCaller(); + adapter.depositDiff(0, 0, false); + _revertsOnNonFacadeCaller(); adapter.depositAll(0, false); _revertsOnNonFacadeCaller(); adapter.withdraw(0, 0); + _revertsOnNonFacadeCaller(); + adapter.withdrawDiff(0, 0); + _revertsOnNonFacadeCaller(); adapter.withdrawAll(0); } @@ -68,13 +74,14 @@ contract ConvexV1BoosterAdapterUnitTest is AdapterUnitTestHelper { /// @notice U:[CVX1B-4]: `depositAll` works as expected function test_U_CVX1B_04_depositAll_works_as_expected() public { + deal({token: tokens[0], to: creditAccount, give: 1000}); for (uint256 i; i < 2; ++i) { bool stake = i == 1; - + _readsActiveAccount(); _executesSwap({ tokenIn: tokens[0], tokenOut: stake ? tokens[2] : tokens[1], - callData: abi.encodeCall(adapter.depositAll, (42, stake)), + callData: abi.encodeCall(adapter.deposit, (42, 999, stake)), requiresApproval: true, validatesTokens: true }); @@ -87,6 +94,28 @@ contract ConvexV1BoosterAdapterUnitTest is AdapterUnitTestHelper { } } + /// @notice U:[CVX1B-4A]: `depositDiff` works as expected + function test_U_CVX1B_04A_depositDiff_works_as_expected() public diffTestCases { + deal({token: tokens[0], to: creditAccount, give: diffMintedAmount}); + for (uint256 i; i < 2; ++i) { + bool stake = i == 1; + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[0], + tokenOut: stake ? tokens[2] : tokens[1], + callData: abi.encodeCall(adapter.deposit, (42, diffInputAmount, stake)), + requiresApproval: true, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.depositDiff(42, diffLeftoverAmount, stake); + + assertEq(tokensToEnable, stake ? 4 : 2, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? 1 : 0, "Incorrect tokensToDisable"); + } + } + /// @notice U:[CVX1B-5]: `withdraw` works as expected function test_U_CVX1B_05_withdraw_works_as_expected() public { _executesSwap({ @@ -106,10 +135,12 @@ contract ConvexV1BoosterAdapterUnitTest is AdapterUnitTestHelper { /// @notice U:[CVX1B-6]: `withdrawAll` works as expected function test_U_CVX1B_06_withdrawAll_works_as_expected() public { + deal({token: tokens[1], to: creditAccount, give: 1000}); + _readsActiveAccount(); _executesSwap({ tokenIn: tokens[1], tokenOut: tokens[0], - callData: abi.encodeCall(adapter.withdrawAll, (42)), + callData: abi.encodeCall(adapter.withdraw, (42, 999)), requiresApproval: false, validatesTokens: true }); @@ -121,6 +152,25 @@ contract ConvexV1BoosterAdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, 2, "Incorrect tokensToDisable"); } + /// @notice U:[CVX1B-6A]: `withdrawDiff` works as expected + function test_U_CVX1B_06A_withdrawDiff_works_as_expected() public diffTestCases { + deal({token: tokens[1], to: creditAccount, give: diffMintedAmount}); + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[1], + tokenOut: tokens[0], + callData: abi.encodeCall(adapter.withdraw, (42, diffInputAmount)), + requiresApproval: false, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdrawDiff(42, diffLeftoverAmount); + + assertEq(tokensToEnable, 1, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? 2 : 0, "Incorrect tokensToDisable"); + } + /// @notice U:[CVX1B-7]: `updatedStakedPhantomTokensMap` reverts on wrong caller function test_U_CVX1B_07_updateStakedPhantomTokensMap_reverts_on_wrong_caller() public { _revertsOnNonConfiguratorCaller(); diff --git a/contracts/test/unit/adapters/curve/CurveV1AdapterBase.unit.t.sol b/contracts/test/unit/adapters/curve/CurveV1AdapterBase.unit.t.sol index fbe17475..6fad19c8 100644 --- a/contracts/test/unit/adapters/curve/CurveV1AdapterBase.unit.t.sol +++ b/contracts/test/unit/adapters/curve/CurveV1AdapterBase.unit.t.sol @@ -166,6 +166,9 @@ contract CurveV1AdapterBaseUnitTest is AdapterUnitTestHelper { _revertsOnNonFacadeCaller(); adapter.exchange(int128(0), int128(0), 0, 0); + _revertsOnNonFacadeCaller(); + adapter.exchange_diff(uint256(0), uint256(0), 0, 0); + _revertsOnNonFacadeCaller(); adapter.exchange_all(uint256(0), uint256(0), 0); @@ -178,6 +181,9 @@ contract CurveV1AdapterBaseUnitTest is AdapterUnitTestHelper { _revertsOnNonFacadeCaller(); adapter.exchange_underlying(int128(0), int128(0), 0, 0); + _revertsOnNonFacadeCaller(); + adapter.exchange_diff_underlying(uint256(0), uint256(0), 0, 0); + _revertsOnNonFacadeCaller(); adapter.exchange_all_underlying(uint256(0), uint256(0), 0); @@ -187,6 +193,9 @@ contract CurveV1AdapterBaseUnitTest is AdapterUnitTestHelper { _revertsOnNonFacadeCaller(); adapter.add_liquidity_one_coin(0, 0, 0); + _revertsOnNonFacadeCaller(); + adapter.add_diff_liquidity_one_coin(0, 0, 0); + _revertsOnNonFacadeCaller(); adapter.add_all_liquidity_one_coin(0, 0); @@ -196,6 +205,9 @@ contract CurveV1AdapterBaseUnitTest is AdapterUnitTestHelper { _revertsOnNonFacadeCaller(); adapter.remove_liquidity_one_coin(0, int128(0), 0); + _revertsOnNonFacadeCaller(); + adapter.remove_diff_liquidity_one_coin(0, uint256(0), 0); + _revertsOnNonFacadeCaller(); adapter.remove_all_liquidity_one_coin(uint256(0), 0); @@ -271,6 +283,33 @@ contract CurveV1AdapterBaseUnitTest is AdapterUnitTestHelper { } } + /// @notice U:[CRVB-4A]: `exchange_diff` works as expected + function test_U_CRVB_04A_exchange_diff_works_as_expected() public bothStableAndCryptoPools diffTestCases { + deal({token: token0, to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: token0, + tokenOut: token1, + callData: abi.encodeWithSignature( + pool.isCrypto() ? "exchange(uint256,uint256,uint256,uint256)" : "exchange(int128,int128,uint256,uint256)", + 0, + 1, + diffInputAmount, + diffInputAmount / 2 + ), + requiresApproval: true, + validatesTokens: false + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = + adapter.exchange_diff(uint256(0), uint256(1), diffLeftoverAmount, 0.5e27); + + assertEq(tokensToEnable, token1Mask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? token0Mask : 0, "Incorrect tokensToDisable"); + } + /// @notice U:[CRVB-5]: `exchange_underlying` works as expected function test_U_CRVB_05_exchange_underlying_works_as_expected() public bothStableAndCryptoPools { for (uint256 i; i < 2; ++i) { @@ -303,7 +342,7 @@ contract CurveV1AdapterBaseUnitTest is AdapterUnitTestHelper { } /// @notice U:[CRVB-6]: `exchange_all_underlying` works as expected - function test_U_CRVB_06_exchange_all_underlying_works_as_expected() public bothStableAndCryptoPools { + function test_U_CRVB_06_exchange_all_underlying_works_as_expected() public bothStableAndCryptoPools diffTestCases { deal({token: token0, to: creditAccount, give: 1001}); for (uint256 i; i < 2; ++i) { bool use256 = i == 2; @@ -335,6 +374,39 @@ contract CurveV1AdapterBaseUnitTest is AdapterUnitTestHelper { } } + /// @notice U:[CRVB-6A]: `exchange_diff_underlying` works as expected + function test_U_CRVB_06A_exchange_diff_underlying_works_as_expected() + public + bothStableAndCryptoPools + diffTestCases + { + deal({token: token0, to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: token0, + tokenOut: underlying0, + callData: abi.encodeWithSignature( + pool.isCrypto() + ? "exchange_underlying(uint256,uint256,uint256,uint256)" + : "exchange_underlying(int128,int128,uint256,uint256)", + 0, + 1, + diffInputAmount, + diffInputAmount / 2 + ), + requiresApproval: true, + validatesTokens: false + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = + adapter.exchange_diff_underlying(uint256(0), uint256(1), diffLeftoverAmount, 0.5e27); + + assertEq(tokensToEnable, underlying0Mask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? token0Mask : 0, "Incorrect tokensToDisable"); + } + // ------------- // // ADD LIQUIDITY // // ------------- // @@ -375,6 +447,26 @@ contract CurveV1AdapterBaseUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, token0Mask, "Incorrect tokensToDisable"); } + /// @notice U:[CRVB-8A]: `add_diff_liquidity_one_coin` works as expected + function test_U_CRVB_08A_add_diff_liquidity_one_coin_works_as_expected() public onlyStablePools diffTestCases { + deal({token: token0, to: creditAccount, give: diffMintedAmount}); + + _executesSwap({ + tokenIn: token0, + tokenOut: lpToken, + callData: abi.encodeWithSignature("add_liquidity(uint256[2],uint256)", diffInputAmount, 0, diffInputAmount / 2), + requiresApproval: true, + validatesTokens: false + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = + adapter.add_diff_liquidity_one_coin(diffLeftoverAmount, 0, 0.5e27); + + assertEq(tokensToEnable, lpTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? token0Mask : 0, "Incorrect tokensToDisable"); + } + // ---------------- // // REMOVE LIQUIDITY // // ---------------- // @@ -441,4 +533,36 @@ contract CurveV1AdapterBaseUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, lpTokenMask, "Incorrect tokensToDisable"); } } + + /// @notice U:[CRVB-10A]: `remove_diff_liquidity_one_coin` works as expected + function test_U_CRVB_10A_remove_diff_liquidity_one_coin_works_as_expected() + public + bothStableAndCryptoPools + diffTestCases + { + deal({token: lpToken, to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: lpToken, + tokenOut: token0, + callData: abi.encodeWithSignature( + pool.isCrypto() + ? "remove_liquidity_one_coin(uint256,uint256,uint256)" + : "remove_liquidity_one_coin(uint256,int128,uint256)", + diffInputAmount, + 0, + diffInputAmount / 2 + ), + requiresApproval: false, + validatesTokens: false + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = + adapter.remove_diff_liquidity_one_coin(diffLeftoverAmount, uint256(0), 0.5e27); + + assertEq(tokensToEnable, token0Mask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? lpTokenMask : 0, "Incorrect tokensToDisable"); + } } diff --git a/contracts/test/unit/adapters/erc4626/ERC4626Adapter.unit.t.sol b/contracts/test/unit/adapters/erc4626/ERC4626Adapter.unit.t.sol index eb5d8b2d..c1f0916c 100644 --- a/contracts/test/unit/adapters/erc4626/ERC4626Adapter.unit.t.sol +++ b/contracts/test/unit/adapters/erc4626/ERC4626Adapter.unit.t.sol @@ -47,6 +47,9 @@ contract ERC4626AdapterUnitTest is AdapterUnitTestHelper { _revertsOnNonFacadeCaller(); adapter.deposit(0, address(0)); + _revertsOnNonFacadeCaller(); + adapter.depositDiff(0); + _revertsOnNonFacadeCaller(); adapter.depositAll(); @@ -59,6 +62,9 @@ contract ERC4626AdapterUnitTest is AdapterUnitTestHelper { _revertsOnNonFacadeCaller(); adapter.redeem(0, address(0), address(0)); + _revertsOnNonFacadeCaller(); + adapter.redeemDiff(0); + _revertsOnNonFacadeCaller(); adapter.redeemAll(); } @@ -99,6 +105,25 @@ contract ERC4626AdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, assetMask, "Incorrect tokensToDisable"); } + /// @notice U:[TV-4A]: `depositDiff` works as expected + function test_U_TV_04A_depositDiff_works_as_expected() public diffTestCases { + deal({token: asset, to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: asset, + tokenOut: vault, + callData: abi.encodeCall(IERC4626.deposit, (diffInputAmount, creditAccount)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.depositDiff(diffLeftoverAmount); + + assertEq(tokensToEnable, sharesMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? assetMask : 0, "Incorrect tokensToDisable"); + } + /// @notice U:[TV-5]: `mint` works as expected function test_U_TV_05_mint_works_as_expected() public { _readsActiveAccount(); @@ -168,4 +193,23 @@ contract ERC4626AdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToEnable, assetMask, "Incorrect tokensToEnable"); assertEq(tokensToDisable, sharesMask, "Incorrect tokensToDisable"); } + + /// @notice U:[TV-8A]: `redeemDiff` works as expected + function test_U_TV_08_redeemDiff_works_as_expected() public diffTestCases { + deal({token: vault, to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: vault, + tokenOut: asset, + callData: abi.encodeCall(IERC4626.redeem, (diffInputAmount, creditAccount, creditAccount)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.redeemDiff(diffLeftoverAmount); + + assertEq(tokensToEnable, assetMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? sharesMask : 0, "Incorrect tokensToDisable"); + } } diff --git a/contracts/test/unit/adapters/lido/LidoV1Adapter.unit.t.sol b/contracts/test/unit/adapters/lido/LidoV1Adapter.unit.t.sol index 5943e736..a9cc9719 100644 --- a/contracts/test/unit/adapters/lido/LidoV1Adapter.unit.t.sol +++ b/contracts/test/unit/adapters/lido/LidoV1Adapter.unit.t.sol @@ -61,6 +61,9 @@ contract LidoV1AdapterUnitTest is AdapterUnitTestHelper { _revertsOnNonFacadeCaller(); adapter.submit(0); + _revertsOnNonFacadeCaller(); + adapter.submitDiff(0); + _revertsOnNonFacadeCaller(); adapter.submitAll(); } @@ -99,4 +102,23 @@ contract LidoV1AdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToEnable, stETHMask, "Incorrect tokensToEnable"); assertEq(tokensToDisable, wethMask, "Incorrect tokensToDisable"); } + + /// @notice U:[LDO1-4A]: `submitDiff` works as expected + function test_U_LDO1_04A_submitDiff_works_as_expected() public diffTestCases { + deal({token: weth, to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: weth, + tokenOut: stETH, + callData: abi.encodeCall(LidoV1Gateway.submit, (diffInputAmount, treasury)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.submitDiff(diffLeftoverAmount); + + assertEq(tokensToEnable, stETHMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? wethMask : 0, "Incorrect tokensToDisable"); + } } diff --git a/contracts/test/unit/adapters/lido/WstETHV1Adapter.unit.t.sol b/contracts/test/unit/adapters/lido/WstETHV1Adapter.unit.t.sol index 86fe4d20..a9d5146b 100644 --- a/contracts/test/unit/adapters/lido/WstETHV1Adapter.unit.t.sol +++ b/contracts/test/unit/adapters/lido/WstETHV1Adapter.unit.t.sol @@ -47,12 +47,18 @@ contract WstETHV1AdapterUnitTest is AdapterUnitTestHelper { _revertsOnNonFacadeCaller(); adapter.wrap(0); + _revertsOnNonFacadeCaller(); + adapter.wrapDiff(0); + _revertsOnNonFacadeCaller(); adapter.wrapAll(); _revertsOnNonFacadeCaller(); adapter.unwrap(0); + _revertsOnNonFacadeCaller(); + adapter.unwrapDiff(0); + _revertsOnNonFacadeCaller(); adapter.unwrapAll(); } @@ -92,6 +98,25 @@ contract WstETHV1AdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, stETHMask, "Incorrect tokensToDisable"); } + /// @notice U:[LDO1W-4A]: `wrapDiff` works as expected + function test_U_LDO1W_04A_wrapDiff_works_as_expected() public diffTestCases { + deal({token: stETH, to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: stETH, + tokenOut: wstETH, + callData: abi.encodeCall(IwstETH.wrap, (diffInputAmount)), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.wrapDiff(diffLeftoverAmount); + + assertEq(tokensToEnable, wstETHMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? stETHMask : 0, "Incorrect tokensToDisable"); + } + /// @notice U:[LDO1W-5]: `unwrap` works as expected function test_U_LDO1W_05_unwrap_works_as_expected() public { _executesSwap({ @@ -126,4 +151,23 @@ contract WstETHV1AdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToEnable, stETHMask, "Incorrect tokensToEnable"); assertEq(tokensToDisable, wstETHMask, "Incorrect tokensToDisable"); } + + /// @notice U:[LDO1W-6A]: `unwrapAll` works as expected + function test_U_LDO1W_06A_unwrapAll_works_as_expected() public diffTestCases { + deal({token: wstETH, to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: wstETH, + tokenOut: stETH, + callData: abi.encodeCall(IwstETH.unwrap, (diffInputAmount)), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.unwrapDiff(diffLeftoverAmount); + + assertEq(tokensToEnable, stETHMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? wstETHMask : 0, "Incorrect tokensToDisable"); + } } diff --git a/contracts/test/unit/adapters/uniswap/UniswapV2Adapter.unit.t.sol b/contracts/test/unit/adapters/uniswap/UniswapV2Adapter.unit.t.sol index 2ca79f35..2dc2dff7 100644 --- a/contracts/test/unit/adapters/uniswap/UniswapV2Adapter.unit.t.sol +++ b/contracts/test/unit/adapters/uniswap/UniswapV2Adapter.unit.t.sol @@ -45,6 +45,9 @@ contract UniswapV2AdapterUnitTest is AdapterUnitTestHelper, IUniswapV2AdapterEve _revertsOnNonFacadeCaller(); adapter.swapExactTokensForTokens(0, 0, emptyPath, address(0), 0); + _revertsOnNonFacadeCaller(); + adapter.swapDiffTokensForTokens(0, 0, emptyPath, 0); + _revertsOnNonFacadeCaller(); adapter.swapAllTokensForTokens(0, emptyPath, 0); } @@ -99,6 +102,36 @@ contract UniswapV2AdapterUnitTest is AdapterUnitTestHelper, IUniswapV2AdapterEve assertEq(tokensToDisable, 0, "Incorrect tokensToDisable"); } + /// @notice U:[UNI2-5A]: `swapDiffTokensForTokens` works as expected + function test_U_UNI2_05A_swapDiffTokensForTokens_works_as_expected() public diffTestCases { + deal({token: tokens[0], to: creditAccount, give: diffMintedAmount}); + + address[] memory path = _makePath(0); + vm.expectRevert(InvalidPathException.selector); + vm.prank(creditFacade); + adapter.swapDiffTokensForTokens(diffInputAmount, 0.5e27, path, 789); + + path = _makePath(2); + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[0], + tokenOut: tokens[1], + callData: abi.encodeCall( + IUniswapV2Router01.swapExactTokensForTokens, + (diffInputAmount, diffInputAmount / 2, path, creditAccount, 789) + ), + requiresApproval: true, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = + adapter.swapDiffTokensForTokens(diffLeftoverAmount, 0.5e27, path, 789); + + assertEq(tokensToEnable, 2, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? 1 : 0, "Incorrect tokensToDisable"); + } + /// @notice U:[UNI2-5]: `swapAllTokensForTokens` works as expected function test_U_UNI2_05_swapAllTokensForTokens_works_as_expected() public { deal({token: tokens[0], to: creditAccount, give: 1001}); diff --git a/contracts/test/unit/adapters/uniswap/UniswapV3Adapter.unit.t.sol b/contracts/test/unit/adapters/uniswap/UniswapV3Adapter.unit.t.sol index 10f2e3cb..d3a8be63 100644 --- a/contracts/test/unit/adapters/uniswap/UniswapV3Adapter.unit.t.sol +++ b/contracts/test/unit/adapters/uniswap/UniswapV3Adapter.unit.t.sol @@ -47,6 +47,10 @@ contract UniswapV3AdapterUnitTest is _revertsOnNonFacadeCaller(); adapter.exactInputSingle(p1); + ExactDiffInputSingleParams memory p2_2; + _revertsOnNonFacadeCaller(); + adapter.exactDiffInputSingle(p2_2); + ExactAllInputSingleParams memory p2; _revertsOnNonFacadeCaller(); adapter.exactAllInputSingle(p2); @@ -55,6 +59,10 @@ contract UniswapV3AdapterUnitTest is _revertsOnNonFacadeCaller(); adapter.exactInput(p3); + ExactDiffInputParams memory p4_2; + _revertsOnNonFacadeCaller(); + adapter.exactDiffInput(p4_2); + ExactAllInputParams memory p4; _revertsOnNonFacadeCaller(); adapter.exactAllInput(p4); @@ -141,6 +149,50 @@ contract UniswapV3AdapterUnitTest is assertEq(tokensToDisable, 1, "Incorrect tokensToDisable"); } + /// @notice U:[UNI3-4A]: `exactDiffInputSingle` works as expected + function test_U_UNI3_04A_exactDiffInputSingle_works_as_expected() public diffTestCases { + deal({token: tokens[0], to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[0], + tokenOut: tokens[1], + callData: abi.encodeCall( + ISwapRouter.exactInputSingle, + ( + ISwapRouter.ExactInputSingleParams({ + tokenIn: tokens[0], + tokenOut: tokens[1], + fee: 500, + amountIn: diffInputAmount, + amountOutMinimum: diffInputAmount / 2, + deadline: 789, + recipient: creditAccount, + sqrtPriceLimitX96: 0 + }) + ) + ), + requiresApproval: true, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.exactDiffInputSingle( + ExactDiffInputSingleParams({ + tokenIn: tokens[0], + tokenOut: tokens[1], + fee: 500, + deadline: 789, + leftoverAmount: diffLeftoverAmount, + rateMinRAY: 0.5e27, + sqrtPriceLimitX96: 0 + }) + ); + + assertEq(tokensToEnable, 2, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? 1 : 0, "Incorrect tokensToDisable"); + } + /// @notice U:[UNI3-5]: `exactInput` works as expected function test_U_UNI3_05_exactInput_works_as_expected() public { ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({ @@ -209,6 +261,48 @@ contract UniswapV3AdapterUnitTest is assertEq(tokensToDisable, 1, "Incorrect tokensToDisable"); } + /// @notice U:[UNI3-6A]: `exactDiffInput` works as expected + function test_U_UNI3_06A_exactDiffInput_works_as_expected() public diffTestCases { + deal({token: tokens[0], to: creditAccount, give: diffMintedAmount}); + + ExactDiffInputParams memory params = ExactDiffInputParams({ + path: _makePath(0), + deadline: 789, + leftoverAmount: diffLeftoverAmount, + rateMinRAY: 0.5e27 + }); + vm.expectRevert(InvalidPathException.selector); + vm.prank(creditFacade); + adapter.exactDiffInput(params); + + params.path = _makePath(3); + _readsActiveAccount(); + _executesSwap({ + tokenIn: tokens[0], + tokenOut: tokens[2], + callData: abi.encodeCall( + ISwapRouter.exactInput, + ( + ISwapRouter.ExactInputParams({ + path: params.path, + amountIn: diffInputAmount, + amountOutMinimum: diffInputAmount / 2, + deadline: 789, + recipient: creditAccount + }) + ) + ), + requiresApproval: true, + validatesTokens: true + }); + + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.exactDiffInput(params); + + assertEq(tokensToEnable, 4, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? 1 : 0, "Incorrect tokensToDisable"); + } + /// @notice U:[UNI3-7]: `exactOutputSingle` works as expected function test_U_UNI3_07_exactOutputSingle_works_as_expected() public { ISwapRouter.ExactOutputSingleParams memory params = ISwapRouter.ExactOutputSingleParams({ diff --git a/contracts/test/unit/adapters/yearn/YearnV2Adapter.unit.t.sol b/contracts/test/unit/adapters/yearn/YearnV2Adapter.unit.t.sol index 9422587d..05a08492 100644 --- a/contracts/test/unit/adapters/yearn/YearnV2Adapter.unit.t.sol +++ b/contracts/test/unit/adapters/yearn/YearnV2Adapter.unit.t.sol @@ -47,6 +47,9 @@ contract YearnV2AdapterUnitTest is AdapterUnitTestHelper { _revertsOnNonFacadeCaller(); adapter.deposit(); + _revertsOnNonFacadeCaller(); + adapter.depositDiff(0); + _revertsOnNonFacadeCaller(); adapter.deposit(0); @@ -56,6 +59,9 @@ contract YearnV2AdapterUnitTest is AdapterUnitTestHelper { _revertsOnNonFacadeCaller(); adapter.withdraw(); + _revertsOnNonFacadeCaller(); + adapter.withdrawDiff(0); + _revertsOnNonFacadeCaller(); adapter.withdraw(0); @@ -85,6 +91,25 @@ contract YearnV2AdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, tokenMask, "Incorrect tokensToDisable"); } + /// @notice U:[YFI2-3A]: `depositDiff()` works as expected + function test_U_YFI2_03A_depositDiff_works_as_expected() public diffTestCases { + deal({token: token, to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: token, + tokenOut: yToken, + callData: abi.encodeWithSignature("deposit(uint256)", diffInputAmount), + requiresApproval: true, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.depositDiff(diffLeftoverAmount); + + assertEq(tokensToEnable, yTokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? tokenMask : 0, "Incorrect tokensToDisable"); + } + /// @notice U:[YFI2-4]: `deposit(uint256)` works as expected function test_U_YFI2_04_deposit_uint256_works_as_expected() public { _executesSwap({ @@ -136,6 +161,25 @@ contract YearnV2AdapterUnitTest is AdapterUnitTestHelper { assertEq(tokensToDisable, yTokenMask, "Incorrect tokensToDisable"); } + /// @notice U:[YFI2-6A]: `withdrawDiff()` works as expected + function test_U_YFI2_06A_withdrawDiff_works_as_expected() public diffTestCases { + deal({token: yToken, to: creditAccount, give: diffMintedAmount}); + + _readsActiveAccount(); + _executesSwap({ + tokenIn: yToken, + tokenOut: token, + callData: abi.encodeWithSignature("withdraw(uint256)", diffInputAmount), + requiresApproval: false, + validatesTokens: false + }); + vm.prank(creditFacade); + (uint256 tokensToEnable, uint256 tokensToDisable) = adapter.withdrawDiff(diffLeftoverAmount); + + assertEq(tokensToEnable, tokenMask, "Incorrect tokensToEnable"); + assertEq(tokensToDisable, diffDisableTokenIn ? yTokenMask : 0, "Incorrect tokensToDisable"); + } + /// @notice U:[YFI2-7]: `withdraw(uint256)` works as expected function test_U_YFI2_07_withdraw_uint256_works_as_expected() public { _executesSwap({