diff --git a/contracts/mainnet/connectors/morpho-aave-v3/events.sol b/contracts/mainnet/connectors/morpho-aave-v3/events.sol new file mode 100644 index 00000000..404eb789 --- /dev/null +++ b/contracts/mainnet/connectors/morpho-aave-v3/events.sol @@ -0,0 +1,152 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +contract Events { + event LogDeposit( + address tokenAddress, + uint256 amount, + uint256 getId, + uint256 setId + ); + + event LogDepositWithMaxIterations( + address tokenAddress, + uint256 amount, + uint256 maxIteration, + uint256 getId, + uint256 setId + ); + + event LogDepositOnBehalf( + address tokenAddress, + uint256 amount, + address onBehalf, + uint256 getId, + uint256 setId + ); + + event LogDepositOnBehalfWithMaxIterations( + address tokenAddress, + uint256 amount, + address onBehalf, + uint256 maxIteration, + uint256 getId, + uint256 setId + ); + + event LogDepositCollateral( + address tokenAddress, + uint256 amount, + uint256 getId, + uint256 setId + ); + + event LogDepositCollateralOnBehalf( + address tokenAddress, + uint256 amount, + address onBehalf, + uint256 getId, + uint256 setId + ); + + event LogBorrow( + address tokenAddress, + uint256 amount, + uint256 getId, + uint256 setId + ); + + event LogBorrowOnBehalf( + address tokenAddress, + uint256 amount, + address onBehalf, + address receiver, + uint256 getId, + uint256 setId + ); + + event LogBorrowWithMaxIterations( + address tokenAddress, + uint256 amount, + address receiver, + uint256 maxIteration, + uint256 getId, + uint256 setId + ); + + event LogBorrowOnBehalfWithMaxIterations( + address tokenAddress, + uint256 amount, + address onBehalf, + address receiver, + uint256 maxIteration, + uint256 getId, + uint256 setId + ); + + event LogWithdraw( + address tokenAddress, + uint256 amount, + uint256 getId, + uint256 setId + ); + + event LogWithdrawOnBehalf( + address tokenAddress, + uint256 amount, + address onBehalf, + address receiver, + uint256 getId, + uint256 setId + ); + + event LogWithdrawOnBehalfWithMaxIterations( + address tokenAddress, + uint256 amount, + address onBehalf, + address receiver, + uint256 maxIteration, + uint256 getId, + uint256 setId + ); + + event LogWithdrawCollateral( + address tokenAddress, + uint256 amount, + address receiver, + uint256 getId, + uint256 setId + ); + + event LogWithdrawCollateralOnBehalf( + address tokenAddress, + uint256 amount, + address onBehalf, + address receiver, + uint256 getId, + uint256 setId + ); + + event LogPayback( + address tokenAddress, + uint256 amount, + uint256 getId, + uint256 setId + ); + + event LogPaybackOnBehalf( + address tokenAddress, + uint256 amount, + address onBehalf, + uint256 getId, + uint256 setId + ); + + event LogApproveManger(address manger, bool isAllowed); + + event LogUpdateMaxIterations( + uint256 oldIterations, + uint256 newIterations + ); +} diff --git a/contracts/mainnet/connectors/morpho-aave-v3/helpers.sol b/contracts/mainnet/connectors/morpho-aave-v3/helpers.sol new file mode 100644 index 00000000..6afd0997 --- /dev/null +++ b/contracts/mainnet/connectors/morpho-aave-v3/helpers.sol @@ -0,0 +1,31 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; +import "./interface.sol"; +import "../../common/stores.sol"; +import "../../common/basic.sol"; +import "../../common/interfaces.sol"; + +abstract contract Helpers is Stores, Basic { + IMorphoCore public constant MORPHO_AAVE_V3 = + IMorphoCore(0x33333aea097c193e66081E930c33020272b33333); + + uint256 public max_iteration = 4; + + function _performEthToWethConversion( + address _tokenAddress, + uint256 _amount, + uint256 _getId + ) internal returns (TokenInterface _tokenContract, uint256 _amt) { + _amt = getUint(_getId, _amount); + + if (_tokenAddress == ethAddr) { + _tokenContract = TokenInterface(wethAddr); + if (_amt == type(uint256).max) _amt = address(this).balance; + convertEthToWeth(true, _tokenContract, _amt); + } else { + _tokenContract = TokenInterface(_tokenAddress); + if (_amt == type(uint256).max) _amt = _tokenContract.balanceOf(address(this)); + } + } +} diff --git a/contracts/mainnet/connectors/morpho-aave-v3/interface.sol b/contracts/mainnet/connectors/morpho-aave-v3/interface.sol new file mode 100644 index 00000000..d1f79f64 --- /dev/null +++ b/contracts/mainnet/connectors/morpho-aave-v3/interface.sol @@ -0,0 +1,30 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +interface IMorphoCore { + + function supply(address underlying, uint256 amount, address onBehalf, uint256 maxIterations) + external + returns (uint256 supplied); + + function supplyCollateral(address underlying, uint256 amount, address onBehalf) + external + returns (uint256 supplied); + + function borrow(address underlying, uint256 amount, address onBehalf, address receiver, uint256 maxIterations) + external + returns (uint256 borrowed); + + function repay(address underlying, uint256 amount, address onBehalf) external returns (uint256 repaid); + + function withdraw(address underlying, uint256 amount, address onBehalf, address receiver, uint256 maxIterations) + external + returns (uint256 withdrawn); + + function withdrawCollateral(address underlying, uint256 amount, address onBehalf, address receiver) + external + returns (uint256 withdrawn); + + function approveManager(address manager, bool isAllowed) external; +} diff --git a/contracts/mainnet/connectors/morpho-aave-v3/main.sol b/contracts/mainnet/connectors/morpho-aave-v3/main.sol new file mode 100644 index 00000000..7fe8679d --- /dev/null +++ b/contracts/mainnet/connectors/morpho-aave-v3/main.sol @@ -0,0 +1,753 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; +import "./helpers.sol"; +import "./events.sol"; + +abstract contract MorphoAaveV3 is Helpers, Events { + /** + * @dev Deposit ETH/ERC20_Token. + * @notice Deposit a token to Morpho Aave for lending. + * @param _tokenAddress The address of underlying token to deposit.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param _amount The amount of the token (in underlying) to deposit. (For max: `uint256(-1)`) + * @param _getId ID to retrieve amt. + * @param _setId ID stores the amount of tokens deposited. + */ + function deposit( + address _tokenAddress, + uint256 _amount, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + ( + TokenInterface _tokenContract, + uint256 _amt + ) = _performEthToWethConversion(_tokenAddress, _amount, _getId); + + approve(_tokenContract, address(MORPHO_AAVE_V3), _amt); + + MORPHO_AAVE_V3.supply(address(_tokenContract), _amt, address(this), max_iteration); + + setUint(_setId, _amt); + + _eventName = "LogDeposit(address,uint256,uint256,uint256)"; + _eventParam = abi.encode( + _tokenAddress, + _amt, + _getId, + _setId + ); + } + + /** + * @dev Deposit ETH/ERC20_Token. + * @notice Deposit a token to Morpho Aave for lending. + * @param _tokenAddress The address of underlying token to deposit.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param _amount The amount of the token (in underlying) to deposit. (For max: `uint256(-1)`) + * @param _maxIteration The maximum number of iterations allowed during the matching process. + * @param _getId ID to retrieve amt. + * @param _setId ID stores the amount of tokens deposited. + */ + function depositWithMaxIterations( + address _tokenAddress, + uint256 _amount, + uint256 _maxIteration, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + ( + TokenInterface _tokenContract, + uint256 _amt + ) = _performEthToWethConversion(_tokenAddress, _amount, _getId); + + approve(_tokenContract, address(MORPHO_AAVE_V3), _amt); + + MORPHO_AAVE_V3.supply(address(_tokenContract), _amt, address(this), _maxIteration); + + setUint(_setId, _amt); + + _eventName = "LogDepositWithMaxIterations(address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode( + _tokenAddress, + _amt, + _maxIteration, + _getId, + _setId + ); + } + + /** + * @dev Deposit ETH/ERC20_Token on behalf of a user. + * @notice Deposit a token to Morpho Aave for lending on behalf of a user. + * @param _tokenAddress The address of underlying token to deposit.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param _amount The amount of the token (in underlying) to deposit. (For max: `uint256(-1)`) + * @param _onBehalf The address of user on behalf of whom we want to deposit. + * @param _getId ID to retrieve amt. + * @param _setId ID stores the amount of tokens deposited. + */ + function depositOnBehalf( + address _tokenAddress, + uint256 _amount, + address _onBehalf, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + ( + TokenInterface _tokenContract, + uint256 _amt + ) = _performEthToWethConversion(_tokenAddress, _amount, _getId); + + approve(_tokenContract, address(MORPHO_AAVE_V3), _amt); + + MORPHO_AAVE_V3.supply(address(_tokenContract), _amt, _onBehalf, max_iteration); + + setUint(_setId, _amt); + + _eventName = "LogDepositOnBehalf(address,uint256,address,uint256,uint256)"; + _eventParam = abi.encode( + _tokenAddress, + _amt, + _onBehalf, + _getId, + _setId + ); + } + + /** + * @dev Deposit ETH/ERC20_Token on behalf of a user. + * @notice Deposit a token to Morpho Aave for lending on behalf of a user with max iterations. + * @param _tokenAddress The address of underlying token to deposit.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param _amount The amount of the token (in underlying) to deposit. (For max: `uint256(-1)`) + * @param _onBehalf The address of user on behalf of whom we want to deposit. + * @param _maxIteration The maximum number of iterations allowed during the matching process. + * @param _getId ID to retrieve amt. + * @param _setId ID stores the amount of tokens deposited. + */ + function depositOnBehalfWithMaxIterations ( + address _tokenAddress, + uint256 _amount, + address _onBehalf, + uint256 _maxIteration, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + ( + TokenInterface _tokenContract, + uint256 _amt + ) = _performEthToWethConversion(_tokenAddress, _amount, _getId); + + approve(_tokenContract, address(MORPHO_AAVE_V3), _amt); + + MORPHO_AAVE_V3.supply(address(_tokenContract), _amt, _onBehalf, _maxIteration); + + setUint(_setId, _amt); + + _eventName = "LogDepositOnBehalfWithMaxIterations(address,uint256,address,uint256,uint256,uint256)"; + _eventParam = abi.encode( + _tokenAddress, + _amt, + _onBehalf, + _maxIteration, + _getId, + _setId + ); + } + + /** + * @dev Deposit ETH/ERC20_Token on behalf of a user. + * @notice Deposit a token to Morpho Aave for lending / collaterization. + * @param _tokenAddress The address of underlying token to deposit.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param _amount The amount of the token (in underlying) to deposit. (For max: `uint256(-1)`) + * @param _getId ID to retrieve amt. + * @param _setId ID stores the amount of tokens deposited. + */ + function depositCollateral( + address _tokenAddress, + uint256 _amount, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + ( + TokenInterface _tokenContract, + uint256 _amt + ) = _performEthToWethConversion(_tokenAddress, _amount, _getId); + + approve(_tokenContract, address(MORPHO_AAVE_V3), _amt); + + MORPHO_AAVE_V3.supplyCollateral(address(_tokenContract), _amt, address(this)); + + setUint(_setId, _amt); + + _eventName = "LogDepositCollateral(address,uint256,uint256,uint256)"; + _eventParam = abi.encode( + _tokenAddress, + _amt, + _getId, + _setId + ); + } + + /** + * @dev Deposit ETH/ERC20_Token on behalf of a user. + * @notice Deposit a token to Morpho Aave for lending / collaterization on behalf of a user. + * @param _tokenAddress The address of underlying token to deposit.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param _amount The amount of the token (in underlying) to deposit. (For max: `uint256(-1)`) + * @param _onBehalf The address of user on behalf to deposit. + * @param _getId ID to retrieve amt. + * @param _setId ID stores the amount of tokens deposited. + */ + function depositCollateralOnBehalf( + address _tokenAddress, + uint256 _amount, + address _onBehalf, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + ( + TokenInterface _tokenContract, + uint256 _amt + ) = _performEthToWethConversion(_tokenAddress, _amount, _getId); + + approve(_tokenContract, address(MORPHO_AAVE_V3), _amt); + + MORPHO_AAVE_V3.supplyCollateral(address(_tokenContract), _amt, _onBehalf); + + setUint(_setId, _amt); + + _eventName = "LogDepositCollateralOnBehalf(address,uint256,address,uint256,uint256)"; + _eventParam = abi.encode( + _tokenAddress, + _amt, + _onBehalf, + _getId, + _setId + ); + } + + /** + * @dev Borrow ETH/ERC20_Token. + * @notice Borrow a token from Morpho Aave. + * @param _tokenAddress The address of underlying token to borrow.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param _amount The amount of the token (in underlying) to borrow. + * @param _getId ID to retrieve amt. + * @param _setId ID stores the amount of tokens borrowed. + */ + function borrow( + address _tokenAddress, + uint256 _amount, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + uint256 _amt = getUint(_getId, _amount); + bool _isETH = _tokenAddress == ethAddr; + address _token = _isETH ? wethAddr : _tokenAddress; + + uint256 _borrowed = MORPHO_AAVE_V3.borrow(_token, _amt, address(this), address(this), max_iteration); + + convertWethToEth(_isETH, TokenInterface(_token), _borrowed); + + setUint(_setId, _borrowed); + + _eventName = "LogBorrow(address,uint256,uint256,uint256)"; + _eventParam = abi.encode( + _tokenAddress, + _borrowed, + _getId, + _setId + ); + } + + /** + * @dev Borrow ETH/ERC20_Token. + * @notice Borrow a token from Morpho Aave V3. + * @param _tokenAddress The address of underlying token to borrow.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param _amount The amount of the token (in underlying) to borrow. + * @param _onBehalf The address of user on behalf to borrow. + * @param _receiver The address of receiver to receive the borrowed tokens. + Note that if receiver is not the same as the borrower, receiver will receive WETH instead of ETH. + * @param _getId ID to retrieve amt. + * @param _setId ID stores the amount of tokens borrowed. + */ + function borrowOnBehalf( + address _tokenAddress, + uint256 _amount, + address _onBehalf, + address _receiver, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + uint256 _amt = getUint(_getId, _amount); + bool _isETH = _tokenAddress == ethAddr; + address _token = _isETH ? wethAddr : _tokenAddress; + + uint256 _borrowed = MORPHO_AAVE_V3.borrow(_token, _amt, _onBehalf, _receiver, max_iteration); + + if(_receiver == address(this)) convertWethToEth(_isETH, TokenInterface(_token), _borrowed); + + setUint(_setId, _borrowed); + + _eventName = "LogBorrowOnBehalf(address,uint256,addresss,address,uint256,uint256)"; + _eventParam = abi.encode( + _tokenAddress, + _borrowed, + _onBehalf, + _receiver, + _getId, + _setId + ); + } + + /** + * @dev Borrow ETH/ERC20_Token. + * @notice Borrow a token from Morpho Aave. + * @param _tokenAddress The address of underlying token to borrow.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param _amount The amount of the token (in underlying) to borrow. + * @param _receiver The address of receiver to receive the borrowed tokens. + Note that if receiver is not the same as the borrower, receiver will receive WETH instead of ETH. + * @param _maxIteration The maximum number of iterations to be used for borrow. + * @param _getId ID to retrieve amt. + * @param _setId ID stores the amount of tokens borrowed. + */ + function borrowWithMaxIterations( + address _tokenAddress, + uint256 _amount, + address _receiver, + uint256 _maxIteration, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + uint256 _amt = getUint(_getId, _amount); + bool _isETH = _tokenAddress == ethAddr; + address _token = _isETH ? wethAddr : _tokenAddress; + + uint256 _borrowed = MORPHO_AAVE_V3.borrow(_token, _amt, address(this), _receiver, _maxIteration); + + if(_receiver == address(this)) convertWethToEth(_isETH, TokenInterface(_token), _borrowed); + + setUint(_setId, _borrowed); + + _eventName = "LogBorrowWithMaxIterations(address,uint256,addresss,uint256,uint256,uint256)"; + _eventParam = abi.encode( + _tokenAddress, + _borrowed, + _receiver, + _maxIteration, + _getId, + _setId + ); + } + + /** + * @dev Borrow ETH/ERC20_Token. + * @notice Borrow a token from Morpho Aave. + * @param _tokenAddress The address of underlying token to borrow.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param _amount The amount of the token (in underlying) to borrow. + * @param _onBehalf The address of user on behalf to borrow. + * @param _receiver The address of receiver to receive the borrowed tokens. + Note that if receiver is not the same as the borrower, receiver will receive WETH instead of ETH. + * @param _maxIteration The maximum number of iterations to be used for borrow. + * @param _getId ID to retrieve amt. + * @param _setId ID stores the amount of tokens borrowed. + */ + function borrowOnBehalfWithMaxIterations ( + address _tokenAddress, + uint256 _amount, + address _onBehalf, + address _receiver, + uint256 _maxIteration, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + uint256 _amt = getUint(_getId, _amount); + bool _isETH = _tokenAddress == ethAddr; + address _token = _isETH ? wethAddr : _tokenAddress; + + uint256 _borrowed = MORPHO_AAVE_V3.borrow(_token, _amt, _onBehalf, _receiver, _maxIteration); + + if(_receiver == address(this)) convertWethToEth(_isETH, TokenInterface(_token), _borrowed); + + setUint(_setId, _borrowed); + + _eventName = "LogBorrowOnBehalfWithMaxIterations(address,uint256,addresss,address,uint256,uint256,uint256)"; + _eventParam = abi.encode( + _tokenAddress, + _borrowed, + _onBehalf, + _receiver, + _maxIteration, + _getId, + _setId + ); + } + + /** + * @dev Withdraw ETH/ERC20_Token. + * @notice Withdraw a token from Morpho Aave. + * @param _tokenAddress The address of underlying token to withdraw.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param _amount The amount of the token (in underlying) to withdraw. (For max: `uint256(-1)`) + * @param _getId ID to retrieve amt. + * @param _setId ID stores the amount of tokens withdrawed. + */ + function withdraw( + address _tokenAddress, + uint256 _amount, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + uint256 _amt = getUint(_getId, _amount); + bool _isEth = _tokenAddress == ethAddr; + address _token = _isEth? wethAddr : _tokenAddress; + + // Morpho will internally handle max amount conversion by taking the minimum of amount or supplied collateral. + uint256 _withdrawn = MORPHO_AAVE_V3.withdraw(_token, _amt, address(this), address(this), max_iteration); + + convertWethToEth(_isEth, TokenInterface(_token), _withdrawn); + + setUint(_setId, _withdrawn); + + _eventName = "LogWithdraw(address,uint256,uint256,uint256)"; + _eventParam = abi.encode( + _tokenAddress, + _withdrawn, + _getId, + _setId + ); + } + + /** + * @dev Withdraw ETH/ERC20_Token. + * @notice Withdraw a token from Morpho Aave. + * @param _tokenAddress The address of underlying token to withdraw.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param _amount The amount of the token (in underlying) to withdraw. (For max: `uint256(-1)`) + * @param _onBehalf Address for which tokens are being withdrawn. + * @param _receiver Address to which tokens are being transferred. + Note that if receiver is not the same as the supplier, receiver will receive WETH instead of ETH. + * @param _getId ID to retrieve amt. + * @param _setId ID stores the amount of tokens withdrawed. + */ + function withdrawOnBehalf( + address _tokenAddress, + uint256 _amount, + address _onBehalf, + address _receiver, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + uint256 _amt = getUint(_getId, _amount); + bool _isEth = _tokenAddress == ethAddr; + address _token = _isEth ? wethAddr : _tokenAddress; + + // Morpho will internally handle max amount conversion by taking the minimum of amount or supplied collateral. + uint256 _withdrawn = MORPHO_AAVE_V3.withdraw(_token, _amt, _onBehalf, _receiver, max_iteration); + + if(_receiver == address(this)) convertWethToEth(_isEth, TokenInterface(_token), _withdrawn); + + setUint(_setId, _withdrawn); + + _eventName = "LogWithdrawOnBehalf(address,uint256,address,address,uint256,uint256)"; + _eventParam = abi.encode( + _tokenAddress, + _withdrawn, + _onBehalf, + _receiver, + _getId, + _setId + ); + } + + /** + * @dev Withdraw ETH/ERC20_Token. + * @notice Withdraw a token from Morpho Aave. + * @param _tokenAddress The address of underlying token to withdraw.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param _amount The amount of the token (in underlying) to withdraw. (For max: `uint256(-1)`) + * @param _onBehalf Address for which tokens are being withdrawn. + * @param _receiver Address to which tokens are being transferred. + Note that if receiver is not the same as the supplier, receiver will receive WETH instead of ETH. + * @param _maxIteration Max number of iterations to run. + * @param _getId ID to retrieve amt. + * @param _setId ID stores the amount of tokens withdrawed. + */ + function withdrawOnBehalfWithMaxIterations( + address _tokenAddress, + uint256 _amount, + address _onBehalf, + address _receiver, + uint256 _maxIteration, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + uint256 _amt = getUint(_getId, _amount); + bool _isEth = _tokenAddress == ethAddr; + address _token = _isEth ? wethAddr : _tokenAddress; + + // Morpho will internally handle max amount conversion by taking the minimum of amount or supplied collateral. + uint256 _withdrawn = MORPHO_AAVE_V3.withdraw(_token, _amt, _onBehalf, _receiver, _maxIteration); + + if(_receiver == address(this)) convertWethToEth(_isEth, TokenInterface(_token), _withdrawn); + + setUint(_setId, _withdrawn); + + _eventName = "LogWithdrawOnBehalfWithMaxIterations(address,uint256,address,address,uint256,uint256,uint256)"; + _eventParam = abi.encode( + _tokenAddress, + _withdrawn, + _onBehalf, + _receiver, + _maxIteration, + _getId, + _setId + ); + } + + /** + * @dev Withdraw ETH/ERC20 collateral token. + * @notice Withdraw a token from Morpho Aave. + * @param _tokenAddress The address of underlying token to withdraw.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param _amount The amount of the token (in underlying) to withdraw. (For max: `uint256(-1)`) + * @param _receiver Address to which tokens are being transferred. + Note that if receiver is not the same as the supplier, receiver will receive WETH instead of ETH. + * @param _getId ID to retrieve amt. + * @param _setId ID stores the amount of tokens withdrawed. + */ + function withdrawCollateral( + address _tokenAddress, + uint256 _amount, + address _receiver, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + uint256 _amt = getUint(_getId, _amount); + bool _isEth = _tokenAddress == ethAddr; + address _token = _isEth ? wethAddr : _tokenAddress; + + uint256 _withdrawn = MORPHO_AAVE_V3.withdrawCollateral(_token, _amt, address(this), _receiver); + + if(_receiver == address(this)) convertWethToEth(_isEth, TokenInterface(_token), _withdrawn); + + setUint(_setId, _withdrawn); + + _eventName = "LogWithdrawCollateral(address,uint256,address,uint256,uint256)"; + _eventParam = abi.encode( + _tokenAddress, + _withdrawn, + _receiver, + _getId, + _setId + ); + } + + /** + * @dev Withdraw ETH/ERC20 collateral token. + * @notice Withdraw a token from Morpho Aave. + * @param _tokenAddress The address of underlying token to withdraw.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param _amount The amount of the token (in underlying) to withdraw. (For max: `uint256(-1)`) + * @param _onBehalf Address for which tokens are being withdrawn. + * @param _receiver Address to which tokens are being transferred. + Note that if receiver is not the same as the supplier, receiver will receive WETH instead of ETH. + * @param _getId ID to retrieve amt. + * @param _setId ID stores the amount of tokens withdrawed. + */ + function withdrawCollateralOnBehalf( + address _tokenAddress, + uint256 _amount, + address _onBehalf, + address _receiver, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + uint256 _amt = getUint(_getId, _amount); + bool _isEth = _tokenAddress == ethAddr; + address _token = _isEth ? wethAddr : _tokenAddress; + + uint256 _withdrawn = MORPHO_AAVE_V3.withdrawCollateral(_token, _amt, _onBehalf, _receiver); + + if(_receiver == address(this)) convertWethToEth(_isEth, TokenInterface(_token), _withdrawn); + + setUint(_setId, _withdrawn); + + _eventName = "LogWithdrawCollateralOnBehalf(address,uint256,address,address,uint256,uint256)"; + _eventParam = abi.encode( + _tokenAddress, + _withdrawn, + _onBehalf, + _receiver, + _getId, + _setId + ); + } + + /** + * @dev Payback ETH/ERC20_Token. + * @notice Payback a token to Morpho Aave. + * @param _tokenAddress The address of underlying token to payback.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param _amount The amount of the token (in underlying) to payback. (For max: `uint256(-1)`) + * @param _getId ID to retrieve amt. + * @param _setId ID stores the amount of tokens paid back. + */ + function payback( + address _tokenAddress, + uint256 _amount, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + ( + TokenInterface _tokenContract, + uint256 _amt + ) = _performEthToWethConversion(_tokenAddress, _amount, _getId); + + approve(_tokenContract, address(MORPHO_AAVE_V3), _amt); + + MORPHO_AAVE_V3.repay(address(_tokenContract), _amt, address(this)); + + setUint(_setId, _amt); + + _eventName = "LogPayback(address,uint256,uint256,uint256)"; + _eventParam = abi.encode( + _tokenAddress, + _amt, + _getId, + _setId + ); + } + + /** + * @dev Payback ETH/ERC20_Token. + * @notice Payback a token to Morpho Aave. + * @param _tokenAddress The address of underlying token to payback.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param _onBehalf The address of user who's debt to repay. + * @param _amount The amount of the token (in underlying) to payback. (For max: `uint256(-1)`) + * @param _getId ID to retrieve amt. + * @param _setId ID stores the amount of tokens paid back. + */ + function paybackOnBehalf( + address _tokenAddress, + address _onBehalf, + uint256 _amount, + uint256 _getId, + uint256 _setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + ( + TokenInterface _tokenContract, + uint256 _amt + ) = _performEthToWethConversion(_tokenAddress, _amount, _getId); + + approve(_tokenContract, address(MORPHO_AAVE_V3), _amt); + + MORPHO_AAVE_V3.repay(address(_tokenContract), _amt, _onBehalf); + + setUint(_setId, _amt); + + _eventName = "LogPaybackOnBehalf(address,uint256,address,uint256,uint256)"; + _eventParam = abi.encode( + _tokenAddress, + _amt, + _onBehalf, + _getId, + _setId + ); + } + + /// @notice Approves a `manager` to borrow/withdraw on behalf of the sender. + /// @param _manager The address of the manager. + /// @param _isAllowed Whether `manager` is allowed to manage `msg.sender`'s position or not. + function approveManager(address _manager, bool _isAllowed) + external + returns (string memory _eventName, bytes memory _eventParam) + { + MORPHO_AAVE_V3.approveManager(_manager, _isAllowed); + + _eventName = "LogApproveManger(address,bool)"; + _eventParam = abi.encode( + _manager, + _isAllowed + ); + } + + /// @notice Updates the max iterations for a `repay` or a `withdraw`. + /// @param _iterations New iteration count. + function updateMaxIterations(uint256 _iterations) + external + returns (string memory _eventName, bytes memory _eventParam) + { + uint256 _oldIterations = max_iteration; + max_iteration = _iterations; + + _eventName = "LogUpdateMaxIterations(uint256,uint256)"; + _eventParam = abi.encode( + _oldIterations, + max_iteration + ); + } +} + +contract ConnectV2MorphoAaveV3 is MorphoAaveV3 { + string public constant name = "Morpho-AaveV3-v1.0"; +} diff --git a/contracts/mainnet/connectors/morpho-rewards/events.sol b/contracts/mainnet/connectors/morpho-rewards/events.sol index 905a08b4..431ce57e 100644 --- a/contracts/mainnet/connectors/morpho-rewards/events.sol +++ b/contracts/mainnet/connectors/morpho-rewards/events.sol @@ -16,6 +16,13 @@ contract Events { uint256 setId ); + event LogClaimedMorphoAaveV3( + address[] poolTokenAddresses, + address onBehalf, + address[] rewardTokens, + uint256[] claimedAmounts + ); + event LogClaimedCompound( address[] poolTokenAddresses, bool tradeForMorphoToken, diff --git a/contracts/mainnet/connectors/morpho-rewards/helpers.sol b/contracts/mainnet/connectors/morpho-rewards/helpers.sol index 7cd6799d..6ede03c8 100644 --- a/contracts/mainnet/connectors/morpho-rewards/helpers.sol +++ b/contracts/mainnet/connectors/morpho-rewards/helpers.sol @@ -13,4 +13,7 @@ abstract contract Helpers is Basic { IMorphoRewardsDistributor public constant MORPHO_REWARDS = IMorphoRewardsDistributor(0x3B14E5C73e0A56D607A8688098326fD4b4292135); + + IMorphoCoreV3 public constant MORPHO_AAVE_V3 = + IMorphoCoreV3(0x33333aea097c193e66081E930c33020272b33333); } diff --git a/contracts/mainnet/connectors/morpho-rewards/interface.sol b/contracts/mainnet/connectors/morpho-rewards/interface.sol index 46fefd84..bbc5016e 100644 --- a/contracts/mainnet/connectors/morpho-rewards/interface.sol +++ b/contracts/mainnet/connectors/morpho-rewards/interface.sol @@ -15,3 +15,10 @@ interface IMorphoRewardsDistributor { bytes32[] calldata _proof ) external; } + +interface IMorphoCoreV3 { + function claimRewards( + address[] calldata _assets, + address _onBehalf + ) external returns (address[] memory _rewardTokens, uint256[] memory _claimedAmounts); +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/morpho-rewards/main.sol b/contracts/mainnet/connectors/morpho-rewards/main.sol index f096c6e4..86cf96c9 100644 --- a/contracts/mainnet/connectors/morpho-rewards/main.sol +++ b/contracts/mainnet/connectors/morpho-rewards/main.sol @@ -100,8 +100,36 @@ abstract contract MorphoRewards is Helpers, Events { _setId ); } + + /** + * @dev Claims rewards for the given assets from Morpho Aave V3. + * @notice Claims rewards for the given assets. + * @param _poolTokenAddresses The assets to claim rewards from (aToken or variable debt token). + * @param _onBehalf The address for which rewards are claimed and sent to. + */ + function claimMorphoAaveV3( + address[] calldata _poolTokenAddresses, + address _onBehalf + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + (address[] memory _rewardTokens, uint256[] memory _claimedAmounts) = MORPHO_AAVE_V3.claimRewards( + _poolTokenAddresses, + _onBehalf + ); + + _eventName = "LogClaimedMorphoAaveV3(address[],address,address[],uint256[])"; + _eventParam = abi.encode( + _poolTokenAddresses, + _onBehalf, + _rewardTokens, + _claimedAmounts + ); + } } contract ConnectV2MorphoRewards is MorphoRewards { - string public constant name = "Morpho-Rewards-v1.0"; + string public constant name = "Morpho-Rewards-v1.1"; } diff --git a/package.json b/package.json index 58b5f2ff..754cf2f3 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "check": "ts-node status-checks/huskyCheck.ts", "check-husky": "ts-node status-checks/huskyCheck.ts", "deploy": "ts-node scripts/deployConnectorsFromCmd.ts", - "test:runner": "hardhat run scripts/tests/run-tests.ts", + "test:runner": "NODE_OPTIONS='--max-old-space-size=4096' hardhat run scripts/tests/run-tests.ts", "typechain": "hardhat typechain", "compile": "hardhat compile", "deploy:runner": "hardhat run scripts/deployment/deployConnectorsFromCmd.ts", diff --git a/test/mainnet/morpho/morpho-aave-v3.test.ts b/test/mainnet/morpho/morpho-aave-v3.test.ts new file mode 100644 index 00000000..1047e38d --- /dev/null +++ b/test/mainnet/morpho/morpho-aave-v3.test.ts @@ -0,0 +1,484 @@ +import { expect } from "chai"; +import hre from "hardhat"; +import { abis } from "../../../scripts/constant/abis"; +import { addresses } from "../../../scripts/tests/mainnet/addresses"; +import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector"; +import { getMasterSigner } from "../../../scripts/tests/getMasterSigner"; +import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2"; +import { ConnectV2MorphoAaveV3__factory, IERC20Minimal__factory } from "../../../typechain"; +import { parseEther, parseUnits } from "@ethersproject/units"; +import { encodeSpells } from "../../../scripts/tests/encodeSpells"; +import { dsaMaxValue, tokens } from "../../../scripts/tests/mainnet/tokens"; + +const { ethers } = hre; +import type { Signer, Contract } from "ethers"; + +const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; +const ACC_USDC = "0xe78388b4ce79068e89bf8aa7f218ef6b9ab0e9d0"; +const Usdc = parseUnits("5000", 6); + +const DAI = "0x6b175474e89094c44da98b954eedeac495271d0f"; +const ACC_DAI = "0xcd6Eb888e76450eF584E8B51bB73c76ffBa21FF2"; +const Dai = parseUnits("1", 18); + +const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; + +const user = "0x41bc7d0687e6cea57fa26da78379dfdc5627c56d"; + +const token_usdc = new ethers.Contract(USDC, IERC20Minimal__factory.abi, ethers.provider); + +const token_dai = new ethers.Contract(DAI, IERC20Minimal__factory.abi, ethers.provider); + +const token_weth = new ethers.Contract(WETH, IERC20Minimal__factory.abi, ethers.provider); + +describe("Morpho-Aave-v3", function () { + const connectorName = "MORPHO-AAVE-V3-TEST-A"; + let connector: any; + + let wallet0: Signer, wallet1: Signer; + let dsaWallet0: any; + let dsaWallet1: any; + let instaConnectorsV2: Contract; + let masterSigner: Signer; + + before(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + // @ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url, + blockNumber: 17544460 + } + } + ] + }); + [wallet0, wallet1] = await ethers.getSigners(); + masterSigner = await getMasterSigner(); + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2MorphoAaveV3__factory, + signer: masterSigner, + connectors: instaConnectorsV2 + }); + console.log("Connector address", connector.address); + }); + + it("should have contracts deployed", async () => { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!(await masterSigner.getAddress())).to.be.true; + }); + + describe("DSA wallet setup", function () { + it("Should build DSA v2", async function () { + dsaWallet0 = await buildDSAv2(wallet0.getAddress()); + expect(!!dsaWallet0.address).to.be.true; + dsaWallet1 = await buildDSAv2(wallet0.getAddress()); + expect(!!dsaWallet1.address).to.be.true; + }); + + it("Deposit 1000 ETH into DSA wallet", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: parseEther("1000") + }); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(parseEther("1000")); + await wallet0.sendTransaction({ + to: dsaWallet1.address, + value: parseEther("1000") + }); + expect(await ethers.provider.getBalance(dsaWallet1.address)).to.be.gte(parseEther("1000")); + }); + + it("Deposit 5000 USDC into DSA wallet", async function () { + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [ACC_USDC] + }); + + const signer_usdc = await ethers.getSigner(ACC_USDC); + await token_usdc.connect(signer_usdc).transfer(wallet0.getAddress(), Usdc); + + await hre.network.provider.request({ + method: "hardhat_stopImpersonatingAccount", + params: [ACC_USDC] + }); + + await token_usdc.connect(wallet0).transfer(dsaWallet0.address, Usdc); + + expect(await token_usdc.connect(masterSigner).balanceOf(dsaWallet0.address)).to.be.gte(parseUnits("5000", 6)); + }); + }); + + describe("Main", function () { + it("Should deposit 10 ETH", async function () { + const spells = [ + { + connector: connectorName, + method: "deposit", + args: [tokens.eth.address, "10000000000000000000", "0", "0"], // 10 ETH + }, + ]; + + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.getAddress()); + + await tx.wait(); + expect(expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.lte( + parseUnits('990', 18)) + ); + }) + + it("Should deposit 1 ETH with MaxIteration", async function () { + const spells = [ + { + connector: connectorName, + method: "depositWithMaxIterations", + args: [tokens.eth.address, "1000000000000000000", 5, "0", "0"], // 1 ETH + }, + ]; + + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.getAddress()); + + await tx.wait(); + expect(expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.lte( + parseUnits('989', 18)) + ); + }) + + it("Should deposit 10 ETH on behalf", async function () { + const spells = [ + { + connector: connectorName, + method: "depositOnBehalf", + args: [tokens.eth.address, "10000000000000000000", user, "0", "0"], // 1 ETH + }, + ]; + + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.getAddress()); + + await tx.wait(); + expect(expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.lte( + parseUnits('979', 18)) + ); + }) + + it("Should deposit 1 ETH on behalf with MaxIteration", async function () { + const spells = [ + { + connector: connectorName, + method: "depositOnBehalfWithMaxIterations", + args: [tokens.eth.address, "1000000000000000000", user, 5, "0", "0"], // 1 ETH + }, + ]; + + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.getAddress()); + + await tx.wait(); + expect(expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.lte( + parseUnits('978', 18)) + ); + }) + + it("Should deposit collateral 2000 USDC", async function () { + const spells = [ + { + connector: connectorName, + method: "depositCollateral", + args: [tokens.usdc.address, "2000000000", "0", "0"], // 50 USDC + }, + ]; + + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.getAddress()); + + await tx.wait(); + expect(await token_usdc.connect(wallet0).balanceOf(dsaWallet0.address)).to.be.lte( + parseUnits('3000', 6) + ); + }) + + it("Should deposit collateral 2000 USDC on behalf with maxValue", async function () { + const spells = [ + { + connector: connectorName, + method: "depositCollateralOnBehalf", + args: [tokens.usdc.address, dsaMaxValue, user, "0", "0"], // ~3000 USDC + }, + ]; + + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.getAddress()); + + await tx.wait(); + expect(await token_usdc.connect(wallet0).balanceOf(dsaWallet0.address)).to.be.lte( + parseUnits('1', 6) + ); + }) + + it("Should withdraw 10 ETH", async function () { + const spells = [ + { + connector: connectorName, + method: "withdraw", + args: [tokens.eth.address, "10000000000000000000", "0", "0"], // 10 ETH + }, + ]; + + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.getAddress()); + + await tx.wait(); + expect(expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte( + parseUnits('978', 18)) + ); + }) + + it("Should withdraw on behalf of user with maxValue", async function () { + let ethBala = await ethers.provider.getBalance(user) + let wethBala = await token_weth.balanceOf(user) + + const spells = [ + { + connector: connectorName, + method: "withdrawOnBehalf", + args: [tokens.eth.address, dsaMaxValue, dsaWallet0.address, user, "0", "0"], // Max ETH + }, + ]; + + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.getAddress()); + + await tx.wait(); + ethBala = await ethers.provider.getBalance(user) + wethBala = await token_weth.balanceOf(user) + + }) + + it("Should borrow ETH into DSA", async function () { + const balanceBefore = await ethers.provider.getBalance(dsaWallet0.address); + const spells = [ + { + connector: connectorName, + method: "borrow", + args: [tokens.eth.address, "500000000000000000", "0", "0"], // 0.5 WETH + }, + ]; + + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.getAddress()); + + await tx.wait(); + const balanceAfter = await ethers.provider.getBalance(dsaWallet0.address); + expect((balanceAfter).sub(balanceBefore)).to.be.gte(parseUnits('4.9', 17)); + }) + + it("Should borrow ETH into user", async function () { + const balance = await token_weth.balanceOf(user); + const spells = [ + { + connector: connectorName, + method: "borrowOnBehalf", + args: [tokens.eth.address, "200000000000000000", dsaWallet0.address, user, "0", "0"], // 0.7 WETH + }, + ]; + + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.getAddress()); + + await tx.wait(); + expect((await token_weth.balanceOf(user)).sub(balance)) + .to.be.eq(parseUnits('2', 17)); + }) + + it("Should borrow WETH into wallet1 using iteration", async function () { + const balance = await token_weth.balanceOf(dsaWallet0.address); + const spells = [ + { + connector: connectorName, + method: "borrowWithMaxIterations", + args: [tokens.weth.address, "20000000000000000", dsaWallet0.address, 10, "0", "0"], // 0.02 WETH + }, + ]; + + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.getAddress()); + + await tx.wait(); + expect((await token_weth.balanceOf(dsaWallet0.address)).sub(balance)) + .to.be.eq(parseUnits('2', 16)); + }) + + it("Test withdrawCollateral ", async function () { + await hre.network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [ACC_USDC], + }) + + const signer_usdc = await ethers.getSigner(ACC_USDC) + await token_usdc.connect(signer_usdc).transfer(dsaWallet0.address, parseUnits('500', 6)) + + await hre.network.provider.request({ + method: 'hardhat_stopImpersonatingAccount', + params: [ACC_USDC], + }) + + expect(await token_usdc.connect(masterSigner).balanceOf(dsaWallet0.address)).to.be.gte( + parseUnits('500', 6) + ); + + const balance = await token_usdc.balanceOf(dsaWallet0.address); + + const spells = [ + { + connector: connectorName, + method: "depositCollateral", + args: [tokens.usdc.address, "20000000", "0", "0"], // 20 USDC + }, + { + connector: connectorName, + method: "withdrawCollateral", + args: [tokens.usdc.address, "19000000", dsaWallet0.address, "0", "0"], // 19 USDC + }, + ]; + + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.getAddress()); + + await tx.wait(); + + }) + + it("Test withdrawCollateralOnBehalf with maxValue", async function () { + await hre.network.provider.request({ + method: "hardhat_impersonateAccount", + params: [ACC_USDC] + }); + + const signer_usdc = await ethers.getSigner(ACC_USDC); + await token_usdc.connect(signer_usdc).transfer(dsaWallet0.address, parseUnits("500", 6)); + + await hre.network.provider.request({ + method: "hardhat_stopImpersonatingAccount", + params: [ACC_USDC] + }); + + expect(await token_usdc.connect(masterSigner).balanceOf(dsaWallet0.address)).to.be.gte(parseUnits("500", 6)); + + const balance = await token_usdc.balanceOf(dsaWallet0.address); + + let spells = [ + { + connector: connectorName, + method: "approveManager", + args: [dsaWallet0.address, true] // 20 USDC + }, + ]; + + let tx = await dsaWallet1.connect(wallet0).cast(...encodeSpells(spells), wallet1.getAddress()); + await tx.wait(); + + spells = [ + { + connector: connectorName, + method: "depositCollateralOnBehalf", + args: [tokens.usdc.address, "20000000", dsaWallet1.address, "0", "0"] // 20 USDC + }, + { + connector: connectorName, + method: "withdrawCollateralOnBehalf", + args: [tokens.usdc.address, dsaMaxValue, dsaWallet1.address, user, "0", "0"] // 20 USDC + } + ]; + + tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet1.getAddress()); + + await tx.wait(); + + expect(await token_usdc.connect(masterSigner).balanceOf(dsaWallet0.address)).to.be.gte(parseUnits("499", 6)); + + }); + + it("Test payback with maxValue", async function () { + await hre.network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [ACC_USDC], + }) + + const signer_usdc = await ethers.getSigner(ACC_USDC) + await token_usdc.connect(signer_usdc).transfer(dsaWallet0.address, parseUnits('500', 6)) + + await hre.network.provider.request({ + method: 'hardhat_stopImpersonatingAccount', + params: [ACC_USDC], + }) + + expect(await token_usdc.connect(masterSigner).balanceOf(dsaWallet0.address)).to.be.gte( + parseUnits('500', 6) + ); + + const balance = await token_usdc.balanceOf(dsaWallet0.address); + + const spells = [ + { + connector: connectorName, + method: "depositCollateral", + args: [tokens.usdc.address, "200000000", "0", "0"], // 2 ETH + }, + { + connector: connectorName, + method: "borrow", + args: [tokens.eth.address, "1000000000000000", "0", "0"], // 20 USDC + }, + { + connector: connectorName, + method: "payback", + args: [tokens.eth.address, dsaMaxValue, "0", "0"], // 20 USDC + }, + ]; + + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.getAddress()); + + await tx.wait(); + + expect(await token_usdc.connect(masterSigner).balanceOf(dsaWallet0.address)).to.be.gte( + parseUnits('499', 6) + ); + }) + + it("approve manger", async () => { + const spells = [ + { + connector: connectorName, + method: "approveManager", + args: [user, true], + }, + ] + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.getAddress()); + + await tx.wait(); + }) + }); +});