diff --git a/src/swaps/AaveSwapper.sol b/src/swaps/AaveSwapper.sol index 66402dacd..432523e91 100644 --- a/src/swaps/AaveSwapper.sol +++ b/src/swaps/AaveSwapper.sol @@ -11,41 +11,28 @@ import {Initializable} from 'solidity-utils/contracts/transparent-proxy/Initiali import {AaveV3Ethereum} from 'aave-address-book/AaveV3Ethereum.sol'; import {AaveGovernanceV2} from 'aave-address-book/AaveGovernanceV2.sol'; +import {IAaveSwapper} from './interfaces/IAaveSwapper.sol'; import {IPriceChecker} from './interfaces/IExpectedOutCalculator.sol'; import {IMilkman} from './interfaces/IMilkman.sol'; /** * @title AaveSwapper - * @author Llama + * @author efecarranza.eth * @notice Helper contract to swap assets using milkman */ -contract AaveSwapper is Initializable, OwnableWithGuardian, Rescuable { +contract AaveSwapper is IAaveSwapper, Initializable, OwnableWithGuardian, Rescuable { using SafeERC20 for IERC20; - event SwapCanceled(address indexed fromToken, address indexed toToken, uint256 amount); - event SwapRequested( - address milkman, - address indexed fromToken, - address indexed toToken, - address fromOracle, - address toOracle, - uint256 amount, - address indexed recipient, - uint256 slippage - ); - - error Invalid0xAddress(); - error InvalidAmount(); - error InvalidRecipient(); - error OracleNotSet(); - + /// @inheritdoc IAaveSwapper address public constant BAL80WETH20 = 0x5c6Ee304399DBdB9C8Ef030aB642B10820DB8F56; + /// @inheritdoc IAaveSwapper function initialize() external initializer { _transferOwnership(AaveGovernanceV2.SHORT_EXECUTOR); _updateGuardian(0xA519a7cE7B24333055781133B13532AEabfAC81b); } + /// @inheritdoc IAaveSwapper function swap( address milkman, address priceChecker, @@ -70,6 +57,7 @@ contract AaveSwapper is Initializable, OwnableWithGuardian, Rescuable { IERC20(fromToken), IERC20(toToken), recipient, + bytes32(0), priceChecker, data ); @@ -86,6 +74,7 @@ contract AaveSwapper is Initializable, OwnableWithGuardian, Rescuable { ); } + /// @inheritdoc IAaveSwapper function cancelSwap( address tradeMilkman, address priceChecker, @@ -104,6 +93,7 @@ contract AaveSwapper is Initializable, OwnableWithGuardian, Rescuable { IERC20(fromToken), IERC20(toToken), recipient, + bytes32(0), priceChecker, data ); @@ -116,6 +106,7 @@ contract AaveSwapper is Initializable, OwnableWithGuardian, Rescuable { emit SwapCanceled(fromToken, toToken, amount); } + /// @inheritdoc IAaveSwapper function getExpectedOut( address priceChecker, uint256 amount, @@ -137,6 +128,7 @@ contract AaveSwapper is Initializable, OwnableWithGuardian, Rescuable { ); } + /// @inheritdoc Rescuable function whoCanRescue() public view override returns (address) { return owner(); } @@ -144,10 +136,11 @@ contract AaveSwapper is Initializable, OwnableWithGuardian, Rescuable { /// @inheritdoc IRescuableBase function maxRescue( address erc20Token - ) public view override(RescuableBase, IRescuableBase) returns (uint256) { + ) public pure override(RescuableBase, IRescuableBase) returns (uint256) { return type(uint256).max; } + /// @dev Internal function to encode swap data function _getPriceCheckerAndData( address toToken, address fromOracle, @@ -161,6 +154,7 @@ contract AaveSwapper is Initializable, OwnableWithGuardian, Rescuable { } } + /// @dev Internal function to encode data for price checker function _getChainlinkCheckerData( address fromOracle, address toOracle diff --git a/src/swaps/interfaces/IAaveSwapper.sol b/src/swaps/interfaces/IAaveSwapper.sol new file mode 100644 index 000000000..e3577f5ef --- /dev/null +++ b/src/swaps/interfaces/IAaveSwapper.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IAaveSwapper { + /// @dev Emitted when a swap is canceled + /// @param fromToken The token to swap from + /// @param toToken The token to swap to + /// @param amount Amount of fromToken to swap + event SwapCanceled(address indexed fromToken, address indexed toToken, uint256 amount); + + /// @dev Emitted when a swap is submitted to Cow Swap + /// @param milkman Address of Milkman (Cow Swap) contract + /// @param fromToken Address of the token to swap from + /// @param toToken Address of the token to swap to + /// @param fromOracle Oracle to use for price validation for fromToken + /// @param toOracle Oracle to use for price validation for toToken + /// @param recipient Address receiving the swap + /// @param amount Amount of fromToken to swap + /// @param slippage The allowed slippage for the swap + event SwapRequested( + address milkman, + address indexed fromToken, + address indexed toToken, + address fromOracle, + address toOracle, + uint256 amount, + address indexed recipient, + uint256 slippage + ); + + /// @dev Provided address cannot be the zero-address + error Invalid0xAddress(); + + /// @dev Amount has to be greater than zero + error InvalidAmount(); + + /// @dev Recipient cannot be the zero-address + error InvalidRecipient(); + + /// @dev Oracle has not be set + error OracleNotSet(); + + /// @notice Returns the address of the 80-BAL-20-WETH Balancer LP + function BAL80WETH20() external view returns (address); + + /// @notice Initializes contract + function initialize() external; + + /// @notice Performs a swap via Cow Swap + /// @param milkman Address of Milkman (Cow Swap) contract + /// @param priceChecker Address of price checker to use for swap + /// @param fromToken Address of the token to swap from + /// @param toToken Address of the token to swap to + /// @param fromOracle Oracle to use for price validation for fromToken + /// @param toOracle Oracle to use for price validation for toToken + /// @param recipient Address receiving the swap + /// @param amount Amount of fromToken to swap + /// @param slippage The allowed slippage for the swap + function swap( + address milkman, + address priceChecker, + address fromToken, + address toToken, + address fromOracle, + address toOracle, + address recipient, + uint256 amount, + uint256 slippage + ) external; + + /// @notice Canceels a pending swap via Cow Swap + /// @param tradeMilkman Address of Milkman instance that holds funds in escrow + /// @param priceChecker Address of price checker to use for swap + /// @param fromToken Address of the token to swap from + /// @param toToken Address of the token to swap to + /// @param fromOracle Oracle to use for price validation for fromToken + /// @param toOracle Oracle to use for price validation for toToken + /// @param recipient Address receiving the swap + /// @param amount Amount of fromToken to swap + /// @param slippage The allowed slippage for the swap + function cancelSwap( + address tradeMilkman, + address priceChecker, + address fromToken, + address toToken, + address fromOracle, + address toOracle, + address recipient, + uint256 amount, + uint256 slippage + ) external; + + /// @notice Returns the expected amount out in token to swap to + /// @param priceChecker Address of price checker to use for swap + /// @param amount Amount of fromToken to swap + /// @param fromToken Address of the token to swap from + /// @param toToken Address of the token to swap to + /// @param fromOracle Oracle to use for price validation for fromToken + /// @param toOracle Oracle to use for price validation for toToken + function getExpectedOut( + address priceChecker, + uint256 amount, + address fromToken, + address toToken, + address fromOracle, + address toOracle + ) external view returns (uint256); +} diff --git a/src/swaps/interfaces/IMilkman.sol b/src/swaps/interfaces/IMilkman.sol index 21d8c318c..88caddd02 100644 --- a/src/swaps/interfaces/IMilkman.sol +++ b/src/swaps/interfaces/IMilkman.sol @@ -10,6 +10,7 @@ interface IMilkman { /// @param fromToken The token that the user wishes to sell. /// @param toToken The token that the user wishes to receive. /// @param to Who should receive the tokens. + /// @param appData The app data to be used in the CoW Protocol order. /// @param priceChecker A contract that verifies an order (mainly its minOut and fee) before Milkman signs it. /// @param priceCheckerData Data that gets passed to the price checker. function requestSwapExactTokensForTokens( @@ -17,6 +18,7 @@ interface IMilkman { IERC20 fromToken, IERC20 toToken, address to, + bytes32 appData, address priceChecker, bytes calldata priceCheckerData ) external; @@ -28,6 +30,7 @@ interface IMilkman { IERC20 fromToken, IERC20 toToken, address to, + bytes32 appData, address priceChecker, bytes calldata priceCheckerData ) external; diff --git a/tests/swaps/AaveSwapperTest.t.sol b/tests/swaps/AaveSwapperTest.t.sol index 30007650f..d7f1b9293 100644 --- a/tests/swaps/AaveSwapperTest.t.sol +++ b/tests/swaps/AaveSwapperTest.t.sol @@ -3,12 +3,13 @@ pragma solidity ^0.8.0; import {Test} from 'forge-std/Test.sol'; -import {AaveGovernanceV2} from 'aave-address-book/AaveGovernanceV2.sol'; +import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol'; import {AaveV2Ethereum, AaveV2EthereumAssets} from 'aave-address-book/AaveV2Ethereum.sol'; import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol'; import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol'; -import {AaveSwapper} from '../../src/swaps/AaveSwapper.sol'; +import {AaveSwapper} from 'src/swaps/AaveSwapper.sol'; +import {IAaveSwapper} from 'src/swaps/interfaces/IAaveSwapper.sol'; contract AaveSwapperTest is Test { event DepositedIntoV2(address indexed token, uint256 amount); @@ -30,14 +31,14 @@ contract AaveSwapperTest is Test { address public constant BAL80WETH20 = 0x5c6Ee304399DBdB9C8Ef030aB642B10820DB8F56; address public constant BPT_PRICE_CHECKER = 0xBeA6AAC5bDCe0206A9f909d80a467C93A7D6Da7c; address public constant CHAINLINK_PRICE_CHECKER = 0xe80a1C615F75AFF7Ed8F08c9F21f9d00982D666c; - address public constant MILKMAN = 0x11C76AD590ABDFFCD980afEC9ad951B160F02797; + address public constant MILKMAN = 0x060373D064d0168931dE2AB8DDA7410923d06E88; AaveSwapper public swaps; function setUp() public { - vm.createSelectFork(vm.rpcUrl('mainnet'), 19733176); + vm.createSelectFork(vm.rpcUrl('mainnet'), 21185924); - vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR); + vm.startPrank(GovernanceV3Ethereum.EXECUTOR_LVL_1); swaps = new AaveSwapper(); vm.stopPrank(); } @@ -58,7 +59,7 @@ contract TransferOwnership is AaveSwapperTest { function test_successful() public { address newAdmin = makeAddr('new-admin'); - vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR); + vm.startPrank(GovernanceV3Ethereum.EXECUTOR_LVL_1); swaps.transferOwnership(newAdmin); vm.stopPrank(); @@ -76,7 +77,7 @@ contract UpdateGuardian is AaveSwapperTest { address newManager = makeAddr('new-admin'); vm.expectEmit(); emit GuardianUpdated(swaps.guardian(), newManager); - vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR); + vm.startPrank(GovernanceV3Ethereum.EXECUTOR_LVL_1); swaps.updateGuardian(newManager); vm.stopPrank(); @@ -93,7 +94,7 @@ contract RemoveGuardian is AaveSwapperTest { function test_successful() public { vm.expectEmit(); emit GuardianUpdated(swaps.guardian(), address(0)); - vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR); + vm.startPrank(GovernanceV3Ethereum.EXECUTOR_LVL_1); swaps.updateGuardian(address(0)); vm.stopPrank(); @@ -119,8 +120,8 @@ contract AaveSwapperSwap is AaveSwapperTest { } function test_revertsIf_amountIsZero() public { - vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR); - vm.expectRevert(AaveSwapper.InvalidAmount.selector); + vm.startPrank(GovernanceV3Ethereum.EXECUTOR_LVL_1); + vm.expectRevert(IAaveSwapper.InvalidAmount.selector); swaps.swap( MILKMAN, CHAINLINK_PRICE_CHECKER, @@ -136,8 +137,8 @@ contract AaveSwapperSwap is AaveSwapperTest { } function test_revertsIf_fromTokenIsZeroAddress() public { - vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR); - vm.expectRevert(AaveSwapper.Invalid0xAddress.selector); + vm.startPrank(GovernanceV3Ethereum.EXECUTOR_LVL_1); + vm.expectRevert(IAaveSwapper.Invalid0xAddress.selector); swaps.swap( MILKMAN, CHAINLINK_PRICE_CHECKER, @@ -153,8 +154,8 @@ contract AaveSwapperSwap is AaveSwapperTest { } function test_revertsIf_toTokenIsZeroAddress() public { - vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR); - vm.expectRevert(AaveSwapper.Invalid0xAddress.selector); + vm.startPrank(GovernanceV3Ethereum.EXECUTOR_LVL_1); + vm.expectRevert(IAaveSwapper.Invalid0xAddress.selector); swaps.swap( MILKMAN, CHAINLINK_PRICE_CHECKER, @@ -170,8 +171,8 @@ contract AaveSwapperSwap is AaveSwapperTest { } function test_revertsIf_invalidRecipient() public { - vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR); - vm.expectRevert(AaveSwapper.InvalidRecipient.selector); + vm.startPrank(GovernanceV3Ethereum.EXECUTOR_LVL_1); + vm.expectRevert(IAaveSwapper.InvalidRecipient.selector); swaps.swap( MILKMAN, CHAINLINK_PRICE_CHECKER, @@ -188,7 +189,7 @@ contract AaveSwapperSwap is AaveSwapperTest { function test_successful() public { deal(AaveV2EthereumAssets.AAVE_UNDERLYING, address(swaps), 1_000e18); - vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR); + vm.startPrank(GovernanceV3Ethereum.EXECUTOR_LVL_1); vm.expectEmit(true, true, true, true); emit SwapRequested( @@ -235,7 +236,7 @@ contract CancelSwap is AaveSwapperTest { function test_revertsIf_noMatchingTrade() public { deal(AaveV2EthereumAssets.AAVE_UNDERLYING, address(swaps), 1_000e18); - vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR); + vm.startPrank(GovernanceV3Ethereum.EXECUTOR_LVL_1); swaps.swap( MILKMAN, CHAINLINK_PRICE_CHECKER, @@ -265,9 +266,9 @@ contract CancelSwap is AaveSwapperTest { function test_successful() public { deal(AaveV2EthereumAssets.AAVE_UNDERLYING, address(swaps), 1_000e18); - vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR); + vm.startPrank(GovernanceV3Ethereum.EXECUTOR_LVL_1); - vm.expectEmit(true, true, true, true); + vm.expectEmit(true, true, true, true, address(swaps)); emit SwapRequested( MILKMAN, AaveV2EthereumAssets.AAVE_UNDERLYING, @@ -290,14 +291,14 @@ contract CancelSwap is AaveSwapperTest { 200 ); - vm.expectEmit(); + vm.expectEmit(true, true, true, true, address(swaps)); emit SwapCanceled( AaveV2EthereumAssets.AAVE_UNDERLYING, AaveV2EthereumAssets.USDC_UNDERLYING, 1_000e18 ); swaps.cancelSwap( - 0x7e05Cf4Ba19B4DF16d1c37845cF925e3Ba1f190b, // Address generated by tests + 0xcd6b416C6bdF7B14C11cedcf9d61f02B28FB6fCB, // Address generated by tests CHAINLINK_PRICE_CHECKER, AaveV2EthereumAssets.AAVE_UNDERLYING, AaveV2EthereumAssets.USDC_UNDERLYING, @@ -322,15 +323,11 @@ contract EmergencyTokenTransfer is AaveSwapperTest { } function test_successful_governanceCaller() public { - address AAVE_WHALE = 0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8; - assertEq(IERC20(AaveV2EthereumAssets.AAVE_UNDERLYING).balanceOf(address(swaps)), 0); uint256 aaveAmount = 1_000e18; - vm.startPrank(AAVE_WHALE); - IERC20(AaveV2EthereumAssets.AAVE_UNDERLYING).transfer(address(swaps), aaveAmount); - vm.stopPrank(); + deal(AaveV2EthereumAssets.AAVE_UNDERLYING, address(swaps), aaveAmount); assertEq(IERC20(AaveV2EthereumAssets.AAVE_UNDERLYING).balanceOf(address(swaps)), aaveAmount); @@ -338,7 +335,7 @@ contract EmergencyTokenTransfer is AaveSwapperTest { address(AaveV2Ethereum.COLLECTOR) ); - vm.startPrank(AaveGovernanceV2.SHORT_EXECUTOR); + vm.startPrank(GovernanceV3Ethereum.EXECUTOR_LVL_1); swaps.emergencyTokenTransfer( AaveV2EthereumAssets.AAVE_UNDERLYING, address(AaveV2Ethereum.COLLECTOR), @@ -357,7 +354,7 @@ contract EmergencyTokenTransfer is AaveSwapperTest { contract GetExpectedOut is AaveSwapperTest { function test_revertsIf_fromOracleIsAddressZero() public { uint256 amount = 1e18; - vm.expectRevert(AaveSwapper.OracleNotSet.selector); + vm.expectRevert(IAaveSwapper.OracleNotSet.selector); swaps.getExpectedOut( CHAINLINK_PRICE_CHECKER, amount, @@ -370,7 +367,7 @@ contract GetExpectedOut is AaveSwapperTest { function test_revertsIf_toOracleIsAddressZero() public { uint256 amount = 1e18; - vm.expectRevert(AaveSwapper.OracleNotSet.selector); + vm.expectRevert(IAaveSwapper.OracleNotSet.selector); swaps.getExpectedOut( CHAINLINK_PRICE_CHECKER, amount, @@ -381,7 +378,7 @@ contract GetExpectedOut is AaveSwapperTest { ); } - function test_aaveToUsdc_withEthBasedOracles() public { + function test_aaveToUsdc_withEthBasedOracles() public view { /* This test is only to show that oracles with the same base * will return the correct value for trading, or at least very * close to USD based oracles. Nonetheless, ETH based oracles @@ -399,11 +396,11 @@ contract GetExpectedOut is AaveSwapperTest { AaveV2EthereumAssets.USDC_ORACLE ); - // April 25, 2024 AAVE/USD is around $90 - assertEq(expected / 1e4, 8941); // USDC is 6 decimals + // November 14, 2024 AAVE/USD is around $170 + assertEq(expected / 1e4, 17270); // USDC is 6 decimals } - function test_aaveToUsdc() public { + function test_aaveToUsdc() public view { uint256 amount = 1e18; uint256 expected = swaps.getExpectedOut( CHAINLINK_PRICE_CHECKER, @@ -414,11 +411,11 @@ contract GetExpectedOut is AaveSwapperTest { AaveV3EthereumAssets.USDC_ORACLE ); - // April 25, 2024 AAVE/USD is around $90 - assertEq(expected / 1e4, 9002); // USDC is 6 decimals + // November 14, 2024 AAVE/USD is around $170 + assertEq(expected / 1e4, 17001); // USDC is 6 decimals } - function test_ethToDai() public { + function test_ethToDai() public view { uint256 amount = 1e18; uint256 expected = swaps.getExpectedOut( CHAINLINK_PRICE_CHECKER, @@ -429,11 +426,11 @@ contract GetExpectedOut is AaveSwapperTest { AaveV3EthereumAssets.DAI_ORACLE ); - // April 25, 2024 ETH/USD is around $3,122 - assertEq(expected / 1e18, 3122); // WETH is 18 decimals + // November 14, 2024 ETH/USD is around $3,190 + assertEq(expected / 1e18, 3187); // WETH is 18 decimals } - function test_ethToBal() public { + function test_ethToBal() public view { uint256 amount = 1e18; uint256 expected = swaps.getExpectedOut( CHAINLINK_PRICE_CHECKER, @@ -444,11 +441,11 @@ contract GetExpectedOut is AaveSwapperTest { AaveV3EthereumAssets.BAL_ORACLE ); - // April 25, 2024 ETH/BAL is 1 ETH is around 823 BAL tokens - assertEq(expected / 1e18, 823); // WETH and BAL are 18 decimals + // November 14, 2024 ETH/BAL is 1 ETH is around 1515 BAL tokens + assertEq(expected / 1e18, 1515); // WETH and BAL are 18 decimals } - function test_balTo80BAL20WETH() public { + function test_balTo80BAL20WETH() public view { uint256 amount = 100e18; uint256 expected = swaps.getExpectedOut( BPT_PRICE_CHECKER, @@ -459,7 +456,7 @@ contract GetExpectedOut is AaveSwapperTest { address(0) ); - // April 25, 2024 BAL/USD should be around 0.30 at 100 units traded, 30 units expected. - assertEq(expected / 1e18, 30); // WETH and BAL are 18 decimals + // November 14, 2024 BAL/USD should be around 2,10 at 100 units traded, 27 units expected. + assertEq(expected / 1e18, 27); // WETH and BAL are 18 decimals } }