diff --git a/.github/workflows/ci-foundry-aave-v3.yml b/.github/workflows/ci-foundry-aave-v3.yml deleted file mode 100644 index a0561f3..0000000 --- a/.github/workflows/ci-foundry-aave-v3.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Foundry tests (Morpho-Aave V3) - -on: - push: - branches: - - main - - dev - pull_request: - paths: - - lib/** - - src/* - - src/aave-v3/** - - test/aave-v3/** - - yarn.lock - - Makefile - - foundry.toml - - remappings.txt - - .github/actions/ci-foundry/* - - .github/workflows/ci-foundry-aave-v3.yml - -jobs: - # morpho-aave-v3-polygon-mainnet: - # name: polygon-mainnet - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v3 - # with: - # submodules: recursive - - # - uses: ./.github/actions/ci-foundry - # with: - # alchemyKey: ${{ secrets.ALCHEMY_KEY }} - # protocol: aave-v3 - # network: polygon-mainnet - - morpho-aave-v3-avalanche-mainnet: - name: avalanche-mainnet - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - ssh-key: ${{ secrets.CI_PRIVATE_KEY }} - - - uses: ./.github/actions/ci-foundry - with: - protocol: aave-v3 - network: avalanche-mainnet - alchemyKey: ${{ secrets.ALCHEMY_KEY }} - codecovToken: ${{ secrets.CODECOV_TOKEN }} diff --git a/Makefile b/Makefile index e2c52dd..7b14991 100644 --- a/Makefile +++ b/Makefile @@ -17,20 +17,12 @@ endif ifeq (${NETWORK}, polygon-mainnet) FOUNDRY_CHAIN_ID=137 FOUNDRY_FORK_BLOCK_NUMBER=22116728 - - ifeq (${PROTOCOL}, aave-v3) - FOUNDRY_FORK_BLOCK_NUMBER=29116728 - endif endif ifeq (${NETWORK}, avalanche-mainnet) FOUNDRY_CHAIN_ID=43114 FOUNDRY_ETH_RPC_URL=https://api.avax.network/ext/bc/C/rpc FOUNDRY_FORK_BLOCK_NUMBER=12675271 - - ifeq (${PROTOCOL}, aave-v3) - FOUNDRY_FORK_BLOCK_NUMBER=15675271 - endif endif diff --git a/remappings.txt b/remappings.txt index 7a3b02a..4c7e8ab 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,5 +1,3 @@ -@aave/core-v3/=lib/morpho-contracts/lib/aave-v3-core/ -@aave/periphery-v3/=lib/morpho-contracts/lib/aave-v3-periphery/ @rari-capital/solmate/=lib/morpho-contracts/lib/solmate/ @morpho-labs/morpho-utils/=lib/morpho-contracts/lib/morpho-utils/src/ diff --git a/src/aave-v3/SupplyVault.sol b/src/aave-v3/SupplyVault.sol deleted file mode 100644 index 9cffcc8..0000000 --- a/src/aave-v3/SupplyVault.sol +++ /dev/null @@ -1,287 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.10; - -import {IRewardsManager} from "@contracts/aave-v3/interfaces/IRewardsManager.sol"; -import {ISupplyVault} from "./interfaces/ISupplyVault.sol"; - -import {ERC20, SafeTransferLib} from "@rari-capital/solmate/src/utils/SafeTransferLib.sol"; -import {FixedPointMathLib} from "@rari-capital/solmate/src/utils/FixedPointMathLib.sol"; -import {SafeCastLib} from "@rari-capital/solmate/src/utils/SafeCastLib.sol"; - -import {SupplyVaultBase} from "./SupplyVaultBase.sol"; - -/// @title SupplyVault. -/// @author Morpho Labs. -/// @custom:contact security@morpho.xyz -/// @notice ERC4626-upgradeable Tokenized Vault implementation for Morpho-Aave V3, which tracks rewards from Aave's pool accrued by its users. -contract SupplyVault is ISupplyVault, SupplyVaultBase { - using FixedPointMathLib for uint256; - using SafeCastLib for uint256; - using SafeTransferLib for ERC20; - - /// EVENTS /// - - /// @notice Emitted when rewards of an asset are accrued on behalf of a user. - /// @param rewardToken The address of the reward token. - /// @param user The address of the user that rewards are accrued on behalf of. - /// @param index The index of the asset distribution on behalf of the user. - /// @param unclaimed The new unclaimed amount of the user. - event Accrued( - address indexed rewardToken, - address indexed user, - uint256 index, - uint256 unclaimed - ); - - /// @notice Emitted when rewards of an asset are claimed on behalf of a user. - /// @param rewardToken The address of the reward token. - /// @param user The address of the user that rewards are claimed on behalf of. - /// @param claimed The amount of rewards claimed. - event Claimed(address indexed rewardToken, address indexed user, uint256 claimed); - - /// STRUCTS /// - - struct UserRewardsData { - uint128 index; // User rewards index for a given reward token (in ray). - uint128 unclaimed; // Unclaimed amount for a given reward token (in reward tokens). - } - - /// CONSTANTS AND IMMUTABLES /// - - uint256 public constant RAY = 1e27; - IRewardsManager public immutable rewardsManager; // Morpho's rewards manager. - - /// STORAGE /// - - mapping(address => uint128) public rewardsIndex; // The current reward index for the given reward token. - mapping(address => mapping(address => UserRewardsData)) public userRewards; // User rewards data. rewardToken -> user -> userRewards. - - /// CONSTRUCTOR /// - - /// @dev Initializes network-wide immutables. - /// @param _morpho The address of the main Morpho contract. - /// @param _morphoToken The address of the Morpho Token. - /// @param _recipient The recipient of the rewards that will redistribute them to vault's users. - constructor( - address _morpho, - address _morphoToken, - address _recipient - ) SupplyVaultBase(_morpho, _morphoToken, _recipient) { - rewardsManager = morpho.rewardsManager(); - } - - /// INITIALIZER /// - - /// @dev Initializes the vault. - /// @param _poolToken The address of the pool token corresponding to the market to supply through this vault. - /// @param _name The name of the ERC20 token associated to this tokenized vault. - /// @param _symbol The symbol of the ERC20 token associated to this tokenized vault. - /// @param _initialDeposit The amount of the initial deposit used to prevent pricePerShare manipulation. - function initialize( - address _poolToken, - string calldata _name, - string calldata _symbol, - uint256 _initialDeposit - ) external initializer { - __SupplyVaultBase_init(_poolToken, _name, _symbol, _initialDeposit); - } - - /// EXTERNAL /// - - /// @notice Claims rewards on behalf of `_user`. - /// @param _user The address of the user to claim rewards for. - /// @return rewardTokens The list of reward tokens. - /// @return claimedAmounts The list of claimed amounts for each reward tokens. - function claimRewards(address _user) - external - returns (address[] memory rewardTokens, uint256[] memory claimedAmounts) - { - (rewardTokens, claimedAmounts) = _accrueUnclaimedRewards(_user); - - for (uint256 i; i < rewardTokens.length; ++i) { - uint256 claimedAmount = claimedAmounts[i]; - if (claimedAmount == 0) continue; - - address rewardToken = rewardTokens[i]; - userRewards[rewardToken][_user].unclaimed = 0; - - ERC20(rewardToken).safeTransfer(_user, claimedAmount); - - emit Claimed(rewardToken, _user, claimedAmount); - } - } - - /// @notice Returns a given user's unclaimed rewards for all reward tokens. - /// @param _user The address of the user. - /// @return rewardTokens The list of reward tokens. - /// @return unclaimedAmounts The list of unclaimed amounts for each reward token. - function getAllUnclaimedRewards(address _user) - external - view - returns (address[] memory rewardTokens, uint256[] memory unclaimedAmounts) - { - address[] memory poolTokens = new address[](1); - poolTokens[0] = poolToken; - - uint256[] memory claimableAmounts; - (rewardTokens, claimableAmounts) = rewardsManager.getAllUserRewards( - poolTokens, - address(this) - ); - - unclaimedAmounts = new uint256[](claimableAmounts.length); - uint256 supply = totalSupply(); - - for (uint256 i; i < rewardTokens.length; ++i) { - address rewardToken = rewardTokens[i]; - unclaimedAmounts[i] = _getUpdatedUnclaimedReward( - _user, - rewardToken, - claimableAmounts[i], - supply - ); - } - } - - /// @notice Returns user's rewards for the specificied reward token. - /// @param _user The address of the user. - /// @param _rewardToken The address of the reward token - /// @return The user's rewards in reward token. - function getUnclaimedRewards(address _user, address _rewardToken) - external - view - returns (uint256) - { - address[] memory poolTokens = new address[](1); - poolTokens[0] = poolToken; - - uint256 claimableRewards = rewardsManager.getUserRewards( - poolTokens, - address(this), - _rewardToken - ); - - return _getUpdatedUnclaimedReward(_user, _rewardToken, claimableRewards, totalSupply()); - } - - /// INTERNAL /// - - function _beforeTokenTransfer( - address _from, - address _to, - uint256 _amount - ) internal virtual override { - (address[] memory rewardTokens, uint256[] memory rewardsIndexes) = _claimVaultRewards(); - _accrueUnclaimedRewardsFromRewardIndexes(_from, rewardTokens, rewardsIndexes); - _accrueUnclaimedRewardsFromRewardIndexes(_to, rewardTokens, rewardsIndexes); - - super._beforeTokenTransfer(_from, _to, _amount); - } - - function _claimVaultRewards() - internal - returns (address[] memory rewardTokens, uint256[] memory rewardsIndexes) - { - address[] memory poolTokens = new address[](1); - poolTokens[0] = poolToken; - - uint256[] memory claimedAmounts; - (rewardTokens, claimedAmounts) = morpho.claimRewards(poolTokens, false); - - rewardsIndexes = new uint256[](rewardTokens.length); - - uint256 supply = totalSupply(); - for (uint256 i; i < rewardTokens.length; ++i) { - address rewardToken = rewardTokens[i]; - uint256 newRewardIndex = rewardsIndex[rewardToken] + - _getUnaccruedRewardIndex(claimedAmounts[i], supply); - - rewardsIndexes[i] = newRewardIndex; - rewardsIndex[rewardToken] = newRewardIndex.safeCastTo128(); - } - } - - function _accrueUnclaimedRewards(address _user) - internal - returns (address[] memory rewardTokens, uint256[] memory unclaimedAmounts) - { - uint256[] memory rewardsIndexes; - (rewardTokens, rewardsIndexes) = _claimVaultRewards(); - - unclaimedAmounts = _accrueUnclaimedRewardsFromRewardIndexes( - _user, - rewardTokens, - rewardsIndexes - ); - } - - function _accrueUnclaimedRewardsFromRewardIndexes( - address _user, - address[] memory _rewardTokens, - uint256[] memory _rewardIndexes - ) internal returns (uint256[] memory unclaimedAmounts) { - if (_user == address(0)) return unclaimedAmounts; - - unclaimedAmounts = new uint256[](_rewardTokens.length); - - for (uint256 i; i < _rewardTokens.length; ++i) { - address rewardToken = _rewardTokens[i]; - uint256 rewardIndex = _rewardIndexes[i]; - - UserRewardsData storage userRewardsData = userRewards[rewardToken][_user]; - - // Safe because we always have `rewardsIndex` >= `userRewardsData.index`. - uint256 rewardsIndexDiff; - unchecked { - rewardsIndexDiff = rewardIndex - userRewardsData.index; - } - - uint256 unclaimedAmount = userRewardsData.unclaimed; - if (rewardsIndexDiff > 0) { - unclaimedAmount += _getUnaccruedRewardsFromRewardsIndexAccrual( - _user, - rewardsIndexDiff - ); - userRewardsData.unclaimed = unclaimedAmount.safeCastTo128(); - userRewardsData.index = rewardIndex.safeCastTo128(); - - emit Accrued(rewardToken, _user, rewardIndex, unclaimedAmount); - } - - unclaimedAmounts[i] = unclaimedAmount; - } - } - - function _getUpdatedUnclaimedReward( - address _user, - address _rewardToken, - uint256 _claimableReward, - uint256 _totalSupply - ) internal view returns (uint256 unclaimed) { - UserRewardsData memory userRewardsData = userRewards[_rewardToken][_user]; - unclaimed = - userRewardsData.unclaimed + - _getUnaccruedRewardsFromRewardsIndexAccrual( - _user, - _getUnaccruedRewardIndex(_claimableReward, _totalSupply) + // The unaccrued reward index - rewardsIndex[_rewardToken] - - userRewardsData.index // The difference between the current reward index and the user's index - ); - } - - function _getUnaccruedRewardsFromRewardsIndexAccrual(address _user, uint256 _indexAccrual) - internal - view - returns (uint256 unaccruedReward) - { - unaccruedReward = balanceOf(_user).mulDivDown(_indexAccrual, RAY); // Equivalent to rayMul rounded down - } - - function _getUnaccruedRewardIndex(uint256 _claimableReward, uint256 _totalSupply) - internal - pure - returns (uint256 unaccruedRewardIndex) - { - if (_totalSupply > 0) unaccruedRewardIndex = _claimableReward.mulDivDown(RAY, _totalSupply); // Equivalent to rayDiv rounded down - } -} diff --git a/src/aave-v3/SupplyVaultBase.sol b/src/aave-v3/SupplyVaultBase.sol deleted file mode 100644 index 9e59cd4..0000000 --- a/src/aave-v3/SupplyVaultBase.sol +++ /dev/null @@ -1,216 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.10; - -import {IERC4626Upgradeable} from "@openzeppelin/contracts-upgradeable/interfaces/IERC4626Upgradeable.sol"; -import {IRewardsController} from "@aave/periphery-v3/contracts/rewards/interfaces/IRewardsController.sol"; -import {IAToken} from "@aave/core-v3/contracts/interfaces/IAToken.sol"; -import {IMorpho} from "@contracts/aave-v3/interfaces/IMorpho.sol"; -import {ISupplyVaultBase} from "./interfaces/ISupplyVaultBase.sol"; - -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {ERC20, SafeTransferLib} from "@rari-capital/solmate/src/utils/SafeTransferLib.sol"; -import {WadRayMath} from "@morpho-labs/morpho-utils/math/WadRayMath.sol"; -import {Math} from "@morpho-labs/morpho-utils/math/Math.sol"; -import {Types} from "@contracts/aave-v3/libraries/Types.sol"; - -import {ERC4626UpgradeableSafe, ERC4626Upgradeable, ERC20Upgradeable} from "../ERC4626UpgradeableSafe.sol"; - -/// @title SupplyVaultBase. -/// @author Morpho Labs. -/// @custom:contact security@morpho.xyz -/// @notice ERC4626-upgradeable Tokenized Vault abstract implementation for Morpho-Aave V3. -abstract contract SupplyVaultBase is ISupplyVaultBase, ERC4626UpgradeableSafe, OwnableUpgradeable { - using WadRayMath for uint256; - using SafeTransferLib for ERC20; - - /// EVENTS /// - - /// @notice Emitted when MORPHO rewards are transferred to `recipient`. - /// @param recipient The recipient of the rewards. - /// @param amount The amount of rewards transferred. - event RewardsTransferred(address recipient, uint256 amount); - - /// ERRORS /// - - /// @notice Thrown when the zero address is passed as input or is the recipient address when calling `transferRewards`. - error ZeroAddress(); - - /// IMMUTABLES /// - - IMorpho public immutable morpho; // The main Morpho contract. - ERC20 public immutable morphoToken; // The address of the Morpho Token. - address public immutable recipient; // The recipient of the rewards that will redistribute them to vault's users. - - /// STORAGE /// - - address public poolToken; // The pool token corresponding to the market to supply to through this vault. - - /// CONSTRUCTOR /// - - /// @dev Initializes network-wide immutables. - /// @param _morpho The address of the main Morpho contract. - /// @param _morphoToken The address of the Morpho Token. - /// @param _recipient The recipient of the rewards that will redistribute them to vault's users. - constructor( - address _morpho, - address _morphoToken, - address _recipient - ) { - if (_morpho == address(0) || _morphoToken == address(0) || _recipient == address(0)) - revert ZeroAddress(); - morpho = IMorpho(_morpho); - morphoToken = ERC20(_morphoToken); - recipient = _recipient; - } - - /// INITIALIZER /// - - /// @dev Initializes the vault. - /// @param _poolToken The address of the pool token corresponding to the market to supply through this vault. - /// @param _name The name of the ERC20 token associated to this tokenized vault. - /// @param _symbol The symbol of the ERC20 token associated to this tokenized vault. - /// @param _initialDeposit The amount of the initial deposit used to prevent pricePerShare manipulation. - function __SupplyVaultBase_init( - address _poolToken, - string calldata _name, - string calldata _symbol, - uint256 _initialDeposit - ) internal onlyInitializing { - ERC20 underlyingToken = __SupplyVaultBase_init_unchained(_poolToken); - - __Ownable_init_unchained(); - __ERC20_init_unchained(_name, _symbol); - __ERC4626_init_unchained(ERC20Upgradeable(address(underlyingToken))); - __ERC4626UpgradeableSafe_init_unchained(_initialDeposit); - } - - /// @dev Initializes the vault whithout initializing parent contracts (avoid the double initialization problem). - /// @param _poolToken The address of the pool token corresponding to the market to supply through this vault. - function __SupplyVaultBase_init_unchained(address _poolToken) - internal - onlyInitializing - returns (ERC20 underlyingToken) - { - poolToken = _poolToken; - - underlyingToken = ERC20(IAToken(poolToken).UNDERLYING_ASSET_ADDRESS()); // Reverts on zero address so no check for pool token needed. - underlyingToken.safeApprove(address(morpho), type(uint256).max); - } - - /// EXTERNAL /// - - /// @notice Transfers the MORPHO rewards to the rewards recipient. - function transferRewards() external { - uint256 amount = morphoToken.balanceOf(address(this)); - morphoToken.safeTransfer(recipient, amount); - emit RewardsTransferred(recipient, amount); - } - - /// PUBLIC /// - - /// @notice The amount of assets in the vault. - /// @dev The indexes used by this function might not be up-to-date. - /// As a consequence, view functions (like `maxWithdraw`) could underestimate the withdrawable amount. - /// To redeem all their assets, users are encouraged to use the `redeem` function passing their vault tokens balance. - function totalAssets() - public - view - virtual - override(IERC4626Upgradeable, ERC4626Upgradeable) - returns (uint256) - { - address poolTokenMem = poolToken; - Types.SupplyBalance memory supplyBalance = morpho.supplyBalanceInOf( - poolTokenMem, - address(this) - ); - - return - supplyBalance.onPool.rayMul(morpho.poolIndexes(poolTokenMem).poolSupplyIndex) + - supplyBalance.inP2P.rayMul(morpho.p2pSupplyIndex(poolTokenMem)); - } - - /// @notice Deposits an amount of assets into the vault and receive vault shares. - /// @param assets The amount of assets to deposit. - /// @param receiver The recipient of the vault shares. - function deposit(uint256 assets, address receiver) - public - virtual - override(IERC4626Upgradeable, ERC4626Upgradeable) - returns (uint256) - { - // Update the indexes to get the most up-to-date total assets balance. - morpho.updateIndexes(poolToken); - return super.deposit(assets, receiver); - } - - /// @notice Mints shares of the vault and transfers assets to the vault. - /// @param shares The number of shares to mint. - /// @param receiver The recipient of the vault shares. - function mint(uint256 shares, address receiver) - public - virtual - override(IERC4626Upgradeable, ERC4626Upgradeable) - returns (uint256) - { - // Update the indexes to get the most up-to-date total assets balance. - morpho.updateIndexes(poolToken); - return super.mint(shares, receiver); - } - - /// @notice Withdraws an amount of assets from the vault and burn an owner's shares. - /// @param assets The number of assets to withdraw. - /// @param receiver The recipient of the assets. - /// @param owner The owner of the vault shares. - function withdraw( - uint256 assets, - address receiver, - address owner - ) public virtual override(IERC4626Upgradeable, ERC4626Upgradeable) returns (uint256) { - // Update the indexes to get the most up-to-date total assets balance. - morpho.updateIndexes(poolToken); - return super.withdraw(assets, receiver, owner); - } - - /// @notice Burn an amount of shares and receive assets. - /// @param shares The number of shares to burn. - /// @param receiver The recipient of the assets. - /// @param owner The owner of the assets. - function redeem( - uint256 shares, - address receiver, - address owner - ) public virtual override(IERC4626Upgradeable, ERC4626Upgradeable) returns (uint256) { - // Update the indexes to get the most up-to-date total assets balance. - morpho.updateIndexes(poolToken); - return super.redeem(shares, receiver, owner); - } - - /// INTERNAL /// - - function _deposit( - address _caller, - address _receiver, - uint256 _assets, - uint256 _shares - ) internal virtual override { - super._deposit(_caller, _receiver, _assets, _shares); - morpho.supply(address(poolToken), address(this), _assets); - } - - function _withdraw( - address _caller, - address _receiver, - address _owner, - uint256 _assets, - uint256 _shares - ) internal virtual override { - morpho.withdraw(address(poolToken), _assets); - super._withdraw(_caller, _receiver, _owner, _assets, _shares); - } - - /// @dev This empty reserved space is put in place to allow future versions to add new - /// variables without shifting down storage in the inheritance chain. - /// See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - uint256[49] private __gap; -} diff --git a/src/aave-v3/interfaces/ISupplyVault.sol b/src/aave-v3/interfaces/ISupplyVault.sol deleted file mode 100644 index 79c1d9d..0000000 --- a/src/aave-v3/interfaces/ISupplyVault.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity >=0.5.0; - -import {IRewardsManager} from "@contracts/aave-v3/interfaces/IRewardsManager.sol"; -import {ISupplyVaultBase} from "./ISupplyVaultBase.sol"; - -interface ISupplyVault is ISupplyVaultBase { - function RAY() external view returns (uint256); - - function rewardsManager() external view returns (IRewardsManager); - - function rewardsIndex(address _rewardToken) external view returns (uint128); - - function userRewards(address _rewardToken, address _user) - external - view - returns (uint128, uint128); - - function getAllUnclaimedRewards(address _user) - external - view - returns (address[] memory rewardTokens, uint256[] memory unclaimedAmounts); - - function getUnclaimedRewards(address _user, address _rewardToken) - external - view - returns (uint256); - - function claimRewards(address _user) - external - returns (address[] memory rewardTokens, uint256[] memory claimedAmounts); -} diff --git a/src/aave-v3/interfaces/ISupplyVaultBase.sol b/src/aave-v3/interfaces/ISupplyVaultBase.sol deleted file mode 100644 index 4d5e13b..0000000 --- a/src/aave-v3/interfaces/ISupplyVaultBase.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity >=0.5.0; - -import {IERC4626Upgradeable} from "@openzeppelin/contracts-upgradeable/interfaces/IERC4626Upgradeable.sol"; -import {IMorpho} from "@contracts/aave-v3/interfaces/IMorpho.sol"; - -interface ISupplyVaultBase is IERC4626Upgradeable { - function morpho() external view returns (IMorpho); - - function poolToken() external view returns (address); - - function transferRewards() external; -} diff --git a/test/aave-v3/TestSupplyVault.t.sol b/test/aave-v3/TestSupplyVault.t.sol deleted file mode 100644 index 8493ff5..0000000 --- a/test/aave-v3/TestSupplyVault.t.sol +++ /dev/null @@ -1,858 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./setup/TestSetupVaults.sol"; - -contract TestSupplyVault is TestSetupVaults { - using WadRayMath for uint256; - - function testCorrectInitialisationDai() public { - assertEq(daiSupplyVault.owner(), address(this)); - assertEq(daiSupplyVault.name(), "MorphoAaveDAI"); - assertEq(daiSupplyVault.symbol(), "maDAI"); - assertEq(daiSupplyVault.poolToken(), aDai); - assertEq(daiSupplyVault.asset(), dai); - assertEq(daiSupplyVault.decimals(), 18); - } - - function testCorrectInitialisationUsdc() public { - assertEq(usdcSupplyVault.owner(), address(this)); - assertEq(usdcSupplyVault.name(), "MorphoAaveUSDC"); - assertEq(usdcSupplyVault.symbol(), "maUSDC"); - assertEq(usdcSupplyVault.poolToken(), aUsdc); - assertEq(usdcSupplyVault.asset(), usdc); - assertEq(usdcSupplyVault.decimals(), 18); - } - - function testShouldDepositAmount() public { - uint256 amount = 10_000 ether; - - vaultSupplier1.depositVault(daiSupplyVault, amount); - - (uint256 balanceInP2P, uint256 balanceOnPool) = morpho.supplyBalanceInOf( - aDai, - address(daiSupplyVault) - ); - - uint256 p2pSupplyIndex = morpho.p2pSupplyIndex(aDai); - uint256 poolSupplyIndex = pool.getReserveNormalizedIncome(dai); - - assertGt(daiSupplyVault.balanceOf(address(vaultSupplier1)), 0, "mcDAI balance is zero"); - assertApproxEqAbs( - balanceInP2P.rayMul(p2pSupplyIndex) + balanceOnPool.rayMul(poolSupplyIndex), - amount.rayDiv(poolSupplyIndex).rayMul(poolSupplyIndex), - 1e10 - ); - } - - function testShouldWithdrawAllAmount() public { - uint256 amount = 10_000 ether; - - uint256 poolSupplyIndex = pool.getReserveNormalizedIncome(dai); - uint256 expectedOnPool = amount.rayDiv(poolSupplyIndex); - - vaultSupplier1.depositVault(daiSupplyVault, amount); - vaultSupplier1.withdrawVault(daiSupplyVault, expectedOnPool.rayMul(poolSupplyIndex)); - - (uint256 balanceInP2P, uint256 balanceOnPool) = morpho.supplyBalanceInOf( - aDai, - address(daiSupplyVault) - ); - - assertApproxEqAbs( - daiSupplyVault.balanceOf(address(vaultSupplier1)), - 0, - 1e3, - "mcDAI balance not zero" - ); - assertEq(balanceOnPool, 0, "onPool amount not zero"); - assertEq(balanceInP2P, 0, "inP2P amount not zero"); - } - - function testShouldWithdrawAllUsdcAmount() public { - uint256 amount = 1e9; - - uint256 poolSupplyIndex = pool.getReserveNormalizedIncome(usdc); - uint256 expectedOnPool = amount.rayDiv(poolSupplyIndex); - - vaultSupplier1.depositVault(usdcSupplyVault, amount); - vaultSupplier1.withdrawVault(usdcSupplyVault, expectedOnPool.rayMul(poolSupplyIndex)); - - (uint256 balanceInP2P, uint256 balanceOnPool) = morpho.supplyBalanceInOf( - address(aUsdc), - address(usdcSupplyVault) - ); - - assertApproxEqAbs( - usdcSupplyVault.balanceOf(address(vaultSupplier1)), - 0, - 10, - "mcUSDT balance not zero" - ); - assertEq(balanceOnPool, 0, "onPool amount not zero"); - assertEq(balanceInP2P, 0, "inP2P amount not zero"); - } - - function testShouldWithdrawAllShares() public { - uint256 amount = 10_000 ether; - - uint256 shares = vaultSupplier1.depositVault(daiSupplyVault, amount); - vaultSupplier1.redeemVault(daiSupplyVault, shares); // cannot withdraw amount because of Compound rounding errors - - (uint256 balanceInP2P, uint256 balanceOnPool) = morpho.supplyBalanceInOf( - aDai, - address(daiSupplyVault) - ); - - assertEq(daiSupplyVault.balanceOf(address(vaultSupplier1)), 0, "mcDAI balance not zero"); - assertEq(balanceOnPool, 0, "onPool amount not zero"); - assertEq(balanceInP2P, 0, "inP2P amount not zero"); - } - - function testShouldNotWithdrawWhenNotDeposited() public { - uint256 amount = 10_000 ether; - - uint256 shares = vaultSupplier1.depositVault(daiSupplyVault, amount); - - vm.expectRevert("ERC4626: redeem more than max"); - vaultSupplier2.redeemVault(daiSupplyVault, shares); - } - - function testShouldNotWithdrawOnBehalfIfNotAllowed() public { - uint256 amount = 10_000 ether; - - uint256 shares = vaultSupplier1.depositVault(daiSupplyVault, amount); - - vm.expectRevert("ERC4626: redeem more than max"); - vaultSupplier1.redeemVault(daiSupplyVault, shares, address(vaultSupplier2)); - } - - function testShouldWithdrawOnBehalfIfAllowed() public { - uint256 amount = 10_000 ether; - - uint256 shares = vaultSupplier1.depositVault(daiSupplyVault, amount); - - vaultSupplier1.approve(address(maDai), address(vaultSupplier2), shares); - vaultSupplier2.redeemVault(daiSupplyVault, shares, address(vaultSupplier1)); - } - - function testShouldNotMintZeroShare() public { - vm.expectRevert(abi.encodeWithSignature("AmountIsZero()")); - vaultSupplier1.mintVault(daiSupplyVault, 0); - } - - function testShouldNotWithdrawGreaterAmount() public { - uint256 amount = 10_000 ether; - - vaultSupplier1.depositVault(daiSupplyVault, amount); - - vm.expectRevert("ERC4626: withdraw more than max"); - vaultSupplier1.withdrawVault(daiSupplyVault, amount * 2); - } - - function testShouldNotRedeemMoreShares() public { - uint256 amount = 10_000 ether; - - uint256 shares = vaultSupplier1.depositVault(daiSupplyVault, amount); - - vm.expectRevert("ERC4626: redeem more than max"); - vaultSupplier1.redeemVault(daiSupplyVault, shares + 1); - } - - function testShouldClaimRewards() public { - uint256 amount = 10_000 ether; - - vaultSupplier1.depositVault(daiSupplyVault, amount); - - vm.warp(block.timestamp + 20 days); - - uint256 balanceBefore = vaultSupplier1.balanceOf(rewardToken); - - (address[] memory rewardTokens, uint256[] memory claimedAmounts) = daiSupplyVault - .claimRewards(address(vaultSupplier1)); - - assertEq(rewardTokens.length, 1); - assertEq(rewardTokens[0], rewardToken); - assertEq(claimedAmounts.length, 1); - - uint256 balanceAfter = vaultSupplier1.balanceOf(rewardToken); - - assertGt(claimedAmounts[0], 0); - assertApproxEqAbs( - ERC20(rewardToken).balanceOf(address(daiSupplyVault)), - 0, - 1e4, - "non zero rewardToken balance on vault" - ); - assertEq(balanceAfter, balanceBefore + claimedAmounts[0], "unexpected rewardToken balance"); - } - - function testShouldClaimTwiceRewardsWhenDepositedForSameAmountAndTwiceDuration() public { - uint256 amount = 10_000 ether; - address[] memory poolTokens = new address[](1); - poolTokens[0] = aDai; - - vaultSupplier1.depositVault(daiSupplyVault, amount); - - vm.warp(block.timestamp + 10 days); - - uint256 expectedTotalRewardsAmount = rewardsManager.getUserRewards( - poolTokens, - address(daiSupplyVault), - rewardToken - ); - - vaultSupplier2.depositVault(daiSupplyVault, amount); - - vm.warp(block.timestamp + 10 days); - - expectedTotalRewardsAmount += rewardsManager.getUserRewards( - poolTokens, - address(daiSupplyVault), - rewardToken - ); - - (address[] memory rewardTokens1, uint256[] memory claimedAmounts1) = daiSupplyVault - .claimRewards(address(vaultSupplier1)); - - assertEq(rewardTokens1.length, 1); - assertEq(rewardTokens1[0], rewardToken); - assertEq(claimedAmounts1.length, 1); - - (address[] memory rewardTokens2, uint256[] memory claimedAmounts2) = daiSupplyVault - .claimRewards(address(vaultSupplier2)); - - assertEq(rewardTokens2.length, 1); - assertEq(rewardTokens2[0], rewardToken); - assertEq(claimedAmounts2.length, 1); - - assertApproxEqAbs( - expectedTotalRewardsAmount, - claimedAmounts1[0] + claimedAmounts2[0], - 1e5, - "unexpected total rewards amount" - ); - assertLe(claimedAmounts1[0] + claimedAmounts2[0], expectedTotalRewardsAmount); - assertApproxEqAbs( - claimedAmounts1[0], - 2 * claimedAmounts2[0], - 1e15, - "unexpected rewards amount" - ); // not exact because of rewardTokenounded interests - } - - function testShouldClaimSameRewardsWhenDepositedAtSameTime() public { - uint256 amount = 10_000 ether; - address[] memory poolTokens = new address[](1); - poolTokens[0] = aDai; - - uint256 shares1 = vaultSupplier1.depositVault(daiSupplyVault, amount); - uint256 shares2 = vaultSupplier2.depositVault(daiSupplyVault, amount); - - vm.warp(block.timestamp + 10 days); - - uint256 expectedTotalRewardsAmount = rewardsManager.getUserRewards( - poolTokens, - address(daiSupplyVault), - rewardToken - ); - - vaultSupplier1.redeemVault(daiSupplyVault, shares1 / 2); - vaultSupplier2.redeemVault(daiSupplyVault, shares2 / 2); - - vm.warp(block.timestamp + 10 days); - - expectedTotalRewardsAmount += rewardsManager.getUserRewards( - poolTokens, - address(daiSupplyVault), - rewardToken - ); - - vaultSupplier1.redeemVault(daiSupplyVault, shares1 / 2); - vaultSupplier2.redeemVault(daiSupplyVault, shares2 / 2); - - (address[] memory rewardTokens1, uint256[] memory claimedAmounts1) = daiSupplyVault - .claimRewards(address(vaultSupplier1)); - - assertEq(rewardTokens1.length, 1); - assertEq(rewardTokens1[0], rewardToken); - assertEq(claimedAmounts1.length, 1); - - (address[] memory rewardTokens2, uint256[] memory claimedAmounts2) = daiSupplyVault - .claimRewards(address(vaultSupplier2)); - - assertEq(rewardTokens2.length, 1); - assertEq(rewardTokens2[0], rewardToken); - assertEq(claimedAmounts2.length, 1); - - assertApproxEqAbs( - expectedTotalRewardsAmount, - claimedAmounts1[0] + claimedAmounts2[0], - 1e5, - "unexpected total rewards amount" - ); - assertGe(expectedTotalRewardsAmount, claimedAmounts1[0] + claimedAmounts2[0]); - assertApproxEqAbs( - claimedAmounts1[0], - claimedAmounts2[0], - 1e15, - "unexpected rewards amount" - ); // not exact because of rewardTokenounded interests - } - - function testShouldClaimSameRewardsWhenDepositedForSameAmountAndDuration1() public { - uint256 amount = 10_000 ether; - address[] memory poolTokens = new address[](1); - poolTokens[0] = aDai; - - uint256 shares1 = vaultSupplier1.depositVault(daiSupplyVault, amount); - - vm.warp(block.timestamp + 10 days); - - uint256 expectedTotalRewardsAmount = rewardsManager.getUserRewards( - poolTokens, - address(daiSupplyVault), - rewardToken - ); - - uint256 shares2 = vaultSupplier2.depositVault(daiSupplyVault, amount); - vaultSupplier1.redeemVault(daiSupplyVault, shares1 / 2); - - vm.warp(block.timestamp + 10 days); - - expectedTotalRewardsAmount += rewardsManager.getUserRewards( - poolTokens, - address(daiSupplyVault), - rewardToken - ); - - vaultSupplier1.redeemVault(daiSupplyVault, shares1 / 2); - vaultSupplier2.redeemVault(daiSupplyVault, shares2 / 2); - - vm.warp(block.timestamp + 10 days); - - expectedTotalRewardsAmount += rewardsManager.getUserRewards( - poolTokens, - address(daiSupplyVault), - rewardToken - ); - - (address[] memory rewardTokens1, uint256[] memory claimedAmounts1) = daiSupplyVault - .claimRewards(address(vaultSupplier1)); - - assertEq(rewardTokens1.length, 1); - assertEq(rewardTokens1[0], rewardToken); - assertEq(claimedAmounts1.length, 1); - - (address[] memory rewardTokens2, uint256[] memory claimedAmounts2) = daiSupplyVault - .claimRewards(address(vaultSupplier2)); - - assertEq(rewardTokens2.length, 1); - assertEq(rewardTokens2[0], rewardToken); - assertEq(claimedAmounts2.length, 1); - - assertApproxEqAbs( - expectedTotalRewardsAmount, - claimedAmounts1[0] + claimedAmounts2[0], - 1e5, - "unexpected total rewards amount" - ); - assertGe(expectedTotalRewardsAmount, claimedAmounts1[0] + claimedAmounts2[0]); - assertApproxEqAbs( - claimedAmounts1[0], - claimedAmounts2[0], - 1e15, - "unexpected rewards amount" - ); // not exact because of rewardTokenounded interests - } - - function testShouldClaimSameRewardsWhenDepositedForSameAmountAndDuration2() public { - uint256 amount = 10_000 ether; - address[] memory poolTokens = new address[](1); - poolTokens[0] = aDai; - - uint256 shares1 = vaultSupplier1.depositVault(daiSupplyVault, amount); - - vm.warp(block.timestamp + 10 days); - - uint256 expectedTotalRewardsAmount = rewardsManager.getUserRewards( - poolTokens, - address(daiSupplyVault), - rewardToken - ); - - uint256 shares2 = vaultSupplier2.depositVault(daiSupplyVault, amount); - vaultSupplier1.redeemVault(daiSupplyVault, shares1 / 2); - - vm.warp(block.timestamp + 10 days); - - expectedTotalRewardsAmount += rewardsManager.getUserRewards( - poolTokens, - address(daiSupplyVault), - rewardToken - ); - - uint256 shares3 = vaultSupplier3.depositVault(daiSupplyVault, amount); - vaultSupplier1.redeemVault(daiSupplyVault, shares1 / 2); - vaultSupplier2.redeemVault(daiSupplyVault, shares2 / 2); - - vm.warp(block.timestamp + 10 days); - - expectedTotalRewardsAmount += rewardsManager.getUserRewards( - poolTokens, - address(daiSupplyVault), - rewardToken - ); - - vaultSupplier2.redeemVault(daiSupplyVault, shares2 / 2); - vaultSupplier3.redeemVault(daiSupplyVault, shares3 / 2); - - vm.warp(block.timestamp + 10 days); - - expectedTotalRewardsAmount += rewardsManager.getUserRewards( - poolTokens, - address(daiSupplyVault), - rewardToken - ); - - vaultSupplier3.redeemVault(daiSupplyVault, shares3 / 2); - - (address[] memory rewardTokens1, uint256[] memory claimedAmounts1) = daiSupplyVault - .claimRewards(address(vaultSupplier1)); - - assertEq(rewardTokens1.length, 1); - assertEq(rewardTokens1[0], rewardToken); - assertEq(claimedAmounts1.length, 1); - - (address[] memory rewardTokens2, uint256[] memory claimedAmounts2) = daiSupplyVault - .claimRewards(address(vaultSupplier2)); - - assertEq(rewardTokens2.length, 1); - assertEq(rewardTokens2[0], rewardToken); - assertEq(claimedAmounts2.length, 1); - - (address[] memory rewardTokens3, uint256[] memory claimedAmounts3) = daiSupplyVault - .claimRewards(address(vaultSupplier3)); - - assertEq(rewardTokens3.length, 1); - assertEq(rewardTokens3[0], rewardToken); - assertEq(claimedAmounts3.length, 1); - - assertApproxEqAbs( - expectedTotalRewardsAmount, - claimedAmounts1[0] + claimedAmounts2[0] + claimedAmounts3[0], - 1e5, - "unexpected total rewards amount" - ); - assertGe( - expectedTotalRewardsAmount, - claimedAmounts1[0] + claimedAmounts2[0] + claimedAmounts3[0] - ); - assertApproxEqAbs( - ERC20(rewardToken).balanceOf(address(daiSupplyVault)), - 0, - 1e15, - "non zero rewardToken balance on vault" - ); - assertApproxEqAbs( - claimedAmounts1[0], - claimedAmounts2[0], - 1e15, - "unexpected rewards amount 1-2" - ); // not exact because of rewardTokenounded interests - assertApproxEqAbs( - claimedAmounts2[0], - claimedAmounts3[0], - 1e15, - "unexpected rewards amount 2-3" - ); // not exact because of rewardTokenounded interests - } - - function testRewardsShouldAccrueWhenDepositingOnBehalf() public { - uint256 amount = 10_000 ether; - address[] memory poolTokens = new address[](1); - poolTokens[0] = aDai; - - vaultSupplier2.depositVault(daiSupplyVault, amount, address(vaultSupplier1)); - vm.warp(block.timestamp + 10 days); - - uint256 expectedTotalRewardsAmount = rewardsManager.getUserRewards( - poolTokens, - address(daiSupplyVault), - rewardToken - ); - - // Should update the unclaimed amount - vaultSupplier2.depositVault(daiSupplyVault, amount, address(vaultSupplier1)); - uint256 userReward1_1 = daiSupplyVault.getUnclaimedRewards( - address(vaultSupplier1), - rewardToken - ); - - vm.warp(block.timestamp + 10 days); - uint256 userReward1_2 = daiSupplyVault.getUnclaimedRewards( - address(vaultSupplier1), - rewardToken - ); - - uint256 userReward2 = daiSupplyVault.getUnclaimedRewards( - address(vaultSupplier2), - rewardToken - ); - assertEq(userReward2, 0); - assertGt(userReward1_1, 0); - assertGt(userReward1_2, 0); - assertApproxEqAbs(userReward1_1, expectedTotalRewardsAmount, 10000); - assertApproxEqAbs(userReward1_1 * 3, userReward1_2, userReward1_2 / 1000); - } - - function testRewardsShouldAccrueWhenMintingOnBehalf() public { - uint256 amount = 10_000 ether; - address[] memory poolTokens = new address[](1); - poolTokens[0] = aDai; - - vaultSupplier2.mintVault( - daiSupplyVault, - daiSupplyVault.previewMint(amount), - address(vaultSupplier1) - ); - vm.warp(block.timestamp + 10 days); - - uint256 expectedTotalRewardsAmount = rewardsManager.getUserRewards( - poolTokens, - address(daiSupplyVault), - rewardToken - ); - morpho.updateIndexes(aDai); - - // Should update the unclaimed amount - vaultSupplier2.mintVault( - daiSupplyVault, - daiSupplyVault.previewMint(amount), - address(vaultSupplier1) - ); - - uint256 userReward1_1 = daiSupplyVault.getUnclaimedRewards( - address(vaultSupplier1), - rewardToken - ); - - vm.warp(block.timestamp + 10 days); - uint256 userReward1_2 = daiSupplyVault.getUnclaimedRewards( - address(vaultSupplier1), - rewardToken - ); - - uint256 userReward2 = daiSupplyVault.getUnclaimedRewards( - address(vaultSupplier2), - rewardToken - ); - assertEq(userReward2, 0); - assertGt(userReward1_1, 0); - assertApproxEqAbs(userReward1_1, expectedTotalRewardsAmount, 10000); - assertApproxEqAbs(userReward1_1 * 3, userReward1_2, userReward1_2 / 1000); - } - - function testRewardsShouldAccrueWhenRedeemingToReceiver() public { - uint256 amount = 10_000 ether; - address[] memory poolTokens = new address[](1); - poolTokens[0] = aDai; - - vaultSupplier1.depositVault(daiSupplyVault, amount); - vm.warp(block.timestamp + 10 days); - - uint256 expectedTotalRewardsAmount = rewardsManager.getUserRewards( - poolTokens, - address(daiSupplyVault), - rewardToken - ); - morpho.updateIndexes(aDai); - - // Should update the unclaimed amount - vaultSupplier1.redeemVault( - daiSupplyVault, - daiSupplyVault.balanceOf(address(vaultSupplier1)), - address(vaultSupplier2), - address(vaultSupplier1) - ); - (, uint128 userReward1_1) = daiSupplyVault.userRewards( - rewardToken, - address(vaultSupplier1) - ); - - vm.warp(block.timestamp + 10 days); - - uint256 userReward1_2 = daiSupplyVault.getUnclaimedRewards( - address(vaultSupplier1), - rewardToken - ); - uint256 userReward2 = daiSupplyVault.getUnclaimedRewards( - address(vaultSupplier2), - rewardToken - ); - - (uint128 index2, ) = daiSupplyVault.userRewards(rewardToken, address(vaultSupplier2)); - assertEq(index2, 0); - assertEq(userReward2, 0); - assertGt(uint256(userReward1_1), 0); - assertApproxEqAbs(uint256(userReward1_1), expectedTotalRewardsAmount, 10000); - assertApproxEqAbs(uint256(userReward1_1), userReward1_2, 1); - } - - function testRewardsShouldAccrueWhenWithdrawingToReceiver() public { - uint256 amount = 10_000 ether; - address[] memory poolTokens = new address[](1); - poolTokens[0] = aDai; - - vaultSupplier1.depositVault(daiSupplyVault, amount); - vm.warp(block.timestamp + 10 days); - - uint256 expectedTotalRewardsAmount = rewardsManager.getUserRewards( - poolTokens, - address(daiSupplyVault), - rewardToken - ); - morpho.updateIndexes(aDai); - - // Should update the unclaimed amount - vaultSupplier1.withdrawVault( - daiSupplyVault, - daiSupplyVault.maxWithdraw(address(vaultSupplier1)), - address(vaultSupplier2), - address(vaultSupplier1) - ); - - (, uint128 userReward1_1) = daiSupplyVault.userRewards( - rewardToken, - address(vaultSupplier1) - ); - - vm.warp(block.timestamp + 10 days); - - uint256 userReward1_2 = daiSupplyVault.getUnclaimedRewards( - address(vaultSupplier1), - rewardToken - ); - uint256 userReward2 = daiSupplyVault.getUnclaimedRewards( - address(vaultSupplier2), - rewardToken - ); - - (uint128 index2, ) = daiSupplyVault.userRewards(rewardToken, address(vaultSupplier2)); - assertEq(index2, 0); - assertEq(userReward2, 0); - assertGt(uint256(userReward1_1), 0); - assertApproxEqAbs(uint256(userReward1_1), expectedTotalRewardsAmount, 10000); - assertApproxEqAbs(uint256(userReward1_1), userReward1_2, 1); - } - - function testTransfer() public { - uint256 amount = 1e6 ether; - - vaultSupplier1.depositVault(daiSupplyVault, amount); - - uint256 balance = daiSupplyVault.balanceOf(address(vaultSupplier1)); - vm.prank(address(vaultSupplier1)); - daiSupplyVault.transfer(address(vaultSupplier2), balance); - - assertEq(daiSupplyVault.balanceOf(address(vaultSupplier1)), 0); - assertEq(daiSupplyVault.balanceOf(address(vaultSupplier2)), balance); - } - - function testTransferFrom() public { - uint256 amount = 1e6 ether; - - vaultSupplier1.depositVault(daiSupplyVault, amount); - - uint256 balance = daiSupplyVault.balanceOf(address(vaultSupplier1)); - vm.prank(address(vaultSupplier1)); - daiSupplyVault.approve(address(vaultSupplier3), balance); - - vm.prank(address(vaultSupplier3)); - daiSupplyVault.transferFrom(address(vaultSupplier1), address(vaultSupplier2), balance); - - assertEq(daiSupplyVault.balanceOf(address(vaultSupplier1)), 0); - assertEq(daiSupplyVault.balanceOf(address(vaultSupplier2)), balance); - } - - function testTransferAccrueRewards() public { - uint256 amount = 1e6 ether; - - vaultSupplier1.depositVault(daiSupplyVault, amount); - - vm.warp(block.timestamp + 10 days); - - uint256 balance = daiSupplyVault.balanceOf(address(vaultSupplier1)); - vm.prank(address(vaultSupplier1)); - daiSupplyVault.transfer(address(vaultSupplier2), balance); - - uint256 rewardAmount = ERC20(rewardToken).balanceOf(address(daiSupplyVault)); - assertGt(rewardAmount, 0); - - uint256 expectedIndex = rewardAmount.rayDiv(daiSupplyVault.totalSupply()); - uint256 rewardsIndex = daiSupplyVault.rewardsIndex(rewardToken); - assertEq(expectedIndex, rewardsIndex); - - (uint256 index1, uint256 unclaimed1) = daiSupplyVault.userRewards( - rewardToken, - address(vaultSupplier1) - ); - assertEq(index1, rewardsIndex); - assertEq(unclaimed1, rewardAmount); - - (uint256 index2, uint256 unclaimed2) = daiSupplyVault.userRewards( - rewardToken, - address(vaultSupplier2) - ); - assertEq(index2, rewardsIndex); - assertEq(unclaimed2, 0); - - (, uint256[] memory rewardsAmount1) = daiSupplyVault.claimRewards(address(vaultSupplier1)); - (, uint256[] memory rewardsAmount2) = daiSupplyVault.claimRewards(address(vaultSupplier2)); - assertGt(rewardsAmount1[0], 0, "rewardsAmount1"); - assertEq(rewardsAmount2[0], 0); - } - - function testTransferFromAccrueRewards() public { - uint256 amount = 1e6 ether; - - vaultSupplier1.depositVault(daiSupplyVault, amount); - - vm.warp(block.timestamp + 10 days); - - uint256 balance = daiSupplyVault.balanceOf(address(vaultSupplier1)); - vm.prank(address(vaultSupplier1)); - daiSupplyVault.approve(address(vaultSupplier3), balance); - - vm.prank(address(vaultSupplier3)); - daiSupplyVault.transferFrom(address(vaultSupplier1), address(vaultSupplier2), balance); - - uint256 rewardAmount = ERC20(rewardToken).balanceOf(address(daiSupplyVault)); - assertGt(rewardAmount, 0); - - uint256 expectedIndex = rewardAmount.rayDiv(daiSupplyVault.totalSupply()); - uint256 rewardsIndex = daiSupplyVault.rewardsIndex(rewardToken); - assertEq(rewardsIndex, expectedIndex); - - (uint256 index1, uint256 unclaimed1) = daiSupplyVault.userRewards( - rewardToken, - address(vaultSupplier1) - ); - assertEq(index1, rewardsIndex); - assertEq(unclaimed1, rewardAmount); - - (uint256 index2, uint256 unclaimed2) = daiSupplyVault.userRewards( - rewardToken, - address(vaultSupplier2) - ); - assertEq(index2, rewardsIndex); - assertEq(unclaimed2, 0); - - (uint256 index3, uint256 unclaimed3) = daiSupplyVault.userRewards( - rewardToken, - address(vaultSupplier3) - ); - assertEq(index3, 0); - assertEq(unclaimed3, 0); - - (, uint256[] memory rewardsAmount1) = daiSupplyVault.claimRewards(address(vaultSupplier1)); - (, uint256[] memory rewardsAmount2) = daiSupplyVault.claimRewards(address(vaultSupplier2)); - (, uint256[] memory rewardsAmount3) = daiSupplyVault.claimRewards(address(vaultSupplier3)); - assertGt(rewardsAmount1[0], 0, "rewardsAmount1"); - assertEq(rewardsAmount2[0], 0); - assertEq(rewardsAmount3[0], 0); - } - - function testTransferAndClaimRewards() public { - uint256 amount = 1e6 ether; - - vaultSupplier1.depositVault(daiSupplyVault, amount); - - vm.warp(block.timestamp + 10 days); - - vaultSupplier2.depositVault(daiSupplyVault, amount); - - vm.warp(block.timestamp + 10 days); - - uint256 balance = daiSupplyVault.balanceOf(address(vaultSupplier1)); - vm.prank(address(vaultSupplier1)); - daiSupplyVault.transfer(address(vaultSupplier2), balance); - - vm.warp(block.timestamp + 10 days); - - uint256 rewardsAmount1 = daiSupplyVault.getUnclaimedRewards( - address(vaultSupplier1), - rewardToken - ); - uint256 rewardsAmount2 = daiSupplyVault.getUnclaimedRewards( - address(vaultSupplier2), - rewardToken - ); - - assertGt(rewardsAmount1, 0); - assertApproxEqAbs(rewardsAmount1, (2 * rewardsAmount2) / 3, rewardsAmount1 / 100); - // Why rewardsAmount1 is 2/3 of rewardsAmount2 can be explained as follows: - // vaultSupplier1 first gets X rewards corresponding to amount over one period of time - // vaultSupplier1 then and vaultSupplier2 get X rewards each (under the approximation that doubling the amount doubles the rewards) - // vaultSupplier2 then gets 2 * X rewards - // In the end, vaultSupplier1 got 2 * X rewards while vaultSupplier2 got 3 * X - } - - function testShouldDepositCorrectAmountWhenMorphoPoolIndexesOutdated() public { - uint256 amount = 10_000 ether; - - vaultSupplier1.depositVault(daiSupplyVault, amount); - - vm.roll(block.number + 100_000); - vm.warp(block.timestamp + 1_000_000); - - uint256 shares = vaultSupplier2.depositVault(daiSupplyVault, amount); - uint256 assets = vaultSupplier2.redeemVault(daiSupplyVault, shares); - - assertApproxEqAbs(assets, amount, 1, "unexpected withdrawn assets"); - } - - function testShouldRedeemAllAmountWhenMorphoPoolIndexesOutdated() public { - uint256 amount = 10_000 ether; - - uint256 expectedOnPool = amount.rayDiv(pool.getReserveNormalizedIncome(dai)); - - uint256 shares = vaultSupplier1.depositVault(daiSupplyVault, amount); - - vm.roll(block.number + 100_000); - vm.warp(block.timestamp + 1_000_000); - - uint256 assets = vaultSupplier1.redeemVault(daiSupplyVault, shares); - - assertEq( - assets, - expectedOnPool.rayMul(pool.getReserveNormalizedIncome(dai)), - "unexpected withdrawn assets" - ); - } - - function testShouldWithdrawAllAmountWhenMorphoPoolIndexesOutdated() public { - uint256 amount = 10_000 ether; - - uint256 expectedOnPool = amount.rayDiv(pool.getReserveNormalizedIncome(dai)); - - vaultSupplier1.depositVault(daiSupplyVault, amount); - - vm.roll(block.number + 100_000); - vm.warp(block.timestamp + 1_000_000); - - vaultSupplier1.withdrawVault( - daiSupplyVault, - expectedOnPool.rayMul(pool.getReserveNormalizedIncome(dai)) - ); - - (uint256 balanceInP2P, uint256 balanceOnPool) = morpho.supplyBalanceInOf( - address(aUsdc), - address(daiSupplyVault) - ); - - assertEq(daiSupplyVault.balanceOf(address(vaultSupplier1)), 0, "mcUSDT balance not zero"); - assertEq(balanceOnPool, 0, "onPool amount not zero"); - assertEq(balanceInP2P, 0, "inP2P amount not zero"); - } -} diff --git a/test/aave-v3/TestSupplyVaultBase.t.sol b/test/aave-v3/TestSupplyVaultBase.t.sol deleted file mode 100644 index 7e3b659..0000000 --- a/test/aave-v3/TestSupplyVaultBase.t.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./setup/TestSetupVaults.sol"; - -contract TestSupplyVaultBase is TestSetupVaults { - function testShouldTransferRewardsToRecipient(address _caller, uint256 _amount) public { - vm.assume(_amount > 0); - - assertEq(ERC20(MORPHO_TOKEN).balanceOf(RECIPIENT), 0); - - deal(MORPHO_TOKEN, address(daiSupplyVault), _amount); - assertEq(ERC20(MORPHO_TOKEN).balanceOf(address(daiSupplyVault)), _amount); - - vm.prank(_caller); - daiSupplyVault.transferRewards(); - - assertEq(ERC20(MORPHO_TOKEN).balanceOf(address(daiSupplyVault)), 0); - assertEq(ERC20(MORPHO_TOKEN).balanceOf(RECIPIENT), _amount); - } -} diff --git a/test/aave-v3/TestUpgradeable.t.sol b/test/aave-v3/TestUpgradeable.t.sol deleted file mode 100644 index 0ed8223..0000000 --- a/test/aave-v3/TestUpgradeable.t.sol +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./setup/TestSetupVaults.sol"; - -contract TestUpgradeable is TestSetupVaults { - using WadRayMath for uint256; - - function testUpgradeSupplyVault() public { - SupplyVault wethSupplyVaultImplV2 = new SupplyVault( - address(morpho), - MORPHO_TOKEN, - RECIPIENT - ); - - vm.record(); - vm.prank(proxyAdmin.owner()); - proxyAdmin.upgrade(wrappedNativeTokenSupplyVaultProxy, address(wethSupplyVaultImplV2)); - (, bytes32[] memory writes) = vm.accesses(address(wrappedNativeTokenSupplyVault)); - - // 1 write for the implemention. - assertEq(writes.length, 1); - address newImplem = bytes32ToAddress( - vm.load( - address(wrappedNativeTokenSupplyVault), - bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1) // Implementation slot. - ) - ); - assertEq(newImplem, address(wethSupplyVaultImplV2)); - } - - function testOnlyProxyOwnerCanUpgradeSupplyVault() public { - SupplyVault supplyVaultImplV2 = new SupplyVault(address(morpho), MORPHO_TOKEN, RECIPIENT); - - vm.prank(address(vaultSupplier1)); - vm.expectRevert("Ownable: caller is not the owner"); - proxyAdmin.upgrade(wrappedNativeTokenSupplyVaultProxy, address(supplyVaultImplV2)); - - vm.prank(proxyAdmin.owner()); - proxyAdmin.upgrade(wrappedNativeTokenSupplyVaultProxy, address(supplyVaultImplV2)); - } - - function testOnlyProxyOwnerCanUpgradeAndCallSupplyVault() public { - SupplyVault wethSupplyVaultImplV2 = new SupplyVault( - address(morpho), - MORPHO_TOKEN, - RECIPIENT - ); - - vm.prank(address(vaultSupplier1)); - vm.expectRevert("Ownable: caller is not the owner"); - proxyAdmin.upgradeAndCall( - wrappedNativeTokenSupplyVaultProxy, - payable(address(wethSupplyVaultImplV2)), - "" - ); - - // Revert for wrong data not wrong caller. - vm.expectRevert("Address: low-level delegate call failed"); - proxyAdmin.upgradeAndCall( - wrappedNativeTokenSupplyVaultProxy, - payable(address(wethSupplyVaultImplV2)), - "" - ); - } - - function testSupplyVaultImplementationsShouldBeInitialized() public { - vm.expectRevert("Initializable: contract is already initialized"); - supplyVaultImplV1.initialize(address(aWrappedNativeToken), "MorphoAaveWETH", "maWETH", 0); - } -} diff --git a/test/aave-v3/helpers/SupplyVaultBaseMock.sol b/test/aave-v3/helpers/SupplyVaultBaseMock.sol deleted file mode 100644 index 6d84ad3..0000000 --- a/test/aave-v3/helpers/SupplyVaultBaseMock.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity >=0.8.0; - -import {SupplyVaultBase} from "src/aave-v3/SupplyVaultBase.sol"; - -contract SupplyVaultBaseMock is SupplyVaultBase { - constructor( - address _morpho, - address _morphoToken, - address _recipient - ) SupplyVaultBase(_morpho, _morphoToken, _recipient) {} -} diff --git a/test/aave-v3/helpers/VaultUser.sol b/test/aave-v3/helpers/VaultUser.sol deleted file mode 100644 index 09c99f5..0000000 --- a/test/aave-v3/helpers/VaultUser.sol +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@vaults/ERC4626UpgradeableSafe.sol"; -import "@tests/aave-v3/helpers/User.sol"; - -contract VaultUser is User { - using SafeTransferLib for ERC20; - - constructor(Morpho _morpho) User(_morpho) {} - - function depositVault(ERC4626UpgradeableSafe tokenizedVault, uint256 _amount) - external - returns (uint256) - { - ERC20(tokenizedVault.asset()).safeApprove(address(tokenizedVault), _amount); - return tokenizedVault.deposit(_amount, address(this)); - } - - function depositVault( - ERC4626UpgradeableSafe tokenizedVault, - uint256 _amount, - address _to - ) external returns (uint256) { - ERC20(tokenizedVault.asset()).safeApprove(address(tokenizedVault), _amount); - return tokenizedVault.deposit(_amount, _to); - } - - function mintVault(ERC4626UpgradeableSafe tokenizedVault, uint256 _shares) - external - returns (uint256) - { - ERC20(tokenizedVault.asset()).safeApprove( - address(tokenizedVault), - tokenizedVault.previewMint(_shares) - ); - return tokenizedVault.mint(_shares, address(this)); - } - - function mintVault( - ERC4626UpgradeableSafe tokenizedVault, - uint256 _shares, - address _to - ) external returns (uint256) { - ERC20(tokenizedVault.asset()).safeApprove( - address(tokenizedVault), - tokenizedVault.previewMint(_shares) - ); - return tokenizedVault.mint(_shares, _to); - } - - function withdrawVault( - ERC4626UpgradeableSafe tokenizedVault, - uint256 _amount, - address _owner - ) public returns (uint256) { - return tokenizedVault.withdraw(_amount, address(this), _owner); - } - - function withdrawVault( - ERC4626UpgradeableSafe tokenizedVault, - uint256 _amount, - address _receiver, - address _owner - ) public returns (uint256) { - return tokenizedVault.withdraw(_amount, _receiver, _owner); - } - - function withdrawVault(ERC4626UpgradeableSafe tokenizedVault, uint256 _amount) - external - returns (uint256) - { - return withdrawVault(tokenizedVault, _amount, address(this)); - } - - function redeemVault( - ERC4626UpgradeableSafe tokenizedVault, - uint256 _shares, - address _owner - ) public returns (uint256) { - return tokenizedVault.redeem(_shares, address(this), _owner); - } - - function redeemVault( - ERC4626UpgradeableSafe tokenizedVault, - uint256 _shares, - address _receiver, - address _owner - ) public returns (uint256) { - return tokenizedVault.redeem(_shares, _receiver, _owner); - } - - function redeemVault(ERC4626UpgradeableSafe tokenizedVault, uint256 _shares) - external - returns (uint256) - { - return redeemVault(tokenizedVault, _shares, address(this)); - } -} diff --git a/test/aave-v3/setup/TestSetupVaults.sol b/test/aave-v3/setup/TestSetupVaults.sol deleted file mode 100644 index 37829fb..0000000 --- a/test/aave-v3/setup/TestSetupVaults.sol +++ /dev/null @@ -1,121 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "@tests/aave-v3/setup/TestSetup.sol"; -import {SupplyVaultBase} from "@vaults/aave-v3/SupplyVaultBase.sol"; -import {SupplyVault} from "@vaults/aave-v3/SupplyVault.sol"; - -import "../../helpers/FakeToken.sol"; -import "../helpers/VaultUser.sol"; -import "../helpers/SupplyVaultBaseMock.sol"; - -contract TestSetupVaults is TestSetup { - using SafeTransferLib for ERC20; - - address internal MORPHO_TOKEN = address(new FakeToken("Morpho Token", "MORPHO")); - address internal constant MORPHO_DAO = 0xcBa28b38103307Ec8dA98377ffF9816C164f9AFa; - address internal constant RECIPIENT = 0x60345417a227ad7E312eAa1B5EC5CD1Fe5E2Cdc6; - - TransparentUpgradeableProxy internal wrappedNativeTokenSupplyVaultProxy; - - SupplyVault internal supplyVaultImplV1; - - SupplyVault internal wrappedNativeTokenSupplyVault; - SupplyVault internal daiSupplyVault; - SupplyVault internal usdcSupplyVault; - SupplyVaultBase internal supplyVaultBase; - - ERC20 internal maWrappedNativeToken; - ERC20 internal maDai; - ERC20 internal maUsdc; - - VaultUser public vaultSupplier1; - VaultUser public vaultSupplier2; - VaultUser public vaultSupplier3; - VaultUser[] public vaultSuppliers; - - FakeToken public token; - address public $token; - - function onSetUp() public override { - initVaultContracts(); - setVaultContractsLabels(); - initVaultUsers(); - - token = new FakeToken("Token", "TKN"); - $token = address(token); - } - - function initVaultContracts() internal { - createMarket(aWrappedNativeToken); - - supplyVaultImplV1 = new SupplyVault(address(morpho), MORPHO_TOKEN, RECIPIENT); - supplyVaultBase = SupplyVaultBase( - address( - new TransparentUpgradeableProxy( - address(new SupplyVaultBaseMock(address(morpho), MORPHO_TOKEN, RECIPIENT)), - address(proxyAdmin), - "" - ) - ) - ); - - wrappedNativeTokenSupplyVaultProxy = new TransparentUpgradeableProxy( - address(supplyVaultImplV1), - address(proxyAdmin), - "" - ); - wrappedNativeTokenSupplyVault = SupplyVault(address(wrappedNativeTokenSupplyVaultProxy)); - wrappedNativeTokenSupplyVault.initialize( - address(aWrappedNativeToken), - "MorphoAaveWNATIVE", - "maWNATIVE", - 0 - ); - maWrappedNativeToken = ERC20(address(wrappedNativeTokenSupplyVault)); - - daiSupplyVault = SupplyVault( - address( - new TransparentUpgradeableProxy(address(supplyVaultImplV1), address(proxyAdmin), "") - ) - ); - daiSupplyVault.initialize(address(aDai), "MorphoAaveDAI", "maDAI", 0); - maDai = ERC20(address(daiSupplyVault)); - - usdcSupplyVault = SupplyVault( - address( - new TransparentUpgradeableProxy(address(supplyVaultImplV1), address(proxyAdmin), "") - ) - ); - usdcSupplyVault.initialize(address(aUsdc), "MorphoAaveUSDC", "maUSDC", 0); - maUsdc = ERC20(address(usdcSupplyVault)); - } - - function initVaultUsers() internal { - for (uint256 i = 0; i < 3; i++) { - suppliers[i] = new VaultUser(morpho); - fillUserBalances(suppliers[i]); - - deal(wrappedNativeToken, address(suppliers[i]), 10000000 ether); - - vm.label( - address(suppliers[i]), - string(abi.encodePacked("VaultSupplier", Strings.toString(i + 1))) - ); - } - - supplier1 = suppliers[0]; - supplier2 = suppliers[1]; - supplier3 = suppliers[2]; - - vaultSupplier1 = VaultUser(payable(suppliers[0])); - vaultSupplier2 = VaultUser(payable(suppliers[1])); - vaultSupplier3 = VaultUser(payable(suppliers[2])); - } - - function setVaultContractsLabels() internal { - vm.label(address(wrappedNativeTokenSupplyVault), "SupplyVault (WNATIVE)"); - vm.label(address(usdcSupplyVault), "SupplyVault (USDC)"); - vm.label(address(daiSupplyVault), "SupplyVault (DAI)"); - } -}