diff --git a/script/child/DeployQWAaveV2.s.sol b/script/child/DeployQWAaveV2.s.sol index 20dfb6c..5420e7a 100644 --- a/script/child/DeployQWAaveV2.s.sol +++ b/script/child/DeployQWAaveV2.s.sol @@ -15,6 +15,8 @@ import {Script} from 'forge-std/Script.sol'; contract DeployQWAaveV2 is Script, DeployBase { struct ConfigParams { address aaveLendingPool; + address investmentToken; + address assetToken; } QWAaveV2 public qwAaveV2; @@ -36,7 +38,12 @@ contract DeployQWAaveV2 is Script, DeployBase { qwRegistry = QWRegistry(registryAddr); // Deploy QwChild - qwAaveV2 = new QWAaveV2(baseParams.qwManager, configParams.aaveLendingPool); + qwAaveV2 = new QWAaveV2( + baseParams.qwManager, + configParams.aaveLendingPool, + configParams.investmentToken, + configParams.assetToken + ); // Register Child in registry qwRegistry.registerChild(address(qwAaveV2)); diff --git a/src/contracts/child/QWAaveV3.sol b/src/contracts/child/QWAaveV3.sol index 16fae9f..211ef2d 100644 --- a/src/contracts/child/QWAaveV3.sol +++ b/src/contracts/child/QWAaveV3.sol @@ -10,75 +10,72 @@ import {IQWChild} from 'interfaces/IQWChild.sol'; * @notice This contract integrates with AaveV3 protocol for Quant Wealth management. */ contract QWAaveV3 is IQWChild { - // Variables - address public immutable QW_MANAGER; - address public immutable POOL; + // Variables + address public immutable QW_MANAGER; + address public immutable POOL; + address public immutable INVESTMENT_TOKEN; + address public immutable ASSET_TOKEN; - // Custom errors - error InvalidCallData(); // Error for invalid call data - error UnauthorizedAccess(); // Error for unauthoruzed caller + // Custom errors + error InvalidCallData(); // Error for invalid call data + error UnauthorizedAccess(); // Error for unauthorized caller - modifier onlyQwManager() { - if (msg.sender != QW_MANAGER) { - revert UnauthorizedAccess(); + modifier onlyQwManager() { + if (msg.sender != QW_MANAGER) { + revert UnauthorizedAccess(); + } + _; } - _; - } - /** - * @dev Constructor to initialize the contract with required addresses. - * @param _qwManager The address of the Quant Wealth Manager contract. - * @param _pool The address of the AaveV3 pool contract. - */ - constructor(address _qwManager, address _pool) { - QW_MANAGER = _qwManager; - POOL = _pool; - } - - // Functions - /** - * @notice Executes a transaction on AaveV3 pool to deposit tokens. - * @dev This function is called by the parent contract to deposit tokens into the AaveV3 pool. - * @param _callData Encoded function call data (not used in this implementation). - * @param _tokenAddress Address of the token to be deposited. - * @param _amount Amount of tokens to be deposited. - * @return success boolean indicating the success of the transaction. - */ - function create( - bytes memory _callData, - address _tokenAddress, - uint256 _amount - ) external override onlyQwManager returns (bool success) { - if (_callData.length != 0) { - revert InvalidCallData(); + /** + * @dev Constructor to initialize the contract with required addresses. + * @param _qwManager The address of the Quant Wealth Manager contract. + * @param _pool The address of the AaveV3 pool contract. + * @param _investmentToken The address of the token to be invested. + * @param _assetToken The address of the asset token received after investment. + */ + constructor(address _qwManager, address _pool, address _investmentToken, address _assetToken) { + QW_MANAGER = _qwManager; + POOL = _pool; + INVESTMENT_TOKEN = _investmentToken; + ASSET_TOKEN = _assetToken; } - IERC20 token = IERC20(_tokenAddress); - token.transferFrom(QW_MANAGER, address(this), _amount); - token.approve(POOL, _amount); + // Functions + /** + * @notice Executes a transaction on AaveV3 pool to deposit tokens. + * @dev This function is called by the parent contract to deposit tokens into the AaveV3 pool. + * @param _amount Amount of tokens to be deposited. + * @return success boolean indicating the success of the transaction. + * @return assetAmountReceived Amount of assets received from the deposit. + */ + function open(uint256 _amount) external override onlyQwManager returns (bool success, uint256 assetAmountReceived) { + IERC20 token = IERC20(INVESTMENT_TOKEN); + token.transferFrom(QW_MANAGER, address(this), _amount); + token.approve(POOL, _amount); + + IPool(POOL).supply(INVESTMENT_TOKEN, _amount, address(this), 0); + assetAmountReceived = IERC20(ASSET_TOKEN).balanceOf(address(this)); - IPool(POOL).supply(_tokenAddress, _amount, QW_MANAGER, 0); - return true; - } + // TODO: Transfer tokens to QWManager - /** - * @notice Executes a transaction on AaveV3 pool to withdraw tokens. - * @dev This function is called by the parent contract to withdraw tokens from the AaveV3 pool. - * @param _callData Encoded function call data containing the asset and amount to be withdrawn. - * @return success boolean indicating the success of the transaction. - */ - function close(bytes memory _callData) external override onlyQwManager returns (bool success) { - if (_callData.length == 0) { - revert InvalidCallData(); + success = true; } - (address asset, address lpAsset, uint256 amount) = abi.decode(_callData, (address, address, uint256)); + /** + * @notice Executes a transaction on AaveV3 pool to withdraw tokens. + * @dev This function is called by the parent contract to withdraw tokens from the AaveV3 pool. + * @param _ratio Percentage of holdings to be withdrawn, with 8 decimal places for precision. + * @return success boolean indicating the success of the transaction. + * @return tokenAmountReceived Amount of tokens received from the withdrawal. + */ + function close(uint256 _ratio) external override onlyQwManager returns (bool success, uint256 tokenAmountReceived) { + uint256 totalHoldings = IERC20(ASSET_TOKEN).balanceOf(address(this)); + uint256 amountToWithdraw = (totalHoldings * _ratio) / 1e8; - IERC20 token = IERC20(lpAsset); - token.transferFrom(QW_MANAGER, address(this), amount); - token.approve(POOL, amount); + IPool(POOL).withdraw(INVESTMENT_TOKEN, amountToWithdraw, QW_MANAGER); + tokenAmountReceived = amountToWithdraw; - IPool(POOL).withdraw(asset, amount, QW_MANAGER); - return true; - } + success = true; + } } diff --git a/src/contracts/child/QWCompound.sol b/src/contracts/child/QWCompound.sol index 3a100de..6f2ba21 100644 --- a/src/contracts/child/QWCompound.sol +++ b/src/contracts/child/QWCompound.sol @@ -10,75 +10,93 @@ import {IQWChild} from 'interfaces/IQWChild.sol'; * @notice This contract integrates with Compound protocol for Quant Wealth management. */ contract QWCompound is IQWChild { - // Variables - address public immutable QW_MANAGER; - address public immutable COMET; + // Variables + address public immutable QW_MANAGER; + address public immutable COMET; + address public immutable INVESTMENT_TOKEN; + address public immutable ASSET_TOKEN; - // Custom errors - error InvalidCallData(); // Error for invalid call data - error UnauthorizedAccess(); // Error for unauthoruzed caller + // Custom errors + error InvalidCallData(); // Error for invalid call data + error UnauthorizedAccess(); // Error for unauthorized caller - modifier onlyQwManager() { - if (msg.sender != QW_MANAGER) { - revert UnauthorizedAccess(); + modifier onlyQwManager() { + if (msg.sender != QW_MANAGER) { + revert UnauthorizedAccess(); + } + _; } - _; - } - /** - * @dev Constructor to initialize the contract with required addresses. - * @param _qwManager The address of the Quant Wealth Manager contract. - * @param _comet The address of the Compound comet contract. - */ - constructor(address _qwManager, address _comet) { - QW_MANAGER = _qwManager; - COMET = _comet; - } - - // Functions - /** - * @notice Executes a transaction on Compound comet to deposit tokens. - * @dev This function is called by the parent contract to deposit tokens into the Compound comet. - * @param _callData Encoded function call data (not used in this implementation). - * @param _tokenAddress Address of the token to be deposited. - * @param _amount Amount of tokens to be deposited. - * @return success boolean indicating the success of the transaction. - */ - function create( - bytes memory _callData, - address _tokenAddress, - uint256 _amount - ) external override onlyQwManager returns (bool success) { - if (_callData.length != 0) { - revert InvalidCallData(); + /** + * @dev Constructor to initialize the contract with required addresses. + * @param _qwManager The address of the Quant Wealth Manager contract. + * @param _comet The address of the Compound comet contract. + * @param _investmentToken The address of the investment token (e.g., USDC). + * @param _assetToken The address of the asset token received from Compound (e.g., cUSDC). + */ + constructor( + address _qwManager, + address _comet, + address _investmentToken, + address _assetToken + ) { + QW_MANAGER = _qwManager; + COMET = _comet; + INVESTMENT_TOKEN = _investmentToken; + ASSET_TOKEN = _assetToken; } - IERC20 token = IERC20(_tokenAddress); - token.transferFrom(QW_MANAGER, address(this), _amount); - token.approve(COMET, _amount); + // Functions + /** + * @notice Executes a transaction on Compound comet to deposit tokens. + * @dev This function is called by the parent contract to deposit tokens into the Compound comet. + * @param _amount Amount of tokens to be deposited. + * @return success boolean indicating the success of the transaction. + * @return assetAmountReceived The amount of asset tokens received from the deposit. + */ + function open( + uint256 _amount + ) external override onlyQwManager returns (bool success, uint256 assetAmountReceived) { + IERC20 token = IERC20(INVESTMENT_TOKEN); + token.transferFrom(QW_MANAGER, address(this), _amount); + token.approve(COMET, _amount); - IComet(COMET).supplyTo(QW_MANAGER, _tokenAddress, _amount); - return true; - } + // Perform the supply to Compound and get the current balance before and after to calculate the received amount + uint256 balanceBefore = IERC20(ASSET_TOKEN).balanceOf(address(this)); + IComet(COMET).supplyTo(address(this), INVESTMENT_TOKEN, _amount); + uint256 balanceAfter = IERC20(ASSET_TOKEN).balanceOf(address(this)); - /** - * @notice Executes a transaction on Compound comet to withdraw tokens. - * @dev This function is called by the parent contract to withdraw tokens from the Compound comet. - * @param _callData Encoded function call data containing the asset and amount to be withdrawn. - * @return success boolean indicating the success of the transaction. - */ - function close(bytes memory _callData) external override onlyQwManager returns (bool success) { - if (_callData.length == 0) { - revert InvalidCallData(); + assetAmountReceived = balanceAfter - balanceBefore; + success = true; } - (address asset, address lpAsset, uint256 amount) = abi.decode(_callData, (address, address, uint256)); + /** + * @notice Executes a transaction on Compound comet to withdraw tokens. + * @dev This function is called by the parent contract to withdraw tokens from the Compound comet. + * @param _ratio Percentage of holdings to be withdrawn, with 8 decimal places for precision. + * @return success boolean indicating the success of the transaction. + * @return tokenAmountReceived The amount of tokens received from the withdrawal. + */ + function close( + uint256 _ratio + ) external override onlyQwManager returns (bool success, uint256 tokenAmountReceived) { + uint256 totalHoldings = IERC20(ASSET_TOKEN).balanceOf(address(this)); + uint256 amountToWithdraw = (totalHoldings * _ratio) / 1e8; + + // Perform the withdraw from Compound and get the current balance before and after to calculate the received amount + uint256 balanceBefore = IERC20(INVESTMENT_TOKEN).balanceOf(address(this)); + IComet(COMET).withdrawTo(address(this), INVESTMENT_TOKEN, amountToWithdraw); + uint256 balanceAfter = IERC20(INVESTMENT_TOKEN).balanceOf(address(this)); - IERC20 token = IERC20(lpAsset); - token.transferFrom(QW_MANAGER, address(this), amount); - token.approve(COMET, amount); + tokenAmountReceived = balanceAfter - balanceBefore; + success = true; + } - IComet(COMET).withdrawTo(QW_MANAGER, asset, amount); - return true; - } + /** + * @notice Gets the address of the Quant Wealth Manager contract. + * @dev Returns the address of the Quant Wealth Manager contract. + */ + function QW_MANAGER() external view override returns (address) { + return QW_MANAGER; + } } diff --git a/src/contracts/child/QWCurve.sol b/src/contracts/child/QWCurve.sol index 730696e..88685e9 100644 --- a/src/contracts/child/QWCurve.sol +++ b/src/contracts/child/QWCurve.sol @@ -1,241 +1,241 @@ // SPDX-License-Identifier: APACHE pragma solidity 0.8.23; -import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -import {IQWChild} from 'interfaces/IQWChild.sol'; -import {ILendingPool} from 'interfaces/aave-v2/ILendingPool.sol'; -import {IAToken} from 'interfaces/aave-v2/IAToken.sol'; -import {ICurvePool} from 'interfaces/curve/ICurvePool.sol'; -import {IPriceOracle} from 'interfaces/IPriceOracle.sol'; -import {IUniswapV2Router02} from 'interfaces/uniswap/IUniswapV2Router02.sol'; - -/** - * @title AaveV2 Integration for Quant Wealth - * @notice This contract integrates with AaveV2 protocol for Quant Wealth management. - */ -contract QWAaveV2 is IQWChild { - // Variables - address public immutable QW_MANAGER; - address public immutable LENDING_POOL; - address public immutable INVESTMENT_TOKEN; - address public immutable LP_TOKEN; - address public immutable DW_TOKEN; - bool public immutable SAME_DW_INVESTMENT_TOKENS; - uint256 public immutable POOL_SIZE; - uint256 public immutable DW_POOL_INDEX; - IPriceOracle public priceOracle; - ICurvePool public curvePool; - IUniswapV2Router02 public uniswapRouter; - - // Custom errors - error InvalidCallData(); // Error for invalid call data - error UnauthorizedAccess(); // Error for unauthorized caller - - modifier onlyQwManager() { - if (msg.sender != QW_MANAGER) { - revert UnauthorizedAccess(); - } - _; - } - - /** - * @dev Constructor to initialize the contract with required addresses. - * @param _qwManager The address of the Quant Wealth Manager contract. - * @param _lendingPool The address of the AaveV2 pool contract. - * @param _investmentToken The address of the investment token (e.g., USDT). - * @param _lpToken The address of the LP token. - * @param _dwToken The address of the deposit/withdraw token. - * @param _priceOracle The address of the price oracle contract. - * @param _curvePool The address of the Curve pool contract. - * @param _uniswapRouter The address of the Uniswap router contract. - * @param _poolSize The size of the Curve pool. - * @param _dwPoolIndex The index of the DW token in the Curve pool. - */ - constructor( - address _qwManager, - address _lendingPool, - address _investmentToken, - address _lpToken, - address _dwToken, - address _priceOracle, - address _curvePool, - address _uniswapRouter, - uint256 _poolSize, - uint256 _dwPoolIndex - ) { - QW_MANAGER = _qwManager; - LENDING_POOL = _lendingPool; - INVESTMENT_TOKEN = _investmentToken; - LP_TOKEN = _lpToken; - DW_TOKEN = _dwToken; - SAME_DW_INVESTMENT_TOKENS = (_investmentToken == _dwToken); - priceOracle = IPriceOracle(_priceOracle); - curvePool = ICurvePool(_curvePool); - uniswapRouter = IUniswapV2Router02(_uniswapRouter); - POOL_SIZE = _poolSize; - DW_POOL_INDEX = _dwPoolIndex; - } - - // Functions - /** - * @notice Executes a transaction on AaveV2 pool to deposit tokens. - * @dev This function is called by the parent contract to deposit tokens into the AaveV2 pool. - * @param _callData Encoded function call data containing total shares. - * @param _tokenAmount Amount of tokens to be deposited. - * @return success boolean indicating the success of the transaction. - * @return shares Number of shares to be allocated to the user in return for investment created. - */ - function create( - bytes memory _callData, - uint256 _tokenAmount - ) external override onlyQwManager returns (bool success, uint256 shares) { - (uint256 _totalShares) = abi.decode(_callData, (uint256)); - - // Transfer tokens from QWManager to this contract. - IERC20 token = IERC20(INVESTMENT_TOKEN); - token.transferFrom(QW_MANAGER, address(this), _tokenAmount); - - uint256 dwTokenAmount = _tokenAmount; - - if (!SAME_DW_INVESTMENT_TOKENS) { - dwTokenAmount = swapInvestmentToDWTokens(_tokenAmount); - } - - // Prepare amounts array for add_liquidity - uint256[] memory amounts = new uint256[](POOL_SIZE); - amounts[DW_POOL_INDEX] = dwTokenAmount; - - // Approve Curve pool to spend the DW tokens. - IERC20(dwToken).approve(address(curvePool), dwTokenAmount); - - // Calculate share price before adding liquidity. - uint256 sharePrice = pricePerShare(_totalShares); - - // Add liquidity to the Curve pool. - curvePool.add_liquidity(amounts, 0); - - // Calculate shares to be issued for the new investment. - shares = _tokenAmount / sharePrice; - - success = true; - } - - /** - * @notice Executes a transaction on Curve pool to withdraw tokens. - * @dev This function is called by the parent contract to withdraw tokens from the Curve pool. - * @param _callData Encoded function call data containing total shares. - * @param _sharesAmount Amount of shares to be withdrawn. - * @return success boolean indicating the success of the transaction. - * @return tokens Number of tokens to be returned to the user in exchange for shares withdrawn. - */ - function close( - bytes memory _callData, - uint256 _sharesAmount - ) external override onlyQwManager returns (bool success, uint256 tokens) { - (uint256 _totalShares) = abi.decode(_callData, (uint256)); - - if (_sharesAmount > _totalShares) { - revert InvalidCallData(); - } - - // Calculate the amount of tokens to withdraw based on the shares. - uint256 totalInvestmentValue = getInvestmentValue(); - uint256 tokenAmount = (_sharesAmount == _totalShares) ? - totalInvestmentValue - : (_sharesAmount * totalInvestmentValue) / _totalShares; - - // Calculate the amount of DW tokens to withdraw from Curve pool. - uint256 tokens = curvePool.calc_withdraw_one_coin(tokenAmount, int128(DW_POOL_INDEX)); - - // Withdraw the tokens from the Curve pool. - curvePool.remove_liquidity_one_coin(tokens, int128(DW_POOL_INDEX), 0); - - // If SAME_DW_INVESTMENT_TOKENS is false, convert DW tokens to investment tokens. - if (!SAME_DW_INVESTMENT_TOKENS) { - tokens = convertDWToInvestmentTokens(tokens); - } - - // Transfer the tokens to the QW Manager. - IERC20(DW_TOKEN).transfer(QW_MANAGER, tokens); - - success = true; - } - - /** - * @notice Gets the price per share in terms of the specified token. - * @dev This function calculates the value of one share in terms of the specified token. - * @param _totalShares The total shares. - * @return pricePerShare uint256 representing the value of one share in the specified token. - */ - function pricePerShare(uint256 _totalShares) public view returns (uint256) { - uint256 decimals = IERC20(LP_TOKEN).decimals(); - return _totalShares == 0 ? - 1 * 10 ** decimals - : getInvestmentValue() / _totalShares; - } - - /** - * @notice Gets the total investment value in terms of the specified token. - * @dev This function calculates the total value of the investment in terms of the specified token. - * @return investmentValue uint256 representing the total value of the investment in the specified token. - */ - function getInvestmentValue() public view returns (uint256 investmentValue) { - uint256 lpTokenBalance = IAToken(LP_TOKEN).balanceOf(address(this)); - uint256 dwTokenAmount = curvePool.calc_withdraw_one_coin(lpTokenBalance, int128(DW_POOL_INDEX)); - - if (SAME_DW_INVESTMENT_TOKENS) { - investmentValue = dwTokenAmount; - } else { - investmentValue = convertDWToInvestmentTokens(dwTokenAmount); - } - } - - /** - * @notice Swaps investment tokens to deposit/withdraw tokens using Uniswap. - * @param _tokenAmount The amount of investment tokens to be swapped. - * @return The amount of DW tokens received. - */ - function swapInvestmentToDWTokens(uint256 _tokenAmount) private returns (uint256) { - IERC20(INVESTMENT_TOKEN).approve(address(uniswapRouter), _tokenAmount); - address[] memory path = new address[](2); - path[0] = INVESTMENT_TOKEN; - path[1] = DW_TOKEN; - - uint256[] memory amounts = uniswapRouter.swapExactTokensForTokens( - _tokenAmount, - 0, - path, - address(this), - block.timestamp - ); - - return amounts[1]; - } - - /** - * @notice Converts DW tokens to investment tokens using a price oracle. - * @param _dwTokenAmount The amount of DW tokens to be converted. - * @return The equivalent amount of investment tokens. - */ - function convertDWToInvestmentTokens(uint256 _dwTokenAmount) private view returns (uint256) { - uint256 dwTokenPrice = priceOracle.getPrice(DW_TOKEN); - uint256 investmentTokenPrice = priceOracle.getPrice(INVESTMENT_TOKEN); - return (_dwTokenAmount * dwTokenPrice) / investmentTokenPrice; - } - - /** - * @notice Returns the address of the Quant Wealth Manager contract. - * @return The address of the Quant Wealth Manager contract. - */ - function QW_MANAGER() external view override returns (address) { - return QW_MANAGER; - } - - /** - * @notice Returns the address of the investment token. - * @return The address of the investment token. - */ - function INVESTMENT_TOKEN() external view override returns (address) { - return INVESTMENT_TOKEN; - } -} +// import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +// import {IQWChild} from 'interfaces/IQWChild.sol'; +// import {ILendingPool} from 'interfaces/aave-v2/ILendingPool.sol'; +// import {IAToken} from 'interfaces/aave-v2/IAToken.sol'; +// import {ICurvePool} from 'interfaces/curve/ICurvePool.sol'; +// import {IPriceOracle} from 'interfaces/IPriceOracle.sol'; +// import {IUniswapV2Router02} from 'interfaces/uniswap/IUniswapV2Router02.sol'; + +// /** +// * @title AaveV2 Integration for Quant Wealth +// * @notice This contract integrates with AaveV2 protocol for Quant Wealth management. +// */ +// contract QWCurve is IQWChild { +// // Variables +// address public immutable QW_MANAGER; +// address public immutable LENDING_POOL; +// address public immutable INVESTMENT_TOKEN; +// address public immutable LP_TOKEN; +// address public immutable DW_TOKEN; +// bool public immutable SAME_DW_INVESTMENT_TOKENS; +// uint256 public immutable POOL_SIZE; +// uint256 public immutable DW_POOL_INDEX; +// IPriceOracle public priceOracle; +// ICurvePool public curvePool; +// IUniswapV2Router02 public uniswapRouter; + +// // Custom errors +// error InvalidCallData(); // Error for invalid call data +// error UnauthorizedAccess(); // Error for unauthorized caller + +// modifier onlyQwManager() { +// if (msg.sender != QW_MANAGER) { +// revert UnauthorizedAccess(); +// } +// _; +// } + +// /** +// * @dev Constructor to initialize the contract with required addresses. +// * @param _qwManager The address of the Quant Wealth Manager contract. +// * @param _lendingPool The address of the AaveV2 pool contract. +// * @param _investmentToken The address of the investment token (e.g., USDT). +// * @param _lpToken The address of the LP token. +// * @param _dwToken The address of the deposit/withdraw token. +// * @param _priceOracle The address of the price oracle contract. +// * @param _curvePool The address of the Curve pool contract. +// * @param _uniswapRouter The address of the Uniswap router contract. +// * @param _poolSize The size of the Curve pool. +// * @param _dwPoolIndex The index of the DW token in the Curve pool. +// */ +// constructor( +// address _qwManager, +// address _lendingPool, +// address _investmentToken, +// address _lpToken, +// address _dwToken, +// address _priceOracle, +// address _curvePool, +// address _uniswapRouter, +// uint256 _poolSize, +// uint256 _dwPoolIndex +// ) { +// QW_MANAGER = _qwManager; +// LENDING_POOL = _lendingPool; +// INVESTMENT_TOKEN = _investmentToken; +// LP_TOKEN = _lpToken; +// DW_TOKEN = _dwToken; +// SAME_DW_INVESTMENT_TOKENS = (_investmentToken == _dwToken); +// priceOracle = IPriceOracle(_priceOracle); +// curvePool = ICurvePool(_curvePool); +// uniswapRouter = IUniswapV2Router02(_uniswapRouter); +// POOL_SIZE = _poolSize; +// DW_POOL_INDEX = _dwPoolIndex; +// } + +// // Functions +// /** +// * @notice Executes a transaction on AaveV2 pool to deposit tokens. +// * @dev This function is called by the parent contract to deposit tokens into the AaveV2 pool. +// * @param _callData Encoded function call data containing total shares. +// * @param _tokenAmount Amount of tokens to be deposited. +// * @return success boolean indicating the success of the transaction. +// * @return shares Number of shares to be allocated to the user in return for investment created. +// */ +// function create( +// bytes memory _callData, +// uint256 _tokenAmount +// ) external override onlyQwManager returns (bool success, uint256 shares) { +// (uint256 _totalShares) = abi.decode(_callData, (uint256)); + +// // Transfer tokens from QWManager to this contract. +// IERC20 token = IERC20(INVESTMENT_TOKEN); +// token.transferFrom(QW_MANAGER, address(this), _tokenAmount); + +// uint256 dwTokenAmount = _tokenAmount; + +// if (!SAME_DW_INVESTMENT_TOKENS) { +// dwTokenAmount = swapInvestmentToDWTokens(_tokenAmount); +// } + +// // Prepare amounts array for add_liquidity +// uint256[] memory amounts = new uint256[](POOL_SIZE); +// amounts[DW_POOL_INDEX] = dwTokenAmount; + +// // Approve Curve pool to spend the DW tokens. +// IERC20(dwToken).approve(address(curvePool), dwTokenAmount); + +// // Calculate share price before adding liquidity. +// uint256 sharePrice = pricePerShare(_totalShares); + +// // Add liquidity to the Curve pool. +// curvePool.add_liquidity(amounts, 0); + +// // Calculate shares to be issued for the new investment. +// shares = _tokenAmount / sharePrice; + +// success = true; +// } + +// /** +// * @notice Executes a transaction on Curve pool to withdraw tokens. +// * @dev This function is called by the parent contract to withdraw tokens from the Curve pool. +// * @param _callData Encoded function call data containing total shares. +// * @param _sharesAmount Amount of shares to be withdrawn. +// * @return success boolean indicating the success of the transaction. +// * @return tokens Number of tokens to be returned to the user in exchange for shares withdrawn. +// */ +// function close( +// bytes memory _callData, +// uint256 _sharesAmount +// ) external override onlyQwManager returns (bool success, uint256 tokens) { +// (uint256 _totalShares) = abi.decode(_callData, (uint256)); + +// if (_sharesAmount > _totalShares) { +// revert InvalidCallData(); +// } + +// // Calculate the amount of tokens to withdraw based on the shares. +// uint256 totalInvestmentValue = getInvestmentValue(); +// uint256 tokenAmount = (_sharesAmount == _totalShares) ? +// totalInvestmentValue +// : (_sharesAmount * totalInvestmentValue) / _totalShares; + +// // Calculate the amount of DW tokens to withdraw from Curve pool. +// uint256 tokens = curvePool.calc_withdraw_one_coin(tokenAmount, int128(DW_POOL_INDEX)); + +// // Withdraw the tokens from the Curve pool. +// curvePool.remove_liquidity_one_coin(tokens, int128(DW_POOL_INDEX), 0); + +// // If SAME_DW_INVESTMENT_TOKENS is false, convert DW tokens to investment tokens. +// if (!SAME_DW_INVESTMENT_TOKENS) { +// tokens = convertDWToInvestmentTokens(tokens); +// } + +// // Transfer the tokens to the QW Manager. +// IERC20(DW_TOKEN).transfer(QW_MANAGER, tokens); + +// success = true; +// } + +// /** +// * @notice Gets the price per share in terms of the specified token. +// * @dev This function calculates the value of one share in terms of the specified token. +// * @param _totalShares The total shares. +// * @return pricePerShare uint256 representing the value of one share in the specified token. +// */ +// function pricePerShare(uint256 _totalShares) public view returns (uint256) { +// uint256 decimals = IERC20(LP_TOKEN).decimals(); +// return _totalShares == 0 ? +// 1 * 10 ** decimals +// : getInvestmentValue() / _totalShares; +// } + +// /** +// * @notice Gets the total investment value in terms of the specified token. +// * @dev This function calculates the total value of the investment in terms of the specified token. +// * @return investmentValue uint256 representing the total value of the investment in the specified token. +// */ +// function getInvestmentValue() public view returns (uint256 investmentValue) { +// uint256 lpTokenBalance = IAToken(LP_TOKEN).balanceOf(address(this)); +// uint256 dwTokenAmount = curvePool.calc_withdraw_one_coin(lpTokenBalance, int128(DW_POOL_INDEX)); + +// if (SAME_DW_INVESTMENT_TOKENS) { +// investmentValue = dwTokenAmount; +// } else { +// investmentValue = convertDWToInvestmentTokens(dwTokenAmount); +// } +// } + +// /** +// * @notice Swaps investment tokens to deposit/withdraw tokens using Uniswap. +// * @param _tokenAmount The amount of investment tokens to be swapped. +// * @return The amount of DW tokens received. +// */ +// function swapInvestmentToDWTokens(uint256 _tokenAmount) private returns (uint256) { +// IERC20(INVESTMENT_TOKEN).approve(address(uniswapRouter), _tokenAmount); +// address[] memory path = new address[](2); +// path[0] = INVESTMENT_TOKEN; +// path[1] = DW_TOKEN; + +// uint256[] memory amounts = uniswapRouter.swapExactTokensForTokens( +// _tokenAmount, +// 0, +// path, +// address(this), +// block.timestamp +// ); + +// return amounts[1]; +// } + +// /** +// * @notice Converts DW tokens to investment tokens using a price oracle. +// * @param _dwTokenAmount The amount of DW tokens to be converted. +// * @return The equivalent amount of investment tokens. +// */ +// function convertDWToInvestmentTokens(uint256 _dwTokenAmount) private view returns (uint256) { +// uint256 dwTokenPrice = priceOracle.getPrice(DW_TOKEN); +// uint256 investmentTokenPrice = priceOracle.getPrice(INVESTMENT_TOKEN); +// return (_dwTokenAmount * dwTokenPrice) / investmentTokenPrice; +// } + +// /** +// * @notice Returns the address of the Quant Wealth Manager contract. +// * @return The address of the Quant Wealth Manager contract. +// */ +// function QW_MANAGER() external view override returns (address) { +// return QW_MANAGER; +// } + +// /** +// * @notice Returns the address of the investment token. +// * @return The address of the investment token. +// */ +// function INVESTMENT_TOKEN() external view override returns (address) { +// return INVESTMENT_TOKEN; +// } +// } diff --git a/src/contracts/child/QWUniswapV3Stable.sol b/src/contracts/child/QWUniswapV3Stable.sol index adfccd4..81cff42 100644 --- a/src/contracts/child/QWUniswapV3Stable.sol +++ b/src/contracts/child/QWUniswapV3Stable.sol @@ -16,210 +16,223 @@ import 'libraries/uniswap-v3/TickMath.sol'; import 'libraries/uniswap-v3/TransferHelper.sol'; /** - * @title AaveV3 Integration for Quant Wealth - * @notice This contract integrates with AaveV3 protocol for Quant Wealth management. + * @title Uniswap V3 Integration for Quant Wealth + * @notice This contract integrates with Uniswap V3 protocol for Quant Wealth management. */ contract QWUniswapV3Stable is IQWChild, Ownable, IERC721Receiver, PeripheryImmutableState { - // Variables - address public immutable QW_MANAGER; - INonfungiblePositionManager public immutable nonfungiblePositionManager; - IUniswapV3Pool public immutable UNISWAP_POOL; - uint256 public uniswapPositionTokenId; - uint128 public liquidityAmount; - bool public isInitialized; - - // Custom errors - error InvalidCallData(); // Error for invalid call data - error UnauthorizedAccess(); // Error for unauthoruzed caller - error Uninitialized(); // Error for not initialized the position - - modifier onlyQwManager() { - if (msg.sender != QW_MANAGER) { - revert UnauthorizedAccess(); + // Variables + address public immutable QW_MANAGER; + INonfungiblePositionManager public immutable nonfungiblePositionManager; + IUniswapV3Pool public immutable UNISWAP_POOL; + uint256 public uniswapPositionTokenId; + uint128 public liquidityAmount; + bool public isInitialized; + + // Custom errors + error InvalidCallData(); // Error for invalid call data + error UnauthorizedAccess(); // Error for unauthorized caller + error Uninitialized(); // Error for not initialized the position + + modifier onlyQwManager() { + if (msg.sender != QW_MANAGER) { + revert UnauthorizedAccess(); + } + _; } - _; - } - modifier whenInitialized() { - if (!isInitialized) { - revert Uninitialized(); + modifier whenInitialized() { + if (!isInitialized) { + revert Uninitialized(); + } + _; } - _; - } - - /** - * @dev Constructor to initialize the contract with required addresses. - * @param _qwManager The address of the Quant Wealth Manager contract. - */ - constructor( - address _qwManager, - address _nonfungiblePositionManager, - address _factory, - address _WETH9, - address _uniswapPool - ) PeripheryImmutableState(_factory, _WETH9) Ownable(msg.sender) { - nonfungiblePositionManager = INonfungiblePositionManager(_nonfungiblePositionManager); - QW_MANAGER = _qwManager; - UNISWAP_POOL = IUniswapV3Pool(_uniswapPool); - } - - /// @notice Calls the mint function defined in periphery, mints the same amount of each token. For this example we are providing 1000 DAI and 1000 USDC in liquidity - /// @return tokenId The id of the newly minted ERC721 - /// @return liquidity The amount of liquidity for the position - /// @return amount0 The amount of token0 - /// @return amount1 The amount of token1 - function mintNewPosition( - uint256 _amount0ToMint, - uint256 _amount1ToMint - ) external onlyOwner returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) { - // Providing liquidity in both assets means liquidity will be earning fees and is considered in-range. - uint256 amount0ToMint = _amount0ToMint; - uint256 amount1ToMint = _amount1ToMint; - address token0 = UNISWAP_POOL.token0(); - address token1 = UNISWAP_POOL.token1(); - uint24 poolFee = UNISWAP_POOL.fee(); - - // Receive the tokens from sender - TransferHelper.safeTransferFrom(token0, msg.sender, address(this), amount0ToMint); - - TransferHelper.safeTransferFrom(token1, msg.sender, address(this), amount1ToMint); - - // Approve the position manager - TransferHelper.safeApprove(token0, address(nonfungiblePositionManager), amount0ToMint); - TransferHelper.safeApprove(token1, address(nonfungiblePositionManager), amount1ToMint); - - INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams({ - token0: token0, - token1: token1, - fee: poolFee, - tickLower: TickMath.MIN_TICK + 2, - tickUpper: TickMath.MAX_TICK - 2, - amount0Desired: amount0ToMint, - amount1Desired: amount1ToMint, - amount0Min: 0, - amount1Min: 0, - recipient: address(this), - deadline: block.timestamp - }); - - // Note that the pool defined by DAI/USDC and fee tier 0.3% must already be created and initialized to mint - (tokenId, liquidity, amount0, amount1) = nonfungiblePositionManager.mint(params); - - uniswapPositionTokenId = tokenId; - liquidityAmount = liquidity; - - // Remove allowance and refund in both assets. - if (amount0 < amount0ToMint) { - TransferHelper.safeApprove(token0, address(nonfungiblePositionManager), 0); - uint256 refund0 = amount0ToMint - amount0; - TransferHelper.safeTransfer(token0, msg.sender, refund0); + + /** + * @dev Constructor to initialize the contract with required addresses. + * @param _qwManager The address of the Quant Wealth Manager contract. + * @param _nonfungiblePositionManager The address of the Uniswap V3 non-fungible position manager contract. + * @param _factory The address of the Uniswap V3 factory contract. + * @param _WETH9 The address of the WETH9 contract. + * @param _uniswapPool The address of the Uniswap V3 pool contract. + */ + constructor( + address _qwManager, + address _nonfungiblePositionManager, + address _factory, + address _WETH9, + address _uniswapPool + ) PeripheryImmutableState(_factory, _WETH9) Ownable(msg.sender) { + nonfungiblePositionManager = INonfungiblePositionManager(_nonfungiblePositionManager); + QW_MANAGER = _qwManager; + UNISWAP_POOL = IUniswapV3Pool(_uniswapPool); + } + + /** + * @notice Calls the mint function defined in periphery, mints the same amount of each token. + * @param _amount0ToMint The amount of token0 to mint. + * @param _amount1ToMint The amount of token1 to mint. + * @return tokenId The id of the newly minted ERC721. + * @return liquidity The amount of liquidity for the position. + * @return amount0 The amount of token0. + * @return amount1 The amount of token1. + */ + function mintNewPosition( + uint256 _amount0ToMint, + uint256 _amount1ToMint + ) external onlyOwner returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) { + uint256 amount0ToMint = _amount0ToMint; + uint256 amount1ToMint = _amount1ToMint; + address token0 = UNISWAP_POOL.token0(); + address token1 = UNISWAP_POOL.token1(); + uint24 poolFee = UNISWAP_POOL.fee(); + + // Transfer the tokens from sender + TransferHelper.safeTransferFrom(token0, msg.sender, address(this), amount0ToMint); + TransferHelper.safeTransferFrom(token1, msg.sender, address(this), amount1ToMint); + + // Approve the position manager + TransferHelper.safeApprove(token0, address(nonfungiblePositionManager), amount0ToMint); + TransferHelper.safeApprove(token1, address(nonfungiblePositionManager), amount1ToMint); + + INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams({ + token0: token0, + token1: token1, + fee: poolFee, + tickLower: TickMath.MIN_TICK + 2, + tickUpper: TickMath.MAX_TICK - 2, + amount0Desired: amount0ToMint, + amount1Desired: amount1ToMint, + amount0Min: 0, + amount1Min: 0, + recipient: address(this), + deadline: block.timestamp + }); + + (tokenId, liquidity, amount0, amount1) = nonfungiblePositionManager.mint(params); + + uniswapPositionTokenId = tokenId; + liquidityAmount = liquidity; + + // Remove allowance and refund in both assets. + if (amount0 < amount0ToMint) { + TransferHelper.safeApprove(token0, address(nonfungiblePositionManager), 0); + uint256 refund0 = amount0ToMint - amount0; + TransferHelper.safeTransfer(token0, msg.sender, refund0); + } + + if (amount1 < amount1ToMint) { + TransferHelper.safeApprove(token1, address(nonfungiblePositionManager), 0); + uint256 refund1 = amount1ToMint - amount1; + TransferHelper.safeTransfer(token1, msg.sender, refund1); + } + + isInitialized = true; } - if (amount1 < amount1ToMint) { - TransferHelper.safeApprove(token1, address(nonfungiblePositionManager), 0); - uint256 refund1 = amount1ToMint - amount1; - TransferHelper.safeTransfer(token1, msg.sender, refund1); + /** + * @notice Executes a transaction on Uniswap V3 pool to deposit tokens. + * @dev This function is called by the parent contract to deposit tokens into the Uniswap V3 pool. + * @param _amount Amount of tokens to be deposited. + * @return success boolean indicating the success of the transaction. + * @return assetAmountReceived The amount of assets received from the deposit. + */ + function open( + uint256 _amount + ) external override onlyQwManager whenInitialized returns (bool success, uint256 assetAmountReceived) { + address token0 = UNISWAP_POOL.token0(); + address token1 = UNISWAP_POOL.token1(); + + IERC20 token = IERC20(token0); + token.transferFrom(QW_MANAGER, address(this), _amount); + uint256 liquidity; + uint256 amount0; + uint256 amount1; + (liquidity, amount0, amount1) = increaseLiquidityCurrentRange(_amount); + + assetAmountReceived = amount0 + amount1; + success = true; } - isInitialized = true; - } - - // Functions - /** - * @notice Executes a transaction on AaveV3 pool to deposit tokens. - * @dev This function is called by the parent contract to deposit tokens into the AaveV3 pool. - * @param _callData Encoded function call data (not used in this implementation). - * @param _tokenAddress Address of the token to be deposited. - * @param _amount Amount of tokens to be deposited. - * @return success boolean indicating the success of the transaction. - */ - function create( - bytes memory _callData, - address _tokenAddress, - uint256 _amount - ) external override onlyQwManager whenInitialized returns (bool success) { - if (_callData.length != 0) { - revert InvalidCallData(); + /** + * @notice Executes a transaction on Uniswap V3 pool to withdraw tokens. + * @dev This function is called by the parent contract to withdraw tokens from the Uniswap V3 pool. + * @param _ratio Percentage of holdings to be withdrawn, with 8 decimal places for precision. + * @return success boolean indicating the success of the transaction. + * @return tokenAmountReceived The amount of tokens received from the withdrawal. + */ + function close( + uint256 _ratio + ) external override onlyQwManager whenInitialized returns (bool success, uint256 tokenAmountReceived) { + if (_ratio == 0 || _ratio > 1e8) { + revert InvalidCallData(); + } + + (uint256 amount0, uint256 amount1) = decreaseLiquidity(); + tokenAmountReceived = amount0 + amount1; + success = true; } - IERC20 token = IERC20(_tokenAddress); - token.transferFrom(QW_MANAGER, address(this), _amount); - - increaseLiquidityCurrentRange(_amount); - return true; - } - - /** - * @notice Executes a transaction on AaveV3 pool to withdraw tokens. - * @dev This function is called by the parent contract to withdraw tokens from the AaveV3 pool. - * @param _callData Encoded function call data containing the asset and amount to be withdrawn. - * @return success boolean indicating the success of the transaction. - */ - function close(bytes memory _callData) external override onlyQwManager whenInitialized returns (bool success) { - if (_callData.length == 0) { - revert InvalidCallData(); + /** + * @notice Increases liquidity in the current range. + * @dev Pool must be initialized already to add liquidity. + * @param amountAdd The amount to add of token0. + * @return liquidity The amount of liquidity. + * @return amount0 The amount of token0. + * @return amount1 The amount of token1. + */ + function increaseLiquidityCurrentRange(uint256 amountAdd) + internal + returns (uint128 liquidity, uint256 amount0, uint256 amount1) + { + address token0 = UNISWAP_POOL.token0(); + address token1 = UNISWAP_POOL.token1(); + + // Approve the position manager + TransferHelper.safeApprove(token0, address(nonfungiblePositionManager), amountAdd); + TransferHelper.safeApprove(token1, address(nonfungiblePositionManager), amountAdd); + + INonfungiblePositionManager.IncreaseLiquidityParams memory params = INonfungiblePositionManager + .IncreaseLiquidityParams({ + tokenId: uniswapPositionTokenId, + amount0Desired: amountAdd, + amount1Desired: amountAdd, + amount0Min: 0, + amount1Min: 0, + deadline: block.timestamp + }); + + (liquidity, amount0, amount1) = nonfungiblePositionManager.increaseLiquidity(params); + liquidityAmount = liquidity; } - decreaseLiquidity(); - return true; - } - - /// @notice Increases liquidity in the current range - /// @dev Pool must be initialized already to add liquidity - /// @param amountAdd The amount to add of token0 - function increaseLiquidityCurrentRange(uint256 amountAdd) - internal - returns (uint128 liquidity, uint256 amount0, uint256 amount1) - { - address token0 = UNISWAP_POOL.token0(); - address token1 = UNISWAP_POOL.token1(); - - // Approve the position manager - TransferHelper.safeApprove(token0, address(nonfungiblePositionManager), amountAdd); - TransferHelper.safeApprove(token1, address(nonfungiblePositionManager), amountAdd); - - INonfungiblePositionManager.IncreaseLiquidityParams memory params = INonfungiblePositionManager - .IncreaseLiquidityParams({ - tokenId: uniswapPositionTokenId, - amount0Desired: amountAdd, - amount1Desired: amountAdd, - amount0Min: 0, - amount1Min: 0, - deadline: block.timestamp - }); - - (liquidity, amount0, amount1) = nonfungiblePositionManager.increaseLiquidity(params); - liquidityAmount = liquidity; - } - - /// @notice A function that decreases the current liquidity by half. An example to show how to call the `decreaseLiquidity` function defined in periphery. - /// @return amount0 The amount received back in token0 - /// @return amount1 The amount returned back in token1 - function decreaseLiquidity() internal returns (uint256 amount0, uint256 amount1) { - // get liquidity data for tokenId - uint128 liquidity = liquidityAmount; - uint24 poolFee = UNISWAP_POOL.fee(); - - // amount0Min and amount1Min are price slippage checks - // if the amount received after burning is not greater than these minimums, transaction will fail - INonfungiblePositionManager.DecreaseLiquidityParams memory params = INonfungiblePositionManager - .DecreaseLiquidityParams({ - tokenId: uniswapPositionTokenId, - liquidity: liquidity, - amount0Min: 0, - amount1Min: 0, - deadline: block.timestamp - }); - - (amount0, amount1) = nonfungiblePositionManager.decreaseLiquidity(params); - } - - function onERC721Received( - address operator, - address, - uint256 tokenId, - bytes calldata - ) external override returns (bytes4) { - return this.onERC721Received.selector; - } + /** + * @notice Decreases the current liquidity by half. + * @return amount0 The amount received back in token0. + * @return amount1 The amount returned back in token1. + */ + function decreaseLiquidity() internal returns (uint256 amount0, uint256 amount1) { + // get liquidity data for tokenId + uint128 liquidity = liquidityAmount; + uint24 poolFee = UNISWAP_POOL.fee(); + + INonfungiblePositionManager.DecreaseLiquidityParams memory params = INonfungiblePositionManager + .DecreaseLiquidityParams({ + tokenId: uniswapPositionTokenId, + liquidity: liquidity, + amount0Min: 0, + amount1Min: 0, + deadline: block.timestamp + }); + + (amount0, amount1) = nonfungiblePositionManager.decreaseLiquidity(params); + } + + function onERC721Received( + address operator, + address, + uint256 tokenId, + bytes calldata + ) external override returns (bytes4) { + return this.onERC721Received.selector; + } } diff --git a/src/interfaces/IQWChild.sol b/src/interfaces/IQWChild.sol index 1fb6d37..ce61a6f 100644 --- a/src/interfaces/IQWChild.sol +++ b/src/interfaces/IQWChild.sol @@ -27,4 +27,10 @@ interface IQWChild { function close( uint256 _amount ) external returns (bool success, uint256 tokenAmountReceived); + + /** + * @notice Gets the address of the Quant Wealth Manager contract. + * @return address The address of the Quant Wealth Manager contract recorded in this child contract. + */ + function QW_MANAGER() external view returns (address); } diff --git a/test/integration/child/QWAaveV2.t.sol b/test/integration/child/QWAaveV2.t.sol index 5961e23..3aabc05 100644 --- a/test/integration/child/QWAaveV2.t.sol +++ b/test/integration/child/QWAaveV2.t.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.23; import {IntegrationBase} from '../IntegrationBase.t.sol'; +import {IQWManager} from 'interfaces/IQWManager.sol'; import {IERC20, ILendingPool, IQWChild, QWAaveV2} from 'contracts/child/QWAaveV2.sol'; import {IIncentivesController} from 'interfaces/aave-v2/IIncentivesController.sol'; @@ -10,41 +11,39 @@ contract AaveIntegrationV2 is IntegrationBase { ILendingPool internal _aaveLendingPool = ILendingPool(0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9); IERC20 internal _aUsdc = IERC20(0xBcca60bB61934080951369a648Fb03DF4F96263C); IIncentivesController internal _rewards = IIncentivesController(0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5); - IQWChild internal _QWAaveV2; + QWAaveV2 internal _QWAaveV2; function setUp() public virtual override { IntegrationBase.setUp(); - _QWAaveV2 = new QWAaveV2(address(_qwManager), address(_aaveLendingPool)); + _QWAaveV2 = new QWAaveV2(address(_qwManager), address(_aaveLendingPool), address(_usdc), address(_aUsdc)); vm.prank(_owner); _qwRegistry.registerChild(address(_QWAaveV2)); } - function test_CreateAaveV2() public { + function test_OpenAaveV2() public { uint256 amount = 1e12; // 1 million usdc - bytes memory callData = ''; - address tokenAddress = address(_usdc); - // transfer usdc from user to qwManager contract + // Transfer usdc from user to qwManager contract vm.prank(_usdcWhale); _usdc.transfer(address(_qwManager), amount); - uint256 aUsdcBalanceBefore = _aUsdc.balanceOf(address(_qwManager)); + uint256 aUsdcBalanceBefore = _aUsdc.balanceOf(address(_QWAaveV2)); uint256 usdcBalanceBefore = _usdc.balanceOf(address(_qwManager)); - // Create dynamic arrays with one element each - address[] memory targetQWChild = new address[](1); - targetQWChild[0] = address(_QWAaveV2); + // Create OpenBatch array + IQWManager.OpenBatch[] memory batches = new IQWManager.OpenBatch[](1); + batches[0] = IQWManager.OpenBatch({ + protocol: address(_QWAaveV2), + amount: amount + }); - bytes[] memory callDataArr = new bytes[](1); - callDataArr[0] = callData; - - // execute the investment + // Execute the investment vm.prank(_owner); - _qwManager.execute(targetQWChild, callDataArr, tokenAddress, amount); - uint256 aUsdcBalanceAfter = _aUsdc.balanceOf(address(_qwManager)); + _qwManager.open(batches); + uint256 aUsdcBalanceAfter = _aUsdc.balanceOf(address(_QWAaveV2)); uint256 usdcBalanceAfter = _usdc.balanceOf(address(_qwManager)); - // example for getting rewards + // Example for getting rewards vm.roll(19_921_492); _rewards.getUserUnclaimedRewards(0xD102D2A88Fa2d23DC4048f559aA05579F2b3d47f); _rewards.getUserUnclaimedRewards(address(_qwManager)); @@ -55,27 +54,27 @@ contract AaveIntegrationV2 is IntegrationBase { } function test_CloseAaveV2() public { - // create investment in aave - test_CreateAaveV2(); + // Create investment in Aave + test_OpenAaveV2(); - uint256 amount = _aUsdc.balanceOf(address(_qwManager)); - bytes memory callData = abi.encode(address(_usdc), address(_aUsdc), amount); + uint256 amount = _aUsdc.balanceOf(address(_QWAaveV2)); + uint256 ratio = 1e8; // 100% withdrawal - uint256 aUsdcBalanceBefore = _aUsdc.balanceOf(address(_qwManager)); + uint256 aUsdcBalanceBefore = _aUsdc.balanceOf(address(_QWAaveV2)); uint256 usdcBalanceBefore = _usdc.balanceOf(address(_qwManager)); - // Create dynamic arrays with one element each - address[] memory targetQWChild = new address[](1); - targetQWChild[0] = address(_QWAaveV2); - - bytes[] memory callDataArr = new bytes[](1); - callDataArr[0] = callData; + // Create CloseBatch array + IQWManager.CloseBatch[] memory batches = new IQWManager.CloseBatch[](1); + batches[0] = IQWManager.CloseBatch({ + protocol: address(_QWAaveV2), + ratio: ratio + }); - // close the position + // Close the position vm.prank(_owner); - _qwManager.close(targetQWChild, callDataArr); + _qwManager.close(batches); - uint256 aUsdcBalanceAfter = _aUsdc.balanceOf(address(_qwManager)); + uint256 aUsdcBalanceAfter = _aUsdc.balanceOf(address(_QWAaveV2)); uint256 usdcBalanceAfter = _usdc.balanceOf(address(_qwManager)); assertGe(usdcBalanceAfter - usdcBalanceBefore, amount); diff --git a/test/integration/child/QWAaveV3.t.sol b/test/integration/child/QWAaveV3.t.sol index 61d6f14..960d42e 100644 --- a/test/integration/child/QWAaveV3.t.sol +++ b/test/integration/child/QWAaveV3.t.sol @@ -15,15 +15,16 @@ contract AaveIntegrationV3 is IntegrationBase { function setUp() public virtual override { IntegrationBase.setUp(); - _QWAaveV3 = new QWAaveV3(address(_qwManager), address(_aavePool)); + address investmentToken = address(_usdc); // Adjust this according to your setup + address assetToken = address(_aUsdc); // Adjust this according to your setup + + _QWAaveV3 = new QWAaveV3(address(_qwManager), address(_aavePool), investmentToken, assetToken); vm.prank(_owner); _qwRegistry.registerChild(address(_QWAaveV3)); } function test_CreateAaveV3() public { uint256 amount = 1e12; // 1 million usdc - bytes memory callData = ''; - address tokenAddress = address(_usdc); // transfer usdc from user to qwManager contract vm.prank(_usdcWhale); @@ -35,24 +36,13 @@ contract AaveIntegrationV3 is IntegrationBase { address[] memory targetQWChild = new address[](1); targetQWChild[0] = address(_QWAaveV3); - bytes[] memory callDataArr = new bytes[](1); - callDataArr[0] = callData; - // execute the investment vm.prank(_owner); - _qwManager.execute(targetQWChild, callDataArr, tokenAddress, amount); + _qwManager.open(targetQWChild, amount); uint256 aUsdcBalanceAfter = _aUsdc.balanceOf(address(_qwManager)); uint256 usdcBalanceAfter = _usdc.balanceOf(address(_qwManager)); - // _aavePool.getUserAccountData(address(_qwManager)); - // address[] memory assetsArr = new address[](2); - // assetsArr[0] = address(_usdc); - // assetsArr[1] = address(_aUsdc); - // _rewards.getAllUserRewards(assetsArr, address(_qwManager)); - // vm.roll(block.number + 1_000_000); - // _rewards.getUserAccruedRewards(address(_qwManager), assetsArr[1]); - // _rewards.getUserAccruedRewards(address(_qwManager), assetsArr[0]); - // _rewards.getRewardsList(); + // Assertions assertGe(aUsdcBalanceAfter - aUsdcBalanceBefore, amount); assertEq(usdcBalanceBefore - usdcBalanceAfter, amount); assertEq(usdcBalanceAfter, 0); @@ -62,8 +52,7 @@ contract AaveIntegrationV3 is IntegrationBase { // create investment in aave test_CreateAaveV3(); - uint256 amount = _aUsdc.balanceOf(address(_qwManager)); - bytes memory callData = abi.encode(address(_usdc), address(_aUsdc), amount); + uint256 ratio = 1e8; // 100% ratio uint256 aUsdcBalanceBefore = _aUsdc.balanceOf(address(_qwManager)); uint256 usdcBalanceBefore = _usdc.balanceOf(address(_qwManager)); @@ -72,18 +61,16 @@ contract AaveIntegrationV3 is IntegrationBase { address[] memory targetQWChild = new address[](1); targetQWChild[0] = address(_QWAaveV3); - bytes[] memory callDataArr = new bytes[](1); - callDataArr[0] = callData; - // close the position vm.prank(_owner); - _qwManager.close(targetQWChild, callDataArr); + _qwManager.close(targetQWChild, ratio); uint256 aUsdcBalanceAfter = _aUsdc.balanceOf(address(_qwManager)); uint256 usdcBalanceAfter = _usdc.balanceOf(address(_qwManager)); - assertGe(usdcBalanceAfter - usdcBalanceBefore, amount); - assertEq(aUsdcBalanceBefore - aUsdcBalanceAfter, amount); + // Assertions + assertGe(usdcBalanceAfter - usdcBalanceBefore, aUsdcBalanceBefore); + assertEq(aUsdcBalanceBefore - aUsdcBalanceAfter, aUsdcBalanceBefore); assertEq(aUsdcBalanceAfter, 0); } } diff --git a/test/integration/child/QWCompound.t.sol b/test/integration/child/QWCompound.t.sol index 9262eb9..ac35554 100644 --- a/test/integration/child/QWCompound.t.sol +++ b/test/integration/child/QWCompound.t.sol @@ -6,81 +6,74 @@ import {IComet, IERC20, IQWChild, QWCompound} from 'contracts/child/QWCompound.s import {Test, console2} from 'forge-std/Test.sol'; contract CompoundIntegration is IntegrationBase { - IComet internal _compoundV3Comet = IComet(0xc3d688B66703497DAA19211EEdff47f25384cdc3); - IERC20 internal _cUsdcV3 = IERC20(0xc3d688B66703497DAA19211EEdff47f25384cdc3); - IQWChild internal _qwCompound; - - function setUp() public virtual override { - IntegrationBase.setUp(); - - _qwCompound = new QWCompound(address(_qwManager), address(_compoundV3Comet)); - vm.prank(_owner); - _qwRegistry.registerChild(address(_qwCompound)); - } - - function test_CreateCompound() public { - uint256 amount = 1e12; // 1 million usdc - bytes memory callData = ''; - address tokenAddress = address(_usdc); - - uint256 supplyFee = 1; // supply fees taken by compound - - // transfer dai from user to qwManager contract - vm.prank(_usdcWhale); - _usdc.transfer(address(_qwManager), amount); - - uint256 cUsdcV3BalanceBefore = _cUsdcV3.balanceOf(address(_qwManager)); - uint256 usdcBalanceBefore = _usdc.balanceOf(address(_qwManager)); - - // Create dynamic arrays with one element each - address[] memory targetQWChild = new address[](1); - targetQWChild[0] = address(_qwCompound); - - bytes[] memory callDataArr = new bytes[](1); - callDataArr[0] = callData; - - // execute the investment - vm.prank(_owner); - _qwManager.execute(targetQWChild, callDataArr, tokenAddress, amount); - uint256 cUsdcV3BalanceAfter = _cUsdcV3.balanceOf(address(_qwManager)); - uint256 usdcBalanceAfter = _usdc.balanceOf(address(_qwManager)); - - assertGe(cUsdcV3BalanceAfter - cUsdcV3BalanceBefore, amount - supplyFee); - assertEq(usdcBalanceBefore - usdcBalanceAfter, amount); - assertEq(usdcBalanceAfter, 0); - } - - function test_CloseCompound() public { - // create investment in compound - test_CreateCompound(); - - /** - * when baseToken == lpAsset - * to withdraw collateral, amount needs to be type(uint256).max - * else it tries to borrow the asset - */ - uint256 amount = type(uint256).max; - bytes memory callData = abi.encode(address(_usdc), address(_cUsdcV3), amount); - uint256 withdrawFee = 2; // withdraw fees taken by compound - - uint256 cUsdcV3BalanceBefore = _cUsdcV3.balanceOf(address(_qwManager)); - uint256 usdcBalanceBefore = _usdc.balanceOf(address(_qwManager)); - - // Create dynamic arrays with one element each - address[] memory targetQWChild = new address[](1); - targetQWChild[0] = address(_qwCompound); - - bytes[] memory callDataArr = new bytes[](1); - callDataArr[0] = callData; - - // close the position - vm.prank(_owner); - _qwManager.close(targetQWChild, callDataArr); - - uint256 cUsdcV3BalanceAfter = _cUsdcV3.balanceOf(address(_qwManager)); - uint256 usdcBalanceAfter = _usdc.balanceOf(address(_qwManager)); - - assertGe(usdcBalanceAfter - usdcBalanceBefore, cUsdcV3BalanceBefore - withdrawFee); - assertEq(cUsdcV3BalanceAfter, 0); - } + IComet internal _compoundV3Comet = IComet(0xc3d688B66703497DAA19211EEdff47f25384cdc3); + IERC20 internal _cUsdcV3 = IERC20(0xc3d688B66703497DAA19211EEdff47f25384cdc3); + IQWChild internal _qwCompound; + + function setUp() public virtual override { + IntegrationBase.setUp(); + + _qwCompound = new QWCompound(address(_qwManager), address(_compoundV3Comet), address(_usdc), address(_cUsdcV3)); + vm.prank(_owner); + _qwRegistry.registerChild(address(_qwCompound)); + } + + function test_OpenCompound() public { + uint256 amount = 1e12; // 1 million usdc + address tokenAddress = address(_usdc); + + uint256 supplyFee = 1; // supply fees taken by compound + + // transfer usdc from user to qwManager contract + vm.prank(_usdcWhale); + _usdc.transfer(address(_qwManager), amount); + + uint256 cUsdcV3BalanceBefore = _cUsdcV3.balanceOf(address(_qwManager)); + uint256 usdcBalanceBefore = _usdc.balanceOf(address(_qwManager)); + + // Create an array with one element + IQWManager.OpenBatch[] memory openBatchArr = new IQWManager.OpenBatch[](1); + openBatchArr[0] = IQWManager.OpenBatch({ + protocol: address(_qwCompound), + amount: amount + }); + + // execute the investment + vm.prank(_owner); + _qwManager.open(openBatchArr); + uint256 cUsdcV3BalanceAfter = _cUsdcV3.balanceOf(address(_qwManager)); + uint256 usdcBalanceAfter = _usdc.balanceOf(address(_qwManager)); + + assertGe(cUsdcV3BalanceAfter - cUsdcV3BalanceBefore, amount - supplyFee); + assertEq(usdcBalanceBefore - usdcBalanceAfter, amount); + assertEq(usdcBalanceAfter, 0); + } + + function test_CloseCompound() public { + // create investment in compound + test_OpenCompound(); + + uint256 ratio = 1e8; // 100% withdrawal + uint256 withdrawFee = 2; // withdraw fees taken by compound + + uint256 cUsdcV3BalanceBefore = _cUsdcV3.balanceOf(address(_qwManager)); + uint256 usdcBalanceBefore = _usdc.balanceOf(address(_qwManager)); + + // Create an array with one element + IQWManager.CloseBatch[] memory closeBatchArr = new IQWManager.CloseBatch[](1); + closeBatchArr[0] = IQWManager.CloseBatch({ + protocol: address(_qwCompound), + ratio: ratio + }); + + // close the position + vm.prank(_owner); + _qwManager.close(closeBatchArr); + + uint256 cUsdcV3BalanceAfter = _cUsdcV3.balanceOf(address(_qwManager)); + uint256 usdcBalanceAfter = _usdc.balanceOf(address(_qwManager)); + + assertGe(usdcBalanceAfter - usdcBalanceBefore, cUsdcV3BalanceBefore - withdrawFee); + assertEq(cUsdcV3BalanceAfter, 0); + } } diff --git a/test/integration/child/QWUniswapV3Stable.t.sol b/test/integration/child/QWUniswapV3Stable.t.sol index 738ee61..1acee19 100644 --- a/test/integration/child/QWUniswapV3Stable.t.sol +++ b/test/integration/child/QWUniswapV3Stable.t.sol @@ -46,13 +46,8 @@ contract UniswapV3stableIntegration is IntegrationBase { function test_CreateUniswapV3Stable() public { uint256 amount = 1e10; // 10k usdc - bytes memory callData = ''; address tokenAddress = address(_usdc); - vm.prank(_usdtWhale); - _usdt.transfer(address(_QWUniswapV3Stable), amount); - - // transfer usdc from user to qwManager contract vm.prank(_usdcWhale); _usdc.transfer(address(_qwManager), amount); uint256 usdcBalanceBefore = _usdc.balanceOf(address(_qwManager)); @@ -61,12 +56,9 @@ contract UniswapV3stableIntegration is IntegrationBase { address[] memory targetQWChild = new address[](1); targetQWChild[0] = address(_QWUniswapV3Stable); - bytes[] memory callDataArr = new bytes[](1); - callDataArr[0] = callData; - // execute the investment vm.prank(_owner); - _qwManager.execute(targetQWChild, callDataArr, tokenAddress, amount); + _qwManager.open(targetQWChild, amount); uint256 usdcBalanceAfter = _usdc.balanceOf(address(_qwManager)); assertEq(usdcBalanceBefore - usdcBalanceAfter, amount); @@ -77,21 +69,19 @@ contract UniswapV3stableIntegration is IntegrationBase { // create investment in uniswap test_CreateUniswapV3Stable(); - bytes memory callData = abi.encode(address(_usdc), address(_usdc), 0); - + uint256 ratio = 1e8; // 100% uint256 usdcBalanceBefore = _usdc.balanceOf(address(_qwManager)); // Create dynamic arrays with one element each address[] memory targetQWChild = new address[](1); targetQWChild[0] = address(_QWUniswapV3Stable); - bytes[] memory callDataArr = new bytes[](1); - callDataArr[0] = callData; - // close the position vm.prank(_owner); - _qwManager.close(targetQWChild, callDataArr); + _qwManager.close(targetQWChild, ratio); uint256 usdcBalanceAfter = _usdc.balanceOf(address(_qwManager)); + + assertGt(usdcBalanceAfter, usdcBalanceBefore); } } diff --git a/test/unit/QWManager.t.sol b/test/unit/QWManager.t.sol index d466291..5d6243c 100644 --- a/test/unit/QWManager.t.sol +++ b/test/unit/QWManager.t.sol @@ -11,36 +11,90 @@ import {MockQWAaveV3} from 'test/smock/child/MockQWAaveV3.sol'; contract UnitQWManagerTest is Test, SmockHelper { MockQWManager public mockQWManager; MockQWAaveV3 public mockQWAaveV3; - address[] public targetQWChild; + MockQWRegistry public mockQWRegistry; address public tokenAddress; uint256 public amount; function setUp() public { - mockQWManager = MockQWManager(deployMock('QWManager', type(MockQWManager).creationCode, abi.encode())); + mockQWRegistry = new MockQWRegistry(); + mockQWManager = MockQWManager(deployMock('QWManager', type(MockQWManager).creationCode, abi.encode(address(mockQWRegistry)))); mockQWAaveV3 = new MockQWAaveV3(address(mockQWManager), address(0x456)); - targetQWChild = [address(mockQWAaveV3)]; - + tokenAddress = address(0x123); amount = 100; + + // Whitelist the mock protocol + mockQWRegistry.addToWhitelist(address(mockQWAaveV3)); + } + + function test_Open_Success() public { + IQWManager.OpenBatch[] memory batches = new IQWManager.OpenBatch[](1); + batches[0] = IQWManager.OpenBatch({ + protocol: address(mockQWAaveV3), + amount: amount + }); + + // Mock a successful open call + mockQWManager.mock_call_open(batches); + + // Call the open function + mockQWManager.open(batches); } - function test_Execute_Success() public { - bytes[] memory callData = new bytes[](1); - // Mock a successful execution - mockQWManager.mock_call_execute(targetQWChild, callData, tokenAddress, amount); + function test_Open_Fail_NotWhitelisted() public { + IQWManager.OpenBatch[] memory batches = new IQWManager.OpenBatch[](1); + batches[0] = IQWManager.OpenBatch({ + protocol: address(0x111), // not whitelisted + amount: amount + }); - // Call the execute function - mockQWManager.execute(targetQWChild, callData, tokenAddress, amount); + // Expect revert due to contract not whitelisted + vm.expectRevert("ContractNotWhitelisted"); + mockQWManager.open(batches); + } + + function test_Open_Fail_CallFailed() public { + IQWManager.OpenBatch[] memory batches = new IQWManager.OpenBatch[](1); + batches[0] = IQWManager.OpenBatch({ + protocol: address(mockQWAaveV3), + amount: amount + }); + + // Mock a failed open call + mockQWManager.mock_call_open_fails(batches); + + // Expect revert due to call failed + vm.expectRevert("CallFailed"); + mockQWManager.open(batches); } function test_Close_Success() public { - bytes[] memory callData = new bytes[](1); - callData[0] = abi.encode(address(0x123), uint256(100)); - // Mock a successful closing - mockQWManager.mock_call_close(targetQWChild, callData); + IQWManager.CloseBatch[] memory batches = new IQWManager.CloseBatch[](1); + batches[0] = IQWManager.CloseBatch({ + protocol: address(mockQWAaveV3), + ratio: 50000000 // 50% with 8 decimal places + }); + + // Mock a successful close call + mockQWManager.mock_call_close(batches); // Call the close function - mockQWManager.close(targetQWChild, callData); + mockQWManager.close(batches); + } + + function test_Close_Fail_CallFailed() public { + IQWManager.CloseBatch[] memory batches = new IQWManager.CloseBatch[](1); + batches[0] = IQWManager.CloseBatch({ + protocol: address(mockQWAaveV3), + ratio: 50000000 // 50% with 8 decimal places + }); + + // Mock a failed close call + mockQWManager.mock_call_close_fails(batches); + + // Expect revert due to call failed + vm.expectRevert("CallFailed"); + mockQWManager.close(batches); } function test_Withdraw_Success() public { @@ -54,6 +108,18 @@ contract UnitQWManagerTest is Test, SmockHelper { mockQWManager.withdraw(user, tokenAddress, withdrawAmount); } + function test_Withdraw_Fail() public { + address user = address(0x789); + uint256 withdrawAmount = 50; + + // Mock a failed withdrawal + mockQWManager.mock_call_withdraw_fails(user, tokenAddress, withdrawAmount); + + // Expect revert due to transfer failed + vm.expectRevert("TransferFailed"); + mockQWManager.withdraw(user, tokenAddress, withdrawAmount); + } + function test_ReceiveFunds_Success() public { address user = address(0x789); uint256 receiveAmount = 50; @@ -64,4 +130,16 @@ contract UnitQWManagerTest is Test, SmockHelper { // Call the receiveFunds function mockQWManager.receiveFunds(user, tokenAddress, receiveAmount); } + + function test_ReceiveFunds_Fail() public { + address user = address(0x789); + uint256 receiveAmount = 50; + + // Mock a failed receive funds + mockQWManager.mock_call_receiveFunds_fails(user, tokenAddress, receiveAmount); + + // Expect revert due to transferFrom failed + vm.expectRevert("TransferFromFailed"); + mockQWManager.receiveFunds(user, tokenAddress, receiveAmount); + } }