diff --git a/.github/actions/ci-foundry/action.yml b/.github/actions/ci-foundry/action.yml index 747722f63..dadb951b3 100644 --- a/.github/actions/ci-foundry/action.yml +++ b/.github/actions/ci-foundry/action.yml @@ -77,7 +77,7 @@ runs: - name: Check coverage threshold if: ${{ inputs.codecovToken != '' }} - run: npx lcov-total lcov.info --gte=90.3 + run: npx lcov-total lcov.info --gte=90 shell: bash - name: Upload coverage to Codecov diff --git a/.github/workflows/ci-docs-autogen.yml b/.github/workflows/ci-docs-autogen.yml new file mode 100644 index 000000000..8eea02ba5 --- /dev/null +++ b/.github/workflows/ci-docs-autogen.yml @@ -0,0 +1,74 @@ +name: Autogenerated documentation + +on: + push: + branches: + - main + +jobs: + autogen-docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: actions/setup-node@v3 + with: + node-version: 16 + cache: yarn + + - name: Install Foundry + uses: onbjerg/foundry-toolchain@v1 + with: + version: nightly + + - name: Node dependencies cache + uses: actions/cache@v3 + with: + path: "node_modules" + key: yarn-${{ hashFiles('yarn.lock') }} + + - name: Install dependencies + run: yarn install --frozen-lockfile + shell: bash + + - name: Generate docs + run: forge doc --build + shell: bash + + - name: Upload docs + uses: actions/upload-artifact@v2 + with: + name: docs-foundry + path: docs/book + + upload-to-s3: + runs-on: ubuntu-latest + needs: autogen-docs + environment: + name: docs + url: https://developers.morpho.xyz + steps: + - name: Download docs + uses: actions/download-artifact@v2 + with: + name: docs-foundry + path: docs + + - name: Setup AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Upload docs to S3 + run: aws s3 sync ./docs s3://$BUCKET --delete --acl public-read + env: + BUCKET: ${{ secrets.AWS_S3_BUCKET }} + + - name: Invalidate CloudFront cache + run: aws cloudfront create-invalidation --distribution-id $DISTRIBUTION --paths "/*" + env: + DISTRIBUTION: ${{ secrets.AWS_CLOUDFRONT_DISTRIBUTION_ID }} diff --git a/.gitignore b/.gitignore index 32e91d606..87882328d 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,6 @@ yarn-error.log* *.ansi *.html lcov.* + +# docs +/docs diff --git a/README.md b/README.md index ac0601b3c..e2d59b981 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,9 @@ It also interacts with `RewardsManager`, which manages the underlying pool's rew ## Documentation - [White Paper](https://whitepaper.morpho.xyz) +- [Yellow Paper](https://yellowpaper.morpho.xyz/) - [Morpho Documentation](https://docs.morpho.xyz) -- Yellow Paper (coming soon) +- [Morpho Developers Documentation](https://developers.morpho.xyz) --- @@ -64,24 +65,25 @@ You can also send an email to [security@morpho.xyz](mailto:security@morpho.xyz) ### Morpho-Compound Ethereum - Morpho Proxy: [0x8888882f8f843896699869179fb6e4f7e3b58888](https://etherscan.io/address/0x8888882f8f843896699869179fb6e4f7e3b58888) -- Morpho Implementation: [0xbbb011b923f382543a94e67e1d0c88d9763356e5](https://etherscan.io/address/0xbbb011b923f382543a94e67e1d0c88d9763356e5) -- PositionsManager: [0x309a4505d79fcc59affaba205fdcb880d400ef39](https://etherscan.io/address/0x309a4505d79fcc59affaba205fdcb880d400ef39) -- InterestRatesManager: [0x3e483225666871d192b686c42e6834e217a9871c](https://etherscan.io/address/0x3e483225666871d192b686c42e6834e217a9871c) +- Morpho Implementation: [0xe3d7a242614174ccf9f96bd479c42795d666fc81](https://etherscan.io/address/0xe3d7a242614174ccf9f96bd479c42795d666fc81) +- PositionsManager: [0x79a1b5888009bB4887E00EA27CF52551aAf2A004](https://etherscan.io/address/0x79a1b5888009bB4887E00EA27CF52551aAf2A004) +- InterestRatesManager: [0xD9B7209eD2936b5c06990A8356D155c3665d43Ab](https://etherscan.io/address/0xD9B7209eD2936b5c06990A8356D155c3665d43Ab) - RewardsManager Proxy: [0x78681e63b6f3ad81ecd64aecc404d765b529c80d](https://etherscan.io/address/0x78681e63b6f3ad81ecd64aecc404d765b529c80d) -- RewardsManager Implementation: [0xf47963cc317ebe4b8ebcf30f6e144b7e7e5571b7](https://etherscan.io/address/0xf47963cc317ebe4b8ebcf30f6e144b7e7e5571b7) +- RewardsManager Implementation: [0x581c3816589ad0de7f9c76bc242c97fe96c9f100](https://etherscan.io/address/0x581c3816589ad0de7f9c76bc242c97fe96c9f100) - Lens Proxy: [0x930f1b46e1d081ec1524efd95752be3ece51ef67](https://etherscan.io/address/0x930f1b46e1d081ec1524efd95752be3ece51ef67) -- Lens Implementation: [0xe54dde06d245fadcba50dd786f717d44c341f81b](https://etherscan.io/address/0xe54dde06d245fadcba50dd786f717d44c341f81b) +- Lens Implementation: [0x834632a7c70ddd7badd3d21ba9d885a9da66b0de](https://etherscan.io/address/0x834632a7c70ddd7badd3d21ba9d885a9da66b0de) +- Lens Extension: [0xc5c3bB32c70d1d547023346BD1E32a6c5BC7FD1e](https://etherscan.io/address/0xc5c3bB32c70d1d547023346BD1E32a6c5BC7FD1e) - CompRewardsLens: [0x9e977f745d5ae26c6d47ac5417ee112312873ba7](https://etherscan.io/address/0x9e977f745d5ae26c6d47ac5417ee112312873ba7) ### Morpho-Aave-V2 Ethereum - Morpho Proxy: [0x777777c9898d384f785ee44acfe945efdff5f3e0](https://etherscan.io/address/0x777777c9898d384f785ee44acfe945efdff5f3e0) -- Morpho Implementation: [0x206a1609a484db5129ca118f138e5a8abb9c61e0](https://etherscan.io/address/0x206a1609a484db5129ca118f138e5a8abb9c61e0) -- EntryPositionsManager: [0x2a46cad23484c15f60663ece368395b3a249632a](https://etherscan.io/address/0x2a46cad23484c15f60663ece368395b3a249632a) -- ExitPositionsManager: [0xfa652aa169c23277a941cf2d23d2d707fda60ed9](https://etherscan.io/address/0xfa652aa169c23277a941cf2d23d2d707fda60ed9) -- InterestRatesManager: [0x4f54235e17eb8dcdfc941a77e7734a537f7bed86](https://etherscan.io/address/0x4f54235e17eb8dcdfc941a77e7734a537f7bed86) +- Morpho Implementation: [0xFBc7693f114273739C74a3FF028C13769C49F2d0](https://etherscan.io/address/0xFBc7693f114273739C74a3FF028C13769C49F2d0) +- EntryPositionsManager: [0x029Ee1AF5BafC481f9E8FBeD5164253f1266B968](https://etherscan.io/address/0x029Ee1AF5BafC481f9E8FBeD5164253f1266B968) +- ExitPositionsManager: [0xfd9b1Ad429667D27cE666EA800f828B931A974D2](https://etherscan.io/address/0xfd9b1Ad429667D27cE666EA800f828B931A974D2) +- InterestRatesManager: [0x22a4ecf5195c87605ae6bad413ae79d5c4170ff1](https://etherscan.io/address/0x22a4ecf5195c87605ae6bad413ae79d5c4170ff1) - Lens Proxy: [0x507fa343d0a90786d86c7cd885f5c49263a91ff4](https://etherscan.io/address/0x507fa343d0a90786d86c7cd885f5c49263a91ff4) -- Lens Implementation: [0xce23e457fb01454b8c59e31f4f72e4bd3d29b5eb](https://etherscan.io/address/0xce23e457fb01454b8c59e31f4f72e4bd3d29b5eb) +- Lens Implementation: [0x4bf26012b64312b462bf70f2e42d1be8881d0f84](https://etherscan.io/address/0x4bf26012b64312b462bf70f2e42d1be8881d0f84) ### Common Ethereum diff --git a/audits/Spearbit_MorphoV1.pdf b/audits/Spearbit_MorphoV1.pdf new file mode 100644 index 000000000..294e3809e Binary files /dev/null and b/audits/Spearbit_MorphoV1.pdf differ diff --git a/config/aave-v2/Config.sol b/config/aave-v2/Config.sol index 57cdf978e..b8d39c9ed 100644 --- a/config/aave-v2/Config.sol +++ b/config/aave-v2/Config.sol @@ -3,14 +3,11 @@ pragma solidity >=0.8.0; import {ILendingPool} from "src/aave-v2/interfaces/aave/ILendingPool.sol"; import {IPriceOracleGetter} from "src/aave-v2/interfaces/aave/IPriceOracleGetter.sol"; -import {IAaveIncentivesController} from "src/aave-v2/interfaces/aave/IAaveIncentivesController.sol"; import {ILendingPoolAddressesProvider} from "src/aave-v2/interfaces/aave/ILendingPoolAddressesProvider.sol"; -import {IRewardsManager} from "src/aave-v2/interfaces/IRewardsManager.sol"; -import {IIncentivesVault} from "src/aave-v2/interfaces/IIncentivesVault.sol"; import {IEntryPositionsManager} from "src/aave-v2/interfaces/IEntryPositionsManager.sol"; import {IExitPositionsManager} from "src/aave-v2/interfaces/IExitPositionsManager.sol"; import {IInterestRatesManager} from "src/aave-v2/interfaces/IInterestRatesManager.sol"; -import {ILendingPoolConfigurator} from "../../../test/aave-v2/helpers/ILendingPoolConfigurator.sol"; +import {ILendingPoolConfigurator} from "test/aave-v2/helpers/ILendingPoolConfigurator.sol"; import {IMorpho} from "src/aave-v2/interfaces/IMorpho.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; @@ -44,29 +41,21 @@ contract Config is BaseConfig { ILendingPoolAddressesProvider(0xB53C1a33016B2DC2fF3653530bfF1848a515c8c5); ILendingPoolConfigurator public lendingPoolConfigurator = ILendingPoolConfigurator(0x311Bb771e4F8952E6Da169b425E7e92d6Ac45756); - IAaveIncentivesController public aaveIncentivesController = - IAaveIncentivesController(0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5); IPriceOracleGetter public oracle = IPriceOracleGetter(poolAddressesProvider.getPriceOracle()); ILendingPool public pool = ILendingPool(poolAddressesProvider.getLendingPool()); - address public REWARD_TOKEN = aaveIncentivesController.REWARD_TOKEN(); - ProxyAdmin public proxyAdmin = ProxyAdmin(0x99917ca0426fbC677e84f873Fb0b726Bb4799cD8); TransparentUpgradeableProxy public lensProxy = TransparentUpgradeableProxy(payable(0x507fA343d0A90786d86C7cd885f5C49263A91FF4)); TransparentUpgradeableProxy public morphoProxy = TransparentUpgradeableProxy(payable(0x777777c9898D384F785Ee44Acfe945efDFf5f3E0)); - TransparentUpgradeableProxy public rewardsManagerProxy; Lens public lensImplV1; Morpho public morphoImplV1; - IRewardsManager public rewardsManagerImplV1; Lens public lens; Morpho public morpho; - IRewardsManager public rewardsManager; - IIncentivesVault public incentivesVault; IEntryPositionsManager public entryPositionsManager; IExitPositionsManager public exitPositionsManager; IInterestRatesManager public interestRatesManager; diff --git a/config/compound/Config.sol b/config/compound/Config.sol index 4c591af9d..2cdbf65d4 100644 --- a/config/compound/Config.sol +++ b/config/compound/Config.sol @@ -2,7 +2,6 @@ pragma solidity >=0.8.0; import "src/compound/interfaces/compound/ICompound.sol"; -import {IIncentivesVault} from "src/compound/interfaces/IIncentivesVault.sol"; import {IPositionsManager} from "src/compound/interfaces/IPositionsManager.sol"; import {IInterestRatesManager} from "src/compound/interfaces/IInterestRatesManager.sol"; import {IMorpho} from "src/compound/interfaces/IMorpho.sol"; @@ -11,6 +10,7 @@ import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transpa import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import {RewardsManager} from "src/compound/RewardsManager.sol"; +import {LensExtension} from "src/compound/lens/LensExtension.sol"; import {Lens} from "src/compound/lens/Lens.sol"; import {Morpho} from "src/compound/Morpho.sol"; import {BaseConfig} from "../BaseConfig.sol"; @@ -50,9 +50,10 @@ contract Config is BaseConfig { RewardsManager public rewardsManagerImplV1; Lens public lens; + LensExtension public lensExtension; + Morpho public morpho; RewardsManager public rewardsManager; - IIncentivesVault public incentivesVault; IPositionsManager public positionsManager; IInterestRatesManager public interestRatesManager; } diff --git a/lib/morpho-data-structures b/lib/morpho-data-structures index f19cd8176..9f0ec723e 160000 --- a/lib/morpho-data-structures +++ b/lib/morpho-data-structures @@ -1 +1 @@ -Subproject commit f19cd81768febf42bf6325b5fa288ed36d39c2ea +Subproject commit 9f0ec723e197c9a917fef0e8b6a5b793119dfc91 diff --git a/scripts/aave-v2/Deploy.s.sol b/scripts/aave-v2/Deploy.s.sol index 5451d0cea..08235ded0 100644 --- a/scripts/aave-v2/Deploy.s.sol +++ b/scripts/aave-v2/Deploy.s.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity 0.8.13; -import "src/aave-v2/interfaces/IIncentivesVault.sol"; import "src/aave-v2/interfaces/IInterestRatesManager.sol"; import "src/aave-v2/interfaces/IExitPositionsManager.sol"; import "src/aave-v2/interfaces/IEntryPositionsManager.sol"; @@ -27,7 +26,6 @@ contract Deploy is Script, Config { IEntryPositionsManager public entryPositionsManager; IExitPositionsManager public exitPositionsManager; IInterestRatesManager public interestRatesManager; - IIncentivesVault public incentivesVault; function run() external { vm.startBroadcast(); diff --git a/scripts/compound/Deploy.s.sol b/scripts/compound/Deploy.s.sol index 2a3ae8f0b..42397a8bb 100644 --- a/scripts/compound/Deploy.s.sol +++ b/scripts/compound/Deploy.s.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.13; import "src/compound/interfaces/IRewardsManager.sol"; -import "src/compound/interfaces/IIncentivesVault.sol"; import "src/compound/interfaces/IInterestRatesManager.sol"; import "src/compound/interfaces/IPositionsManager.sol"; import "src/compound/interfaces/compound/ICompound.sol"; @@ -10,7 +9,6 @@ import "src/compound/interfaces/compound/ICompound.sol"; import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import {IncentivesVault} from "src/compound/IncentivesVault.sol"; import {RewardsManager} from "src/compound/RewardsManager.sol"; import {InterestRatesManager} from "src/compound/InterestRatesManager.sol"; import {PositionsManager} from "src/compound/PositionsManager.sol"; @@ -27,7 +25,6 @@ contract Deploy is Script, Config { Morpho public morpho; IPositionsManager public positionsManager; IInterestRatesManager public interestRatesManager; - IIncentivesVault public incentivesVault; RewardsManager public rewardsManager; function run() external { diff --git a/snapshots/.storage-layout-aave-v2 b/snapshots/.storage-layout-aave-v2 index f0c43e4ff..e09a189e9 100644 --- a/snapshots/.storage-layout-aave-v2 +++ b/snapshots/.storage-layout-aave-v2 @@ -30,13 +30,13 @@ | deltas | mapping(address => struct Types.Delta) | 166 | 0 | 32 | src/aave-v2/Morpho.sol:Morpho | | borrowMask | mapping(address => bytes32) | 167 | 0 | 32 | src/aave-v2/Morpho.sol:Morpho | | addressesProvider | contract ILendingPoolAddressesProvider | 168 | 0 | 20 | src/aave-v2/Morpho.sol:Morpho | -| aaveIncentivesController | contract IAaveIncentivesController | 169 | 0 | 20 | src/aave-v2/Morpho.sol:Morpho | +| aaveIncentivesController | address | 169 | 0 | 20 | src/aave-v2/Morpho.sol:Morpho | | pool | contract ILendingPool | 170 | 0 | 20 | src/aave-v2/Morpho.sol:Morpho | | entryPositionsManager | contract IEntryPositionsManager | 171 | 0 | 20 | src/aave-v2/Morpho.sol:Morpho | | exitPositionsManager | contract IExitPositionsManager | 172 | 0 | 20 | src/aave-v2/Morpho.sol:Morpho | | interestRatesManager | contract IInterestRatesManager | 173 | 0 | 20 | src/aave-v2/Morpho.sol:Morpho | -| incentivesVault | contract IIncentivesVault | 174 | 0 | 20 | src/aave-v2/Morpho.sol:Morpho | -| rewardsManager | contract IRewardsManager | 175 | 0 | 20 | src/aave-v2/Morpho.sol:Morpho | +| incentivesVault | address | 174 | 0 | 20 | src/aave-v2/Morpho.sol:Morpho | +| rewardsManager | address | 175 | 0 | 20 | src/aave-v2/Morpho.sol:Morpho | | treasuryVault | address | 176 | 0 | 20 | src/aave-v2/Morpho.sol:Morpho | | marketPauseStatus | mapping(address => struct Types.MarketPauseStatus) | 177 | 0 | 32 | src/aave-v2/Morpho.sol:Morpho | diff --git a/snapshots/.storage-layout-compound b/snapshots/.storage-layout-compound index b0351c45f..6b720f448 100644 --- a/snapshots/.storage-layout-compound +++ b/snapshots/.storage-layout-compound @@ -32,7 +32,7 @@ | marketStatus | mapping(address => struct Types.MarketStatus) | 168 | 0 | 32 | src/compound/Morpho.sol:Morpho | | deltas | mapping(address => struct Types.Delta) | 169 | 0 | 32 | src/compound/Morpho.sol:Morpho | | positionsManager | contract IPositionsManager | 170 | 0 | 20 | src/compound/Morpho.sol:Morpho | -| incentivesVault | contract IIncentivesVault | 171 | 0 | 20 | src/compound/Morpho.sol:Morpho | +| incentivesVault | address | 171 | 0 | 20 | src/compound/Morpho.sol:Morpho | | rewardsManager | contract IRewardsManager | 172 | 0 | 20 | src/compound/Morpho.sol:Morpho | | interestRatesManager | contract IInterestRatesManager | 173 | 0 | 20 | src/compound/Morpho.sol:Morpho | | comptroller | contract IComptroller | 174 | 0 | 20 | src/compound/Morpho.sol:Morpho | diff --git a/src/aave-v2/EntryPositionsManager.sol b/src/aave-v2/EntryPositionsManager.sol index 0d0666c24..c2c85fcc7 100644 --- a/src/aave-v2/EntryPositionsManager.sol +++ b/src/aave-v2/EntryPositionsManager.sol @@ -72,13 +72,6 @@ contract EntryPositionsManager is IEntryPositionsManager, PositionsManagerUtils uint256 toRepay; } - // Struct to avoid stack too deep. - struct BorrowAllowedVars { - uint256 i; - bytes32 userMarkets; - uint256 numberOfMarketsCreated; - } - /// LOGIC /// /// @dev Implements supply logic. @@ -97,13 +90,14 @@ contract EntryPositionsManager is IEntryPositionsManager, PositionsManagerUtils if (_onBehalf == address(0)) revert AddressIsZero(); if (_amount == 0) revert AmountIsZero(); Types.Market memory market = market[_poolToken]; + ERC20 underlyingToken = ERC20(market.underlyingToken); + if (!market.isCreated) revert MarketNotCreated(); if (marketPauseStatus[_poolToken].isSupplyPaused) revert SupplyIsPaused(); _updateIndexes(_poolToken); _setSupplying(_onBehalf, borrowMask[_poolToken], true); - ERC20 underlyingToken = ERC20(market.underlyingToken); underlyingToken.safeTransferFrom(_from, address(this), _amount); Types.Delta storage delta = deltas[_poolToken]; @@ -290,6 +284,6 @@ contract EntryPositionsManager is IEntryPositionsManager, PositionsManagerUtils uint256 _borrowedAmount ) internal returns (bool) { Types.LiquidityData memory values = _liquidityData(_user, _poolToken, 0, _borrowedAmount); - return values.debt <= values.maxDebt; + return values.debtEth <= values.borrowableEth; } } diff --git a/src/aave-v2/ExitPositionsManager.sol b/src/aave-v2/ExitPositionsManager.sol index afc700c3c..fa5ff436b 100644 --- a/src/aave-v2/ExitPositionsManager.sol +++ b/src/aave-v2/ExitPositionsManager.sol @@ -136,13 +136,6 @@ contract ExitPositionsManager is IExitPositionsManager, PositionsManagerUtils { bool liquidationAllowed; // Whether the liquidation is allowed or not. } - // Struct to avoid stack too deep. - struct HealthFactorVars { - uint256 i; - bytes32 userMarkets; - uint256 numberOfMarketsCreated; - } - /// LOGIC /// /// @dev Implements withdraw logic with security checks. @@ -207,6 +200,7 @@ contract ExitPositionsManager is IExitPositionsManager, PositionsManagerUtils { address _borrower, uint256 _amount ) external { + if (_amount == 0) revert AmountIsZero(); Types.Market memory collateralMarket = market[_poolTokenCollateral]; if (!collateralMarket.isCreated) revert MarketNotCreated(); if (marketPauseStatus[_poolTokenCollateral].isLiquidateCollateralPaused) @@ -666,16 +660,12 @@ contract ExitPositionsManager is IExitPositionsManager, PositionsManagerUtils { address _poolToken, uint256 _withdrawnAmount ) internal returns (uint256) { - HealthFactorVars memory vars; - vars.userMarkets = userMarkets[_user]; - // If the user is not borrowing any asset, return an infinite health factor. - if (!_isBorrowingAny(vars.userMarkets)) return type(uint256).max; + if (!_isBorrowingAny(userMarkets[_user])) return type(uint256).max; Types.LiquidityData memory values = _liquidityData(_user, _poolToken, _withdrawnAmount, 0); - return - values.debt > 0 ? values.liquidationThreshold.wadDiv(values.debt) : type(uint256).max; + return values.debtEth > 0 ? values.maxDebtEth.wadDiv(values.debtEth) : type(uint256).max; } /// @dev Checks whether the user can withdraw or not. diff --git a/src/aave-v2/IncentivesVault.sol b/src/aave-v2/IncentivesVault.sol deleted file mode 100644 index d5961636e..000000000 --- a/src/aave-v2/IncentivesVault.sol +++ /dev/null @@ -1,150 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.13; - -import "./interfaces/IIncentivesVault.sol"; -import "./interfaces/IOracle.sol"; -import "./interfaces/IMorpho.sol"; - -import "@morpho-dao/morpho-utils/math/PercentageMath.sol"; -import "@rari-capital/solmate/src/utils/SafeTransferLib.sol"; - -import "@openzeppelin/contracts/access/Ownable.sol"; - -/// @title IncentivesVault. -/// @author Morpho Labs. -/// @custom:contact security@morpho.xyz -/// @notice Contract handling Morpho incentives. -contract IncentivesVault is IIncentivesVault, Ownable { - using SafeTransferLib for ERC20; - using PercentageMath for uint256; - - /// STORAGE /// - - uint256 public constant MAX_BASIS_POINTS = 100_00; - - IMorpho public immutable morpho; // The address of the main Morpho contract. - ERC20 public immutable rewardToken; // The reward token. - ERC20 public immutable morphoToken; // The MORPHO token. - - IOracle public oracle; // The oracle used to get the price of MORPHO tokens against token reward tokens. - address public incentivesTreasuryVault; // The address of the incentives treasury vault. - uint256 public bonus; // The bonus percentage of MORPHO tokens to give to the user. - bool public isPaused; // Whether the trade of token rewards for MORPHO rewards is paused or not. - - /// EVENTS /// - - /// @notice Emitted when the oracle is set. - /// @param newOracle The new oracle set. - event OracleSet(address newOracle); - - /// @notice Emitted when the incentives treasury vault is set. - /// @param newIncentivesTreasuryVault The address of the incentives treasury vault. - event IncentivesTreasuryVaultSet(address newIncentivesTreasuryVault); - - /// @notice Emitted when the reward bonus is set. - /// @param newBonus The new bonus set. - event BonusSet(uint256 newBonus); - - /// @notice Emitted when the pause status is changed. - /// @param newStatus The new newStatus set. - event PauseStatusSet(bool newStatus); - - /// @notice Emitted when tokens are transferred to the DAO. - /// @param token The address of the token transferred. - /// @param amount The amount of token transferred to the DAO. - event TokensTransferred(address indexed token, uint256 amount); - - /// @notice Emitted when reward tokens are traded for MORPHO tokens. - /// @param receiver The address of the receiver. - /// @param rewardAmount The amount of reward token traded. - /// @param morphoAmount The amount of MORPHO transferred. - event RewardTokensTraded(address indexed receiver, uint256 rewardAmount, uint256 morphoAmount); - - /// ERRORS /// - - /// @notice Thrown when an other address than Morpho triggers the function. - error OnlyMorpho(); - - /// @notice Thrown when the vault is paused. - error VaultIsPaused(); - - /// @notice Thrown when the input is above the max basis points value (100%). - error ExceedsMaxBasisPoints(); - - /// CONSTRUCTOR /// - - /// @notice Constructs the IncentivesVault contract. - /// @param _morpho The main Morpho contract. - /// @param _morphoToken The MORPHO token. - /// @param _rewardToken The reward token. - /// @param _incentivesTreasuryVault The address of the incentives treasury vault. - /// @param _oracle The oracle. - constructor( - IMorpho _morpho, - ERC20 _morphoToken, - ERC20 _rewardToken, - address _incentivesTreasuryVault, - IOracle _oracle - ) { - morpho = _morpho; - morphoToken = _morphoToken; - rewardToken = _rewardToken; - incentivesTreasuryVault = _incentivesTreasuryVault; - oracle = _oracle; - } - - /// EXTERNAL /// - - /// @notice Sets the oracle. - /// @param _newOracle The address of the new oracle. - function setOracle(IOracle _newOracle) external onlyOwner { - oracle = _newOracle; - emit OracleSet(address(_newOracle)); - } - - /// @notice Sets the incentives treasury vault. - /// @param _newIncentivesTreasuryVault The address of the incentives treasury vault. - function setIncentivesTreasuryVault(address _newIncentivesTreasuryVault) external onlyOwner { - incentivesTreasuryVault = _newIncentivesTreasuryVault; - emit IncentivesTreasuryVaultSet(_newIncentivesTreasuryVault); - } - - /// @notice Sets the reward bonus. - /// @param _newBonus The new reward bonus. - function setBonus(uint256 _newBonus) external onlyOwner { - if (_newBonus > MAX_BASIS_POINTS) revert ExceedsMaxBasisPoints(); - - bonus = _newBonus; - emit BonusSet(_newBonus); - } - - /// @notice Sets the pause status. - /// @param _newStatus The new pause status. - function setPauseStatus(bool _newStatus) external onlyOwner { - isPaused = _newStatus; - emit PauseStatusSet(_newStatus); - } - - /// @notice Transfers the specified token to the DAO. - /// @param _token The address of the token to transfer. - /// @param _amount The amount of token to transfer to the DAO. - function transferTokensToDao(address _token, uint256 _amount) external onlyOwner { - ERC20(_token).safeTransfer(incentivesTreasuryVault, _amount); - emit TokensTransferred(_token, _amount); - } - - /// @notice Trades reward tokens for MORPHO tokens and sends them to the receiver. - /// @dev The amount of rewards to trade for MORPHO tokens is supposed to have been transferred to this contract before calling the function. - /// @param _receiver The address of the receiver. - /// @param _amount The amount claimed, to trade for MORPHO tokens. - function tradeRewardTokensForMorphoTokens(address _receiver, uint256 _amount) external { - if (msg.sender != address(morpho)) revert OnlyMorpho(); - if (isPaused) revert VaultIsPaused(); - - // Add a bonus on MORPHO rewards. - uint256 amountOut = oracle.consult(_amount).percentAdd(bonus); - morphoToken.safeTransfer(_receiver, amountOut); - - emit RewardTokensTraded(_receiver, _amount, amountOut); - } -} diff --git a/src/aave-v2/InterestRatesManager.sol b/src/aave-v2/InterestRatesManager.sol index 659e4d866..48d2c1d4b 100644 --- a/src/aave-v2/InterestRatesManager.sol +++ b/src/aave-v2/InterestRatesManager.sol @@ -4,9 +4,8 @@ pragma solidity 0.8.13; import "./interfaces/aave/IAToken.sol"; import "./interfaces/lido/ILido.sol"; -import "@morpho-dao/morpho-utils/math/PercentageMath.sol"; +import "./libraries/InterestRatesModel.sol"; import "@morpho-dao/morpho-utils/math/WadRayMath.sol"; -import "@morpho-dao/morpho-utils/math/Math.sol"; import "./MorphoStorage.sol"; @@ -16,7 +15,6 @@ import "./MorphoStorage.sol"; /// @notice Smart contract handling the computation of indexes used for peer-to-peer interactions. /// @dev This contract inherits from MorphoStorage so that Morpho can delegate calls to this contract. contract InterestRatesManager is IInterestRatesManager, MorphoStorage { - using PercentageMath for uint256; using WadRayMath for uint256; /// STRUCTS /// @@ -26,8 +24,7 @@ contract InterestRatesManager is IInterestRatesManager, MorphoStorage { uint256 lastP2PBorrowIndex; // The peer-to-peer borrow index at last update. uint256 poolSupplyIndex; // The current pool supply index. uint256 poolBorrowIndex; // The current pool borrow index. - uint256 lastPoolSupplyIndex; // The pool supply index at last update. - uint256 lastPoolBorrowIndex; // The pool borrow index at last update. + Types.PoolIndexes lastPoolIndexes; // The pool indexes at last update. uint256 reserveFactor; // The reserve factor percentage (10 000 = 100%). uint256 p2pIndexCursor; // The peer-to-peer index cursor (10 000 = 100%). Types.Delta delta; // The deltas and peer-to-peer amounts. @@ -59,34 +56,22 @@ contract InterestRatesManager is IInterestRatesManager, MorphoStorage { if (block.timestamp == marketPoolIndexes.lastUpdateTimestamp) return; Types.Market storage market = market[_poolToken]; - - address underlyingToken = market.underlyingToken; - uint256 newPoolSupplyIndex = pool.getReserveNormalizedIncome(underlyingToken); - uint256 newPoolBorrowIndex = pool.getReserveNormalizedVariableDebt(underlyingToken); - - if (underlyingToken == ST_ETH) { - uint256 stEthRebaseIndex = ILido(ST_ETH).getPooledEthByShares(WadRayMath.RAY); - newPoolSupplyIndex = newPoolSupplyIndex.rayMul(stEthRebaseIndex).rayDiv( - ST_ETH_BASE_REBASE_INDEX - ); - newPoolBorrowIndex = newPoolBorrowIndex.rayMul(stEthRebaseIndex).rayDiv( - ST_ETH_BASE_REBASE_INDEX - ); - } - - Params memory params = Params( - p2pSupplyIndex[_poolToken], - p2pBorrowIndex[_poolToken], - newPoolSupplyIndex, - newPoolBorrowIndex, - marketPoolIndexes.poolSupplyIndex, - marketPoolIndexes.poolBorrowIndex, - market.reserveFactor, - market.p2pIndexCursor, - deltas[_poolToken] + (uint256 newPoolSupplyIndex, uint256 newPoolBorrowIndex) = _getPoolIndexes( + market.underlyingToken ); - (uint256 newP2PSupplyIndex, uint256 newP2PBorrowIndex) = _computeP2PIndexes(params); + (uint256 newP2PSupplyIndex, uint256 newP2PBorrowIndex) = _computeP2PIndexes( + Params({ + lastP2PSupplyIndex: p2pSupplyIndex[_poolToken], + lastP2PBorrowIndex: p2pBorrowIndex[_poolToken], + poolSupplyIndex: newPoolSupplyIndex, + poolBorrowIndex: newPoolBorrowIndex, + lastPoolIndexes: marketPoolIndexes, + reserveFactor: market.reserveFactor, + p2pIndexCursor: market.p2pIndexCursor, + delta: deltas[_poolToken] + }) + ); p2pSupplyIndex[_poolToken] = newP2PSupplyIndex; p2pBorrowIndex[_poolToken] = newP2PBorrowIndex; @@ -106,6 +91,26 @@ contract InterestRatesManager is IInterestRatesManager, MorphoStorage { /// INTERNAL /// + /// @notice Returns the current pool indexes. + /// @param _underlyingToken The address of the underlying token. + /// @return poolSupplyIndex The pool supply index. + /// @return poolBorrowIndex The pool borrow index. + function _getPoolIndexes(address _underlyingToken) + internal + view + returns (uint256 poolSupplyIndex, uint256 poolBorrowIndex) + { + poolSupplyIndex = pool.getReserveNormalizedIncome(_underlyingToken); + poolBorrowIndex = pool.getReserveNormalizedVariableDebt(_underlyingToken); + + if (_underlyingToken == ST_ETH) { + uint256 rebaseIndex = ILido(ST_ETH).getPooledEthByShares(WadRayMath.RAY); + + poolSupplyIndex = poolSupplyIndex.rayMul(rebaseIndex).rayDiv(ST_ETH_BASE_REBASE_INDEX); + poolBorrowIndex = poolBorrowIndex.rayMul(rebaseIndex).rayDiv(ST_ETH_BASE_REBASE_INDEX); + } + } + /// @notice Computes and returns new peer-to-peer indexes. /// @param _params Computation parameters. /// @return newP2PSupplyIndex The updated p2pSupplyIndex. @@ -115,74 +120,34 @@ contract InterestRatesManager is IInterestRatesManager, MorphoStorage { pure returns (uint256 newP2PSupplyIndex, uint256 newP2PBorrowIndex) { - // Compute pool growth factors + InterestRatesModel.GrowthFactors memory growthFactors = InterestRatesModel + .computeGrowthFactors( + _params.poolSupplyIndex, + _params.poolBorrowIndex, + _params.lastPoolIndexes, + _params.p2pIndexCursor, + _params.reserveFactor + ); - uint256 poolSupplyGrowthFactor = _params.poolSupplyIndex.rayDiv( - _params.lastPoolSupplyIndex + newP2PSupplyIndex = InterestRatesModel.computeP2PIndex( + InterestRatesModel.P2PIndexComputeParams({ + poolGrowthFactor: growthFactors.poolSupplyGrowthFactor, + p2pGrowthFactor: growthFactors.p2pSupplyGrowthFactor, + lastPoolIndex: _params.lastPoolIndexes.poolSupplyIndex, + lastP2PIndex: _params.lastP2PSupplyIndex, + p2pDelta: _params.delta.p2pSupplyDelta, + p2pAmount: _params.delta.p2pSupplyAmount + }) ); - uint256 poolBorrowGrowthFactor = _params.poolBorrowIndex.rayDiv( - _params.lastPoolBorrowIndex + newP2PBorrowIndex = InterestRatesModel.computeP2PIndex( + InterestRatesModel.P2PIndexComputeParams({ + poolGrowthFactor: growthFactors.poolBorrowGrowthFactor, + p2pGrowthFactor: growthFactors.p2pBorrowGrowthFactor, + lastPoolIndex: _params.lastPoolIndexes.poolBorrowIndex, + lastP2PIndex: _params.lastP2PBorrowIndex, + p2pDelta: _params.delta.p2pBorrowDelta, + p2pAmount: _params.delta.p2pBorrowAmount + }) ); - - // Compute peer-to-peer growth factors. - - uint256 p2pSupplyGrowthFactor; - uint256 p2pBorrowGrowthFactor; - if (poolSupplyGrowthFactor <= poolBorrowGrowthFactor) { - uint256 p2pGrowthFactor = PercentageMath.weightedAvg( - poolSupplyGrowthFactor, - poolBorrowGrowthFactor, - _params.p2pIndexCursor - ); - - p2pSupplyGrowthFactor = - p2pGrowthFactor - - (p2pGrowthFactor - poolSupplyGrowthFactor).percentMul(_params.reserveFactor); - p2pBorrowGrowthFactor = - p2pGrowthFactor + - (poolBorrowGrowthFactor - p2pGrowthFactor).percentMul(_params.reserveFactor); - } else { - // The case poolSupplyGrowthFactor > poolBorrowGrowthFactor happens because someone has done a flashloan on Aave, or because the interests - // generated by the stable rate borrowing are high (making the supply rate higher than the variable borrow rate). In this case the peer-to-peer - // growth factors are set to the pool borrow growth factor. - p2pSupplyGrowthFactor = poolBorrowGrowthFactor; - p2pBorrowGrowthFactor = poolBorrowGrowthFactor; - } - - // Compute new peer-to-peer supply index. - - if (_params.delta.p2pSupplyAmount == 0 || _params.delta.p2pSupplyDelta == 0) { - newP2PSupplyIndex = _params.lastP2PSupplyIndex.rayMul(p2pSupplyGrowthFactor); - } else { - uint256 shareOfTheDelta = Math.min( - (_params.delta.p2pSupplyDelta.rayMul(_params.lastPoolSupplyIndex)).rayDiv( - _params.delta.p2pSupplyAmount.rayMul(_params.lastP2PSupplyIndex) - ), // Using ray division of an amount in underlying decimals by an amount in underlying decimals yields a value in ray. - WadRayMath.RAY // To avoid shareOfTheDelta > 1 with rounding errors. - ); // In ray. - - newP2PSupplyIndex = _params.lastP2PSupplyIndex.rayMul( - (WadRayMath.RAY - shareOfTheDelta).rayMul(p2pSupplyGrowthFactor) + - shareOfTheDelta.rayMul(poolSupplyGrowthFactor) - ); - } - - // Compute new peer-to-peer borrow index. - - if (_params.delta.p2pBorrowAmount == 0 || _params.delta.p2pBorrowDelta == 0) { - newP2PBorrowIndex = _params.lastP2PBorrowIndex.rayMul(p2pBorrowGrowthFactor); - } else { - uint256 shareOfTheDelta = Math.min( - (_params.delta.p2pBorrowDelta.rayMul(_params.lastPoolBorrowIndex)).rayDiv( - _params.delta.p2pBorrowAmount.rayMul(_params.lastP2PBorrowIndex) - ), // Using ray division of an amount in underlying decimals by an amount in underlying decimals yields a value in ray. - WadRayMath.RAY // To avoid shareOfTheDelta > 1 with rounding errors. - ); // In ray. - - newP2PBorrowIndex = _params.lastP2PBorrowIndex.rayMul( - (WadRayMath.RAY - shareOfTheDelta).rayMul(p2pBorrowGrowthFactor) + - shareOfTheDelta.rayMul(poolBorrowGrowthFactor) - ); - } } } diff --git a/src/aave-v2/MatchingEngine.sol b/src/aave-v2/MatchingEngine.sol index 168129a1d..70f6f245b 100644 --- a/src/aave-v2/MatchingEngine.sol +++ b/src/aave-v2/MatchingEngine.sol @@ -299,55 +299,45 @@ abstract contract MatchingEngine is MorphoUtils { /// @param _poolToken The address of the market on which to update the suppliers data structure. /// @param _user The address of the user. function _updateSupplierInDS(address _poolToken, address _user) internal { - Types.SupplyBalance storage supplierSupplyBalance = supplyBalanceInOf[_poolToken][_user]; - uint256 onPool = supplierSupplyBalance.onPool; - uint256 inP2P = supplierSupplyBalance.inP2P; + Types.SupplyBalance memory supplyBalance = supplyBalanceInOf[_poolToken][_user]; HeapOrdering.HeapArray storage marketSuppliersOnPool = suppliersOnPool[_poolToken]; HeapOrdering.HeapArray storage marketSuppliersInP2P = suppliersInP2P[_poolToken]; - - uint256 formerValueOnPool = marketSuppliersOnPool.getValueOf(_user); - uint256 formerValueInP2P = marketSuppliersInP2P.getValueOf(_user); - - marketSuppliersOnPool.update(_user, formerValueOnPool, onPool, maxSortedUsers); - marketSuppliersInP2P.update(_user, formerValueInP2P, inP2P, maxSortedUsers); - - if (formerValueOnPool != onPool && address(rewardsManager) != address(0)) - rewardsManager.updateUserAssetAndAccruedRewards( - aaveIncentivesController, - _user, - _poolToken, - formerValueOnPool, - IScaledBalanceToken(_poolToken).scaledTotalSupply() - ); + uint256 maxSortedUsersMem = maxSortedUsers; + + marketSuppliersOnPool.update( + _user, + marketSuppliersOnPool.getValueOf(_user), + supplyBalance.onPool, + maxSortedUsersMem + ); + marketSuppliersInP2P.update( + _user, + marketSuppliersInP2P.getValueOf(_user), + supplyBalance.inP2P, + maxSortedUsersMem + ); } /// @notice Updates the given `_user`'s position in the borrower data structures. /// @param _poolToken The address of the market on which to update the borrowers data structure. /// @param _user The address of the user. function _updateBorrowerInDS(address _poolToken, address _user) internal { - Types.BorrowBalance storage borrowerBorrowBalance = borrowBalanceInOf[_poolToken][_user]; - uint256 onPool = borrowerBorrowBalance.onPool; - uint256 inP2P = borrowerBorrowBalance.inP2P; + Types.BorrowBalance memory borrowBalance = borrowBalanceInOf[_poolToken][_user]; HeapOrdering.HeapArray storage marketBorrowersOnPool = borrowersOnPool[_poolToken]; HeapOrdering.HeapArray storage marketBorrowersInP2P = borrowersInP2P[_poolToken]; - - uint256 formerValueOnPool = marketBorrowersOnPool.getValueOf(_user); - uint256 formerValueInP2P = marketBorrowersInP2P.getValueOf(_user); - - marketBorrowersOnPool.update(_user, formerValueOnPool, onPool, maxSortedUsers); - marketBorrowersInP2P.update(_user, formerValueInP2P, inP2P, maxSortedUsers); - - if (formerValueOnPool != onPool && address(rewardsManager) != address(0)) { - address variableDebtToken = pool - .getReserveData(market[_poolToken].underlyingToken) - .variableDebtTokenAddress; - rewardsManager.updateUserAssetAndAccruedRewards( - aaveIncentivesController, - _user, - variableDebtToken, - formerValueOnPool, - IScaledBalanceToken(variableDebtToken).scaledTotalSupply() - ); - } + uint256 maxSortedUsersMem = maxSortedUsers; + + marketBorrowersOnPool.update( + _user, + marketBorrowersOnPool.getValueOf(_user), + borrowBalance.onPool, + maxSortedUsersMem + ); + marketBorrowersInP2P.update( + _user, + marketBorrowersInP2P.getValueOf(_user), + borrowBalance.inP2P, + maxSortedUsersMem + ); } } diff --git a/src/aave-v2/Morpho.sol b/src/aave-v2/Morpho.sol index 79dd83e99..9de6f4ffb 100644 --- a/src/aave-v2/Morpho.sol +++ b/src/aave-v2/Morpho.sol @@ -12,19 +12,6 @@ contract Morpho is MorphoGovernance { using DelegateCall for address; using WadRayMath for uint256; - /// EVENTS /// - - /// @notice Emitted when a user claims rewards. - /// @param _user The address of the claimer. - /// @param _amountClaimed The amount of reward token claimed. - /// @param _traded Whether or not the pool tokens are traded against Morpho tokens. - event RewardsClaimed(address indexed _user, uint256 _amountClaimed, bool indexed _traded); - - /// ERRORS /// - - /// @notice Thrown when claiming rewards is paused. - error ClaimRewardsPaused(); - /// EXTERNAL /// /// @notice Supplies underlying tokens to a specific market. @@ -145,31 +132,8 @@ contract Morpho is MorphoGovernance { ); } - /// @notice Claims rewards for the given assets. - /// @param _assets The assets to claim rewards from (aToken or variable debt token). - /// @param _tradeForMorphoToken Whether or not to trade reward tokens for MORPHO tokens. - /// @return claimedAmount The amount of rewards claimed (in reward token). - function claimRewards(address[] calldata _assets, bool _tradeForMorphoToken) - external - nonReentrant - returns (uint256 claimedAmount) - { - if (isClaimRewardsPaused) revert ClaimRewardsPaused(); - claimedAmount = rewardsManager.claimRewards(aaveIncentivesController, _assets, msg.sender); - - if (claimedAmount > 0) { - if (_tradeForMorphoToken) { - aaveIncentivesController.claimRewards( - _assets, - claimedAmount, - address(incentivesVault) - ); - incentivesVault.tradeRewardTokensForMorphoTokens(msg.sender, claimedAmount); - } else aaveIncentivesController.claimRewards(_assets, claimedAmount, msg.sender); - - emit RewardsClaimed(msg.sender, claimedAmount, _tradeForMorphoToken); - } - } + /// @notice Deprecated. + function claimRewards(address[] calldata, bool) external returns (uint256) {} /// INTERNAL /// diff --git a/src/aave-v2/MorphoGovernance.sol b/src/aave-v2/MorphoGovernance.sol index 183139f75..55f41ff25 100644 --- a/src/aave-v2/MorphoGovernance.sol +++ b/src/aave-v2/MorphoGovernance.sol @@ -30,10 +30,6 @@ abstract contract MorphoGovernance is MorphoUtils { /// @param _newTreasuryVaultAddress The new address of the `treasuryVault`. event TreasuryVaultSet(address indexed _newTreasuryVaultAddress); - /// @notice Emitted when the address of the `incentivesVault` is set. - /// @param _newIncentivesVaultAddress The new address of the `incentivesVault`. - event IncentivesVaultSet(address indexed _newIncentivesVaultAddress); - /// @notice Emitted when the `entryPositionsManager` is set. /// @param _entryPositionsManager The new address of the `entryPositionsManager`. event EntryPositionsManagerSet(address indexed _entryPositionsManager); @@ -42,18 +38,10 @@ abstract contract MorphoGovernance is MorphoUtils { /// @param _exitPositionsManager The new address of the `exitPositionsManager`. event ExitPositionsManagerSet(address indexed _exitPositionsManager); - /// @notice Emitted when the `rewardsManager` is set. - /// @param _newRewardsManagerAddress The new address of the `rewardsManager`. - event RewardsManagerSet(address indexed _newRewardsManagerAddress); - /// @notice Emitted when the `interestRatesManager` is set. /// @param _interestRatesManager The new address of the `interestRatesManager`. event InterestRatesSet(address indexed _interestRatesManager); - /// @notice Emitted when the address of the `aaveIncentivesController` is set. - /// @param _aaveIncentivesController The new address of the `aaveIncentivesController`. - event AaveIncentivesControllerSet(address indexed _aaveIncentivesController); - /// @notice Emitted when the `reserveFactor` is set. /// @param _poolToken The address of the concerned market. /// @param _newValue The new value of the `reserveFactor`. @@ -109,14 +97,10 @@ abstract contract MorphoGovernance is MorphoUtils { /// @param _isDeprecated The new deprecated status. event IsDeprecatedSet(address indexed _poolToken, bool _isDeprecated); - /// @notice Emitted when claiming rewards is paused or unpaused. - /// @param _isPaused The new pause status. - event ClaimRewardsPauseStatusSet(bool _isPaused); - /// @notice Emitted when a new market is created. /// @param _poolToken The address of the market that has been created. /// @param _reserveFactor The reserve factor set for this market. - /// @param _poolToken The P2P index cursor set for this market. + /// @param _p2pIndexCursor The P2P index cursor set for this market. event MarketCreated(address indexed _poolToken, uint16 _reserveFactor, uint16 _p2pIndexCursor); /// ERRORS /// @@ -139,6 +123,12 @@ abstract contract MorphoGovernance is MorphoUtils { /// @notice Thrown when the address is the zero address. error ZeroAddress(); + /// @notice Thrown when market borrow is not paused. + error BorrowNotPaused(); + + /// @notice Thrown when market is deprecated. + error MarketIsDeprecated(); + /// UPGRADE /// /// @notice Initializes the Morpho contract. @@ -224,13 +214,6 @@ abstract contract MorphoGovernance is MorphoUtils { emit InterestRatesSet(address(_interestRatesManager)); } - /// @notice Sets the `rewardsManager`. - /// @param _rewardsManager The new `rewardsManager`. - function setRewardsManager(IRewardsManager _rewardsManager) external onlyOwner { - rewardsManager = _rewardsManager; - emit RewardsManagerSet(address(_rewardsManager)); - } - /// @notice Sets the `treasuryVault`. /// @param _treasuryVault The address of the new `treasuryVault`. function setTreasuryVault(address _treasuryVault) external onlyOwner { @@ -238,20 +221,6 @@ abstract contract MorphoGovernance is MorphoUtils { emit TreasuryVaultSet(_treasuryVault); } - /// @notice Sets the `aaveIncentivesController`. - /// @param _aaveIncentivesController The address of the `aaveIncentivesController`. - function setAaveIncentivesController(address _aaveIncentivesController) external onlyOwner { - aaveIncentivesController = IAaveIncentivesController(_aaveIncentivesController); - emit AaveIncentivesControllerSet(_aaveIncentivesController); - } - - /// @notice Sets the `incentivesVault`. - /// @param _incentivesVault The new `incentivesVault`. - function setIncentivesVault(IIncentivesVault _incentivesVault) external onlyOwner { - incentivesVault = _incentivesVault; - emit IncentivesVaultSet(address(_incentivesVault)); - } - /// @notice Sets the `reserveFactor`. /// @param _poolToken The market on which to set the `_newReserveFactor`. /// @param _newReserveFactor The proportion of the interest earned by users sent to the DAO, in basis point. @@ -302,6 +271,7 @@ abstract contract MorphoGovernance is MorphoUtils { onlyOwner isMarketCreated(_poolToken) { + if (!_isPaused && marketPauseStatus[_poolToken].isDeprecated) revert MarketIsDeprecated(); marketPauseStatus[_poolToken].isBorrowPaused = _isPaused; emit IsBorrowPausedSet(_poolToken, _isPaused); } @@ -382,13 +352,6 @@ abstract contract MorphoGovernance is MorphoUtils { emit P2PStatusSet(_poolToken, _isP2PDisabled); } - /// @notice Sets `isClaimRewardsPaused`. - /// @param _isPaused The new pause status, true to pause the mechanism. - function setIsClaimRewardsPaused(bool _isPaused) external onlyOwner { - isClaimRewardsPaused = _isPaused; - emit ClaimRewardsPauseStatusSet(_isPaused); - } - /// @notice Sets a market as deprecated (allows liquidation of every position on this market). /// @param _poolToken The address of the market to update. /// @param _isDeprecated The new deprecated status, true to deprecate the market. @@ -397,21 +360,11 @@ abstract contract MorphoGovernance is MorphoUtils { onlyOwner isMarketCreated(_poolToken) { + if (!marketPauseStatus[_poolToken].isBorrowPaused) revert BorrowNotPaused(); marketPauseStatus[_poolToken].isDeprecated = _isDeprecated; emit IsDeprecatedSet(_poolToken, _isDeprecated); } - /// @notice Sets a market's asset as collateral. - /// @param _poolToken The address of the market to (un)set as collateral. - /// @param _assetAsCollateral True to set the asset as collateral (True by default). - function setAssetAsCollateral(address _poolToken, bool _assetAsCollateral) - external - onlyOwner - isMarketCreated(_poolToken) - { - pool.setUserUseReserveAsCollateral(market[_poolToken].underlyingToken, _assetAsCollateral); - } - /// @notice Increases peer-to-peer deltas, to put some liquidity back on the pool. /// @dev The current Morpho supply on the pool might not be enough to borrow `_amount` before resuppling it. /// In this case, consider calling multiple times this function. @@ -514,17 +467,24 @@ abstract contract MorphoGovernance is MorphoUtils { Types.MarketPauseStatus storage pause = marketPauseStatus[_poolToken]; pause.isSupplyPaused = _isPaused; - pause.isBorrowPaused = _isPaused; - pause.isWithdrawPaused = _isPaused; - pause.isRepayPaused = _isPaused; - pause.isLiquidateCollateralPaused = _isPaused; - pause.isLiquidateBorrowPaused = _isPaused; - emit IsSupplyPausedSet(_poolToken, _isPaused); - emit IsBorrowPausedSet(_poolToken, _isPaused); + + // Note that pause.isDeprecated implies pause.isBorrowPaused. + if (!pause.isDeprecated) { + pause.isBorrowPaused = _isPaused; + emit IsBorrowPausedSet(_poolToken, _isPaused); + } + + pause.isWithdrawPaused = _isPaused; emit IsWithdrawPausedSet(_poolToken, _isPaused); + + pause.isRepayPaused = _isPaused; emit IsRepayPausedSet(_poolToken, _isPaused); + + pause.isLiquidateCollateralPaused = _isPaused; emit IsLiquidateCollateralPausedSet(_poolToken, _isPaused); + + pause.isLiquidateBorrowPaused = _isPaused; emit IsLiquidateBorrowPausedSet(_poolToken, _isPaused); } } diff --git a/src/aave-v2/MorphoStorage.sol b/src/aave-v2/MorphoStorage.sol index 6b6e6a139..bb9b53e35 100644 --- a/src/aave-v2/MorphoStorage.sol +++ b/src/aave-v2/MorphoStorage.sol @@ -5,8 +5,6 @@ import "./interfaces/aave/ILendingPool.sol"; import "./interfaces/IEntryPositionsManager.sol"; import "./interfaces/IExitPositionsManager.sol"; import "./interfaces/IInterestRatesManager.sol"; -import "./interfaces/IIncentivesVault.sol"; -import "./interfaces/IRewardsManager.sol"; import "@morpho-dao/morpho-data-structures/HeapOrdering.sol"; import "./libraries/Types.sol"; @@ -34,10 +32,10 @@ abstract contract MorphoStorage is OwnableUpgradeable, ReentrancyGuardUpgradeabl address public constant ST_ETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; // stETH is a rebasing token, so the rebase index's value when the astEth market was created is stored - // and used for internal calculations to convert `stEth.balanceOf` into an amount in scaled units. + // and used for internal calculations to convert `stEth.balanceOf` into an amount in pool supply unit. uint256 public constant ST_ETH_BASE_REBASE_INDEX = 1_086492192583716523804482274; - bool public isClaimRewardsPaused; // Whether claiming rewards is paused or not. + bool public isClaimRewardsPaused; // Deprecated: whether claiming rewards is paused or not. uint256 public maxSortedUsers; // The max number of users to sort in the data structure. Types.MaxGasForMatching public defaultMaxGasForMatching; // The default max gas to consume within loops in matching engine functions. @@ -64,14 +62,14 @@ abstract contract MorphoStorage is OwnableUpgradeable, ReentrancyGuardUpgradeabl /// CONTRACTS AND ADDRESSES /// ILendingPoolAddressesProvider public addressesProvider; - IAaveIncentivesController public aaveIncentivesController; + address public aaveIncentivesController; // Deprecated. ILendingPool public pool; IEntryPositionsManager public entryPositionsManager; IExitPositionsManager public exitPositionsManager; IInterestRatesManager public interestRatesManager; - IIncentivesVault public incentivesVault; - IRewardsManager public rewardsManager; + address public incentivesVault; // Deprecated. + address public rewardsManager; // Deprecated. address public treasuryVault; /// APPENDIX STORAGE /// diff --git a/src/aave-v2/MorphoUtils.sol b/src/aave-v2/MorphoUtils.sol index c974bcdea..868d43fd4 100644 --- a/src/aave-v2/MorphoUtils.sol +++ b/src/aave-v2/MorphoUtils.sol @@ -5,7 +5,6 @@ import "./interfaces/aave/IPriceOracleGetter.sol"; import "./interfaces/aave/IAToken.sol"; import "./libraries/aave/ReserveConfiguration.sol"; -import "./libraries/aave/UserConfiguration.sol"; import "@morpho-dao/morpho-utils/DelegateCall.sol"; import "@morpho-dao/morpho-utils/math/WadRayMath.sol"; @@ -20,7 +19,6 @@ import "./MorphoStorage.sol"; /// @notice Modifiers, getters and other util functions for Morpho. abstract contract MorphoUtils is MorphoStorage { using ReserveConfiguration for DataTypes.ReserveConfigurationMap; - using UserConfiguration for DataTypes.UserConfigurationMap; using HeapOrdering for HeapOrdering.HeapArray; using PercentageMath for uint256; using DelegateCall for address; @@ -32,6 +30,18 @@ abstract contract MorphoUtils is MorphoStorage { /// @notice Thrown when the market is not created yet. error MarketNotCreated(); + /// STRUCTS /// + + // Struct to avoid stack too deep. + struct LiquidityVars { + address poolToken; + uint256 poolTokensLength; + bytes32 userMarkets; + bytes32 borrowMask; + address underlyingToken; + uint256 underlyingPrice; + } + /// MODIFIERS /// /// @notice Prevents to update a market not created yet. @@ -257,11 +267,8 @@ abstract contract MorphoUtils is MorphoStorage { ) internal returns (Types.LiquidityData memory values) { IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle()); Types.AssetLiquidityData memory assetData; - Types.LiquidityStackVars memory vars; + LiquidityVars memory vars; - DataTypes.UserConfigurationMap memory morphoPoolConfig = pool.getUserConfiguration( - address(this) - ); vars.poolTokensLength = marketsCreated.length; vars.userMarkets = userMarkets[_user]; @@ -276,24 +283,17 @@ abstract contract MorphoUtils is MorphoStorage { if (vars.poolToken != _poolToken) _updateIndexes(vars.poolToken); + // Assumes that the Morpho contract has not disabled the asset as collateral in the underlying pool. (assetData.ltv, assetData.liquidationThreshold, , assetData.decimals, ) = pool .getConfiguration(vars.underlyingToken) .getParamsMemory(); - // LTV and liquidation threshold should be zero if Morpho has not enabled this asset as collateral. - if ( - !morphoPoolConfig.isUsingAsCollateral(pool.getReserveData(vars.underlyingToken).id) - ) { - assetData.ltv = 0; - assetData.liquidationThreshold = 0; - } - unchecked { assetData.tokenUnit = 10**assetData.decimals; } if (_isBorrowing(vars.userMarkets, vars.borrowMask)) { - values.debt += _debtValue( + values.debtEth += _debtValue( vars.poolToken, _user, vars.underlyingPrice, @@ -302,35 +302,36 @@ abstract contract MorphoUtils is MorphoStorage { } // Cache current asset collateral value. - uint256 assetCollateralValue; if (_isSupplying(vars.userMarkets, vars.borrowMask)) { - assetCollateralValue = _collateralValue( + uint256 assetCollateralEth = _collateralValue( vars.poolToken, _user, vars.underlyingPrice, assetData.tokenUnit ); - values.collateral += assetCollateralValue; + values.collateralEth += assetCollateralEth; // Calculate LTV for borrow. - values.maxDebt += assetCollateralValue.percentMul(assetData.ltv); + values.borrowableEth += assetCollateralEth.percentMul(assetData.ltv); + + // Update LT variable for withdraw. + if (assetCollateralEth > 0) + values.maxDebtEth += assetCollateralEth.percentMul( + assetData.liquidationThreshold + ); } // Update debt variable for borrowed token. if (_poolToken == vars.poolToken && _amountBorrowed > 0) - values.debt += (_amountBorrowed * vars.underlyingPrice).divUp(assetData.tokenUnit); - - // Update LT variable for withdraw. - if (assetCollateralValue > 0) - values.liquidationThreshold += assetCollateralValue.percentMul( - assetData.liquidationThreshold + values.debtEth += (_amountBorrowed * vars.underlyingPrice).divUp( + assetData.tokenUnit ); // Subtract withdrawn amount from liquidation threshold and collateral. if (_poolToken == vars.poolToken && _amountWithdrawn > 0) { uint256 withdrawn = (_amountWithdrawn * vars.underlyingPrice) / assetData.tokenUnit; - values.collateral -= withdrawn; - values.liquidationThreshold -= withdrawn.percentMul(assetData.liquidationThreshold); - values.maxDebt -= withdrawn.percentMul(assetData.ltv); + values.collateralEth -= withdrawn; + values.maxDebtEth -= withdrawn.percentMul(assetData.liquidationThreshold); + values.borrowableEth -= withdrawn.percentMul(assetData.ltv); } } } diff --git a/src/aave-v2/interfaces/IIncentivesVault.sol b/src/aave-v2/interfaces/IIncentivesVault.sol deleted file mode 100644 index fd58f60ff..000000000 --- a/src/aave-v2/interfaces/IIncentivesVault.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity >=0.5.0; - -import "./IOracle.sol"; - -interface IIncentivesVault { - function isPaused() external view returns (bool); - - function bonus() external view returns (uint256); - - function MAX_BASIS_POINTS() external view returns (uint256); - - function incentivesTreasuryVault() external view returns (address); - - function oracle() external view returns (IOracle); - - function setOracle(IOracle _newOracle) external; - - function setIncentivesTreasuryVault(address _newIncentivesTreasuryVault) external; - - function setBonus(uint256 _newBonus) external; - - function setPauseStatus(bool _newStatus) external; - - function transferTokensToDao(address _token, uint256 _amount) external; - - function tradeRewardTokensForMorphoTokens(address _to, uint256 _amount) external; -} diff --git a/src/aave-v2/interfaces/IMorpho.sol b/src/aave-v2/interfaces/IMorpho.sol index c6191bb4f..3e661f322 100644 --- a/src/aave-v2/interfaces/IMorpho.sol +++ b/src/aave-v2/interfaces/IMorpho.sol @@ -6,8 +6,6 @@ import "./aave/ILendingPool.sol"; import "./IEntryPositionsManager.sol"; import "./IExitPositionsManager.sol"; import "./IInterestRatesManager.sol"; -import "./IIncentivesVault.sol"; -import "./IRewardsManager.sol"; import "../libraries/Types.sol"; @@ -40,12 +38,9 @@ interface IMorpho { function p2pBorrowIndex(address) external view returns (uint256); function poolIndexes(address) external view returns (Types.PoolIndexes memory); function interestRatesManager() external view returns (IInterestRatesManager); - function rewardsManager() external view returns (IRewardsManager); function entryPositionsManager() external view returns (IEntryPositionsManager); function exitPositionsManager() external view returns (IExitPositionsManager); - function aaveIncentivesController() external view returns (IAaveIncentivesController); function addressesProvider() external view returns (ILendingPoolAddressesProvider); - function incentivesVault() external view returns (IIncentivesVault); function pool() external view returns (ILendingPool); function treasuryVault() external view returns (address); function borrowMask(address) external view returns (bytes32); @@ -65,8 +60,6 @@ interface IMorpho { function setMaxSortedUsers(uint256 _newMaxSortedUsers) external; function setDefaultMaxGasForMatching(Types.MaxGasForMatching memory _maxGasForMatching) external; - function setIncentivesVault(address _newIncentivesVault) external; - function setRewardsManager(address _rewardsManagerAddress) external; function setExitPositionsManager(IExitPositionsManager _exitPositionsManager) external; function setEntryPositionsManager(IEntryPositionsManager _entryPositionsManager) external; function setInterestRatesManager(IInterestRatesManager _interestRatesManager) external; @@ -75,7 +68,6 @@ interface IMorpho { function setReserveFactor(address _poolToken, uint256 _newReserveFactor) external; function setP2PIndexCursor(address _poolToken, uint16 _p2pIndexCursor) external; function setIsPausedForAllMarkets(bool _isPaused) external; - function setIsClaimRewardsPaused(bool _isPaused) external; function setIsSupplyPaused(address _poolToken, bool _isPaused) external; function setIsBorrowPaused(address _poolToken, bool _isPaused) external; function setIsWithdrawPaused(address _poolToken, bool _isPaused) external; @@ -98,5 +90,4 @@ interface IMorpho { function repay(address _poolToken, uint256 _amount) external; function repay(address _poolToken, address _onBehalf, uint256 _amount) external; function liquidate(address _poolTokenBorrowed, address _poolTokenCollateral, address _borrower, uint256 _amount) external; - function claimRewards(address[] calldata _assets, bool _tradeForMorphoToken) external returns (uint256 claimedAmount); } diff --git a/src/aave-v2/interfaces/IRewardsManager.sol b/src/aave-v2/interfaces/IRewardsManager.sol deleted file mode 100644 index 58058d6d9..000000000 --- a/src/aave-v2/interfaces/IRewardsManager.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity >=0.5.0; - -import "./aave/IAaveIncentivesController.sol"; - -interface IRewardsManager { - function initialize(address _morpho) external; - - function getUserIndex(address, address) external returns (uint256); - - function getUserUnclaimedRewards(address[] calldata, address) external view returns (uint256); - - function claimRewards( - IAaveIncentivesController _aaveIncentivesController, - address[] calldata, - address - ) external returns (uint256); - - function updateUserAssetAndAccruedRewards( - IAaveIncentivesController _aaveIncentivesController, - address _user, - address _asset, - uint256 _userBalance, - uint256 _totalBalance - ) external; -} diff --git a/src/aave-v2/interfaces/aave/IAaveIncentivesController.sol b/src/aave-v2/interfaces/aave/IAaveIncentivesController.sol deleted file mode 100644 index 1067f4448..000000000 --- a/src/aave-v2/interfaces/aave/IAaveIncentivesController.sol +++ /dev/null @@ -1,153 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity >=0.5.0; - -interface IAaveDistributionManager { - event AssetConfigUpdated(address indexed asset, uint256 emission); - event AssetIndexUpdated(address indexed asset, uint256 index); - event UserIndexUpdated(address indexed user, address indexed asset, uint256 index); - event DistributionEndUpdated(uint256 newDistributionEnd); - - /** - * @dev Sets the end date for the distribution - * @param distributionEnd The end date timestamp - **/ - function setDistributionEnd(uint256 distributionEnd) external; - - /** - * @dev Gets the end date for the distribution - * @return The end of the distribution - **/ - function getDistributionEnd() external view returns (uint256); - - /** - * @dev for backwards compatibility with the previous DistributionManager used - * @return The end of the distribution - **/ - function DISTRIBUTION_END() external view returns (uint256); - - /** - * @dev Returns the data of an user on a distribution - * @param user Address of the user - * @param asset The address of the reference asset of the distribution - * @return The new index - **/ - function getUserAssetData(address user, address asset) external view returns (uint256); - - /** - * @dev Returns the configuration of the distribution for a certain asset - * @param asset The address of the reference asset of the distribution - * @return The asset index, the emission per second and the last updated timestamp - **/ - function getAssetData(address asset) - external - view - returns ( - uint256, - uint256, - uint256 - ); -} - -interface IAaveIncentivesController is IAaveDistributionManager { - event RewardsAccrued(address indexed user, uint256 amount); - - event RewardsClaimed( - address indexed user, - address indexed to, - address indexed claimer, - uint256 amount - ); - - event ClaimerSet(address indexed user, address indexed claimer); - - /** - * @dev Whitelists an address to claim the rewards on behalf of another address - * @param user The address of the user - * @param claimer The address of the claimer - */ - function setClaimer(address user, address claimer) external; - - /** - * @dev Returns the whitelisted claimer for a certain address (0x0 if not set) - * @param user The address of the user - * @return The claimer address - */ - function getClaimer(address user) external view returns (address); - - /** - * @dev Configure assets for a certain rewards emission - * @param assets The assets to incentivize - * @param emissionsPerSecond The emission for each asset - */ - function configureAssets(address[] calldata assets, uint256[] calldata emissionsPerSecond) - external; - - /** - * @dev Called by the corresponding asset on any update that affects the rewards distribution - * @param asset The address of the user - * @param userBalance The balance of the user of the asset in the lending pool - * @param totalSupply The total supply of the asset in the lending pool - **/ - function handleAction( - address asset, - uint256 userBalance, - uint256 totalSupply - ) external; - - /** - * @dev Returns the total of rewards of an user, already accrued + not yet accrued - * @param user The address of the user - * @return The rewards - **/ - function getRewardsBalance(address[] calldata assets, address user) - external - view - returns (uint256); - - /** - * @dev Claims reward for an user, on all the assets of the lending pool, accumulating the pending rewards - * @param amount Amount of rewards to claim - * @param to Address that will be receiving the rewards - * @return Rewards claimed - **/ - function claimRewards( - address[] calldata assets, - uint256 amount, - address to - ) external returns (uint256); - - /** - * @dev Claims reward for an user on behalf, on all the assets of the lending pool, accumulating the pending rewards. The caller must - * be whitelisted via "allowClaimOnBehalf" function by the RewardsAdmin role manager - * @param amount Amount of rewards to claim - * @param user Address to check and claim rewards - * @param to Address that will be receiving the rewards - * @return Rewards claimed - **/ - function claimRewardsOnBehalf( - address[] calldata assets, - uint256 amount, - address user, - address to - ) external returns (uint256); - - /** - * @dev returns the unclaimed rewards of the user - * @param user the address of the user - * @return the unclaimed user rewards - */ - function getUserUnclaimedRewards(address user) external view returns (uint256); - - /** - * @dev for backward compatibility with previous implementation of the Incentives controller - */ - function REWARD_TOKEN() external view returns (address); - - struct AssetData { - uint128 emissionPerSecond; - uint128 lastUpdateTimestamp; - uint256 index; - } - - function assets(address) external view returns (AssetData memory); -} diff --git a/src/aave-v2/interfaces/aave/IReserveInterestRateStrategy.sol b/src/aave-v2/interfaces/aave/IReserveInterestRateStrategy.sol new file mode 100644 index 000000000..4ce1e7a72 --- /dev/null +++ b/src/aave-v2/interfaces/aave/IReserveInterestRateStrategy.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.5.0; + +interface IReserveInterestRateStrategy { + function baseVariableBorrowRate() external view returns (uint256); + + function getMaxVariableBorrowRate() external view returns (uint256); + + function calculateInterestRates( + address reserve, + uint256 utilizationRate, + uint256 totalStableDebt, + uint256 totalVariableDebt, + uint256 averageStableBorrowRate, + uint256 reserveFactor + ) + external + view + returns ( + uint256 liquidityRate, + uint256 stableBorrowRate, + uint256 variableBorrowRate + ); +} diff --git a/src/aave-v2/interfaces/aave/IStableDebtToken.sol b/src/aave-v2/interfaces/aave/IStableDebtToken.sol new file mode 100644 index 000000000..462c30902 --- /dev/null +++ b/src/aave-v2/interfaces/aave/IStableDebtToken.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.5.0; + +/** + * @title IStableDebtToken + * @author Aave + * @notice Defines the interface for the stable debt token + **/ +interface IStableDebtToken { + /** + * @dev Emitted when new stable debt is minted + * @param user The address of the user who triggered the minting + * @param onBehalfOf The recipient of stable debt tokens + * @param amount The amount minted (user entered amount + balance increase from interest) + * @param currentBalance The current balance of the user + * @param balanceIncrease The increase in balance since the last action of the user + * @param newRate The rate of the debt after the minting + * @param avgStableRate The next average stable rate after the minting + * @param newTotalSupply The next total supply of the stable debt token after the action + **/ + event Mint( + address indexed user, + address indexed onBehalfOf, + uint256 amount, + uint256 currentBalance, + uint256 balanceIncrease, + uint256 newRate, + uint256 avgStableRate, + uint256 newTotalSupply + ); + + /** + * @dev Emitted when new stable debt is burned + * @param from The address from which the debt will be burned + * @param amount The amount being burned (user entered amount - balance increase from interest) + * @param currentBalance The current balance of the user + * @param balanceIncrease The the increase in balance since the last action of the user + * @param avgStableRate The next average stable rate after the burning + * @param newTotalSupply The next total supply of the stable debt token after the action + **/ + event Burn( + address indexed from, + uint256 amount, + uint256 currentBalance, + uint256 balanceIncrease, + uint256 avgStableRate, + uint256 newTotalSupply + ); + + /** + * @notice Mints debt token to the `onBehalfOf` address. + * @dev The resulting rate is the weighted average between the rate of the new debt + * and the rate of the previous debt + * @param user The address receiving the borrowed underlying, being the delegatee in case + * of credit delegate, or same as `onBehalfOf` otherwise + * @param onBehalfOf The address receiving the debt tokens + * @param amount The amount of debt tokens to mint + * @param rate The rate of the debt being minted + * @return True if it is the first borrow, false otherwise + * @return The total stable debt + * @return The average stable borrow rate + **/ + function mint( + address user, + address onBehalfOf, + uint256 amount, + uint256 rate + ) + external + returns ( + bool, + uint256, + uint256 + ); + + /** + * @notice Burns debt of `user` + * @dev The resulting rate is the weighted average between the rate of the new debt + * and the rate of the previous debt + * @dev In some instances, a burn transaction will emit a mint event + * if the amount to burn is less than the interest the user earned + * @param from The address from which the debt will be burned + * @param amount The amount of debt tokens getting burned + * @return The total stable debt + * @return The average stable borrow rate + **/ + function burn(address from, uint256 amount) external returns (uint256, uint256); + + /** + * @notice Returns the average rate of all the stable rate loans. + * @return The average stable rate + **/ + function getAverageStableRate() external view returns (uint256); + + /** + * @notice Returns the stable rate of the user debt + * @param user The address of the user + * @return The stable rate of the user + **/ + function getUserStableRate(address user) external view returns (uint256); + + /** + * @notice Returns the timestamp of the last update of the user + * @param user The address of the user + * @return The timestamp + **/ + function getUserLastUpdated(address user) external view returns (uint40); + + /** + * @notice Returns the principal, the total supply, the average stable rate and the timestamp for the last update + * @return The principal + * @return The total supply + * @return The average stable rate + * @return The timestamp of the last update + **/ + function getSupplyData() + external + view + returns ( + uint256, + uint256, + uint256, + uint40 + ); + + /** + * @notice Returns the timestamp of the last update of the total supply + * @return The timestamp + **/ + function getTotalSupplyLastUpdated() external view returns (uint40); + + /** + * @notice Returns the total supply and the average stable rate + * @return The total supply + * @return The average rate + **/ + function getTotalSupplyAndAvgRate() external view returns (uint256, uint256); + + /** + * @notice Returns the principal debt balance of the user + * @return The debt balance of the user since the last burn/mint action + **/ + function principalBalanceOf(address user) external view returns (uint256); + + /** + * @notice Returns the address of the underlying asset of this stableDebtToken (E.g. WETH for stableDebtWETH) + * @return The address of the underlying asset + **/ + function UNDERLYING_ASSET_ADDRESS() external view returns (address); +} diff --git a/src/aave-v2/lens/IndexesLens.sol b/src/aave-v2/lens/IndexesLens.sol index cc20c98c5..756563729 100644 --- a/src/aave-v2/lens/IndexesLens.sol +++ b/src/aave-v2/lens/IndexesLens.sol @@ -81,7 +81,7 @@ abstract contract IndexesLens is LensStorage { market.reserveFactor ); - indexes.p2pSupplyIndex = InterestRatesModel.computeP2PSupplyIndex( + indexes.p2pSupplyIndex = InterestRatesModel.computeP2PIndex( InterestRatesModel.P2PIndexComputeParams({ poolGrowthFactor: growthFactors.poolSupplyGrowthFactor, p2pGrowthFactor: growthFactors.p2pSupplyGrowthFactor, @@ -91,7 +91,7 @@ abstract contract IndexesLens is LensStorage { p2pAmount: delta.p2pSupplyAmount }) ); - indexes.p2pBorrowIndex = InterestRatesModel.computeP2PBorrowIndex( + indexes.p2pBorrowIndex = InterestRatesModel.computeP2PIndex( InterestRatesModel.P2PIndexComputeParams({ poolGrowthFactor: growthFactors.poolBorrowGrowthFactor, p2pGrowthFactor: growthFactors.p2pBorrowGrowthFactor, diff --git a/src/aave-v2/lens/LensStorage.sol b/src/aave-v2/lens/LensStorage.sol index 73542dc40..a548d3417 100644 --- a/src/aave-v2/lens/LensStorage.sol +++ b/src/aave-v2/lens/LensStorage.sol @@ -7,12 +7,12 @@ import "../interfaces/aave/IAToken.sol"; import "../interfaces/IMorpho.sol"; import "./interfaces/ILens.sol"; +import "../libraries/aave/DataTypes.sol"; +import "../libraries/InterestRatesModel.sol"; import "../libraries/aave/ReserveConfiguration.sol"; import "@morpho-dao/morpho-utils/math/PercentageMath.sol"; import "@morpho-dao/morpho-utils/math/WadRayMath.sol"; import "@morpho-dao/morpho-utils/math/Math.sol"; -import "../libraries/aave/DataTypes.sol"; -import "../libraries/InterestRatesModel.sol"; /// @title LensStorage. /// @author Morpho Labs. @@ -29,7 +29,7 @@ abstract contract LensStorage is ILens { /// IMMUTABLES /// // stETH is a rebasing token, so the rebase index's value when the astEth market was created is stored - // and used for internal calculations to convert `stEth.balanceOf` into an amount in scaled units. + // and used for internal calculations to convert `stEth.balanceOf` into an amount in pool supply unit. uint256 public immutable ST_ETH_BASE_REBASE_INDEX; IMorpho public immutable morpho; diff --git a/src/aave-v2/lens/RatesLens.sol b/src/aave-v2/lens/RatesLens.sol index 98d325a47..c35b52698 100644 --- a/src/aave-v2/lens/RatesLens.sol +++ b/src/aave-v2/lens/RatesLens.sol @@ -1,7 +1,9 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity 0.8.13; +import "../interfaces/aave/IStableDebtToken.sol"; import "../interfaces/aave/IVariableDebtToken.sol"; +import "../interfaces/aave/IReserveInterestRateStrategy.sol"; import "./UsersLens.sol"; @@ -10,6 +12,7 @@ import "./UsersLens.sol"; /// @custom:contact security@morpho.xyz /// @notice Intermediary layer exposing endpoints to query live data related to the Morpho Protocol users and their positions. abstract contract RatesLens is UsersLens { + using ReserveConfiguration for DataTypes.ReserveConfigurationMap; using WadRayMath for uint256; using Math for uint256; @@ -46,32 +49,36 @@ abstract contract RatesLens is UsersLens { ) = _getIndexes(_poolToken); Types.SupplyBalance memory supplyBalance = morpho.supplyBalanceInOf(_poolToken, _user); - if (_amount > 0 && delta.p2pBorrowDelta > 0) { - uint256 matchedDelta = Math.min( - delta.p2pBorrowDelta.rayMul(indexes.poolBorrowIndex), - _amount - ); - supplyBalance.inP2P += matchedDelta.rayDiv(indexes.p2pSupplyIndex); - _amount -= matchedDelta; - } + uint256 repaidToPool; + if (!market.isP2PDisabled) { + if (delta.p2pBorrowDelta > 0) { + uint256 matchedDelta = Math.min( + delta.p2pBorrowDelta.rayMul(indexes.poolBorrowIndex), + _amount + ); - if (_amount > 0 && !market.isP2PDisabled) { - address firstPoolBorrower = morpho.getHead( - _poolToken, - Types.PositionType.BORROWERS_ON_POOL - ); - uint256 firstPoolBorrowerBalance = morpho - .borrowBalanceInOf(_poolToken, firstPoolBorrower) - .onPool; + supplyBalance.inP2P += matchedDelta.rayDiv(indexes.p2pSupplyIndex); + repaidToPool += matchedDelta; + _amount -= matchedDelta; + } + + if (_amount > 0) { + address firstPoolBorrower = morpho.getHead( + _poolToken, + Types.PositionType.BORROWERS_ON_POOL + ); + uint256 firstPoolBorrowerBalance = morpho + .borrowBalanceInOf(_poolToken, firstPoolBorrower) + .onPool; - if (firstPoolBorrowerBalance > 0) { uint256 matchedP2P = Math.min( firstPoolBorrowerBalance.rayMul(indexes.poolBorrowIndex), _amount ); supplyBalance.inP2P += matchedP2P.rayDiv(indexes.p2pSupplyIndex); + repaidToPool += matchedP2P; _amount -= matchedP2P; } } @@ -84,7 +91,9 @@ abstract contract RatesLens is UsersLens { (nextSupplyRatePerYear, totalBalance) = _getUserSupplyRatePerYear( _poolToken, balanceInP2P, - balanceOnPool + balanceOnPool, + _amount, + repaidToPool ); } @@ -119,32 +128,36 @@ abstract contract RatesLens is UsersLens { ) = _getIndexes(_poolToken); Types.BorrowBalance memory borrowBalance = morpho.borrowBalanceInOf(_poolToken, _user); - if (_amount > 0 && delta.p2pSupplyDelta > 0) { - uint256 matchedDelta = Math.min( - delta.p2pSupplyDelta.rayMul(indexes.poolSupplyIndex), - _amount - ); - borrowBalance.inP2P += matchedDelta.rayDiv(indexes.p2pBorrowIndex); - _amount -= matchedDelta; - } + uint256 withdrawnFromPool; + if (!market.isP2PDisabled) { + if (delta.p2pSupplyDelta > 0) { + uint256 matchedDelta = Math.min( + delta.p2pSupplyDelta.rayMul(indexes.poolSupplyIndex), + _amount + ); - if (_amount > 0 && !market.isP2PDisabled) { - address firstPoolSupplier = morpho.getHead( - _poolToken, - Types.PositionType.SUPPLIERS_ON_POOL - ); - uint256 firstPoolSupplierBalance = morpho - .supplyBalanceInOf(_poolToken, firstPoolSupplier) - .onPool; + borrowBalance.inP2P += matchedDelta.rayDiv(indexes.p2pBorrowIndex); + withdrawnFromPool += matchedDelta; + _amount -= matchedDelta; + } + + if (_amount > 0) { + address firstPoolSupplier = morpho.getHead( + _poolToken, + Types.PositionType.SUPPLIERS_ON_POOL + ); + uint256 firstPoolSupplierBalance = morpho + .supplyBalanceInOf(_poolToken, firstPoolSupplier) + .onPool; - if (firstPoolSupplierBalance > 0) { uint256 matchedP2P = Math.min( firstPoolSupplierBalance.rayMul(indexes.poolSupplyIndex), _amount ); borrowBalance.inP2P += matchedP2P.rayDiv(indexes.p2pBorrowIndex); + withdrawnFromPool += matchedP2P; _amount -= matchedP2P; } } @@ -157,7 +170,9 @@ abstract contract RatesLens is UsersLens { (nextBorrowRatePerYear, totalBalance) = _getUserBorrowRatePerYear( _poolToken, balanceInP2P, - balanceOnPool + balanceOnPool, + _amount, + withdrawnFromPool ); } @@ -175,7 +190,13 @@ abstract contract RatesLens is UsersLens { _user ); - (supplyRatePerYear, ) = _getUserSupplyRatePerYear(_poolToken, balanceInP2P, balanceOnPool); + (supplyRatePerYear, ) = _getUserSupplyRatePerYear( + _poolToken, + balanceInP2P, + balanceOnPool, + 0, + 0 + ); } /// @notice Returns the borrow rate per year a given user is currently experiencing on a given market. @@ -192,7 +213,13 @@ abstract contract RatesLens is UsersLens { _user ); - (borrowRatePerYear, ) = _getUserBorrowRatePerYear(_poolToken, balanceInP2P, balanceOnPool); + (borrowRatePerYear, ) = _getUserBorrowRatePerYear( + _poolToken, + balanceInP2P, + balanceOnPool, + 0, + 0 + ); } /// PUBLIC /// @@ -311,6 +338,37 @@ abstract contract RatesLens is UsersLens { function getRatesPerYear(address _poolToken) public view + returns ( + uint256, + uint256, + uint256, + uint256 + ) + { + return _getRatesPerYear(_poolToken, 0, 0, 0, 0); + } + + /// INTERNAL /// + + /// @dev Computes and returns peer-to-peer and pool rates for a specific market. + /// @param _poolToken The market address. + /// @param _suppliedOnPool The amount hypothetically supplied to the underlying's pool (in underlying). + /// @param _borrowedFromPool The amount hypothetically borrowed from the underlying's pool (in underlying). + /// @param _repaidOnPool The amount hypothetically repaid to the underlying's pool (in underlying). + /// @param _withdrawnFromPool The amount hypothetically withdrawn from the underlying's pool (in underlying). + /// @return p2pSupplyRate The market's peer-to-peer supply rate per year (in ray). + /// @return p2pBorrowRate The market's peer-to-peer borrow rate per year (in ray). + /// @return poolSupplyRate The market's pool supply rate per year (in ray). + /// @return poolBorrowRate The market's pool borrow rate per year (in ray). + function _getRatesPerYear( + address _poolToken, + uint256 _suppliedOnPool, + uint256 _borrowedFromPool, + uint256 _withdrawnFromPool, + uint256 _repaidOnPool + ) + internal + view returns ( uint256 p2pSupplyRate, uint256 p2pBorrowRate, @@ -324,9 +382,13 @@ abstract contract RatesLens is UsersLens { Types.Indexes memory indexes ) = _getIndexes(_poolToken); - DataTypes.ReserveData memory reserve = pool.getReserveData(market.underlyingToken); - poolSupplyRate = reserve.currentLiquidityRate; - poolBorrowRate = reserve.currentVariableBorrowRate; + (poolSupplyRate, poolBorrowRate) = _getPoolRatesPerYear( + market.underlyingToken, + _suppliedOnPool, + _borrowedFromPool, + _withdrawnFromPool, + _repaidOnPool + ); p2pSupplyRate = InterestRatesModel.computeP2PSupplyRatePerYear( InterestRatesModel.P2PRateComputeParams({ @@ -355,9 +417,41 @@ abstract contract RatesLens is UsersLens { ); } - /// INTERNAL /// + /// @dev Computes and returns the underlying pool rates for a specific market. + /// @param _underlying The underlying pool market address. + /// @param _supplied The amount hypothetically supplied (in underlying). + /// @param _borrowed The amount hypothetically borrowed (in underlying). + /// @param _repaid The amount hypothetically repaid (in underlying). + /// @param _withdrawn The amount hypothetically withdrawn (in underlying). + /// @return poolSupplyRate The market's pool supply rate per year (in ray). + /// @return poolBorrowRate The market's pool borrow rate per year (in ray). + function _getPoolRatesPerYear( + address _underlying, + uint256 _supplied, + uint256 _borrowed, + uint256 _withdrawn, + uint256 _repaid + ) internal view returns (uint256 poolSupplyRate, uint256 poolBorrowRate) { + DataTypes.ReserveData memory reserve = pool.getReserveData(_underlying); + (, , , , uint256 reserveFactor) = reserve.configuration.getParamsMemory(); + + (poolSupplyRate, , poolBorrowRate) = IReserveInterestRateStrategy( + reserve.interestRateStrategyAddress + ).calculateInterestRates( + _underlying, + ERC20(_underlying).balanceOf(reserve.aTokenAddress) + + _supplied + + _repaid - + _borrowed - + _withdrawn, + ERC20(reserve.stableDebtTokenAddress).totalSupply(), + ERC20(reserve.variableDebtTokenAddress).totalSupply() + _borrowed - _repaid, + IStableDebtToken(reserve.stableDebtTokenAddress).getAverageStableRate(), + reserveFactor + ); + } - /// @notice Computes and returns the total distribution of supply for a given market, using virtually updated indexes. + /// @dev Computes and returns the total distribution of supply for a given market, using virtually updated indexes. /// @param _poolToken The address of the market to check. /// @param _p2pSupplyIndex The given market's peer-to-peer supply index. /// @param _poolSupplyIndex The given market's pool supply index. @@ -376,7 +470,7 @@ abstract contract RatesLens is UsersLens { poolSupplyAmount = IAToken(_poolToken).balanceOf(address(morpho)); } - /// @notice Computes and returns the total distribution of borrows for a given market, using virtually updated indexes. + /// @dev Computes and returns the total distribution of borrows for a given market, using virtually updated indexes. /// @param reserve The reserve data of the underlying pool. /// @param _p2pBorrowIndex The given market's peer-to-peer borrow index. /// @param _poolBorrowIndex The given market's pool borrow index. @@ -398,18 +492,27 @@ abstract contract RatesLens is UsersLens { } /// @dev Returns the supply rate per year experienced on a market based on a given position distribution. + /// The calculation takes into account the change in pool rates implied by an hypothetical supply and/or repay. /// @param _poolToken The address of the market. /// @param _balanceOnPool The amount of balance supplied on pool (in a unit common to `_balanceInP2P`). /// @param _balanceInP2P The amount of balance matched peer-to-peer (in a unit common to `_balanceOnPool`). + /// @param _suppliedOnPool The amount hypothetically supplied on pool (in underlying). + /// @param _repaidToPool The amount hypothetically repaid to the pool (in underlying). /// @return The supply rate per year experienced by the given position (in ray). /// @return The sum of peer-to-peer & pool balances. function _getUserSupplyRatePerYear( address _poolToken, uint256 _balanceInP2P, - uint256 _balanceOnPool + uint256 _balanceOnPool, + uint256 _suppliedOnPool, + uint256 _repaidToPool ) internal view returns (uint256, uint256) { - (uint256 p2pSupplyRatePerYear, , uint256 poolSupplyRatePerYear, ) = getRatesPerYear( - _poolToken + (uint256 p2pSupplyRatePerYear, , uint256 poolSupplyRatePerYear, ) = _getRatesPerYear( + _poolToken, + _suppliedOnPool, + 0, + 0, + _repaidToPool ); return @@ -422,18 +525,27 @@ abstract contract RatesLens is UsersLens { } /// @dev Returns the borrow rate per year experienced on a market based on a given position distribution. + /// The calculation takes into account the change in pool rates implied by an hypothetical borrow and/or withdraw. /// @param _poolToken The address of the market. /// @param _balanceOnPool The amount of balance supplied on pool (in a unit common to `_balanceInP2P`). /// @param _balanceInP2P The amount of balance matched peer-to-peer (in a unit common to `_balanceOnPool`). + /// @param _borrowedFromPool The amount hypothetically borrowed from the pool (in underlying). + /// @param _withdrawnFromPool The amount hypothetically withdrawn from the pool (in underlying). /// @return The borrow rate per year experienced by the given position (in ray). /// @return The sum of peer-to-peer & pool balances. function _getUserBorrowRatePerYear( address _poolToken, uint256 _balanceInP2P, - uint256 _balanceOnPool + uint256 _balanceOnPool, + uint256 _borrowedFromPool, + uint256 _withdrawnFromPool ) internal view returns (uint256, uint256) { - (, uint256 p2pBorrowRatePerYear, , uint256 poolBorrowRatePerYear) = getRatesPerYear( - _poolToken + (, uint256 p2pBorrowRatePerYear, , uint256 poolBorrowRatePerYear) = _getRatesPerYear( + _poolToken, + 0, + _borrowedFromPool, + _withdrawnFromPool, + 0 ); return diff --git a/src/aave-v2/lens/UsersLens.sol b/src/aave-v2/lens/UsersLens.sol index 49a5c42b7..bbafd38a1 100644 --- a/src/aave-v2/lens/UsersLens.sol +++ b/src/aave-v2/lens/UsersLens.sol @@ -65,8 +65,8 @@ abstract contract UsersLens is IndexesLens { ); if ( - liquidityData.debt > 0 && - liquidityData.liquidationThreshold.wadDiv(liquidityData.debt) <= + liquidityData.debtEth > 0 && + liquidityData.maxDebtEth.wadDiv(liquidityData.debtEth) <= HEALTH_FACTOR_LIQUIDATION_THRESHOLD ) return (0, 0); @@ -74,22 +74,22 @@ abstract contract UsersLens is IndexesLens { _poolToken ); - if (liquidityData.debt < liquidityData.maxDebt) + if (liquidityData.debtEth < liquidityData.borrowableEth) borrowable = Math.min( poolTokenBalance, - ((liquidityData.maxDebt - liquidityData.debt) * assetData.tokenUnit) / + ((liquidityData.borrowableEth - liquidityData.debtEth) * assetData.tokenUnit) / assetData.underlyingPrice ); withdrawable = Math.min( poolTokenBalance, - (assetData.collateral * assetData.tokenUnit) / assetData.underlyingPrice + (assetData.collateralEth * assetData.tokenUnit) / assetData.underlyingPrice ); if (assetData.liquidationThreshold > 0) withdrawable = Math.min( withdrawable, - ((liquidityData.liquidationThreshold - liquidityData.debt).percentDiv( + ((liquidityData.maxDebtEth - liquidityData.debtEth).percentDiv( assetData.liquidationThreshold ) * assetData.tokenUnit) / assetData.underlyingPrice ); @@ -220,12 +220,12 @@ abstract contract UsersLens is IndexesLens { ) : _getUserHypotheticalLiquidityDataForAsset(_user, poolToken, oracle, 0, 0); - liquidityData.collateral += assetData.collateral; - liquidityData.maxDebt += assetData.collateral.percentMul(assetData.ltv); - liquidityData.liquidationThreshold += assetData.collateral.percentMul( + liquidityData.collateralEth += assetData.collateralEth; + liquidityData.borrowableEth += assetData.collateralEth.percentMul(assetData.ltv); + liquidityData.maxDebtEth += assetData.collateralEth.percentMul( assetData.liquidationThreshold ); - liquidityData.debt += assetData.debt; + liquidityData.debtEth += assetData.debtEth; } } @@ -268,9 +268,9 @@ abstract contract UsersLens is IndexesLens { _withdrawnAmount, _borrowedAmount ); - if (liquidityData.debt == 0) return type(uint256).max; + if (liquidityData.debtEth == 0) return type(uint256).max; - return liquidityData.liquidationThreshold.wadDiv(liquidityData.debt); + return liquidityData.maxDebtEth.wadDiv(liquidityData.debtEth); } /// @notice Returns whether a liquidation can be performed on a given user, based on their health factor. @@ -472,10 +472,9 @@ abstract contract UsersLens is IndexesLens { indexes.poolBorrowIndex ); - assetData.debt = ((totalDebtBalance + _borrowedAmount) * assetData.underlyingPrice).divUp( - assetData.tokenUnit - ); - assetData.collateral = + assetData.debtEth = ((totalDebtBalance + _borrowedAmount) * assetData.underlyingPrice) + .divUp(assetData.tokenUnit); + assetData.collateralEth = ((totalCollateralBalance.zeroFloorSub(_withdrawnAmount)) * assetData.underlyingPrice) / assetData.tokenUnit; } diff --git a/src/aave-v2/libraries/InterestRatesModel.sol b/src/aave-v2/libraries/InterestRatesModel.sol index 09cf3eef5..4ed5c333f 100644 --- a/src/aave-v2/libraries/InterestRatesModel.sol +++ b/src/aave-v2/libraries/InterestRatesModel.sol @@ -75,23 +75,24 @@ library InterestRatesModel { p2pGrowthFactor + (growthFactors.poolBorrowGrowthFactor - p2pGrowthFactor).percentMul(_reserveFactor); } else { - // The case poolSupplyGrowthFactor > poolBorrowGrowthFactor happens because someone has done a flashloan on Aave: - // the peer-to-peer growth factors are set to the pool borrow growth factor. + // The case poolSupplyGrowthFactor > poolBorrowGrowthFactor happens because someone has done a flashloan on Aave, or because the interests + // generated by the stable rate borrowing are high (making the supply rate higher than the variable borrow rate). In this case the peer-to-peer + // growth factors are set to the pool borrow growth factor. growthFactors.p2pSupplyGrowthFactor = growthFactors.poolBorrowGrowthFactor; growthFactors.p2pBorrowGrowthFactor = growthFactors.poolBorrowGrowthFactor; } } - /// @notice Computes and returns the new peer-to-peer supply index of a market given its parameters. + /// @notice Computes and returns the new peer-to-peer supply/borrow index of a market given its parameters. /// @param _params The computation parameters. - /// @return newP2PSupplyIndex The updated peer-to-peer index (in ray). - function computeP2PSupplyIndex(P2PIndexComputeParams memory _params) + /// @return newP2PIndex The updated peer-to-peer index (in ray). + function computeP2PIndex(P2PIndexComputeParams memory _params) internal pure - returns (uint256 newP2PSupplyIndex) + returns (uint256 newP2PIndex) { if (_params.p2pAmount == 0 || _params.p2pDelta == 0) { - newP2PSupplyIndex = _params.lastP2PIndex.rayMul(_params.p2pGrowthFactor); + newP2PIndex = _params.lastP2PIndex.rayMul(_params.p2pGrowthFactor); } else { uint256 shareOfTheDelta = Math.min( _params.p2pDelta.rayMul(_params.lastPoolIndex).rayDiv( @@ -100,32 +101,7 @@ library InterestRatesModel { WadRayMath.RAY // To avoid shareOfTheDelta > 1 with rounding errors. ); // In ray. - newP2PSupplyIndex = _params.lastP2PIndex.rayMul( - (WadRayMath.RAY - shareOfTheDelta).rayMul(_params.p2pGrowthFactor) + - shareOfTheDelta.rayMul(_params.poolGrowthFactor) - ); - } - } - - /// @notice Computes and returns the new peer-to-peer borrow index of a market given its parameters. - /// @param _params The computation parameters. - /// @return newP2PBorrowIndex The updated peer-to-peer index (in ray). - function computeP2PBorrowIndex(P2PIndexComputeParams memory _params) - internal - pure - returns (uint256 newP2PBorrowIndex) - { - if (_params.p2pAmount == 0 || _params.p2pDelta == 0) { - newP2PBorrowIndex = _params.lastP2PIndex.rayMul(_params.p2pGrowthFactor); - } else { - uint256 shareOfTheDelta = Math.min( - _params.p2pDelta.rayMul(_params.lastPoolIndex).rayDiv( - _params.p2pAmount.rayMul(_params.lastP2PIndex) - ), // Using ray division of an amount in underlying decimals by an amount in underlying decimals yields a value in ray. - WadRayMath.RAY // To avoid shareOfTheDelta > 1 with rounding errors. - ); // In ray. - - newP2PBorrowIndex = _params.lastP2PIndex.rayMul( + newP2PIndex = _params.lastP2PIndex.rayMul( (WadRayMath.RAY - shareOfTheDelta).rayMul(_params.p2pGrowthFactor) + shareOfTheDelta.rayMul(_params.poolGrowthFactor) ); diff --git a/src/aave-v2/libraries/Types.sol b/src/aave-v2/libraries/Types.sol index f617e2558..451e1e0d0 100644 --- a/src/aave-v2/libraries/Types.sol +++ b/src/aave-v2/libraries/Types.sol @@ -18,20 +18,20 @@ library Types { /// STRUCTS /// struct SupplyBalance { - uint256 inP2P; // In peer-to-peer supply scaled unit, a unit that grows in underlying value, to keep track of the interests earned by suppliers in peer-to-peer. Multiply by the peer-to-peer supply index to get the underlying amount. - uint256 onPool; // In pool supply scaled unit. Multiply by the pool supply index to get the underlying amount. + uint256 inP2P; // In peer-to-peer supply unit, a unit that grows in underlying value, to keep track of the interests earned by suppliers in peer-to-peer. Multiply by the peer-to-peer supply index to get the underlying amount. + uint256 onPool; // In pool supply unit. Multiply by the pool supply index to get the underlying amount. } struct BorrowBalance { - uint256 inP2P; // In peer-to-peer borrow scaled unit, a unit that grows in underlying value, to keep track of the interests paid by borrowers in peer-to-peer. Multiply by the peer-to-peer borrow index to get the underlying amount. - uint256 onPool; // In pool borrow scaled unit, a unit that grows in value, to keep track of the debt increase when borrowers are on Aave. Multiply by the pool borrow index to get the underlying amount. + uint256 inP2P; // In peer-to-peer borrow unit, a unit that grows in underlying value, to keep track of the interests paid by borrowers in peer-to-peer. Multiply by the peer-to-peer borrow index to get the underlying amount. + uint256 onPool; // In pool borrow unit, a unit that grows in value, to keep track of the debt increase when borrowers are on Aave. Multiply by the pool borrow index to get the underlying amount. } struct Indexes { - uint256 p2pSupplyIndex; // The peer-to-peer supply index (in ray), used to multiply the scaled peer-to-peer supply balance and get the peer-to-peer supply balance (in underlying). - uint256 p2pBorrowIndex; // The peer-to-peer borrow index (in ray), used to multiply the scaled peer-to-peer borrow balance and get the peer-to-peer borrow balance (in underlying). - uint256 poolSupplyIndex; // The pool supply index (in ray), used to multiply the scaled pool supply balance and get the pool supply balance (in underlying). - uint256 poolBorrowIndex; // The pool borrow index (in ray), used to multiply the scaled pool borrow balance and get the pool borrow balance (in underlying). + uint256 p2pSupplyIndex; // The peer-to-peer supply index (in ray), used to multiply the peer-to-peer supply scaled balance and get the peer-to-peer supply balance (in underlying). + uint256 p2pBorrowIndex; // The peer-to-peer borrow index (in ray), used to multiply the peer-to-peer borrow scaled balance and get the peer-to-peer borrow balance (in underlying). + uint256 poolSupplyIndex; // The pool supply index (in ray), used to multiply the pool supply scaled balance and get the pool supply balance (in underlying). + uint256 poolBorrowIndex; // The pool borrow index (in ray), used to multiply the pool borrow scaled balance and get the pool borrow balance (in underlying). } // Max gas to consume during the matching process for supply, borrow, withdraw and repay functions. @@ -55,15 +55,15 @@ library Types { uint256 liquidationThreshold; // The liquidation threshold applied on this token (in basis point). uint256 ltv; // The LTV applied on this token (in basis point). uint256 underlyingPrice; // The price of the token (in ETH). - uint256 collateral; // The collateral value of the asset (in ETH). - uint256 debt; // The debt value of the asset (in ETH). + uint256 collateralEth; // The collateral value of the asset (in ETH). + uint256 debtEth; // The debt value of the asset (in ETH). } struct LiquidityData { - uint256 collateral; // The collateral value (in ETH). - uint256 maxDebt; // The max debt value (in ETH). - uint256 liquidationThreshold; // The liquidation threshold value (in ETH). - uint256 debt; // The debt value (in ETH). + uint256 collateralEth; // The collateral value (in ETH). + uint256 borrowableEth; // The maximum debt value allowed to borrow (in ETH). + uint256 maxDebtEth; // The maximum debt value allowed before being liquidatable (in ETH). + uint256 debtEth; // The debt value (in ETH). } // Variables are packed together to save gas (will not exceed their limit during Morpho's lifetime). @@ -92,13 +92,4 @@ library Types { bool isLiquidateBorrowPaused; // Whether the liquidatation on this market as borrow is paused or not. bool isDeprecated; // Whether a market is deprecated or not. } - - struct LiquidityStackVars { - address poolToken; - uint256 poolTokensLength; - bytes32 userMarkets; - bytes32 borrowMask; - address underlyingToken; - uint256 underlyingPrice; - } } diff --git a/src/compound/IncentivesVault.sol b/src/compound/IncentivesVault.sol deleted file mode 100644 index 1f46565d0..000000000 --- a/src/compound/IncentivesVault.sol +++ /dev/null @@ -1,155 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity 0.8.13; - -import "./interfaces/IIncentivesVault.sol"; -import "./interfaces/IOracle.sol"; -import "./interfaces/IMorpho.sol"; - -import "@morpho-dao/morpho-utils/math/PercentageMath.sol"; -import "@rari-capital/solmate/src/utils/SafeTransferLib.sol"; - -import "@openzeppelin/contracts/access/Ownable.sol"; - -/// @title IncentivesVault. -/// @author Morpho Labs. -/// @custom:contact security@morpho.xyz -/// @notice Contract handling Morpho incentives. -contract IncentivesVault is IIncentivesVault, Ownable { - using SafeTransferLib for ERC20; - using PercentageMath for uint256; - - /// STORAGE /// - - uint256 public constant MAX_BASIS_POINTS = 100_00; - - IMorpho public immutable morpho; // The address of the main Morpho contract. - IComptroller public immutable comptroller; // Compound's comptroller proxy. - ERC20 public immutable morphoToken; // The MORPHO token. - - IOracle public oracle; // The oracle used to get the price of MORPHO tokens against COMP tokens. - address public incentivesTreasuryVault; // The address of the incentives treasury vault. - uint256 public bonus; // The bonus percentage of MORPHO tokens to give to the user. - bool public isPaused; // Whether the trade of COMP rewards for MORPHO rewards is paused or not. - - /// EVENTS /// - - /// @notice Emitted when the oracle is set. - /// @param newOracle The new oracle set. - event OracleSet(address newOracle); - - /// @notice Emitted when the incentives treasury vault is set. - /// @param newIncentivesTreasuryVault The address of the incentives treasury vault. - event IncentivesTreasuryVaultSet(address newIncentivesTreasuryVault); - - /// @notice Emitted when the reward bonus is set. - /// @param newBonus The new bonus set. - event BonusSet(uint256 newBonus); - - /// @notice Emitted when the pause status is changed. - /// @param newStatus The new newStatus set. - event PauseStatusSet(bool newStatus); - - /// @notice Emitted when tokens are transferred to the DAO. - /// @param token The address of the token transferred. - /// @param amount The amount of token transferred to the DAO. - event TokensTransferred(address indexed token, uint256 amount); - - /// @notice Emitted when COMP tokens are traded for MORPHO tokens. - /// @param receiver The address of the receiver. - /// @param compAmount The amount of COMP traded. - /// @param morphoAmount The amount of MORPHO sent. - event CompTokensTraded(address indexed receiver, uint256 compAmount, uint256 morphoAmount); - - /// ERRORS /// - - /// @notice Thrown when an other address than Morpho triggers the function. - error OnlyMorpho(); - - /// @notice Thrown when the vault is paused. - error VaultIsPaused(); - - /// @notice Thrown when the input is above the max basis points value (100%). - error ExceedsMaxBasisPoints(); - - /// CONSTRUCTOR /// - - /// @notice Constructs the IncentivesVault contract. - /// @param _comptroller The Compound comptroller. - /// @param _morpho The main Morpho contract. - /// @param _morphoToken The MORPHO token. - /// @param _incentivesTreasuryVault The address of the incentives treasury vault. - /// @param _oracle The oracle. - constructor( - IComptroller _comptroller, - IMorpho _morpho, - ERC20 _morphoToken, - address _incentivesTreasuryVault, - IOracle _oracle - ) { - morpho = _morpho; - comptroller = _comptroller; - morphoToken = _morphoToken; - incentivesTreasuryVault = _incentivesTreasuryVault; - oracle = _oracle; - } - - /// EXTERNAL /// - - /// @notice Sets the oracle. - /// @param _newOracle The address of the new oracle. - function setOracle(IOracle _newOracle) external onlyOwner { - oracle = _newOracle; - emit OracleSet(address(_newOracle)); - } - - /// @notice Sets the incentives treasury vault. - /// @param _newIncentivesTreasuryVault The address of the incentives treasury vault. - function setIncentivesTreasuryVault(address _newIncentivesTreasuryVault) external onlyOwner { - incentivesTreasuryVault = _newIncentivesTreasuryVault; - emit IncentivesTreasuryVaultSet(_newIncentivesTreasuryVault); - } - - /// @notice Sets the reward bonus. - /// @param _newBonus The new reward bonus. - function setBonus(uint256 _newBonus) external onlyOwner { - if (_newBonus > MAX_BASIS_POINTS) revert ExceedsMaxBasisPoints(); - - bonus = _newBonus; - emit BonusSet(_newBonus); - } - - /// @notice Sets the pause status. - /// @param _newStatus The new pause status. - function setPauseStatus(bool _newStatus) external onlyOwner { - isPaused = _newStatus; - emit PauseStatusSet(_newStatus); - } - - /// @notice Transfers the specified token to the DAO. - /// @param _token The address of the token to transfer. - /// @param _amount The amount of token to transfer to the DAO. - function transferTokensToDao(address _token, uint256 _amount) external onlyOwner { - ERC20(_token).safeTransfer(incentivesTreasuryVault, _amount); - emit TokensTransferred(_token, _amount); - } - - /// @notice Trades COMP tokens for MORPHO tokens and sends them to the receiver. - /// @param _receiver The address of the receiver. - /// @param _amount The amount to transfer to the receiver. - function tradeCompForMorphoTokens(address _receiver, uint256 _amount) external { - if (msg.sender != address(morpho)) revert OnlyMorpho(); - if (isPaused) revert VaultIsPaused(); - // Transfer COMP to the DAO. - ERC20(comptroller.getCompAddress()).safeTransferFrom( - msg.sender, - incentivesTreasuryVault, - _amount - ); - - // Add a bonus on MORPHO rewards. - uint256 amountOut = oracle.consult(_amount).percentAdd(bonus); - morphoToken.safeTransfer(_receiver, amountOut); - - emit CompTokensTraded(_receiver, _amount, amountOut); - } -} diff --git a/src/compound/InterestRatesManager.sol b/src/compound/InterestRatesManager.sol index 5da253633..164a2d430 100644 --- a/src/compound/InterestRatesManager.sol +++ b/src/compound/InterestRatesManager.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.13; import "./interfaces/IInterestRatesManager.sol"; +import "./libraries/InterestRatesModel.sol"; import "@morpho-dao/morpho-utils/math/PercentageMath.sol"; -import "./libraries/CompoundMath.sol"; import "./MorphoStorage.sol"; @@ -15,7 +15,6 @@ import "./MorphoStorage.sol"; /// @dev This contract inherits from MorphoStorage so that Morpho can delegate calls to this contract. contract InterestRatesManager is IInterestRatesManager, MorphoStorage { using PercentageMath for uint256; - using CompoundMath for uint256; /// STRUCTS /// @@ -24,8 +23,7 @@ contract InterestRatesManager is IInterestRatesManager, MorphoStorage { uint256 lastP2PBorrowIndex; // The peer-to-peer borrow index at last update. uint256 poolSupplyIndex; // The current pool supply index. uint256 poolBorrowIndex; // The current pool borrow index. - uint256 lastPoolSupplyIndex; // The pool supply index at last update. - uint256 lastPoolBorrowIndex; // The pool borrow index at last update. + Types.LastPoolIndexes lastPoolIndexes; // The pool indexes at last update. uint256 reserveFactor; // The reserve factor percentage (10 000 = 100%). uint256 p2pIndexCursor; // The peer-to-peer index cursor (10 000 = 100%). Types.Delta delta; // The deltas and peer-to-peer amounts. @@ -54,41 +52,40 @@ contract InterestRatesManager is IInterestRatesManager, MorphoStorage { function updateP2PIndexes(address _poolToken) external { Types.LastPoolIndexes storage poolIndexes = lastPoolIndexes[_poolToken]; - if (block.number > poolIndexes.lastUpdateBlockNumber) { - Types.MarketParameters storage marketParams = marketParameters[_poolToken]; - - uint256 poolSupplyIndex = ICToken(_poolToken).exchangeRateCurrent(); - uint256 poolBorrowIndex = ICToken(_poolToken).borrowIndex(); - - Params memory params = Params( - p2pSupplyIndex[_poolToken], - p2pBorrowIndex[_poolToken], - poolSupplyIndex, - poolBorrowIndex, - poolIndexes.lastSupplyPoolIndex, - poolIndexes.lastBorrowPoolIndex, - marketParams.reserveFactor, - marketParams.p2pIndexCursor, - deltas[_poolToken] - ); - - (uint256 newP2PSupplyIndex, uint256 newP2PBorrowIndex) = _computeP2PIndexes(params); - - p2pSupplyIndex[_poolToken] = newP2PSupplyIndex; - p2pBorrowIndex[_poolToken] = newP2PBorrowIndex; - - poolIndexes.lastUpdateBlockNumber = uint32(block.number); - poolIndexes.lastSupplyPoolIndex = uint112(poolSupplyIndex); - poolIndexes.lastBorrowPoolIndex = uint112(poolBorrowIndex); - - emit P2PIndexesUpdated( - _poolToken, - newP2PSupplyIndex, - newP2PBorrowIndex, - poolSupplyIndex, - poolBorrowIndex - ); - } + if (block.number == poolIndexes.lastUpdateBlockNumber) return; + + Types.MarketParameters memory marketParams = marketParameters[_poolToken]; + + uint256 poolSupplyIndex = ICToken(_poolToken).exchangeRateCurrent(); + uint256 poolBorrowIndex = ICToken(_poolToken).borrowIndex(); + + (uint256 newP2PSupplyIndex, uint256 newP2PBorrowIndex) = _computeP2PIndexes( + Params({ + lastP2PSupplyIndex: p2pSupplyIndex[_poolToken], + lastP2PBorrowIndex: p2pBorrowIndex[_poolToken], + poolSupplyIndex: poolSupplyIndex, + poolBorrowIndex: poolBorrowIndex, + lastPoolIndexes: poolIndexes, + reserveFactor: marketParams.reserveFactor, + p2pIndexCursor: marketParams.p2pIndexCursor, + delta: deltas[_poolToken] + }) + ); + + p2pSupplyIndex[_poolToken] = newP2PSupplyIndex; + p2pBorrowIndex[_poolToken] = newP2PBorrowIndex; + + poolIndexes.lastUpdateBlockNumber = uint32(block.number); + poolIndexes.lastSupplyPoolIndex = uint112(poolSupplyIndex); + poolIndexes.lastBorrowPoolIndex = uint112(poolBorrowIndex); + + emit P2PIndexesUpdated( + _poolToken, + newP2PSupplyIndex, + newP2PBorrowIndex, + poolSupplyIndex, + poolBorrowIndex + ); } /// INTERNAL /// @@ -102,69 +99,34 @@ contract InterestRatesManager is IInterestRatesManager, MorphoStorage { pure returns (uint256 newP2PSupplyIndex, uint256 newP2PBorrowIndex) { - // Compute pool growth factors - - uint256 poolSupplyGrowthFactor = _params.poolSupplyIndex.div(_params.lastPoolSupplyIndex); - uint256 poolBorrowGrowthFactor = _params.poolBorrowIndex.div(_params.lastPoolBorrowIndex); - - // Compute peer-to-peer growth factors. - - uint256 p2pSupplyGrowthFactor; - uint256 p2pBorrowGrowthFactor; - if (poolSupplyGrowthFactor <= poolBorrowGrowthFactor) { - uint256 p2pGrowthFactor = PercentageMath.weightedAvg( - poolSupplyGrowthFactor, - poolBorrowGrowthFactor, - _params.p2pIndexCursor - ); - - p2pSupplyGrowthFactor = - p2pGrowthFactor - - (p2pGrowthFactor - poolSupplyGrowthFactor).percentMul(_params.reserveFactor); - p2pBorrowGrowthFactor = - p2pGrowthFactor + - (poolBorrowGrowthFactor - p2pGrowthFactor).percentMul(_params.reserveFactor); - } else { - // The case poolSupplyGrowthFactor > poolBorrowGrowthFactor happens because someone sent underlying tokens to the - // cToken contract: the peer-to-peer growth factors are set to the pool borrow growth factor. - p2pSupplyGrowthFactor = poolBorrowGrowthFactor; - p2pBorrowGrowthFactor = poolBorrowGrowthFactor; - } - - // Compute new peer-to-peer supply index - - if (_params.delta.p2pSupplyAmount == 0 || _params.delta.p2pSupplyDelta == 0) { - newP2PSupplyIndex = _params.lastP2PSupplyIndex.mul(p2pSupplyGrowthFactor); - } else { - uint256 shareOfTheDelta = CompoundMath.min( - (_params.delta.p2pSupplyDelta.mul(_params.lastPoolSupplyIndex)).div( - (_params.delta.p2pSupplyAmount).mul(_params.lastP2PSupplyIndex) - ), - WAD // To avoid shareOfTheDelta > 1 with rounding errors. - ); - - newP2PSupplyIndex = _params.lastP2PSupplyIndex.mul( - (WAD - shareOfTheDelta).mul(p2pSupplyGrowthFactor) + - shareOfTheDelta.mul(poolSupplyGrowthFactor) - ); - } - - // Compute new peer-to-peer borrow index - - if (_params.delta.p2pBorrowAmount == 0 || _params.delta.p2pBorrowDelta == 0) { - newP2PBorrowIndex = _params.lastP2PBorrowIndex.mul(p2pBorrowGrowthFactor); - } else { - uint256 shareOfTheDelta = CompoundMath.min( - (_params.delta.p2pBorrowDelta.mul(_params.lastPoolBorrowIndex)).div( - (_params.delta.p2pBorrowAmount).mul(_params.lastP2PBorrowIndex) - ), - WAD // To avoid shareOfTheDelta > 1 with rounding errors. - ); - - newP2PBorrowIndex = _params.lastP2PBorrowIndex.mul( - (WAD - shareOfTheDelta).mul(p2pBorrowGrowthFactor) + - shareOfTheDelta.mul(poolBorrowGrowthFactor) - ); - } + InterestRatesModel.GrowthFactors memory growthFactors = InterestRatesModel + .computeGrowthFactors( + _params.poolSupplyIndex, + _params.poolBorrowIndex, + _params.lastPoolIndexes, + _params.p2pIndexCursor, + _params.reserveFactor + ); + + newP2PSupplyIndex = InterestRatesModel.computeP2PIndex( + InterestRatesModel.P2PIndexComputeParams({ + poolGrowthFactor: growthFactors.poolSupplyGrowthFactor, + p2pGrowthFactor: growthFactors.p2pSupplyGrowthFactor, + lastPoolIndex: _params.lastPoolIndexes.lastSupplyPoolIndex, + lastP2PIndex: _params.lastP2PSupplyIndex, + p2pDelta: _params.delta.p2pSupplyDelta, + p2pAmount: _params.delta.p2pSupplyAmount + }) + ); + newP2PBorrowIndex = InterestRatesModel.computeP2PIndex( + InterestRatesModel.P2PIndexComputeParams({ + poolGrowthFactor: growthFactors.poolBorrowGrowthFactor, + p2pGrowthFactor: growthFactors.p2pBorrowGrowthFactor, + lastPoolIndex: _params.lastPoolIndexes.lastBorrowPoolIndex, + lastP2PIndex: _params.lastP2PBorrowIndex, + p2pDelta: _params.delta.p2pBorrowDelta, + p2pAmount: _params.delta.p2pBorrowAmount + }) + ); } } diff --git a/src/compound/Morpho.sol b/src/compound/Morpho.sol index e3353b706..a994c0fe8 100644 --- a/src/compound/Morpho.sol +++ b/src/compound/Morpho.sol @@ -145,10 +145,10 @@ contract Morpho is MorphoGovernance { } /// @notice Claims rewards for the given assets. + /// @dev The incentives vault will never be implemented. Thus the second parameter of this function becomes useless. /// @param _cTokenAddresses The cToken addresses to claim rewards from. - /// @param _tradeForMorphoToken Whether or not to trade COMP tokens for MORPHO tokens. /// @return amountOfRewards The amount of rewards claimed (in COMP). - function claimRewards(address[] calldata _cTokenAddresses, bool _tradeForMorphoToken) + function claimRewards(address[] calldata _cTokenAddresses, bool) external nonReentrant returns (uint256 amountOfRewards) @@ -157,16 +157,10 @@ contract Morpho is MorphoGovernance { amountOfRewards = rewardsManager.claimRewards(_cTokenAddresses, msg.sender); if (amountOfRewards > 0) { - ERC20 comp = ERC20(comptroller.getCompAddress()); - comptroller.claimComp(address(this), _cTokenAddresses); + ERC20(comptroller.getCompAddress()).safeTransfer(msg.sender, amountOfRewards); - if (_tradeForMorphoToken) { - comp.safeApprove(address(incentivesVault), amountOfRewards); - incentivesVault.tradeCompForMorphoTokens(msg.sender, amountOfRewards); - } else comp.safeTransfer(msg.sender, amountOfRewards); - - emit RewardsClaimed(msg.sender, amountOfRewards, _tradeForMorphoToken); + emit RewardsClaimed(msg.sender, amountOfRewards, false); } } diff --git a/src/compound/MorphoGovernance.sol b/src/compound/MorphoGovernance.sol index e87ed5536..59be26d22 100644 --- a/src/compound/MorphoGovernance.sol +++ b/src/compound/MorphoGovernance.sol @@ -25,10 +25,6 @@ abstract contract MorphoGovernance is MorphoUtils { /// @param _newTreasuryVaultAddress The new address of the `treasuryVault`. event TreasuryVaultSet(address indexed _newTreasuryVaultAddress); - /// @notice Emitted when the address of the `incentivesVault` is set. - /// @param _newIncentivesVaultAddress The new address of the `incentivesVault`. - event IncentivesVaultSet(address indexed _newIncentivesVaultAddress); - /// @notice Emitted when the `positionsManager` is set. /// @param _positionsManager The new address of the `positionsManager`. event PositionsManagerSet(address indexed _positionsManager); @@ -107,13 +103,13 @@ abstract contract MorphoGovernance is MorphoUtils { /// @notice Emitted when a new market is created. /// @param _poolToken The address of the market that has been created. /// @param _reserveFactor The reserve factor set for this market. - /// @param _poolToken The P2P index cursor set for this market. + /// @param _p2pIndexCursor The P2P index cursor set for this market. event MarketCreated(address indexed _poolToken, uint16 _reserveFactor, uint16 _p2pIndexCursor); /// ERRORS /// - /// @notice Thrown when the creation of a market failed on Compound. - error MarketCreationFailedOnCompound(); + /// @notice Thrown when the creation of a market failed on Compound and kicks back Compound error code. + error MarketCreationFailedOnCompound(uint256 errorCode); /// @notice Thrown when the input is above the max basis points value (100%). error ExceedsMaxBasisPoints(); @@ -127,6 +123,12 @@ abstract contract MorphoGovernance is MorphoUtils { /// @notice Thrown when the address is the zero address. error ZeroAddress(); + /// @notice Thrown when market borrow is not paused. + error BorrowNotPaused(); + + /// @notice Thrown when market is deprecated. + error MarketIsDeprecated(); + /// UPGRADE /// /// @notice Initializes the Morpho contract. @@ -215,13 +217,6 @@ abstract contract MorphoGovernance is MorphoUtils { emit TreasuryVaultSet(_treasuryVault); } - /// @notice Sets the `incentivesVault`. - /// @param _incentivesVault The new `incentivesVault`. - function setIncentivesVault(IIncentivesVault _incentivesVault) external onlyOwner { - incentivesVault = _incentivesVault; - emit IncentivesVaultSet(address(_incentivesVault)); - } - /// @dev Sets `dustThreshold`. /// @param _dustThreshold The new `dustThreshold`. function setDustThreshold(uint256 _dustThreshold) external onlyOwner { @@ -279,6 +274,7 @@ abstract contract MorphoGovernance is MorphoUtils { onlyOwner isMarketCreated(_poolToken) { + if (!_isPaused && marketPauseStatus[_poolToken].isDeprecated) revert MarketIsDeprecated(); marketPauseStatus[_poolToken].isBorrowPaused = _isPaused; emit IsBorrowPausedSet(_poolToken, _isPaused); } @@ -374,6 +370,7 @@ abstract contract MorphoGovernance is MorphoUtils { onlyOwner isMarketCreated(_poolToken) { + if (!marketPauseStatus[_poolToken].isBorrowPaused) revert BorrowNotPaused(); marketPauseStatus[_poolToken].isDeprecated = _isDeprecated; emit IsDeprecatedSet(_poolToken, _isDeprecated); } @@ -440,7 +437,7 @@ abstract contract MorphoGovernance is MorphoUtils { address[] memory marketToEnter = new address[](1); marketToEnter[0] = _poolToken; uint256[] memory results = comptroller.enterMarkets(marketToEnter); - if (results[0] != 0) revert MarketCreationFailedOnCompound(); + if (results[0] != 0) revert MarketCreationFailedOnCompound(results[0]); // Same initial index as Compound. uint256 initialIndex; @@ -470,17 +467,24 @@ abstract contract MorphoGovernance is MorphoUtils { Types.MarketPauseStatus storage pause = marketPauseStatus[_poolToken]; pause.isSupplyPaused = _isPaused; - pause.isBorrowPaused = _isPaused; - pause.isWithdrawPaused = _isPaused; - pause.isRepayPaused = _isPaused; - pause.isLiquidateCollateralPaused = _isPaused; - pause.isLiquidateBorrowPaused = _isPaused; - emit IsSupplyPausedSet(_poolToken, _isPaused); - emit IsBorrowPausedSet(_poolToken, _isPaused); + + // Note that pause.isDeprecated implies pause.isBorrowPaused. + if (!pause.isDeprecated) { + pause.isBorrowPaused = _isPaused; + emit IsBorrowPausedSet(_poolToken, _isPaused); + } + + pause.isWithdrawPaused = _isPaused; emit IsWithdrawPausedSet(_poolToken, _isPaused); + + pause.isRepayPaused = _isPaused; emit IsRepayPausedSet(_poolToken, _isPaused); + + pause.isLiquidateCollateralPaused = _isPaused; emit IsLiquidateCollateralPausedSet(_poolToken, _isPaused); + + pause.isLiquidateBorrowPaused = _isPaused; emit IsLiquidateBorrowPausedSet(_poolToken, _isPaused); } } diff --git a/src/compound/MorphoStorage.sol b/src/compound/MorphoStorage.sol index 811e2d07a..2f9523860 100644 --- a/src/compound/MorphoStorage.sol +++ b/src/compound/MorphoStorage.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.13; import "./interfaces/compound/ICompound.sol"; import "./interfaces/IPositionsManager.sol"; -import "./interfaces/IIncentivesVault.sol"; import "./interfaces/IRewardsManager.sol"; import "./interfaces/IInterestRatesManager.sol"; @@ -53,7 +52,7 @@ abstract contract MorphoStorage is OwnableUpgradeable, ReentrancyGuardUpgradeabl /// CONTRACTS AND ADDRESSES /// IPositionsManager public positionsManager; - IIncentivesVault public incentivesVault; + address public incentivesVault; // Deprecated. IRewardsManager public rewardsManager; IInterestRatesManager public interestRatesManager; IComptroller public comptroller; diff --git a/src/compound/MorphoUtils.sol b/src/compound/MorphoUtils.sol index 0fd793a11..aa915aa96 100644 --- a/src/compound/MorphoUtils.sol +++ b/src/compound/MorphoUtils.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.13; import "@rari-capital/solmate/src/utils/SafeTransferLib.sol"; -import "@openzeppelin/contracts/utils/math/Math.sol"; -import "./libraries/CompoundMath.sol"; +import "@morpho-dao/morpho-utils/math/Math.sol"; +import "@morpho-dao/morpho-utils/math/CompoundMath.sol"; import "@morpho-dao/morpho-utils/DelegateCall.sol"; import "./MorphoStorage.sol"; @@ -38,18 +38,14 @@ abstract contract MorphoUtils is MorphoStorage { /// @notice Returns all markets entered by a given user. /// @param _user The address of the user. - /// @return enteredMarkets_ The list of markets entered by this user. - function getEnteredMarkets(address _user) - external - view - returns (address[] memory enteredMarkets_) - { + /// @return The list of markets entered by this user. + function getEnteredMarkets(address _user) external view returns (address[] memory) { return enteredMarkets[_user]; } /// @notice Returns all created markets. - /// @return marketsCreated_ The list of market addresses. - function getAllMarkets() external view returns (address[] memory marketsCreated_) { + /// @return The list of market addresses. + function getAllMarkets() external view returns (address[] memory) { return marketsCreated; } @@ -94,7 +90,7 @@ abstract contract MorphoUtils is MorphoStorage { } /// @notice Updates the peer-to-peer indexes. - /// @dev Note: This function updates the exchange rate on Compound. As a consequence only a call to exchangeRatesStored() is necessary to get the most up to date exchange rate. + /// @dev Note: This function updates the exchange rate on Compound. As a consequence only a call to exchangeRateStored() is necessary to get the most up to date exchange rate. /// @param _poolToken The address of the market to update. function updateP2PIndexes(address _poolToken) external isMarketCreated(_poolToken) { _updateP2PIndexes(_poolToken); @@ -103,7 +99,7 @@ abstract contract MorphoUtils is MorphoStorage { /// INTERNAL /// /// @dev Updates the peer-to-peer indexes. - /// @dev Note: This function updates the exchange rate on Compound. As a consequence only a call to exchangeRatesStored() is necessary to get the most up to date exchange rate. + /// @dev Note: This function updates the exchange rate on Compound. As a consequence only a call to exchangeRateStored() is necessary to get the most up to date exchange rate. /// @param _poolToken The address of the market to update. function _updateP2PIndexes(address _poolToken) internal { address(interestRatesManager).functionDelegateCall( @@ -129,23 +125,22 @@ abstract contract MorphoUtils is MorphoStorage { uint256 numberOfEnteredMarkets = enteredMarkets[_user].length; Types.AssetLiquidityData memory assetData; - uint256 maxDebtValue; - uint256 debtValue; + uint256 maxDebtUsd; + uint256 debtUsd; uint256 i; while (i < numberOfEnteredMarkets) { address poolTokenEntered = enteredMarkets[_user][i]; assetData = _getUserLiquidityDataForAsset(_user, poolTokenEntered, oracle); - maxDebtValue += assetData.maxDebtValue; - debtValue += assetData.debtValue; + maxDebtUsd += assetData.maxDebtUsd; + debtUsd += assetData.debtUsd; if (_poolToken == poolTokenEntered) { - if (_borrowedAmount > 0) - debtValue += _borrowedAmount.mul(assetData.underlyingPrice); + if (_borrowedAmount > 0) debtUsd += _borrowedAmount.mul(assetData.underlyingPrice); if (_withdrawnAmount > 0) - maxDebtValue -= _withdrawnAmount.mul(assetData.underlyingPrice).mul( + maxDebtUsd -= _withdrawnAmount.mul(assetData.underlyingPrice).mul( assetData.collateralFactor ); } @@ -155,7 +150,7 @@ abstract contract MorphoUtils is MorphoStorage { } } - return debtValue > maxDebtValue; + return debtUsd > maxDebtUsd; } /// @notice Returns the data related to `_poolToken` for the `_user`. @@ -173,13 +168,13 @@ abstract contract MorphoUtils is MorphoStorage { if (assetData.underlyingPrice == 0) revert CompoundOracleFailed(); (, assetData.collateralFactor, ) = comptroller.markets(_poolToken); - assetData.collateralValue = _getUserSupplyBalanceInOf(_poolToken, _user).mul( + assetData.collateralUsd = _getUserSupplyBalanceInOf(_poolToken, _user).mul( assetData.underlyingPrice ); - assetData.debtValue = _getUserBorrowBalanceInOf(_poolToken, _user).mul( + assetData.debtUsd = _getUserBorrowBalanceInOf(_poolToken, _user).mul( assetData.underlyingPrice ); - assetData.maxDebtValue = assetData.collateralValue.mul(assetData.collateralFactor); + assetData.maxDebtUsd = assetData.collateralUsd.mul(assetData.collateralFactor); } /// @dev Returns the supply balance of `_user` in the `_poolToken` market. diff --git a/src/compound/PositionsManager.sol b/src/compound/PositionsManager.sol index 97d19ca28..d64e19fdd 100644 --- a/src/compound/PositionsManager.sol +++ b/src/compound/PositionsManager.sol @@ -14,6 +14,7 @@ contract PositionsManager is IPositionsManager, MatchingEngine { using DoubleLinkedList for DoubleLinkedList.List; using SafeTransferLib for ERC20; using CompoundMath for uint256; + using Math for uint256; /// EVENTS /// @@ -125,17 +126,17 @@ contract PositionsManager is IPositionsManager, MatchingEngine { /// @notice Thrown when the amount repaid during the liquidation is above what is allowed to be repaid. error AmountAboveWhatAllowedToRepay(); - /// @notice Thrown when the borrow on Compound failed. - error BorrowOnCompoundFailed(); + /// @notice Thrown when the borrow on Compound failed and throws back the Compound error code. + error BorrowOnCompoundFailed(uint256 errorCode); - /// @notice Thrown when the redeem on Compound failed . - error RedeemOnCompoundFailed(); + /// @notice Thrown when the redeem on Compound failed and throws back the Compound error code. + error RedeemOnCompoundFailed(uint256 errorCode); - /// @notice Thrown when the repay on Compound failed. - error RepayOnCompoundFailed(); + /// @notice Thrown when the repay on Compound failed and throws back the Compound error code. + error RepayOnCompoundFailed(uint256 errorCode); - /// @notice Thrown when the mint on Compound failed. - error MintOnCompoundFailed(); + /// @notice Thrown when the mint on Compound failed and throws back the Compound error code. + error MintOnCompoundFailed(uint256 errorCode); /// @notice Thrown when user is not a member of the market. error UserNotMemberOfMarket(); @@ -408,7 +409,7 @@ contract PositionsManager is IPositionsManager, MatchingEngine { if (remainingToBorrow > 0) { borrowerBorrowBalance.onPool += remainingToBorrow.div( lastPoolIndexes[_poolToken].lastBorrowPoolIndex - ); // In cdUnit. + ); // In pool borrow unit. _borrowFromPool(_poolToken, remainingToBorrow); } @@ -486,6 +487,7 @@ contract PositionsManager is IPositionsManager, MatchingEngine { address _borrower, uint256 _amount ) external { + if (_amount == 0) revert AmountIsZero(); if (!marketStatus[_poolTokenCollateral].isCreated) revert MarketNotCreated(); if (marketPauseStatus[_poolTokenCollateral].isLiquidateCollateralPaused) revert LiquidateCollateralIsPaused(); @@ -557,10 +559,10 @@ contract PositionsManager is IPositionsManager, MatchingEngine { _amount = Math.min( _amount, Math.min( - deltas.p2pSupplyAmount.mul(p2pSupplyIndex[_poolToken]).safeSub( + deltas.p2pSupplyAmount.mul(p2pSupplyIndex[_poolToken]).zeroFloorSub( deltas.p2pSupplyDelta.mul(poolSupplyIndex) ), - deltas.p2pBorrowAmount.mul(p2pBorrowIndex[_poolToken]).safeSub( + deltas.p2pBorrowAmount.mul(p2pBorrowIndex[_poolToken]).zeroFloorSub( deltas.p2pBorrowDelta.mul(lastPoolIndexes.lastBorrowPoolIndex) ) ) @@ -647,7 +649,7 @@ contract PositionsManager is IPositionsManager, MatchingEngine { Types.Delta storage delta = deltas[_poolToken]; vars.p2pSupplyIndex = p2pSupplyIndex[_poolToken]; - supplierSupplyBalance.inP2P -= CompoundMath.min( + supplierSupplyBalance.inP2P -= Math.min( supplierSupplyBalance.inP2P, vars.remainingToWithdraw.div(vars.p2pSupplyIndex) ); // In peer-to-peer supply unit. @@ -775,10 +777,10 @@ contract PositionsManager is IPositionsManager, MatchingEngine { if (vars.maxToRepayOnPool > vars.remainingToRepay) { vars.toRepay = vars.remainingToRepay; - borrowerBorrowBalance.onPool -= CompoundMath.min( + borrowerBorrowBalance.onPool -= Math.min( vars.borrowedOnPool, vars.toRepay.div(vars.poolBorrowIndex) - ); // In cdUnit. + ); // In pool borrow unit. _updateBorrowerInDS(_poolToken, _onBehalf); _repayToPool(_poolToken, underlyingToken, vars.toRepay); // Reverts on error. @@ -806,7 +808,7 @@ contract PositionsManager is IPositionsManager, MatchingEngine { vars.p2pSupplyIndex = p2pSupplyIndex[_poolToken]; vars.p2pBorrowIndex = p2pBorrowIndex[_poolToken]; - borrowerBorrowBalance.inP2P -= CompoundMath.min( + borrowerBorrowBalance.inP2P -= Math.min( borrowerBorrowBalance.inP2P, vars.remainingToRepay.div(vars.p2pBorrowIndex) ); // In peer-to-peer borrow unit. @@ -835,15 +837,15 @@ contract PositionsManager is IPositionsManager, MatchingEngine { if (vars.remainingToRepay > 0) { // Fee = (p2pBorrowAmount - p2pBorrowDelta) - (p2pSupplyAmount - p2pSupplyDelta). // No need to subtract p2pBorrowDelta as it is zero. - vars.feeToRepay = CompoundMath.safeSub( + vars.feeToRepay = Math.zeroFloorSub( delta.p2pBorrowAmount.mul(vars.p2pBorrowIndex), - delta.p2pSupplyAmount.mul(vars.p2pSupplyIndex).safeSub( + delta.p2pSupplyAmount.mul(vars.p2pSupplyIndex).zeroFloorSub( delta.p2pSupplyDelta.mul(ICToken(_poolToken).exchangeRateStored()) ) ); if (vars.feeToRepay > 0) { - uint256 feeRepaid = CompoundMath.min(vars.feeToRepay, vars.remainingToRepay); + uint256 feeRepaid = Math.min(vars.feeToRepay, vars.remainingToRepay); vars.remainingToRepay -= feeRepaid; delta.p2pBorrowAmount -= feeRepaid.div(vars.p2pBorrowIndex); emit P2PAmountsUpdated(_poolToken, delta.p2pSupplyAmount, delta.p2pBorrowAmount); @@ -877,7 +879,7 @@ contract PositionsManager is IPositionsManager, MatchingEngine { /// Breaking repay /// - // Unmote peer-to-peer suppliers. + // Demote peer-to-peer suppliers. if (vars.remainingToRepay > 0) { uint256 unmatched = _unmatchSuppliers( _poolToken, @@ -926,7 +928,8 @@ contract PositionsManager is IPositionsManager, MatchingEngine { ICEther(_poolToken).mint{value: _amount}(); } else { _underlyingToken.safeApprove(_poolToken, _amount); - if (ICToken(_poolToken).mint(_amount) != 0) revert MintOnCompoundFailed(); + uint256 errorCode = ICToken(_poolToken).mint(_amount); + if (errorCode != 0) revert MintOnCompoundFailed(errorCode); } } @@ -935,8 +938,11 @@ contract PositionsManager is IPositionsManager, MatchingEngine { /// @param _amount The amount of token (in underlying). function _withdrawFromPool(address _poolToken, uint256 _amount) internal { // Withdraw only what is possible. The remaining dust is taken from the contract balance. - _amount = CompoundMath.min(ICToken(_poolToken).balanceOfUnderlying(address(this)), _amount); - if (ICToken(_poolToken).redeemUnderlying(_amount) != 0) revert RedeemOnCompoundFailed(); + _amount = Math.min(ICToken(_poolToken).balanceOfUnderlying(address(this)), _amount); + + uint256 errorCode = ICToken(_poolToken).redeemUnderlying(_amount); + if (errorCode != 0) revert RedeemOnCompoundFailed(errorCode); + if (_poolToken == cEth) IWETH(address(wEth)).deposit{value: _amount}(); // Turn the ETH received in wETH. } @@ -944,7 +950,9 @@ contract PositionsManager is IPositionsManager, MatchingEngine { /// @param _poolToken The address of the pool token. /// @param _amount The amount of token (in underlying). function _borrowFromPool(address _poolToken, uint256 _amount) internal { - if ((ICToken(_poolToken).borrow(_amount) != 0)) revert BorrowOnCompoundFailed(); + uint256 errorCode = ICToken(_poolToken).borrow(_amount); + if (errorCode != 0) revert BorrowOnCompoundFailed(errorCode); + if (_poolToken == cEth) IWETH(address(wEth)).deposit{value: _amount}(); // Turn the ETH received in wETH. } @@ -969,7 +977,8 @@ contract PositionsManager is IPositionsManager, MatchingEngine { ICEther(_poolToken).repayBorrow{value: _amount}(); } else { _underlyingToken.safeApprove(_poolToken, _amount); - if (ICToken(_poolToken).repayBorrow(_amount) != 0) revert RepayOnCompoundFailed(); + uint256 errorCode = ICToken(_poolToken).repayBorrow(_amount); + if (errorCode != 0) revert RepayOnCompoundFailed(errorCode); } } } @@ -978,8 +987,9 @@ contract PositionsManager is IPositionsManager, MatchingEngine { /// @param _user The address of the user to update. /// @param _poolToken The address of the market to check. function _enterMarketIfNeeded(address _poolToken, address _user) internal { - if (!userMembership[_poolToken][_user]) { - userMembership[_poolToken][_user] = true; + mapping(address => bool) storage userMembership = userMembership[_poolToken]; + if (!userMembership[_user]) { + userMembership[_user] = true; enteredMarkets[_user].push(_poolToken); } } @@ -988,25 +998,29 @@ contract PositionsManager is IPositionsManager, MatchingEngine { /// @param _user The address of the user to update. /// @param _poolToken The address of the market to check. function _leaveMarketIfNeeded(address _poolToken, address _user) internal { + Types.SupplyBalance storage supplyBalance = supplyBalanceInOf[_poolToken][_user]; + Types.BorrowBalance storage borrowBalance = borrowBalanceInOf[_poolToken][_user]; + mapping(address => bool) storage userMembership = userMembership[_poolToken]; if ( - userMembership[_poolToken][_user] && - supplyBalanceInOf[_poolToken][_user].inP2P == 0 && - supplyBalanceInOf[_poolToken][_user].onPool == 0 && - borrowBalanceInOf[_poolToken][_user].inP2P == 0 && - borrowBalanceInOf[_poolToken][_user].onPool == 0 + userMembership[_user] && + supplyBalance.inP2P == 0 && + supplyBalance.onPool == 0 && + borrowBalance.inP2P == 0 && + borrowBalance.onPool == 0 ) { + address[] storage enteredMarkets = enteredMarkets[_user]; uint256 index; - while (enteredMarkets[_user][index] != _poolToken) { + while (enteredMarkets[index] != _poolToken) { unchecked { ++index; } } - userMembership[_poolToken][_user] = false; - uint256 length = enteredMarkets[_user].length; - if (index != length - 1) - enteredMarkets[_user][index] = enteredMarkets[_user][length - 1]; - enteredMarkets[_user].pop(); + userMembership[_user] = false; + + uint256 length = enteredMarkets.length; + if (index != length - 1) enteredMarkets[index] = enteredMarkets[length - 1]; + enteredMarkets.pop(); } } diff --git a/src/compound/RewardsManager.sol b/src/compound/RewardsManager.sol index ea126d030..4e573a6b7 100644 --- a/src/compound/RewardsManager.sol +++ b/src/compound/RewardsManager.sol @@ -4,7 +4,8 @@ pragma solidity 0.8.13; import "./interfaces/IRewardsManager.sol"; import "./interfaces/IMorpho.sol"; -import "./libraries/CompoundMath.sol"; +import "@morpho-dao/morpho-utils/math/CompoundMath.sol"; +import "@openzeppelin/contracts/utils/math/SafeCast.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; @@ -14,6 +15,7 @@ import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; /// @notice This contract is used to manage the COMP rewards from the Compound protocol. contract RewardsManager is IRewardsManager, Initializable { using CompoundMath for uint256; + using SafeCast for uint256; /// STORAGE /// @@ -222,7 +224,7 @@ contract RewardsManager is IRewardsManager, Initializable { localCompSupplyState[_poolToken] = IComptroller.CompMarketState({ index: newCompSupplyIndex, - block: CompoundMath.safe32(block.number) + block: block.number.toUint32() }); } } @@ -254,7 +256,7 @@ contract RewardsManager is IRewardsManager, Initializable { localCompBorrowState[_poolToken] = IComptroller.CompMarketState({ index: newCompBorrowIndex, - block: CompoundMath.safe32(block.number) + block: block.number.toUint32() }); } } diff --git a/src/compound/interfaces/IIncentivesVault.sol b/src/compound/interfaces/IIncentivesVault.sol deleted file mode 100644 index f85287246..000000000 --- a/src/compound/interfaces/IIncentivesVault.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity >=0.5.0; - -import "./IOracle.sol"; - -interface IIncentivesVault { - function isPaused() external view returns (bool); - - function bonus() external view returns (uint256); - - function MAX_BASIS_POINTS() external view returns (uint256); - - function incentivesTreasuryVault() external view returns (address); - - function oracle() external view returns (IOracle); - - function setOracle(IOracle _newOracle) external; - - function setIncentivesTreasuryVault(address _newIncentivesTreasuryVault) external; - - function setBonus(uint256 _newBonus) external; - - function setPauseStatus(bool _newStatus) external; - - function transferTokensToDao(address _token, uint256 _amount) external; - - function tradeCompForMorphoTokens(address _to, uint256 _amount) external; -} diff --git a/src/compound/interfaces/IMorpho.sol b/src/compound/interfaces/IMorpho.sol index 83d4a4ffe..d9240d5db 100644 --- a/src/compound/interfaces/IMorpho.sol +++ b/src/compound/interfaces/IMorpho.sol @@ -4,7 +4,6 @@ pragma solidity >=0.5.0; import "./IInterestRatesManager.sol"; import "./IPositionsManager.sol"; import "./IRewardsManager.sol"; -import "./IIncentivesVault.sol"; import "../libraries/Types.sol"; @@ -22,7 +21,7 @@ interface IMorpho { function dustThreshold() external view returns (uint256); function supplyBalanceInOf(address, address) external view returns (Types.SupplyBalance memory); function borrowBalanceInOf(address, address) external view returns (Types.BorrowBalance memory); - function enteredMarkets(address) external view returns (address); + function enteredMarkets(address, uint256) external view returns (address); function deltas(address) external view returns (Types.Delta memory); function marketParameters(address) external view returns (Types.MarketParameters memory); function marketPauseStatus(address) external view returns (Types.MarketPauseStatus memory); @@ -35,15 +34,15 @@ interface IMorpho { function interestRatesManager() external view returns (IInterestRatesManager); function rewardsManager() external view returns (IRewardsManager); function positionsManager() external view returns (IPositionsManager); - function incentiveVault() external view returns (IIncentivesVault); + function incentivesVault() external view returns (address); function treasuryVault() external view returns (address); function cEth() external view returns (address); function wEth() external view returns (address); /// GETTERS /// - function getEnteredMarkets(address _user) external view returns (address[] memory enteredMarkets_); - function getAllMarkets() external view returns (address[] memory marketsCreated_); + function getEnteredMarkets(address _user) external view returns (address[] memory); + function getAllMarkets() external view returns (address[] memory); function getHead(address _poolToken, Types.PositionType _positionType) external view returns (address head); function getNext(address _poolToken, Types.PositionType _positionType, address _user) external view returns (address next); @@ -51,7 +50,6 @@ interface IMorpho { function setMaxSortedUsers(uint256 _newMaxSortedUsers) external; function setDefaultMaxGasForMatching(Types.MaxGasForMatching memory _maxGasForMatching) external; - function setIncentivesVault(address _newIncentivesVault) external; function setRewardsManager(address _rewardsManagerAddress) external; function setPositionsManager(IPositionsManager _positionsManager) external; function setInterestRatesManager(IInterestRatesManager _interestRatesManager) external; diff --git a/src/compound/interfaces/compound/ICompound.sol b/src/compound/interfaces/compound/ICompound.sol index dcb0feb4f..50c1e44a6 100644 --- a/src/compound/interfaces/compound/ICompound.sol +++ b/src/compound/interfaces/compound/ICompound.sol @@ -250,17 +250,17 @@ interface IComptroller { } interface IInterestRateModel { - function getBorrowRate( + function getSupplyRate( uint256 cash, uint256 borrows, - uint256 reserves + uint256 reserves, + uint256 reserveFactorMantissa ) external view returns (uint256); - function getSupplyRate( + function getBorrowRate( uint256 cash, uint256 borrows, - uint256 reserves, - uint256 reserveFactorMantissa + uint256 reserves ) external view returns (uint256); } diff --git a/src/compound/lens/IndexesLens.sol b/src/compound/lens/IndexesLens.sol index 3d5a2f7bc..e4b5c915b 100644 --- a/src/compound/lens/IndexesLens.sol +++ b/src/compound/lens/IndexesLens.sol @@ -40,15 +40,13 @@ abstract contract IndexesLens is LensStorage { p2pBorrowIndex = indexes.p2pBorrowIndex; } - /// PUBLIC /// - /// @notice Returns the most up-to-date or virtually updated peer-to-peer and pool indexes. /// @dev If not virtually updated, the indexes returned are those used by Morpho for non-updated markets during the liquidity check. /// @param _poolToken The address of the market. /// @param _updated Whether to compute virtually updated pool and peer-to-peer indexes. /// @return indexes The given market's virtually updated indexes. function getIndexes(address _poolToken, bool _updated) - public + external view returns (Types.Indexes memory indexes) { @@ -61,42 +59,22 @@ abstract contract IndexesLens is LensStorage { /// @return poolSupplyIndex The supply index. /// @return poolBorrowIndex The borrow index. function getCurrentPoolIndexes(address _poolToken) - public + external view returns (uint256 poolSupplyIndex, uint256 poolBorrowIndex) { - ICToken cToken = ICToken(_poolToken); - - uint256 accrualBlockNumberPrior = cToken.accrualBlockNumber(); - if (block.number == accrualBlockNumberPrior) - return (cToken.exchangeRateStored(), cToken.borrowIndex()); - - // Read the previous values out of storage - uint256 cashPrior = cToken.getCash(); - uint256 totalSupply = cToken.totalSupply(); - uint256 borrowsPrior = cToken.totalBorrows(); - uint256 reservesPrior = cToken.totalReserves(); - uint256 borrowIndexPrior = cToken.borrowIndex(); - - // Calculate the current borrow interest rate - uint256 borrowRateMantissa = cToken.borrowRatePerBlock(); - require(borrowRateMantissa <= 0.0005e16, "borrow rate is absurdly high"); - - uint256 blockDelta = block.number - accrualBlockNumberPrior; - - // Calculate the interest accumulated into borrows and reserves and the current index. - uint256 simpleInterestFactor = borrowRateMantissa * blockDelta; - uint256 interestAccumulated = simpleInterestFactor.mul(borrowsPrior); - uint256 totalBorrowsNew = interestAccumulated + borrowsPrior; - uint256 totalReservesNew = cToken.reserveFactorMantissa().mul(interestAccumulated) + - reservesPrior; - - poolSupplyIndex = (cashPrior + totalBorrowsNew - totalReservesNew).div(totalSupply); - poolBorrowIndex = simpleInterestFactor.mul(borrowIndexPrior) + borrowIndexPrior; + (poolSupplyIndex, poolBorrowIndex, ) = _accruePoolInterests(ICToken(_poolToken)); } /// INTERNAL /// + struct PoolInterestsVars { + uint256 cash; + uint256 totalBorrows; + uint256 totalReserves; + uint256 reserveFactorMantissa; + } + /// @notice Returns the most up-to-date or virtually updated peer-to-peer and pool indexes. /// @dev If not virtually updated, the indexes returned are those used by Morpho for non-updated markets during the liquidity check. /// @param _poolToken The address of the market. @@ -115,44 +93,128 @@ abstract contract IndexesLens is LensStorage { indexes.poolSupplyIndex = ICToken(_poolToken).exchangeRateStored(); indexes.poolBorrowIndex = ICToken(_poolToken).borrowIndex(); } else { - (indexes.poolSupplyIndex, indexes.poolBorrowIndex) = getCurrentPoolIndexes(_poolToken); + (indexes.poolSupplyIndex, indexes.poolBorrowIndex, ) = _accruePoolInterests( + ICToken(_poolToken) + ); } - if (!_updated || block.number == lastPoolIndexes.lastUpdateBlockNumber) { - indexes.p2pSupplyIndex = morpho.p2pSupplyIndex(_poolToken); - indexes.p2pBorrowIndex = morpho.p2pBorrowIndex(_poolToken); - } else { - Types.MarketParameters memory marketParams = morpho.marketParameters(_poolToken); - - InterestRatesModel.GrowthFactors memory growthFactors = InterestRatesModel - .computeGrowthFactors( - indexes.poolSupplyIndex, - indexes.poolBorrowIndex, - lastPoolIndexes, - marketParams.p2pIndexCursor, - marketParams.reserveFactor - ); + (indexes.p2pSupplyIndex, indexes.p2pBorrowIndex, ) = _computeP2PIndexes( + _poolToken, + _updated, + indexes.poolSupplyIndex, + indexes.poolBorrowIndex, + delta, + lastPoolIndexes + ); + } - indexes.p2pSupplyIndex = InterestRatesModel.computeP2PSupplyIndex( - InterestRatesModel.P2PSupplyIndexComputeParams({ - poolSupplyGrowthFactor: growthFactors.poolSupplyGrowthFactor, - p2pSupplyGrowthFactor: growthFactors.p2pSupplyGrowthFactor, - lastPoolSupplyIndex: lastPoolIndexes.lastSupplyPoolIndex, - lastP2PSupplyIndex: morpho.p2pSupplyIndex(_poolToken), - p2pSupplyDelta: delta.p2pSupplyDelta, - p2pSupplyAmount: delta.p2pSupplyAmount - }) - ); - indexes.p2pBorrowIndex = InterestRatesModel.computeP2PBorrowIndex( - InterestRatesModel.P2PBorrowIndexComputeParams({ - poolBorrowGrowthFactor: growthFactors.poolBorrowGrowthFactor, - p2pBorrowGrowthFactor: growthFactors.p2pBorrowGrowthFactor, - lastPoolBorrowIndex: lastPoolIndexes.lastBorrowPoolIndex, - lastP2PBorrowIndex: morpho.p2pBorrowIndex(_poolToken), - p2pBorrowDelta: delta.p2pBorrowDelta, - p2pBorrowAmount: delta.p2pBorrowAmount - }) + /// @notice Returns the virtually updated pool indexes of a given market. + /// @dev Mimicks `CToken.accrueInterest`'s calculations, without writing to the storage. + /// @param _poolToken The address of the market. + /// @return poolSupplyIndex The supply index. + /// @return poolBorrowIndex The borrow index. + function _accruePoolInterests(ICToken _poolToken) + internal + view + returns ( + uint256 poolSupplyIndex, + uint256 poolBorrowIndex, + PoolInterestsVars memory vars + ) + { + poolBorrowIndex = _poolToken.borrowIndex(); + vars.cash = _poolToken.getCash(); + vars.totalBorrows = _poolToken.totalBorrows(); + vars.totalReserves = _poolToken.totalReserves(); + vars.reserveFactorMantissa = _poolToken.reserveFactorMantissa(); + + uint256 accrualBlockNumberPrior = _poolToken.accrualBlockNumber(); + if (block.number == accrualBlockNumberPrior) { + poolSupplyIndex = _poolToken.exchangeRateStored(); + + return (poolSupplyIndex, poolBorrowIndex, vars); + } + + uint256 borrowRateMantissa = _poolToken.borrowRatePerBlock(); + require(borrowRateMantissa <= 0.0005e16, "borrow rate is absurdly high"); + + uint256 simpleInterestFactor = borrowRateMantissa * + (block.number - accrualBlockNumberPrior); + uint256 interestAccumulated = simpleInterestFactor.mul(vars.totalBorrows); + + vars.totalBorrows += interestAccumulated; + vars.totalReserves += vars.reserveFactorMantissa.mul(interestAccumulated); + + poolSupplyIndex = (vars.cash + vars.totalBorrows - vars.totalReserves).div( + _poolToken.totalSupply() + ); + poolBorrowIndex += simpleInterestFactor.mul(poolBorrowIndex); + } + + /// @notice Returns the most up-to-date or virtually updated peer-to-peer indexes. + /// @dev If not virtually updated, the indexes returned are those used by Morpho for non-updated markets during the liquidity check. + /// @param _poolToken The address of the market. + /// @param _updated Whether to compute virtually updated peer-to-peer indexes. + /// @param _poolSupplyIndex The underlying pool supply index. + /// @param _poolBorrowIndex The underlying pool borrow index. + /// @param _delta The given market's deltas. + /// @param _lastPoolIndexes The last pool indexes stored on Morpho. + /// @return _p2pSupplyIndex The given market's peer-to-peer supply index. + /// @return _p2pBorrowIndex The given market's peer-to-peer borrow index. + function _computeP2PIndexes( + address _poolToken, + bool _updated, + uint256 _poolSupplyIndex, + uint256 _poolBorrowIndex, + Types.Delta memory _delta, + Types.LastPoolIndexes memory _lastPoolIndexes + ) + internal + view + returns ( + uint256 _p2pSupplyIndex, + uint256 _p2pBorrowIndex, + Types.MarketParameters memory marketParameters + ) + { + marketParameters = morpho.marketParameters(_poolToken); + + if (!_updated || block.number == _lastPoolIndexes.lastUpdateBlockNumber) { + return ( + morpho.p2pSupplyIndex(_poolToken), + morpho.p2pBorrowIndex(_poolToken), + marketParameters ); } + + InterestRatesModel.GrowthFactors memory growthFactors = InterestRatesModel + .computeGrowthFactors( + _poolSupplyIndex, + _poolBorrowIndex, + _lastPoolIndexes, + marketParameters.p2pIndexCursor, + marketParameters.reserveFactor + ); + + _p2pSupplyIndex = InterestRatesModel.computeP2PIndex( + InterestRatesModel.P2PIndexComputeParams({ + poolGrowthFactor: growthFactors.poolSupplyGrowthFactor, + p2pGrowthFactor: growthFactors.p2pSupplyGrowthFactor, + lastPoolIndex: _lastPoolIndexes.lastSupplyPoolIndex, + lastP2PIndex: morpho.p2pSupplyIndex(_poolToken), + p2pDelta: _delta.p2pSupplyDelta, + p2pAmount: _delta.p2pSupplyAmount + }) + ); + _p2pBorrowIndex = InterestRatesModel.computeP2PIndex( + InterestRatesModel.P2PIndexComputeParams({ + poolGrowthFactor: growthFactors.poolBorrowGrowthFactor, + p2pGrowthFactor: growthFactors.p2pBorrowGrowthFactor, + lastPoolIndex: _lastPoolIndexes.lastBorrowPoolIndex, + lastP2PIndex: morpho.p2pBorrowIndex(_poolToken), + p2pDelta: _delta.p2pBorrowDelta, + p2pAmount: _delta.p2pBorrowAmount + }) + ); } } diff --git a/src/compound/lens/Lens.sol b/src/compound/lens/Lens.sol index 712f51f39..9820204a3 100644 --- a/src/compound/lens/Lens.sol +++ b/src/compound/lens/Lens.sol @@ -13,8 +13,8 @@ contract Lens is RewardsLens { /// CONSTRUCTOR /// /// @notice Constructs the contract. - /// @param _morpho The address of the main Morpho contract. - constructor(address _morpho) LensStorage(_morpho) {} + /// @param _lensExtension The address of the Lens extension. + constructor(address _lensExtension) LensStorage(_lensExtension) {} /// EXTERNAL /// diff --git a/src/compound/lens/LensExtension.sol b/src/compound/lens/LensExtension.sol new file mode 100644 index 000000000..7050c15b5 --- /dev/null +++ b/src/compound/lens/LensExtension.sol @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.13; + +import "../interfaces/IRewardsManager.sol"; +import "../interfaces/IMorpho.sol"; +import "./interfaces/ILensExtension.sol"; + +import "@morpho-dao/morpho-utils/math/CompoundMath.sol"; + +/// @title LensExtension. +/// @author Morpho Labs. +/// @custom:contact security@morpho.xyz +/// @notice This contract is an extension of the Lens. It should be deployed before the Lens, as the Lens depends on its address to extends its functionalities. +contract LensExtension is ILensExtension { + using CompoundMath for uint256; + + /// STORAGE /// + + IMorpho public immutable morpho; + IComptroller internal immutable comptroller; + IRewardsManager internal immutable rewardsManager; + + /// ERRORS /// + + /// @notice Thrown when an invalid cToken address is passed to claim rewards. + error InvalidCToken(); + + /// CONSTRUCTOR /// + + /// @notice Constructs the contract. + /// @param _morpho The address of the main Morpho contract. + constructor(address _morpho) { + morpho = IMorpho(_morpho); + comptroller = IComptroller(morpho.comptroller()); + rewardsManager = IRewardsManager(morpho.rewardsManager()); + } + + /// EXTERNAL /// + + /// @notice Returns the unclaimed COMP rewards for the given cToken addresses. + /// @param _poolTokens The cToken addresses for which to compute the rewards. + /// @param _user The address of the user. + function getUserUnclaimedRewards(address[] calldata _poolTokens, address _user) + external + view + returns (uint256 unclaimedRewards) + { + unclaimedRewards = rewardsManager.userUnclaimedCompRewards(_user); + + for (uint256 i; i < _poolTokens.length; ) { + address poolToken = _poolTokens[i]; + + (bool isListed, , ) = comptroller.markets(poolToken); + if (!isListed) revert InvalidCToken(); + + unclaimedRewards += + getAccruedSupplierComp(_user, poolToken) + + getAccruedBorrowerComp(_user, poolToken); + + unchecked { + ++i; + } + } + } + + /// PUBLIC /// + + /// @notice Returns the accrued COMP rewards of a user since the last update. + /// @param _supplier The address of the supplier. + /// @param _poolToken The cToken address. + /// @return The accrued COMP rewards. + function getAccruedSupplierComp(address _supplier, address _poolToken) + public + view + returns (uint256) + { + return + getAccruedSupplierComp( + _supplier, + _poolToken, + morpho.supplyBalanceInOf(_poolToken, _supplier).onPool + ); + } + + /// @notice Returns the accrued COMP rewards of a user since the last update. + /// @param _borrower The address of the borrower. + /// @param _poolToken The cToken address. + /// @return The accrued COMP rewards. + function getAccruedBorrowerComp(address _borrower, address _poolToken) + public + view + returns (uint256) + { + return + getAccruedBorrowerComp( + _borrower, + _poolToken, + morpho.borrowBalanceInOf(_poolToken, _borrower).onPool + ); + } + + /// @notice Returns the accrued COMP rewards of a user since the last update. + /// @param _supplier The address of the supplier. + /// @param _poolToken The cToken address. + /// @param _balance The user balance of tokens in the distribution. + /// @return The accrued COMP rewards. + function getAccruedSupplierComp( + address _supplier, + address _poolToken, + uint256 _balance + ) public view returns (uint256) { + uint256 supplierIndex = rewardsManager.compSupplierIndex(_poolToken, _supplier); + + if (supplierIndex == 0) return 0; + return (_balance * (getCurrentCompSupplyIndex(_poolToken) - supplierIndex)) / 1e36; + } + + /// @notice Returns the accrued COMP rewards of a user since the last update. + /// @param _borrower The address of the borrower. + /// @param _poolToken The cToken address. + /// @param _balance The user balance of tokens in the distribution. + /// @return The accrued COMP rewards. + function getAccruedBorrowerComp( + address _borrower, + address _poolToken, + uint256 _balance + ) public view returns (uint256) { + uint256 borrowerIndex = rewardsManager.compBorrowerIndex(_poolToken, _borrower); + + if (borrowerIndex == 0) return 0; + return (_balance * (getCurrentCompBorrowIndex(_poolToken) - borrowerIndex)) / 1e36; + } + + /// @notice Returns the updated COMP supply index. + /// @param _poolToken The cToken address. + /// @return The updated COMP supply index. + function getCurrentCompSupplyIndex(address _poolToken) public view returns (uint256) { + IComptroller.CompMarketState memory localSupplyState = rewardsManager + .getLocalCompSupplyState(_poolToken); + + if (localSupplyState.block == block.number) return localSupplyState.index; + else { + IComptroller.CompMarketState memory supplyState = comptroller.compSupplyState( + _poolToken + ); + + uint256 deltaBlocks = block.number - supplyState.block; + uint256 supplySpeed = comptroller.compSupplySpeeds(_poolToken); + + if (deltaBlocks > 0 && supplySpeed > 0) { + uint256 supplyTokens = ICToken(_poolToken).totalSupply(); + uint256 ratio = supplyTokens > 0 + ? (deltaBlocks * supplySpeed * 1e36) / supplyTokens + : 0; + + return supplyState.index + ratio; + } + + return supplyState.index; + } + } + + /// @notice Returns the updated COMP borrow index. + /// @param _poolToken The cToken address. + /// @return The updated COMP borrow index. + function getCurrentCompBorrowIndex(address _poolToken) public view returns (uint256) { + IComptroller.CompMarketState memory localBorrowState = rewardsManager + .getLocalCompBorrowState(_poolToken); + + if (localBorrowState.block == block.number) return localBorrowState.index; + else { + IComptroller.CompMarketState memory borrowState = comptroller.compBorrowState( + _poolToken + ); + uint256 deltaBlocks = block.number - borrowState.block; + uint256 borrowSpeed = comptroller.compBorrowSpeeds(_poolToken); + + if (deltaBlocks > 0 && borrowSpeed > 0) { + uint256 borrowAmount = ICToken(_poolToken).totalBorrows().div( + ICToken(_poolToken).borrowIndex() + ); + uint256 ratio = borrowAmount > 0 + ? (deltaBlocks * borrowSpeed * 1e36) / borrowAmount + : 0; + + return borrowState.index + ratio; + } + + return borrowState.index; + } + } +} diff --git a/src/compound/lens/LensStorage.sol b/src/compound/lens/LensStorage.sol index eda6428cd..4e2344535 100644 --- a/src/compound/lens/LensStorage.sol +++ b/src/compound/lens/LensStorage.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.13; import "../interfaces/compound/ICompound.sol"; +import "./interfaces/ILensExtension.sol"; import "../interfaces/IMorpho.sol"; import "./interfaces/ILens.sol"; @@ -28,6 +29,7 @@ abstract contract LensStorage is ILens, Initializable { IMorpho public immutable morpho; IComptroller public immutable comptroller; IRewardsManager public immutable rewardsManager; + ILensExtension internal immutable lensExtension; /// STORAGE /// @@ -38,9 +40,10 @@ abstract contract LensStorage is ILens, Initializable { /// CONSTRUCTOR /// /// @notice Constructs the contract. - /// @param _morpho The address of the main Morpho contract. - constructor(address _morpho) { - morpho = IMorpho(_morpho); + /// @param _lensExtension The address of the Lens extension. + constructor(address _lensExtension) { + lensExtension = ILensExtension(_lensExtension); + morpho = IMorpho(lensExtension.morpho()); comptroller = IComptroller(morpho.comptroller()); rewardsManager = IRewardsManager(morpho.rewardsManager()); } diff --git a/src/compound/lens/RatesLens.sol b/src/compound/lens/RatesLens.sol index d04dc8530..3db892bd6 100644 --- a/src/compound/lens/RatesLens.sol +++ b/src/compound/lens/RatesLens.sol @@ -11,6 +11,10 @@ abstract contract RatesLens is UsersLens { using CompoundMath for uint256; using Math for uint256; + /// ERRORS /// + + error BorrowRateFailed(); + /// EXTERNAL /// /// @notice Returns the supply rate per block experienced on a market after having supplied the given amount on behalf of the given user. @@ -40,30 +44,36 @@ abstract contract RatesLens is UsersLens { (Types.Delta memory delta, Types.Indexes memory indexes) = _getIndexes(_poolToken, true); Types.SupplyBalance memory supplyBalance = morpho.supplyBalanceInOf(_poolToken, _user); - if (_amount > 0 && delta.p2pBorrowDelta > 0) { - uint256 matchedDelta = Math.min( - delta.p2pBorrowDelta.mul(indexes.poolBorrowIndex), - _amount - ); - supplyBalance.inP2P += matchedDelta.div(indexes.p2pSupplyIndex); - _amount -= matchedDelta; - } + uint256 repaidToPool; + if (!morpho.p2pDisabled(_poolToken)) { + if (delta.p2pBorrowDelta > 0) { + uint256 matchedDelta = Math.min( + delta.p2pBorrowDelta.mul(indexes.poolBorrowIndex), + _amount + ); - if (_amount > 0 && !morpho.p2pDisabled(_poolToken)) { - uint256 firstPoolBorrowerBalance = morpho - .borrowBalanceInOf( - _poolToken, - morpho.getHead(_poolToken, Types.PositionType.BORROWERS_ON_POOL) - ).onPool; + supplyBalance.inP2P += matchedDelta.div(indexes.p2pSupplyIndex); + repaidToPool += matchedDelta; + _amount -= matchedDelta; + } + + if (_amount > 0) { + address firstPoolBorrower = morpho.getHead( + _poolToken, + Types.PositionType.BORROWERS_ON_POOL + ); + uint256 firstPoolBorrowerBalance = morpho + .borrowBalanceInOf(_poolToken, firstPoolBorrower) + .onPool; - if (firstPoolBorrowerBalance > 0) { uint256 matchedP2P = Math.min( firstPoolBorrowerBalance.mul(indexes.poolBorrowIndex), _amount ); supplyBalance.inP2P += matchedP2P.div(indexes.p2pSupplyIndex); + repaidToPool += matchedP2P; _amount -= matchedP2P; } } @@ -76,7 +86,9 @@ abstract contract RatesLens is UsersLens { (nextSupplyRatePerBlock, totalBalance) = _getUserSupplyRatePerBlock( _poolToken, balanceInP2P, - balanceOnPool + balanceOnPool, + _amount, + repaidToPool ); } @@ -107,30 +119,36 @@ abstract contract RatesLens is UsersLens { (Types.Delta memory delta, Types.Indexes memory indexes) = _getIndexes(_poolToken, true); Types.BorrowBalance memory borrowBalance = morpho.borrowBalanceInOf(_poolToken, _user); - if (_amount > 0 && delta.p2pSupplyDelta > 0) { - uint256 matchedDelta = Math.min( - delta.p2pSupplyDelta.mul(indexes.poolSupplyIndex), - _amount - ); - borrowBalance.inP2P += matchedDelta.div(indexes.p2pBorrowIndex); - _amount -= matchedDelta; - } + uint256 withdrawnFromPool; + if (!morpho.p2pDisabled(_poolToken)) { + if (delta.p2pSupplyDelta > 0) { + uint256 matchedDelta = Math.min( + delta.p2pSupplyDelta.mul(indexes.poolSupplyIndex), + _amount + ); + + borrowBalance.inP2P += matchedDelta.div(indexes.p2pBorrowIndex); + withdrawnFromPool += matchedDelta; + _amount -= matchedDelta; + } - if (_amount > 0 && !morpho.p2pDisabled(_poolToken)) { - uint256 firstPoolSupplierBalance = morpho - .supplyBalanceInOf( - _poolToken, - morpho.getHead(_poolToken, Types.PositionType.SUPPLIERS_ON_POOL) - ).onPool; + if (_amount > 0) { + address firstPoolSupplier = morpho.getHead( + _poolToken, + Types.PositionType.SUPPLIERS_ON_POOL + ); + uint256 firstPoolSupplierBalance = morpho + .supplyBalanceInOf(_poolToken, firstPoolSupplier) + .onPool; - if (firstPoolSupplierBalance > 0) { uint256 matchedP2P = Math.min( firstPoolSupplierBalance.mul(indexes.poolSupplyIndex), _amount ); borrowBalance.inP2P += matchedP2P.div(indexes.p2pBorrowIndex); + withdrawnFromPool += matchedP2P; _amount -= matchedP2P; } } @@ -143,7 +161,9 @@ abstract contract RatesLens is UsersLens { (nextBorrowRatePerBlock, totalBalance) = _getUserBorrowRatePerBlock( _poolToken, balanceInP2P, - balanceOnPool + balanceOnPool, + _amount, + withdrawnFromPool ); } @@ -164,7 +184,9 @@ abstract contract RatesLens is UsersLens { (supplyRatePerBlock, ) = _getUserSupplyRatePerBlock( _poolToken, balanceInP2P, - balanceOnPool + balanceOnPool, + 0, + 0 ); } @@ -185,7 +207,9 @@ abstract contract RatesLens is UsersLens { (borrowRatePerBlock, ) = _getUserBorrowRatePerBlock( _poolToken, balanceInP2P, - balanceOnPool + balanceOnPool, + 0, + 0 ); } @@ -307,7 +331,45 @@ abstract contract RatesLens is UsersLens { /// @return poolSupplyRate The market's pool supply rate per block (in wad). /// @return poolBorrowRate The market's pool borrow rate per block (in wad). function getRatesPerBlock(address _poolToken) - public + external + view + returns ( + uint256, + uint256, + uint256, + uint256 + ) + { + return _getRatesPerBlock(_poolToken, 0, 0, 0, 0); + } + + /// INTERNAL /// + + struct PoolRatesVars { + Types.Delta delta; + Types.LastPoolIndexes lastPoolIndexes; + Types.Indexes indexes; + Types.MarketParameters params; + } + + /// @dev Computes and returns peer-to-peer and pool rates for a specific market. + /// @param _poolToken The market address. + /// @param _suppliedOnPool The amount hypothetically supplied to the underlying's pool (in underlying). + /// @param _borrowedFromPool The amount hypothetically borrowed from the underlying's pool (in underlying). + /// @param _repaidOnPool The amount hypothetically repaid to the underlying's pool (in underlying). + /// @param _withdrawnFromPool The amount hypothetically withdrawn from the underlying's pool (in underlying). + /// @return p2pSupplyRate The market's peer-to-peer supply rate per block (in wad). + /// @return p2pBorrowRate The market's peer-to-peer borrow rate per block (in wad). + /// @return poolSupplyRate The market's pool supply rate per block (in wad). + /// @return poolBorrowRate The market's pool borrow rate per block (in wad). + function _getRatesPerBlock( + address _poolToken, + uint256 _suppliedOnPool, + uint256 _borrowedFromPool, + uint256 _repaidOnPool, + uint256 _withdrawnFromPool + ) + internal view returns ( uint256 p2pSupplyRate, @@ -316,29 +378,91 @@ abstract contract RatesLens is UsersLens { uint256 poolBorrowRate ) { - ICToken cToken = ICToken(_poolToken); + PoolRatesVars memory ratesVars; + + ratesVars.delta = morpho.deltas(_poolToken); + ratesVars.lastPoolIndexes = morpho.lastPoolIndexes(_poolToken); + + bool updated = _suppliedOnPool > 0 || + _borrowedFromPool > 0 || + _repaidOnPool > 0 || + _withdrawnFromPool > 0; + if (updated) { + PoolInterestsVars memory interestsVars; + ( + ratesVars.indexes.poolSupplyIndex, + ratesVars.indexes.poolBorrowIndex, + interestsVars + ) = _accruePoolInterests(ICToken(_poolToken)); + + interestsVars.cash = + interestsVars.cash + + _suppliedOnPool + + _repaidOnPool - + _borrowedFromPool - + _withdrawnFromPool; + interestsVars.totalBorrows = + interestsVars.totalBorrows + + _borrowedFromPool - + _repaidOnPool; + + IInterestRateModel interestRateModel = ICToken(_poolToken).interestRateModel(); + + poolSupplyRate = IInterestRateModel(interestRateModel).getSupplyRate( + interestsVars.cash, + interestsVars.totalBorrows, + interestsVars.totalReserves, + interestsVars.reserveFactorMantissa + ); - poolSupplyRate = cToken.supplyRatePerBlock(); - poolBorrowRate = cToken.borrowRatePerBlock(); + (bool success, bytes memory result) = address(interestRateModel).staticcall( + abi.encodeWithSelector( + IInterestRateModel.getBorrowRate.selector, + interestsVars.cash, + interestsVars.totalBorrows, + interestsVars.totalReserves + ) + ); + if (!success) revert BorrowRateFailed(); + + if (result.length > 32) (, poolBorrowRate) = abi.decode(result, (uint256, uint256)); + else poolBorrowRate = abi.decode(result, (uint256)); + } else { + ratesVars.indexes.poolSupplyIndex = ICToken(_poolToken).exchangeRateStored(); + ratesVars.indexes.poolBorrowIndex = ICToken(_poolToken).borrowIndex(); + + poolSupplyRate = ICToken(_poolToken).supplyRatePerBlock(); + poolBorrowRate = ICToken(_poolToken).borrowRatePerBlock(); + } + + ( + ratesVars.indexes.p2pSupplyIndex, + ratesVars.indexes.p2pBorrowIndex, + ratesVars.params + ) = _computeP2PIndexes( + _poolToken, + updated, + ratesVars.indexes.poolSupplyIndex, + ratesVars.indexes.poolBorrowIndex, + ratesVars.delta, + ratesVars.lastPoolIndexes + ); - Types.MarketParameters memory marketParams = morpho.marketParameters(_poolToken); uint256 p2pRate = PercentageMath.weightedAvg( poolSupplyRate, poolBorrowRate, - marketParams.p2pIndexCursor + ratesVars.params.p2pIndexCursor ); - (Types.Delta memory delta, Types.Indexes memory indexes) = _getIndexes(_poolToken, false); - p2pSupplyRate = InterestRatesModel.computeP2PSupplyRatePerBlock( InterestRatesModel.P2PRateComputeParams({ p2pRate: p2pRate, poolRate: poolSupplyRate, - poolIndex: indexes.poolSupplyIndex, - p2pIndex: indexes.p2pSupplyIndex, - p2pDelta: delta.p2pSupplyDelta, - p2pAmount: delta.p2pSupplyAmount, - reserveFactor: marketParams.reserveFactor + poolIndex: ratesVars.indexes.poolSupplyIndex, + p2pIndex: ratesVars.indexes.p2pSupplyIndex, + p2pDelta: ratesVars.delta.p2pSupplyDelta, + p2pAmount: ratesVars.delta.p2pSupplyAmount, + reserveFactor: ratesVars.params.reserveFactor }) ); @@ -346,18 +470,16 @@ abstract contract RatesLens is UsersLens { InterestRatesModel.P2PRateComputeParams({ p2pRate: p2pRate, poolRate: poolBorrowRate, - poolIndex: indexes.poolBorrowIndex, - p2pIndex: indexes.p2pBorrowIndex, - p2pDelta: delta.p2pBorrowDelta, - p2pAmount: delta.p2pBorrowAmount, - reserveFactor: marketParams.reserveFactor + poolIndex: ratesVars.indexes.poolBorrowIndex, + p2pIndex: ratesVars.indexes.p2pBorrowIndex, + p2pDelta: ratesVars.delta.p2pBorrowDelta, + p2pAmount: ratesVars.delta.p2pBorrowAmount, + reserveFactor: ratesVars.params.reserveFactor }) ); } - /// INTERNAL /// - - /// @notice Computes and returns the total distribution of supply for a given market, using virtually updated indexes. + /// @dev Computes and returns the total distribution of supply for a given market, using virtually updated indexes. /// @param _poolToken The address of the market to check. /// @param _p2pSupplyIndex The given market's peer-to-peer supply index. /// @param _poolSupplyIndex The given market's pool supply index. @@ -376,7 +498,7 @@ abstract contract RatesLens is UsersLens { poolSupplyAmount = ICToken(_poolToken).balanceOf(address(morpho)).mul(_poolSupplyIndex); } - /// @notice Computes and returns the total distribution of borrows for a given market, using virtually updated indexes. + /// @dev Computes and returns the total distribution of borrows for a given market, using virtually updated indexes. /// @param _poolToken The address of the market to check. /// @param _p2pBorrowIndex The given market's peer-to-peer borrow index. /// @param _poolBorrowIndex The given market's borrow index. @@ -399,33 +521,55 @@ abstract contract RatesLens is UsersLens { } /// @dev Returns the supply rate per block experienced on a market based on a given position distribution. + /// The calculation takes into account the change in pool rates implied by an hypothetical supply and/or repay. /// @param _poolToken The address of the market. /// @param _balanceOnPool The amount of balance supplied on pool (in a unit common to `_balanceInP2P`). /// @param _balanceInP2P The amount of balance matched peer-to-peer (in a unit common to `_balanceOnPool`). + /// @param _suppliedOnPool The amount hypothetically supplied on pool (in underlying). + /// @param _repaidToPool The amount hypothetically repaid to the pool (in underlying). /// @return The supply rate per block experienced by the given position (in wad). /// @return The sum of peer-to-peer & pool balances. function _getUserSupplyRatePerBlock( address _poolToken, uint256 _balanceInP2P, - uint256 _balanceOnPool + uint256 _balanceOnPool, + uint256 _suppliedOnPool, + uint256 _repaidToPool ) internal view returns (uint256, uint256) { - (uint256 p2pSupplyRate, , uint256 poolSupplyRate, ) = getRatesPerBlock(_poolToken); + (uint256 p2pSupplyRate, , uint256 poolSupplyRate, ) = _getRatesPerBlock( + _poolToken, + _suppliedOnPool, + 0, + 0, + _repaidToPool + ); return _getWeightedRate(p2pSupplyRate, poolSupplyRate, _balanceInP2P, _balanceOnPool); } /// @dev Returns the borrow rate per block experienced on a market based on a given position distribution. + /// The calculation takes into account the change in pool rates implied by an hypothetical borrow and/or withdraw. /// @param _poolToken The address of the market. /// @param _balanceOnPool The amount of balance supplied on pool (in a unit common to `_balanceInP2P`). /// @param _balanceInP2P The amount of balance matched peer-to-peer (in a unit common to `_balanceOnPool`). + /// @param _borrowedFromPool The amount hypothetically borrowed from the pool (in underlying). + /// @param _withdrawnFromPool The amount hypothetically withdrawn from the pool (in underlying). /// @return The borrow rate per block experienced by the given position (in wad). /// @return The sum of peer-to-peer & pool balances. function _getUserBorrowRatePerBlock( address _poolToken, uint256 _balanceInP2P, - uint256 _balanceOnPool + uint256 _balanceOnPool, + uint256 _borrowedFromPool, + uint256 _withdrawnFromPool ) internal view returns (uint256, uint256) { - (, uint256 p2pBorrowRate, , uint256 poolBorrowRate) = getRatesPerBlock(_poolToken); + (, uint256 p2pBorrowRate, , uint256 poolBorrowRate) = _getRatesPerBlock( + _poolToken, + 0, + _borrowedFromPool, + _withdrawnFromPool, + 0 + ); return _getWeightedRate(p2pBorrowRate, poolBorrowRate, _balanceInP2P, _balanceOnPool); } diff --git a/src/compound/lens/RewardsLens.sol b/src/compound/lens/RewardsLens.sol index 5b5bfe49c..d65d73d8d 100644 --- a/src/compound/lens/RewardsLens.sol +++ b/src/compound/lens/RewardsLens.sol @@ -6,15 +6,8 @@ import "./MarketsLens.sol"; /// @title RewardsLens. /// @author Morpho Labs. /// @custom:contact security@morpho.xyz -/// @notice Intermediary layer exposing endpoints to query live data related to the Morpho Protocol rewards distribution. +/// @notice Intermediary layer serving as proxy to lighten the bytecode weight of the Lens. abstract contract RewardsLens is MarketsLens { - using CompoundMath for uint256; - - /// ERRORS /// - - /// @notice Thrown when an invalid cToken address is passed to compute accrued rewards. - error InvalidPoolToken(); - /// EXTERNAL /// /// @notice Returns the unclaimed COMP rewards for the given cToken addresses. @@ -23,36 +16,11 @@ abstract contract RewardsLens is MarketsLens { function getUserUnclaimedRewards(address[] calldata _poolTokens, address _user) external view - returns (uint256 unclaimedRewards) + returns (uint256) { - unclaimedRewards = rewardsManager.userUnclaimedCompRewards(_user); - - for (uint256 i; i < _poolTokens.length; ) { - address poolToken = _poolTokens[i]; - - (bool isListed, , ) = comptroller.markets(poolToken); - if (!isListed) revert InvalidPoolToken(); - - unclaimedRewards += - getAccruedSupplierComp( - _user, - poolToken, - morpho.supplyBalanceInOf(poolToken, _user).onPool - ) + - getAccruedBorrowerComp( - _user, - poolToken, - morpho.borrowBalanceInOf(poolToken, _user).onPool - ); - - unchecked { - ++i; - } - } + return lensExtension.getUserUnclaimedRewards(_poolTokens, _user); } - /// PUBLIC /// - /// @notice Returns the accrued COMP rewards of a user since the last update. /// @param _supplier The address of the supplier. /// @param _poolToken The cToken address. @@ -62,11 +30,8 @@ abstract contract RewardsLens is MarketsLens { address _supplier, address _poolToken, uint256 _balance - ) public view returns (uint256) { - uint256 supplierIndex = rewardsManager.compSupplierIndex(_poolToken, _supplier); - - if (supplierIndex == 0) return 0; - return (_balance * (getCurrentCompSupplyIndex(_poolToken) - supplierIndex)) / 1e36; + ) external view returns (uint256) { + return lensExtension.getAccruedSupplierComp(_supplier, _poolToken, _balance); } /// @notice Returns the accrued COMP rewards of a user since the last update. @@ -78,12 +43,7 @@ abstract contract RewardsLens is MarketsLens { view returns (uint256) { - return - getAccruedSupplierComp( - _supplier, - _poolToken, - morpho.supplyBalanceInOf(_poolToken, _supplier).onPool - ); + return lensExtension.getAccruedSupplierComp(_supplier, _poolToken); } /// @notice Returns the accrued COMP rewards of a user since the last update. @@ -95,11 +55,8 @@ abstract contract RewardsLens is MarketsLens { address _borrower, address _poolToken, uint256 _balance - ) public view returns (uint256) { - uint256 borrowerIndex = rewardsManager.compBorrowerIndex(_poolToken, _borrower); - - if (borrowerIndex == 0) return 0; - return (_balance * (getCurrentCompBorrowIndex(_poolToken) - borrowerIndex)) / 1e36; + ) external view returns (uint256) { + return lensExtension.getAccruedBorrowerComp(_borrower, _poolToken, _balance); } /// @notice Returns the accrued COMP rewards of a user since the last update. @@ -111,70 +68,20 @@ abstract contract RewardsLens is MarketsLens { view returns (uint256) { - return - getAccruedBorrowerComp( - _borrower, - _poolToken, - morpho.borrowBalanceInOf(_poolToken, _borrower).onPool - ); + return lensExtension.getAccruedBorrowerComp(_borrower, _poolToken); } /// @notice Returns the updated COMP supply index. /// @param _poolToken The cToken address. /// @return The updated COMP supply index. - function getCurrentCompSupplyIndex(address _poolToken) public view returns (uint256) { - IComptroller.CompMarketState memory localSupplyState = rewardsManager - .getLocalCompSupplyState(_poolToken); - - if (localSupplyState.block == block.number) return localSupplyState.index; - else { - IComptroller.CompMarketState memory supplyState = comptroller.compSupplyState( - _poolToken - ); - - uint256 deltaBlocks = block.number - supplyState.block; - uint256 supplySpeed = comptroller.compSupplySpeeds(_poolToken); - - if (deltaBlocks > 0 && supplySpeed > 0) { - uint256 supplyTokens = ICToken(_poolToken).totalSupply(); - uint256 ratio = supplyTokens > 0 - ? (deltaBlocks * supplySpeed * 1e36) / supplyTokens - : 0; - - return supplyState.index + ratio; - } - - return supplyState.index; - } + function getCurrentCompSupplyIndex(address _poolToken) external view returns (uint256) { + return lensExtension.getCurrentCompSupplyIndex(_poolToken); } /// @notice Returns the updated COMP borrow index. /// @param _poolToken The cToken address. /// @return The updated COMP borrow index. - function getCurrentCompBorrowIndex(address _poolToken) public view returns (uint256) { - IComptroller.CompMarketState memory localBorrowState = rewardsManager - .getLocalCompBorrowState(_poolToken); - - if (localBorrowState.block == block.number) return localBorrowState.index; - else { - IComptroller.CompMarketState memory borrowState = comptroller.compBorrowState( - _poolToken - ); - uint256 deltaBlocks = block.number - borrowState.block; - uint256 borrowSpeed = comptroller.compBorrowSpeeds(_poolToken); - - if (deltaBlocks > 0 && borrowSpeed > 0) { - uint256 borrowAmount = ICToken(_poolToken).totalBorrows().div( - ICToken(_poolToken).borrowIndex() - ); - uint256 ratio = borrowAmount > 0 - ? (deltaBlocks * borrowSpeed * 1e36) / borrowAmount - : 0; - - return borrowState.index + ratio; - } - - return borrowState.index; - } + function getCurrentCompBorrowIndex(address _poolToken) external view returns (uint256) { + return lensExtension.getCurrentCompBorrowIndex(_poolToken); } } diff --git a/src/compound/lens/UsersLens.sol b/src/compound/lens/UsersLens.sol index 2b8e6e593..716891c76 100644 --- a/src/compound/lens/UsersLens.sol +++ b/src/compound/lens/UsersLens.sol @@ -50,8 +50,8 @@ abstract contract UsersLens is IndexesLens { if (_poolToken != poolTokenEntered) { assetData = getUserLiquidityDataForAsset(_user, poolTokenEntered, false, oracle); - data.maxDebtValue += assetData.maxDebtValue; - data.debtValue += assetData.debtValue; + data.maxDebtUsd += assetData.maxDebtUsd; + data.debtUsd += assetData.debtUsd; } unchecked { @@ -61,11 +61,11 @@ abstract contract UsersLens is IndexesLens { assetData = getUserLiquidityDataForAsset(_user, _poolToken, true, oracle); - data.maxDebtValue += assetData.maxDebtValue; - data.debtValue += assetData.debtValue; + data.maxDebtUsd += assetData.maxDebtUsd; + data.debtUsd += assetData.debtUsd; // Not possible to withdraw nor borrow. - if (data.maxDebtValue < data.debtValue) return (0, 0); + if (data.maxDebtUsd < data.debtUsd) return (0, 0); uint256 poolTokenBalance = _poolToken == morpho.cEth() ? _poolToken.balance @@ -73,11 +73,11 @@ abstract contract UsersLens is IndexesLens { borrowable = Math.min( poolTokenBalance, - (data.maxDebtValue - data.debtValue).div(assetData.underlyingPrice) + (data.maxDebtUsd - data.debtUsd).div(assetData.underlyingPrice) ); withdrawable = Math.min( poolTokenBalance, - assetData.collateralValue.div(assetData.underlyingPrice) + assetData.collateralUsd.div(assetData.underlyingPrice) ); if (assetData.collateralFactor != 0) { @@ -140,10 +140,10 @@ abstract contract UsersLens is IndexesLens { view returns (uint256) { - (, uint256 debtValue, uint256 maxDebtValue) = getUserBalanceStates(_user, _updatedMarkets); - if (debtValue == 0) return type(uint256).max; + (, uint256 debtUsd, uint256 maxDebtUsd) = getUserBalanceStates(_user, _updatedMarkets); + if (debtUsd == 0) return type(uint256).max; - return maxDebtValue.div(debtValue); + return maxDebtUsd.div(debtUsd); } /// @dev Returns the debt value, max debt value of a given user. @@ -151,14 +151,14 @@ abstract contract UsersLens is IndexesLens { /// @param _poolToken The market to hypothetically withdraw/borrow in. /// @param _withdrawnAmount The amount to hypothetically withdraw from the given market (in underlying). /// @param _borrowedAmount The amount to hypothetically borrow from the given market (in underlying). - /// @return debtValue The current debt value of the user. - /// @return maxDebtValue The maximum debt value possible of the user. + /// @return debtUsd The current debt value of the user (in wad). + /// @return maxDebtUsd The maximum debt value possible of the user (in wad). function getUserHypotheticalBalanceStates( address _user, address _poolToken, uint256 _withdrawnAmount, uint256 _borrowedAmount - ) external view returns (uint256 debtValue, uint256 maxDebtValue) { + ) external view returns (uint256 debtUsd, uint256 maxDebtUsd) { ICompoundOracle oracle = ICompoundOracle(comptroller.oracle()); address[] memory createdMarkets = morpho.getAllMarkets(); @@ -177,8 +177,8 @@ abstract contract UsersLens is IndexesLens { ) : _getUserHypotheticalLiquidityDataForAsset(_user, poolToken, true, oracle, 0, 0); - maxDebtValue += assetData.maxDebtValue; - debtValue += assetData.debtValue; + maxDebtUsd += assetData.maxDebtUsd; + debtUsd += assetData.debtUsd; } } @@ -187,16 +187,16 @@ abstract contract UsersLens is IndexesLens { /// @notice Returns the collateral value, debt value and max debt value of a given user. /// @param _user The user to determine liquidity for. /// @param _updatedMarkets The list of markets of which to compute virtually updated pool and peer-to-peer indexes. - /// @return collateralValue The collateral value of the user. - /// @return debtValue The current debt value of the user. - /// @return maxDebtValue The maximum possible debt value of the user. - function getUserBalanceStates(address _user, address[] memory _updatedMarkets) + /// @return collateralUsd The collateral value of the user (in wad). + /// @return debtUsd The current debt value of the user (in wad). + /// @return maxDebtUsd The maximum possible debt value of the user (in wad). + function getUserBalanceStates(address _user, address[] calldata _updatedMarkets) public view returns ( - uint256 collateralValue, - uint256 debtValue, - uint256 maxDebtValue + uint256 collateralUsd, + uint256 debtUsd, + uint256 maxDebtUsd ) { ICompoundOracle oracle = ICompoundOracle(comptroller.oracle()); @@ -225,9 +225,9 @@ abstract contract UsersLens is IndexesLens { oracle ); - collateralValue += assetData.collateralValue; - maxDebtValue += assetData.maxDebtValue; - debtValue += assetData.debtValue; + collateralUsd += assetData.collateralUsd; + maxDebtUsd += assetData.maxDebtUsd; + debtUsd += assetData.debtUsd; unchecked { ++i; @@ -388,15 +388,15 @@ abstract contract UsersLens is IndexesLens { ICompoundOracle _oracle, uint256 _withdrawnAmount, uint256 _borrowedAmount - ) public view returns (Types.AssetLiquidityData memory assetData) { + ) internal view returns (Types.AssetLiquidityData memory assetData) { assetData.underlyingPrice = _oracle.getUnderlyingPrice(_poolToken); if (assetData.underlyingPrice == 0) revert CompoundOracleFailed(); (, assetData.collateralFactor, ) = comptroller.markets(_poolToken); - Types.Indexes memory indexes = getIndexes(_poolToken, _getUpdatedIndexes); + (, Types.Indexes memory indexes) = _getIndexes(_poolToken, _getUpdatedIndexes); - assetData.collateralValue = _getUserSupplyBalanceInOf( + assetData.collateralUsd = _getUserSupplyBalanceInOf( _poolToken, _user, indexes.p2pSupplyIndex, @@ -404,7 +404,7 @@ abstract contract UsersLens is IndexesLens { ).zeroFloorSub(_withdrawnAmount) .mul(assetData.underlyingPrice); - assetData.debtValue = (_getUserBorrowBalanceInOf( + assetData.debtUsd = (_getUserBorrowBalanceInOf( _poolToken, _user, indexes.p2pBorrowIndex, @@ -412,7 +412,7 @@ abstract contract UsersLens is IndexesLens { ) + _borrowedAmount) .mul(assetData.underlyingPrice); - assetData.maxDebtValue = assetData.collateralValue.mul(assetData.collateralFactor); + assetData.maxDebtUsd = assetData.collateralUsd.mul(assetData.collateralFactor); } /// @dev Returns whether a liquidation can be performed on the given user. @@ -429,8 +429,8 @@ abstract contract UsersLens is IndexesLens { ICompoundOracle oracle = ICompoundOracle(comptroller.oracle()); address[] memory enteredMarkets = morpho.getEnteredMarkets(_user); - uint256 debtValue; - uint256 maxDebtValue; + uint256 maxDebtUsd; + uint256 debtUsd; uint256 nbUpdatedMarkets = _updatedMarkets.length; for (uint256 i; i < enteredMarkets.length; ) { @@ -455,16 +455,16 @@ abstract contract UsersLens is IndexesLens { oracle ); - if (_poolTokenDeprecated == poolTokenEntered && assetData.debtValue > 0) return true; + if (_poolTokenDeprecated == poolTokenEntered && assetData.debtUsd > 0) return true; - maxDebtValue += assetData.maxDebtValue; - debtValue += assetData.debtValue; + maxDebtUsd += assetData.maxDebtUsd; + debtUsd += assetData.debtUsd; unchecked { ++i; } } - return debtValue > maxDebtValue; + return debtUsd > maxDebtUsd; } } diff --git a/src/compound/lens/interfaces/ILens.sol b/src/compound/lens/interfaces/ILens.sol index e6ad1d3f6..0dff67d6e 100644 --- a/src/compound/lens/interfaces/ILens.sol +++ b/src/compound/lens/interfaces/ILens.sol @@ -40,7 +40,6 @@ interface ILens { /// MARKETS /// - /// @dev Deprecated. function isMarketCreated(address _poolToken) external view returns (bool); /// @dev Deprecated. @@ -284,6 +283,16 @@ interface ILens { uint256 _balance ) external view returns (uint256); + function getAccruedSupplierComp(address _supplier, address _poolToken) + external + view + returns (uint256); + + function getAccruedBorrowerComp(address _borrower, address _poolToken) + external + view + returns (uint256); + function getCurrentCompSupplyIndex(address _poolToken) external view returns (uint256); function getCurrentCompBorrowIndex(address _poolToken) external view returns (uint256); diff --git a/src/compound/lens/interfaces/ILensExtension.sol b/src/compound/lens/interfaces/ILensExtension.sol new file mode 100644 index 000000000..29ef57aa3 --- /dev/null +++ b/src/compound/lens/interfaces/ILensExtension.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.5.0; + +import "../../interfaces/compound/ICompound.sol"; +import "../../interfaces/IRewardsManager.sol"; +import "../../interfaces/IMorpho.sol"; + +interface ILensExtension { + function morpho() external view returns (IMorpho); + + function getUserUnclaimedRewards(address[] calldata _poolTokens, address _user) + external + view + returns (uint256 unclaimedRewards); + + function getAccruedSupplierComp( + address _supplier, + address _poolToken, + uint256 _balance + ) external view returns (uint256); + + function getAccruedBorrowerComp( + address _borrower, + address _poolToken, + uint256 _balance + ) external view returns (uint256); + + function getAccruedSupplierComp(address _supplier, address _poolToken) + external + view + returns (uint256); + + function getAccruedBorrowerComp(address _borrower, address _poolToken) + external + view + returns (uint256); + + function getCurrentCompSupplyIndex(address _poolToken) external view returns (uint256); + + function getCurrentCompBorrowIndex(address _poolToken) external view returns (uint256); +} diff --git a/src/compound/libraries/CompoundMath.sol b/src/compound/libraries/CompoundMath.sol deleted file mode 100644 index 4bcac944c..000000000 --- a/src/compound/libraries/CompoundMath.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -/// @title CompoundMath. -/// @author Morpho Labs. -/// @custom:contact security@morpho.xyz -/// @dev Library emulating in solidity 8+ the behavior of Compound's mulScalarTruncate and divScalarByExpTruncate functions. -library CompoundMath { - /// ERRORS /// - - /// @notice Reverts when the number exceeds 224 bits. - error NumberExceeds224Bits(); - - /// @notice Reverts when the number exceeds 32 bits. - error NumberExceeds32Bits(); - - /// INTERNAL /// - - function mul(uint256 x, uint256 y) internal pure returns (uint256) { - return (x * y) / 1e18; - } - - function div(uint256 x, uint256 y) internal pure returns (uint256) { - return ((1e18 * x * 1e18) / y) / 1e18; - } - - function safe224(uint256 n) internal pure returns (uint224) { - if (n >= 2**224) revert NumberExceeds224Bits(); - return uint224(n); - } - - function safe32(uint256 n) internal pure returns (uint32) { - if (n >= 2**32) revert NumberExceeds32Bits(); - return uint32(n); - } - - function min( - uint256 a, - uint256 b, - uint256 c - ) internal pure returns (uint256) { - return a < b ? a < c ? a : c : b < c ? b : c; - } - - function min(uint256 a, uint256 b) internal pure returns (uint256) { - return a < b ? a : b; - } - - function safeSub(uint256 a, uint256 b) internal pure returns (uint256) { - return a >= b ? a - b : 0; - } -} diff --git a/src/compound/libraries/InterestRatesModel.sol b/src/compound/libraries/InterestRatesModel.sol index 8f8706629..b5044daeb 100644 --- a/src/compound/libraries/InterestRatesModel.sol +++ b/src/compound/libraries/InterestRatesModel.sol @@ -10,9 +10,6 @@ library InterestRatesModel { using PercentageMath for uint256; using CompoundMath for uint256; - uint256 public constant MAX_BASIS_POINTS = 100_00; // 100% (in basis points). - uint256 public constant WAD = 1e18; - /// STRUCTS /// struct GrowthFactors { @@ -22,22 +19,13 @@ library InterestRatesModel { uint256 p2pBorrowGrowthFactor; // Peer-to-peer borrow index growth factor (in wad). } - struct P2PSupplyIndexComputeParams { - uint256 poolSupplyGrowthFactor; // The pool supply index growth factor (in wad). - uint256 p2pSupplyGrowthFactor; // The peer-to-peer supply index growth factor (in wad). - uint256 lastP2PSupplyIndex; // The last stored peer-to-peer supply index (in wad). - uint256 lastPoolSupplyIndex; // The last stored pool supply index (in wad). - uint256 p2pSupplyDelta; // The peer-to-peer delta for the given market (in pool unit). - uint256 p2pSupplyAmount; // The peer-to-peer amount for the given market (in peer-to-peer unit). - } - - struct P2PBorrowIndexComputeParams { - uint256 poolBorrowGrowthFactor; // Pool borrow index growth factor (in wad). - uint256 p2pBorrowGrowthFactor; // Peer-to-peer borrow index growth factor (in wad). - uint256 lastP2PBorrowIndex; // Last stored peer-to-peer borrow index (in wad). - uint256 lastPoolBorrowIndex; // Last stored pool borrow index (in wad). - uint256 p2pBorrowDelta; // The peer-to-peer delta for the given market (in pool unit). - uint256 p2pBorrowAmount; // The peer-to-peer amount for the given market (in peer-to-peer unit). + struct P2PIndexComputeParams { + uint256 poolGrowthFactor; // The pool index growth factor (in wad). + uint256 p2pGrowthFactor; // Morpho's peer-to-peer median index growth factor (in wad). + uint256 lastPoolIndex; // The last stored pool index (in wad). + uint256 lastP2PIndex; // The last stored peer-to-peer index (in wad). + uint256 p2pDelta; // The peer-to-peer delta for the given market (in pool unit). + uint256 p2pAmount; // The peer-to-peer amount for the given market (in peer-to-peer unit). } struct P2PRateComputeParams { @@ -56,99 +44,70 @@ library InterestRatesModel { /// @param _lastPoolIndexes The last stored pool indexes. /// @param _p2pIndexCursor The peer-to-peer index cursor for the given market. /// @param _reserveFactor The reserve factor of the given market. - /// @return growthFactors_ The market's indexes growth factors (in wad). + /// @return growthFactors The market's indexes growth factors (in wad). function computeGrowthFactors( uint256 _newPoolSupplyIndex, uint256 _newPoolBorrowIndex, Types.LastPoolIndexes memory _lastPoolIndexes, - uint16 _p2pIndexCursor, + uint256 _p2pIndexCursor, uint256 _reserveFactor - ) internal pure returns (GrowthFactors memory growthFactors_) { - growthFactors_.poolSupplyGrowthFactor = _newPoolSupplyIndex.div( + ) internal pure returns (GrowthFactors memory growthFactors) { + growthFactors.poolSupplyGrowthFactor = _newPoolSupplyIndex.div( _lastPoolIndexes.lastSupplyPoolIndex ); - growthFactors_.poolBorrowGrowthFactor = _newPoolBorrowIndex.div( + growthFactors.poolBorrowGrowthFactor = _newPoolBorrowIndex.div( _lastPoolIndexes.lastBorrowPoolIndex ); - if (growthFactors_.poolSupplyGrowthFactor <= growthFactors_.poolBorrowGrowthFactor) { + if (growthFactors.poolSupplyGrowthFactor <= growthFactors.poolBorrowGrowthFactor) { uint256 p2pGrowthFactor = PercentageMath.weightedAvg( - growthFactors_.poolSupplyGrowthFactor, - growthFactors_.poolBorrowGrowthFactor, + growthFactors.poolSupplyGrowthFactor, + growthFactors.poolBorrowGrowthFactor, _p2pIndexCursor ); - growthFactors_.p2pSupplyGrowthFactor = + growthFactors.p2pSupplyGrowthFactor = p2pGrowthFactor - - (p2pGrowthFactor - growthFactors_.poolSupplyGrowthFactor).percentMul( - _reserveFactor - ); - growthFactors_.p2pBorrowGrowthFactor = + (p2pGrowthFactor - growthFactors.poolSupplyGrowthFactor).percentMul(_reserveFactor); + growthFactors.p2pBorrowGrowthFactor = p2pGrowthFactor + - (growthFactors_.poolBorrowGrowthFactor - p2pGrowthFactor).percentMul( - _reserveFactor - ); + (growthFactors.poolBorrowGrowthFactor - p2pGrowthFactor).percentMul(_reserveFactor); } else { // The case poolSupplyGrowthFactor > poolBorrowGrowthFactor happens because someone sent underlying tokens to the // cToken contract: the peer-to-peer growth factors are set to the pool borrow growth factor. - growthFactors_.p2pSupplyGrowthFactor = growthFactors_.poolBorrowGrowthFactor; - growthFactors_.p2pBorrowGrowthFactor = growthFactors_.poolBorrowGrowthFactor; - } - } - - /// @notice Computes and returns the new peer-to-peer supply index of a market given its parameters. - /// @param _params The computation parameters. - /// @return newP2PSupplyIndex_ The updated peer-to-peer index. - function computeP2PSupplyIndex(P2PSupplyIndexComputeParams memory _params) - internal - pure - returns (uint256 newP2PSupplyIndex_) - { - if (_params.p2pSupplyAmount == 0 || _params.p2pSupplyDelta == 0) { - newP2PSupplyIndex_ = _params.lastP2PSupplyIndex.mul(_params.p2pSupplyGrowthFactor); - } else { - uint256 shareOfTheDelta = Math.min( - (_params.p2pSupplyDelta.mul(_params.lastPoolSupplyIndex)).div( - (_params.p2pSupplyAmount).mul(_params.lastP2PSupplyIndex) - ), - WAD // To avoid shareOfTheDelta > 1 with rounding errors. - ); - - newP2PSupplyIndex_ = _params.lastP2PSupplyIndex.mul( - (WAD - shareOfTheDelta).mul(_params.p2pSupplyGrowthFactor) + - shareOfTheDelta.mul(_params.poolSupplyGrowthFactor) - ); + growthFactors.p2pSupplyGrowthFactor = growthFactors.poolBorrowGrowthFactor; + growthFactors.p2pBorrowGrowthFactor = growthFactors.poolBorrowGrowthFactor; } } - /// @notice Computes and returns the new peer-to-peer borrow index of a market given its parameters. + /// @notice Computes and returns the new peer-to-peer supply/borrow index of a market given its parameters. /// @param _params The computation parameters. - /// @return newP2PBorrowIndex_ The updated peer-to-peer index. - function computeP2PBorrowIndex(P2PBorrowIndexComputeParams memory _params) + /// @return newP2PIndex The updated peer-to-peer index (in wad). + function computeP2PIndex(P2PIndexComputeParams memory _params) internal pure - returns (uint256 newP2PBorrowIndex_) + returns (uint256 newP2PIndex) { - if (_params.p2pBorrowAmount == 0 || _params.p2pBorrowDelta == 0) { - newP2PBorrowIndex_ = _params.lastP2PBorrowIndex.mul(_params.p2pBorrowGrowthFactor); + if (_params.p2pAmount == 0 || _params.p2pDelta == 0) { + newP2PIndex = _params.lastP2PIndex.mul(_params.p2pGrowthFactor); } else { uint256 shareOfTheDelta = Math.min( - (_params.p2pBorrowDelta.mul(_params.lastPoolBorrowIndex)).div( - (_params.p2pBorrowAmount).mul(_params.lastP2PBorrowIndex) + (_params.p2pDelta.mul(_params.lastPoolIndex)).div( + (_params.p2pAmount).mul(_params.lastP2PIndex) ), - WAD // To avoid shareOfTheDelta > 1 with rounding errors. + CompoundMath.WAD // To avoid shareOfTheDelta > 1 with rounding errors. ); - newP2PBorrowIndex_ = _params.lastP2PBorrowIndex.mul( - (WAD - shareOfTheDelta).mul(_params.p2pBorrowGrowthFactor) + - shareOfTheDelta.mul(_params.poolBorrowGrowthFactor) + newP2PIndex = _params.lastP2PIndex.mul( + (CompoundMath.WAD - shareOfTheDelta).mul(_params.p2pGrowthFactor) + + shareOfTheDelta.mul(_params.poolGrowthFactor) ); } } /// @notice Computes and returns the peer-to-peer supply rate per block of a market given its parameters. /// @param _params The computation parameters. - /// @return p2pSupplyRate The peer-to-peer supply rate per block. + /// @return p2pSupplyRate The peer-to-peer supply rate per block (in wad). function computeP2PSupplyRatePerBlock(P2PRateComputeParams memory _params) internal pure @@ -156,26 +115,25 @@ library InterestRatesModel { { p2pSupplyRate = _params.p2pRate - - ((_params.p2pRate - _params.poolRate) * _params.reserveFactor) / - MAX_BASIS_POINTS; + (_params.p2pRate - _params.poolRate).percentMul(_params.reserveFactor); if (_params.p2pDelta > 0 && _params.p2pAmount > 0) { uint256 shareOfTheDelta = Math.min( _params.p2pDelta.mul(_params.poolIndex).div( _params.p2pAmount.mul(_params.p2pIndex) ), - WAD // To avoid shareOfTheDelta > 1 with rounding errors. + CompoundMath.WAD // To avoid shareOfTheDelta > 1 with rounding errors. ); p2pSupplyRate = - p2pSupplyRate.mul(WAD - shareOfTheDelta) + + p2pSupplyRate.mul(CompoundMath.WAD - shareOfTheDelta) + _params.poolRate.mul(shareOfTheDelta); } } /// @notice Computes and returns the peer-to-peer borrow rate per block of a market given its parameters. /// @param _params The computation parameters. - /// @return p2pBorrowRate The peer-to-peer borrow rate per block. + /// @return p2pBorrowRate The peer-to-peer borrow rate per block (in wad). function computeP2PBorrowRatePerBlock(P2PRateComputeParams memory _params) internal pure @@ -183,19 +141,18 @@ library InterestRatesModel { { p2pBorrowRate = _params.p2pRate + - ((_params.poolRate - _params.p2pRate) * _params.reserveFactor) / - MAX_BASIS_POINTS; + (_params.poolRate - _params.p2pRate).percentMul(_params.reserveFactor); if (_params.p2pDelta > 0 && _params.p2pAmount > 0) { uint256 shareOfTheDelta = Math.min( _params.p2pDelta.mul(_params.poolIndex).div( _params.p2pAmount.mul(_params.p2pIndex) ), - WAD // To avoid shareOfTheDelta > 1 with rounding errors. + CompoundMath.WAD // To avoid shareOfTheDelta > 1 with rounding errors. ); p2pBorrowRate = - p2pBorrowRate.mul(WAD - shareOfTheDelta) + + p2pBorrowRate.mul(CompoundMath.WAD - shareOfTheDelta) + _params.poolRate.mul(shareOfTheDelta); } } diff --git a/src/compound/libraries/Types.sol b/src/compound/libraries/Types.sol index 4a1d4472e..b01d8f92d 100644 --- a/src/compound/libraries/Types.sol +++ b/src/compound/libraries/Types.sol @@ -18,20 +18,20 @@ library Types { /// STRUCTS /// struct SupplyBalance { - uint256 inP2P; // In peer-to-peer supply scaled unit, a unit that grows in underlying value, to keep track of the interests earned by suppliers in peer-to-peer. Multiply by the peer-to-peer supply index to get the underlying amount. - uint256 onPool; // In pool supply scaled unit. Multiply by the pool supply index to get the underlying amount. + uint256 inP2P; // In peer-to-peer supply unit, a unit that grows in underlying value, to keep track of the interests earned by suppliers in peer-to-peer. Multiply by the peer-to-peer supply index to get the underlying amount. + uint256 onPool; // In pool supply unit. Multiply by the pool supply index to get the underlying amount. } struct BorrowBalance { - uint256 inP2P; // In peer-to-peer borrow scaled unit, a unit that grows in underlying value, to keep track of the interests paid by borrowers in peer-to-peer. Multiply by the peer-to-peer borrow index to get the underlying amount. + uint256 inP2P; // In peer-to-peer borrow unit, a unit that grows in underlying value, to keep track of the interests paid by borrowers in peer-to-peer. Multiply by the peer-to-peer borrow index to get the underlying amount. uint256 onPool; // In pool borrow unit, a unit that grows in value, to keep track of the debt increase when borrowers are on Compound. Multiply by the pool borrow index to get the underlying amount. } struct Indexes { - uint256 p2pSupplyIndex; // The peer-to-peer supply index (in wad), used to multiply the scaled peer-to-peer supply balance and get the peer-to-peer supply balance (in underlying). - uint256 p2pBorrowIndex; // The peer-to-peer borrow index (in wad), used to multiply the scaled peer-to-peer borrow balance and get the peer-to-peer borrow balance (in underlying). - uint256 poolSupplyIndex; // The pool supply index (in wad), used to multiply the scaled pool supply balance and get the pool supply balance (in underlying). - uint256 poolBorrowIndex; // The pool borrow index (in wad), used to multiply the scaled pool borrow balance and get the pool borrow balance (in underlying). + uint256 p2pSupplyIndex; // The peer-to-peer supply index (in wad), used to multiply the peer-to-peer supply scaled balance and get the peer-to-peer supply balance (in underlying). + uint256 p2pBorrowIndex; // The peer-to-peer borrow index (in wad), used to multiply the peer-to-peer borrow scaled balance and get the peer-to-peer borrow balance (in underlying). + uint256 poolSupplyIndex; // The pool supply index (in wad), used to multiply the pool supply scaled balance and get the pool supply balance (in underlying). + uint256 poolBorrowIndex; // The pool borrow index (in wad), used to multiply the pool borrow scaled balance and get the pool borrow balance (in underlying). } // Max gas to consume during the matching process for supply, borrow, withdraw and repay functions. @@ -50,17 +50,17 @@ library Types { } struct AssetLiquidityData { - uint256 collateralValue; // The collateral value of the asset. - uint256 maxDebtValue; // The maximum possible debt value of the asset. - uint256 debtValue; // The debt value of the asset. + uint256 collateralUsd; // The collateral value of the asset (in wad). + uint256 maxDebtUsd; // The maximum possible debt value of the asset (in wad). + uint256 debtUsd; // The debt value of the asset (in wad). uint256 underlyingPrice; // The price of the token. - uint256 collateralFactor; // The liquidation threshold applied on this token. + uint256 collateralFactor; // The liquidation threshold applied on this token (in wad). } struct LiquidityData { - uint256 collateralValue; // The collateral value. - uint256 maxDebtValue; // The maximum debt value possible. - uint256 debtValue; // The debt value. + uint256 collateralUsd; // The collateral value (in wad). + uint256 maxDebtUsd; // The maximum debt value allowed before being liquidatable (in wad). + uint256 debtUsd; // The debt value (in wad). } // Variables are packed together to save gas (will not exceed their limit during Morpho's lifetime). diff --git a/test/aave-v2/TestBorrow.t.sol b/test/aave-v2/TestBorrow.t.sol index 2941c7f8a..c41528cfd 100644 --- a/test/aave-v2/TestBorrow.t.sol +++ b/test/aave-v2/TestBorrow.t.sol @@ -247,23 +247,6 @@ contract TestBorrow is TestSetup { borrower1.borrow(aUsdc, amount); } - function testShouldNotBorrowWithDisabledCollateral() public { - uint256 amount = 100 ether; - - borrower1.approve(dai, type(uint256).max); - borrower1.supply(aDai, amount * 10); - - // Give Morpho a position on the pool to be able to unset the DAI asset as collateral. - // Without this position on the pool, it's not possible to do so. - supplier1.approve(usdc, to6Decimals(amount)); - supplier1.supply(aUsdc, to6Decimals(amount)); - - morpho.setAssetAsCollateral(aDai, false); - - hevm.expectRevert(EntryPositionsManager.UnauthorisedBorrow.selector); - borrower1.borrow(aUsdc, to6Decimals(amount)); - } - function testBorrowLargerThanDeltaShouldClearDelta() public { // Allows only 10 unmatch suppliers. diff --git a/test/aave-v2/TestGovernance.t.sol b/test/aave-v2/TestGovernance.t.sol index a50ce089a..cc30e2f71 100644 --- a/test/aave-v2/TestGovernance.t.sol +++ b/test/aave-v2/TestGovernance.t.sol @@ -137,15 +137,6 @@ contract TestGovernance is TestSetup { assertEq(address(morpho.entryPositionsManager()), address(entryPositionsManagerV2)); } - function testOnlyOwnerShouldSetRewardsManager() public { - hevm.prank(address(0)); - hevm.expectRevert("Ownable: caller is not the owner"); - morpho.setRewardsManager(IRewardsManager(address(1))); - - morpho.setRewardsManager(IRewardsManager(address(1))); - assertEq(address(morpho.rewardsManager()), address(1)); - } - function testOnlyOwnerShouldSetInterestRatesManager() public { IInterestRatesManager interestRatesV2 = new InterestRatesManager(); @@ -157,23 +148,6 @@ contract TestGovernance is TestSetup { assertEq(address(morpho.interestRatesManager()), address(interestRatesV2)); } - function testOnlyOwnerShouldSetIncentivesVault() public { - IIncentivesVault incentivesVaultV2 = new IncentivesVault( - IMorpho(address(morpho)), - morphoToken, - ERC20(address(1)), - address(2), - dumbOracle - ); - - hevm.prank(address(0)); - hevm.expectRevert("Ownable: caller is not the owner"); - morpho.setIncentivesVault(incentivesVaultV2); - - morpho.setIncentivesVault(incentivesVaultV2); - assertEq(address(morpho.incentivesVault()), address(incentivesVaultV2)); - } - function testOnlyOwnerShouldSetTreasuryVault() public { address treasuryVaultV2 = address(2); @@ -185,15 +159,6 @@ contract TestGovernance is TestSetup { assertEq(address(morpho.treasuryVault()), treasuryVaultV2); } - function testOnlyOwnerCanSetIsClaimRewardsPaused() public { - hevm.prank(address(0)); - hevm.expectRevert("Ownable: caller is not the owner"); - morpho.setIsClaimRewardsPaused(true); - - morpho.setIsClaimRewardsPaused(true); - assertTrue(morpho.isClaimRewardsPaused()); - } - function testOnlyOwnerCanSetPauseStatusForAllMarkets() public { hevm.prank(address(0)); hevm.expectRevert("Ownable: caller is not the owner"); @@ -203,6 +168,8 @@ contract TestGovernance is TestSetup { } function testOnlyOwnerShouldSetDeprecatedMarket() public { + morpho.setIsBorrowPaused(aDai, true); + hevm.prank(address(supplier1)); hevm.expectRevert("Ownable: caller is not the owner"); morpho.setIsDeprecated(aDai, true); @@ -440,4 +407,18 @@ contract TestGovernance is TestSetup { function testFailCallIncreaseP2PDeltasFromImplementation() public { exitPositionsManager.increaseP2PDeltasLogic(aDai, 0); } + + function testDeprecateCycle() public { + hevm.expectRevert(abi.encodeWithSignature("BorrowNotPaused()")); + morpho.setIsDeprecated(aDai, true); + + morpho.setIsBorrowPaused(aDai, true); + morpho.setIsDeprecated(aDai, true); + + hevm.expectRevert(abi.encodeWithSignature("MarketIsDeprecated()")); + morpho.setIsBorrowPaused(aDai, false); + + morpho.setIsDeprecated(aDai, false); + morpho.setIsBorrowPaused(aDai, false); + } } diff --git a/test/aave-v2/TestIncentivesVault.t.sol b/test/aave-v2/TestIncentivesVault.t.sol deleted file mode 100644 index e951ac735..000000000 --- a/test/aave-v2/TestIncentivesVault.t.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./setup/TestSetup.sol"; - -contract TestIncentivesVault is TestSetup { - using SafeTransferLib for ERC20; - - function testShouldNotSetBonusAboveMaxBasisPoints() public { - uint256 moreThanMaxBasisPoints = PercentageMath.PERCENTAGE_FACTOR + 1; - hevm.expectRevert(abi.encodeWithSelector(IncentivesVault.ExceedsMaxBasisPoints.selector)); - incentivesVault.setBonus(moreThanMaxBasisPoints); - } - - function testOnlyOwnerShouldSetBonus() public { - uint256 bonusToSet = 1; - - hevm.prank(address(0)); - hevm.expectRevert("Ownable: caller is not the owner"); - incentivesVault.setBonus(bonusToSet); - - incentivesVault.setBonus(bonusToSet); - assertEq(incentivesVault.bonus(), bonusToSet); - } - - function testOnlyOwnerShouldSetIncentivesTreasuryVault() public { - hevm.prank(address(0)); - hevm.expectRevert("Ownable: caller is not the owner"); - incentivesVault.setIncentivesTreasuryVault(address(treasuryVault)); - - incentivesVault.setIncentivesTreasuryVault(address(treasuryVault)); - assertEq(incentivesVault.incentivesTreasuryVault(), address(treasuryVault)); - } - - function testOnlyOwnerShouldSetOracle() public { - IOracle oracle = IOracle(address(1)); - - hevm.prank(address(0)); - hevm.expectRevert("Ownable: caller is not the owner"); - incentivesVault.setOracle(oracle); - - incentivesVault.setOracle(oracle); - assertEq(address(incentivesVault.oracle()), address(oracle)); - } - - function testOnlyOwnerShouldSetPauseStatus() public { - hevm.prank(address(0)); - hevm.expectRevert("Ownable: caller is not the owner"); - incentivesVault.setPauseStatus(true); - - incentivesVault.setPauseStatus(true); - assertTrue(incentivesVault.isPaused()); - - incentivesVault.setPauseStatus(false); - assertFalse(incentivesVault.isPaused()); - } - - function testOnlyOwnerShouldTransferTokensToDao() public { - hevm.prank(address(supplier1)); - hevm.expectRevert("Ownable: caller is not the owner"); - incentivesVault.transferTokensToDao(address(morphoToken), 1); - - incentivesVault.transferTokensToDao(address(morphoToken), 1); - assertEq(ERC20(morphoToken).balanceOf(address(treasuryVault)), 1); - } - - function testFailWhenContractNotActive() public { - incentivesVault.setPauseStatus(true); - - hevm.prank(address(morphoProxy)); - incentivesVault.tradeRewardTokensForMorphoTokens(address(1), 0); - } - - function testOnlyMorphoShouldTriggerRewardTradeFunction() public { - incentivesVault.setIncentivesTreasuryVault(address(1)); - uint256 amount = 100; - deal(REWARD_TOKEN, address(morphoProxy), amount); - - hevm.prank(address(morphoProxy)); - ERC20(REWARD_TOKEN).safeApprove(address(incentivesVault), amount); - - hevm.expectRevert(abi.encodeWithSignature("OnlyMorpho()")); - incentivesVault.tradeRewardTokensForMorphoTokens(address(2), amount); - - hevm.prank(address(morphoProxy)); - incentivesVault.tradeRewardTokensForMorphoTokens(address(2), amount); - } - - function testShouldGiveTheRightAmountOfRewards() public { - incentivesVault.setIncentivesTreasuryVault(address(1)); - uint256 toApprove = 1_000 ether; - deal(REWARD_TOKEN, address(morphoProxy), toApprove); - - hevm.prank(address(morphoProxy)); - ERC20(REWARD_TOKEN).safeApprove(address(incentivesVault), toApprove); - uint256 amount = 100; - - // O% bonus. - uint256 balanceBefore = ERC20(morphoToken).balanceOf(address(2)); - hevm.prank(address(morphoProxy)); - incentivesVault.tradeRewardTokensForMorphoTokens(address(2), amount); - uint256 balanceAfter = ERC20(morphoToken).balanceOf(address(2)); - assertEq(balanceAfter - balanceBefore, 100); - - // 10% bonus. - incentivesVault.setBonus(1_000); - balanceBefore = ERC20(morphoToken).balanceOf(address(2)); - hevm.prank(address(morphoProxy)); - incentivesVault.tradeRewardTokensForMorphoTokens(address(2), amount); - balanceAfter = ERC20(morphoToken).balanceOf(address(2)); - assertEq(balanceAfter - balanceBefore, 110); - } -} diff --git a/test/aave-v2/TestInterestRates.t.sol b/test/aave-v2/TestInterestRates.t.sol index 91b1db259..cf5b4d625 100644 --- a/test/aave-v2/TestInterestRates.t.sol +++ b/test/aave-v2/TestInterestRates.t.sol @@ -16,8 +16,8 @@ contract TestInterestRates is InterestRatesManager, Test { uint256 public p2pBorrowIndexTest = 1 * RAY; uint256 public poolSupplyIndexTest = 2 * RAY; uint256 public poolBorrowIndexTest = 3 * RAY; - uint256 public lastPoolSupplyIndexTest = 1 * RAY; - uint256 public lastPoolBorrowIndexTest = 1 * RAY; + Types.PoolIndexes public lastPoolIndexesTest = + Types.PoolIndexes(uint32(block.timestamp), uint112(1 * RAY), uint112(1 * RAY)); uint256 public reserveFactor0PerCentTest = 0; uint256 public reserveFactor50PerCentTest = 5_000; uint256 public p2pIndexCursorTest = 3_333; @@ -28,15 +28,15 @@ contract TestInterestRates is InterestRatesManager, Test { pure returns (uint256 p2pSupplyIndex_, uint256 p2pBorrowIndex_) { - uint256 poolSupplyGrowthFactor = _params.poolSupplyIndex.rayDiv(_params.lastPoolSupplyIndex); - uint256 poolBorrowGrowthFactor = _params.poolBorrowIndex.rayDiv(_params.lastPoolBorrowIndex); + uint256 poolSupplyGrowthFactor = _params.poolSupplyIndex.rayDiv(_params.lastPoolIndexes.poolSupplyIndex); + uint256 poolBorrowGrowthFactor = _params.poolBorrowIndex.rayDiv(_params.lastPoolIndexes.poolBorrowIndex); uint256 p2pGrowthFactor = (poolSupplyGrowthFactor.percentMul(PercentageMath.PERCENTAGE_FACTOR - _params.p2pIndexCursor) + poolBorrowGrowthFactor.percentMul(_params.p2pIndexCursor)); uint256 shareOfTheSupplyDelta = _params.delta.p2pBorrowAmount > 0 - ? (_params.delta.p2pSupplyDelta.rayMul(_params.lastPoolSupplyIndex)).rayDiv( + ? (_params.delta.p2pSupplyDelta.rayMul(_params.lastPoolIndexes.poolSupplyIndex)).rayDiv( _params.delta.p2pSupplyAmount.rayMul(_params.lastP2PSupplyIndex)) : 0; uint256 shareOfTheBorrowDelta = _params.delta.p2pSupplyAmount > 0 - ? (_params.delta.p2pBorrowDelta.rayMul(_params.lastPoolBorrowIndex)).rayDiv( + ? (_params.delta.p2pBorrowDelta.rayMul(_params.lastPoolIndexes.poolBorrowIndex)).rayDiv( _params.delta.p2pBorrowAmount.rayMul(_params.lastP2PBorrowIndex)) : 0; if (poolSupplyGrowthFactor <= poolBorrowGrowthFactor) { @@ -69,8 +69,7 @@ contract TestInterestRates is InterestRatesManager, Test { p2pBorrowIndexTest, poolSupplyIndexTest, poolBorrowIndexTest, - lastPoolSupplyIndexTest, - lastPoolBorrowIndexTest, + lastPoolIndexesTest, reserveFactor0PerCentTest, p2pIndexCursorTest, Types.Delta(0, 0, 0, 0) @@ -88,8 +87,7 @@ contract TestInterestRates is InterestRatesManager, Test { p2pBorrowIndexTest, poolSupplyIndexTest, poolBorrowIndexTest, - lastPoolSupplyIndexTest, - lastPoolBorrowIndexTest, + lastPoolIndexesTest, reserveFactor50PerCentTest, p2pIndexCursorTest, Types.Delta(0, 0, 0, 0) @@ -107,8 +105,7 @@ contract TestInterestRates is InterestRatesManager, Test { p2pBorrowIndexTest, poolSupplyIndexTest, poolBorrowIndexTest, - lastPoolSupplyIndexTest, - lastPoolBorrowIndexTest, + lastPoolIndexesTest, reserveFactor0PerCentTest, p2pIndexCursorTest, Types.Delta(1 * RAY, 1 * RAY, 4 * RAY, 6 * RAY) @@ -126,8 +123,7 @@ contract TestInterestRates is InterestRatesManager, Test { p2pBorrowIndexTest, poolSupplyIndexTest, poolBorrowIndexTest, - lastPoolSupplyIndexTest, - lastPoolBorrowIndexTest, + lastPoolIndexesTest, reserveFactor50PerCentTest, p2pIndexCursorTest, Types.Delta(1 * RAY, 1 * RAY, 4 * RAY, 6 * RAY) @@ -145,8 +141,7 @@ contract TestInterestRates is InterestRatesManager, Test { p2pBorrowIndexTest, poolBorrowIndexTest * 2, poolBorrowIndexTest, - lastPoolSupplyIndexTest, - lastPoolBorrowIndexTest, + lastPoolIndexesTest, reserveFactor50PerCentTest, p2pIndexCursorTest, Types.Delta(0, 0, 0, 0) @@ -164,8 +159,7 @@ contract TestInterestRates is InterestRatesManager, Test { p2pBorrowIndexTest, poolBorrowIndexTest * 2, poolBorrowIndexTest, - lastPoolSupplyIndexTest, - lastPoolBorrowIndexTest, + lastPoolIndexesTest, reserveFactor50PerCentTest, p2pIndexCursorTest, Types.Delta(1 * RAY, 1 * RAY, 4 * RAY, 6 * RAY) diff --git a/test/aave-v2/TestLens.t.sol b/test/aave-v2/TestLens.t.sol index a5afa0c5f..ac901243b 100644 --- a/test/aave-v2/TestLens.t.sol +++ b/test/aave-v2/TestLens.t.sol @@ -42,8 +42,8 @@ contract TestLens is TestSetup { assertEq(assetData.decimals, decimals); assertEq(assetData.underlyingPrice, underlyingPrice); assertEq(assetData.tokenUnit, tokenUnit); - assertEq(assetData.collateral, 0); - assertEq(assetData.debt, 0); + assertEq(assetData.collateralEth, 0); + assertEq(assetData.debtEth, 0); } function testUserLiquidityDataForAssetWithSupply() public { @@ -68,8 +68,8 @@ contract TestLens is TestSetup { assertEq(assetData.liquidationThreshold, liquidationThreshold, "liquidationThreshold"); assertEq(assetData.underlyingPrice, underlyingPrice, "underlyingPrice"); assertEq(assetData.tokenUnit, tokenUnit, "tokenUnit"); - assertEq(assetData.collateral, (amount * underlyingPrice) / tokenUnit, "collateral"); - assertEq(assetData.debt, 0, "debt"); + assertEq(assetData.collateralEth, (amount * underlyingPrice) / tokenUnit, "collateral"); + assertEq(assetData.debtEth, 0, "debt"); } function testUserLiquidityDataForOtherAssetThanSupply() public { @@ -94,8 +94,8 @@ contract TestLens is TestSetup { assertEq(assetData.liquidationThreshold, liquidationThreshold, "liquidationThreshold"); assertEq(assetData.underlyingPrice, underlyingPrice, "underlyingPrice"); assertEq(assetData.tokenUnit, tokenUnit, "tokenUnit"); - assertEq(assetData.collateral, 0, "collateral"); - assertEq(assetData.debt, 0, "debt"); + assertEq(assetData.collateralEth, 0, "collateral"); + assertEq(assetData.debtEth, 0, "debt"); } function testUserLiquidityDataForAssetWithSupplyAndBorrow() public { @@ -125,8 +125,8 @@ contract TestLens is TestSetup { assertEq(assetData.underlyingPrice, underlyingPrice, "underlyingPrice"); assertEq(assetData.decimals, decimals, "decimals"); assertEq(assetData.tokenUnit, tokenUnit, "tokenUnit"); - assertApproxEqAbs(assetData.collateral, collateral, 2, "collateral"); - assertEq(assetData.debt, debt, "debt"); + assertApproxEqAbs(assetData.collateralEth, collateral, 2, "collateral"); + assertEq(assetData.debtEth, debt, "debt"); } function testUserLiquidityDataForAssetWithSupplyAndBorrowWithMultipleAssets() public { @@ -157,7 +157,7 @@ contract TestLens is TestSetup { .getParamsMemory(); expectedDataUsdc.underlyingPrice = oracle.getAssetPrice(usdc); expectedDataUsdc.tokenUnit = 10**decimalsUsdc; - expectedDataUsdc.debt = + expectedDataUsdc.debtEth = (toBorrow * expectedDataUsdc.underlyingPrice) / expectedDataUsdc.tokenUnit; @@ -173,8 +173,8 @@ contract TestLens is TestSetup { "underlyingPriceUsdc" ); assertEq(assetDataUsdc.tokenUnit, expectedDataUsdc.tokenUnit, "tokenUnitUsdc"); - assertEq(assetDataUsdc.collateral, 0, "collateralUsdc"); - assertEq(assetDataUsdc.debt, expectedDataUsdc.debt, "debtUsdc"); + assertEq(assetDataUsdc.collateralEth, 0, "collateralUsdc"); + assertEq(assetDataUsdc.debtEth, expectedDataUsdc.debtEth, "debtUsdc"); Types.AssetLiquidityData memory expectedDataDai; uint256 decimalsDai; @@ -184,7 +184,7 @@ contract TestLens is TestSetup { .getParamsMemory(); expectedDataDai.underlyingPrice = oracle.getAssetPrice(dai); expectedDataDai.tokenUnit = 10**decimalsDai; - expectedDataDai.collateral = + expectedDataDai.collateralEth = (amount * expectedDataDai.underlyingPrice) / expectedDataDai.tokenUnit; @@ -200,8 +200,8 @@ contract TestLens is TestSetup { "underlyingPriceDai" ); assertEq(assetDataDai.tokenUnit, expectedDataDai.tokenUnit, "tokenUnitDai"); - assertEq(assetDataDai.collateral, expectedDataDai.collateral, "collateralDai"); - assertEq(assetDataDai.debt, 0, "debtDai"); + assertEq(assetDataDai.collateralEth, expectedDataDai.collateralEth, "collateralDai"); + assertEq(assetDataDai.debtEth, 0, "debtDai"); } function testUserHypotheticalBalanceStatesUnenteredMarket() public { @@ -226,14 +226,14 @@ contract TestLens is TestSetup { uint256 collateral = (amount * oracle.getAssetPrice(dai)) / 10**daiDecimals; assertEq( - liquidityData.liquidationThreshold, + liquidityData.maxDebtEth, collateral.percentMul(daiLiquidationThreshold), - "liquidationThreshold" + "maxDebtEth" ); - assertEq(liquidityData.maxDebt, collateral.percentMul(daiLtv), "maxDebt"); - assertEq(liquidityData.collateral, collateral, "collateral"); + assertEq(liquidityData.borrowableEth, collateral.percentMul(daiLtv), "borrowableEth"); + assertEq(liquidityData.collateralEth, collateral, "collateralEth"); assertEq( - liquidityData.debt, + liquidityData.debtEth, (hypotheticalBorrow * oracle.getAssetPrice(usdc)).divUp(10**usdcDecimals), "debt" ); @@ -256,13 +256,13 @@ contract TestLens is TestSetup { (, , , uint256 daiDecimals, ) = pool.getConfiguration(dai).getParamsMemory(); - assertEq(liquidityData.liquidationThreshold, 0, "liquidationThreshold"); - assertEq(liquidityData.maxDebt, 0, "maxDebt"); - assertEq(liquidityData.collateral, 0, "collateral"); + assertEq(liquidityData.maxDebtEth, 0, "maxDebtEth"); + assertEq(liquidityData.borrowableEth, 0, "borrowableEth"); + assertEq(liquidityData.collateralEth, 0, "collateralEth"); assertEq( - liquidityData.debt, + liquidityData.debtEth, (hypotheticalBorrow * oracle.getAssetPrice(dai)).divUp(10**daiDecimals), - "debt" + "debtEth" ); } @@ -294,9 +294,10 @@ contract TestLens is TestSetup { oracle ); - uint256 expectedBorrowableUsdc = (assetDataUsdc.collateral.percentMul(assetDataUsdc.ltv) * - assetDataUsdc.tokenUnit) / assetDataUsdc.underlyingPrice; - uint256 expectedBorrowableDai = (assetDataUsdc.collateral.percentMul(assetDataUsdc.ltv) * + uint256 expectedBorrowableUsdc = (assetDataUsdc.collateralEth.percentMul( + assetDataUsdc.ltv + ) * assetDataUsdc.tokenUnit) / assetDataUsdc.underlyingPrice; + uint256 expectedBorrowableDai = (assetDataUsdc.collateralEth.percentMul(assetDataUsdc.ltv) * assetDataDai.tokenUnit) / assetDataDai.underlyingPrice; (uint256 withdrawable, uint256 borrowable) = lens.getUserMaxCapacitiesForAsset( @@ -548,8 +549,9 @@ contract TestLens is TestSetup { (uint256 withdrawableUsdc, ) = lens.getUserMaxCapacitiesForAsset(address(borrower1), aUsdc); (, uint256 borrowableUsdt) = lens.getUserMaxCapacitiesForAsset(address(borrower1), aUsdt); - uint256 expectedBorrowableUsdt = ((assetDataUsdc.collateral.percentMul(assetDataUsdc.ltv) + - assetDataDai.collateral.percentMul(assetDataDai.ltv)) * assetDataUsdt.tokenUnit) / + uint256 expectedBorrowableUsdt = ((assetDataUsdc.collateralEth.percentMul( + assetDataUsdc.ltv + ) + assetDataDai.collateralEth.percentMul(assetDataDai.ltv)) * assetDataUsdt.tokenUnit) / assetDataUsdt.underlyingPrice; assertEq(withdrawableUsdc, to6Decimals(amount), "unexpected new withdrawable usdc"); @@ -592,26 +594,20 @@ contract TestLens is TestSetup { uint256 underlyingPriceDai = oracle.getAssetPrice(dai); uint256 tokenUnitDai = 10**decimalsDai; - expectedStates.collateral = (amount * underlyingPriceDai) / tokenUnitDai; - expectedStates.debt = (toBorrow * underlyingPriceUsdc) / tokenUnitUsdc; - expectedStates.liquidationThreshold = expectedStates.collateral.percentMul( + expectedStates.collateralEth = (amount * underlyingPriceDai) / tokenUnitDai; + expectedStates.debtEth = (toBorrow * underlyingPriceUsdc) / tokenUnitUsdc; + expectedStates.maxDebtEth = expectedStates.collateralEth.percentMul( liquidationThresholdDai ); - expectedStates.maxDebt = expectedStates.collateral.percentMul(ltvDai); + expectedStates.borrowableEth = expectedStates.collateralEth.percentMul(ltvDai); - uint256 healthFactor = states.liquidationThreshold.wadDiv(states.debt); - uint256 expectedHealthFactor = expectedStates.liquidationThreshold.wadDiv( - expectedStates.debt - ); + uint256 healthFactor = states.maxDebtEth.wadDiv(states.debtEth); + uint256 expectedHealthFactor = expectedStates.maxDebtEth.wadDiv(expectedStates.debtEth); - assertEq(states.collateral, expectedStates.collateral, "collateral"); - assertEq(states.debt, expectedStates.debt, "debt"); - assertEq( - states.liquidationThreshold, - expectedStates.liquidationThreshold, - "liquidationThreshold" - ); - assertEq(states.maxDebt, expectedStates.maxDebt, "maxDebt"); + assertEq(states.collateralEth, expectedStates.collateralEth, "collateral"); + assertEq(states.debtEth, expectedStates.debtEth, "debt"); + assertEq(states.maxDebtEth, expectedStates.maxDebtEth, "liquidationThreshold"); + assertEq(states.borrowableEth, expectedStates.borrowableEth, "maxDebt"); assertEq(healthFactor, expectedHealthFactor, "healthFactor"); } @@ -637,42 +633,34 @@ contract TestLens is TestSetup { .getParamsMemory(); uint256 collateralValueToAdd = (to6Decimals(amount) * oracle.getAssetPrice(usdc)) / 10**decimals; - expectedStates.collateral += collateralValueToAdd; - expectedStates.liquidationThreshold += collateralValueToAdd.percentMul( - liquidationThreshold - ); - expectedStates.maxDebt += collateralValueToAdd.percentMul(ltv); + expectedStates.collateralEth += collateralValueToAdd; + expectedStates.maxDebtEth += collateralValueToAdd.percentMul(liquidationThreshold); + expectedStates.borrowableEth += collateralValueToAdd.percentMul(ltv); // DAI data (ltv, liquidationThreshold, , decimals, ) = pool.getConfiguration(dai).getParamsMemory(); collateralValueToAdd = (amount * oracle.getAssetPrice(dai)) / 10**decimals; - expectedStates.collateral += collateralValueToAdd; - expectedStates.liquidationThreshold += collateralValueToAdd.percentMul( - liquidationThreshold - ); - expectedStates.maxDebt += collateralValueToAdd.percentMul(ltv); + expectedStates.collateralEth += collateralValueToAdd; + expectedStates.maxDebtEth += collateralValueToAdd.percentMul(liquidationThreshold); + expectedStates.borrowableEth += collateralValueToAdd.percentMul(ltv); // WBTC data (, , , decimals, ) = pool.getConfiguration(wbtc).getParamsMemory(); - expectedStates.debt += (toBorrowWbtc * oracle.getAssetPrice(wbtc)) / 10**decimals; + expectedStates.debtEth += (toBorrowWbtc * oracle.getAssetPrice(wbtc)) / 10**decimals; // USDT data (, , , decimals, ) = pool.getConfiguration(usdt).getParamsMemory(); - expectedStates.debt += (to6Decimals(toBorrow) * oracle.getAssetPrice(usdt)) / 10**decimals; + expectedStates.debtEth += + (to6Decimals(toBorrow) * oracle.getAssetPrice(usdt)) / + 10**decimals; - uint256 healthFactor = states.liquidationThreshold.wadDiv(states.debt); - uint256 expectedHealthFactor = expectedStates.liquidationThreshold.wadDiv( - expectedStates.debt - ); + uint256 healthFactor = states.maxDebtEth.wadDiv(states.debtEth); + uint256 expectedHealthFactor = expectedStates.maxDebtEth.wadDiv(expectedStates.debtEth); - assertApproxEqAbs(states.collateral, expectedStates.collateral, 2, "collateral"); - assertApproxEqAbs(states.debt, expectedStates.debt, 1, "debt"); - assertEq( - states.liquidationThreshold, - expectedStates.liquidationThreshold, - "liquidationThreshold" - ); - assertEq(states.maxDebt, expectedStates.maxDebt, "maxDebt"); + assertApproxEqAbs(states.collateralEth, expectedStates.collateralEth, 2, "collateral"); + assertApproxEqAbs(states.debtEth, expectedStates.debtEth, 1, "debt"); + assertEq(states.maxDebtEth, expectedStates.maxDebtEth, "liquidationThreshold"); + assertEq(states.borrowableEth, expectedStates.borrowableEth, "maxDebt"); assertApproxEqAbs(healthFactor, expectedHealthFactor, 1e4, "healthFactor"); } @@ -726,38 +714,32 @@ contract TestLens is TestSetup { (ltv, liquidationThreshold, , decimals, ) = pool.getConfiguration(usdt).getParamsMemory(); uint256 collateralValueUsdt = (to6Decimals(amount) * oracle.getAssetPrice(usdt)) / 10**decimals; - expectedStates.collateral += collateralValueUsdt; - expectedStates.liquidationThreshold += collateralValueUsdt.percentMul(liquidationThreshold); - expectedStates.maxDebt += collateralValueUsdt.percentMul(ltv); + expectedStates.collateralEth += collateralValueUsdt; + expectedStates.maxDebtEth += collateralValueUsdt.percentMul(liquidationThreshold); + expectedStates.borrowableEth += collateralValueUsdt.percentMul(ltv); // DAI data (ltv, liquidationThreshold, , decimals, ) = pool.getConfiguration(dai).getParamsMemory(); uint256 collateralValueDai = (amount * oracle.getAssetPrice(dai)) / 10**decimals; - expectedStates.collateral += collateralValueDai; - expectedStates.liquidationThreshold += collateralValueDai.percentMul(liquidationThreshold); - expectedStates.maxDebt += collateralValueDai.percentMul(ltv); + expectedStates.collateralEth += collateralValueDai; + expectedStates.maxDebtEth += collateralValueDai.percentMul(liquidationThreshold); + expectedStates.borrowableEth += collateralValueDai.percentMul(ltv); // USDC data (, , , decimals, ) = pool.getConfiguration(usdc).getParamsMemory(); - expectedStates.debt += (toBorrow * oracle.getAssetPrice(usdc)) / 10**decimals; + expectedStates.debtEth += (toBorrow * oracle.getAssetPrice(usdc)) / 10**decimals; // USDT data (, , , decimals, ) = pool.getConfiguration(usdt).getParamsMemory(); - expectedStates.debt += (toBorrow * oracle.getAssetPrice(usdt)) / 10**decimals; + expectedStates.debtEth += (toBorrow * oracle.getAssetPrice(usdt)) / 10**decimals; - uint256 healthFactor = states.liquidationThreshold.wadDiv(states.debt); - uint256 expectedHealthFactor = expectedStates.liquidationThreshold.wadDiv( - expectedStates.debt - ); + uint256 healthFactor = states.maxDebtEth.wadDiv(states.debtEth); + uint256 expectedHealthFactor = expectedStates.maxDebtEth.wadDiv(expectedStates.debtEth); - assertApproxEqAbs(states.collateral, expectedStates.collateral, 1e3, "collateral"); - assertEq(states.debt, expectedStates.debt, "debt"); - assertEq( - states.liquidationThreshold, - expectedStates.liquidationThreshold, - "liquidationThreshold" - ); - assertEq(states.maxDebt, expectedStates.maxDebt, "maxDebt"); + assertApproxEqAbs(states.collateralEth, expectedStates.collateralEth, 1e3, "collateral"); + assertEq(states.debtEth, expectedStates.debtEth, "debt"); + assertEq(states.maxDebtEth, expectedStates.maxDebtEth, "liquidationThreshold"); + assertEq(states.borrowableEth, expectedStates.borrowableEth, "maxDebt"); assertEq(healthFactor, expectedHealthFactor, "healthFactor"); } @@ -1351,7 +1333,7 @@ contract TestLens is TestSetup { uint256 toRepay = lens.computeLiquidationRepayAmount(address(borrower1), aUsdc, aDai); - if (states.debt <= states.liquidationThreshold) { + if (states.debtEth <= states.maxDebtEth) { assertEq(toRepay, 0, "Should return 0 when the position is solvent"); return; } @@ -1373,14 +1355,14 @@ contract TestLens is TestSetup { } while (lens.isLiquidatable(address(borrower1)) && toRepay > 0); // either the liquidatee's position (borrow value divided by supply value) was under the [1 / liquidationBonus] threshold and returned to a solvent position - if (states.collateral.percentDiv(liquidationBonus) > states.debt) { + if (states.collateralEth.percentDiv(liquidationBonus) > states.debtEth) { assertFalse(lens.isLiquidatable(address(borrower1)), "borrower1 liquidatable"); } else { // or the liquidator has drained all the collateral states = lens.getUserBalanceStates(address(borrower1)); assertGt( - states.debt, - states.collateral.percentDiv(liquidationBonus), + states.debtEth, + states.collateralEth.percentDiv(liquidationBonus), "debt value under collateral value" ); assertEq(toRepay, 0, "to repay not zero"); @@ -1388,8 +1370,8 @@ contract TestLens is TestSetup { } else { // liquidator cannot repay anything iff 1 wei of borrow is greater than the repayable collateral + the liquidation bonus assertGt( - states.debt, - states.collateral.percentDiv(liquidationBonus), + states.debtEth, + states.collateralEth.percentDiv(liquidationBonus), "debt value under collateral value" ); } @@ -1416,6 +1398,7 @@ contract TestLens is TestSetup { assertFalse(lens.isLiquidatable(address(borrower1), aUsdc)); + morpho.setIsBorrowPaused(aUsdc, true); morpho.setIsDeprecated(aUsdc, true); assertTrue(lens.isLiquidatable(address(borrower1), aUsdc)); @@ -1641,6 +1624,7 @@ contract TestLens is TestSetup { } function testGetMarketPauseStatusesDeprecatedMarket() public { + morpho.setIsBorrowPaused(aDai, true); morpho.setIsDeprecated(aDai, true); assertTrue(lens.getMarketPauseStatus(aDai).isDeprecated); } diff --git a/test/aave-v2/TestLiquidate.t.sol b/test/aave-v2/TestLiquidate.t.sol index 2d29c4584..383f31ad0 100644 --- a/test/aave-v2/TestLiquidate.t.sol +++ b/test/aave-v2/TestLiquidate.t.sol @@ -26,16 +26,22 @@ contract TestLiquidate is TestSetup { liquidator.liquidate(aDai, aUsdc, address(borrower1), toRepay); } + function testShouldNotLiquidateZero() public { + hevm.expectRevert(abi.encodeWithSignature("AmountIsZero()")); + borrower2.liquidate(aDai, aUsdc, address(borrower1), 0); + } + function testLiquidateWhenMarketDeprecated() public { uint256 amount = 10_000 ether; uint256 collateral = to6Decimals(3 * amount); - morpho.setIsDeprecated(aDai, true); - borrower1.approve(usdc, address(morpho), collateral); borrower1.supply(aUsdc, collateral); borrower1.borrow(aDai, amount); + morpho.setIsBorrowPaused(aDai, true); + morpho.setIsDeprecated(aDai, true); + (, uint256 supplyOnPoolBefore) = morpho.supplyBalanceInOf(aUsdc, address(borrower1)); (, uint256 borrowOnPoolBefore) = morpho.borrowBalanceInOf(aDai, address(borrower1)); diff --git a/test/aave-v2/TestPausableMarket.t.sol b/test/aave-v2/TestPausableMarket.t.sol index 49f78acd4..151fa2641 100644 --- a/test/aave-v2/TestPausableMarket.t.sol +++ b/test/aave-v2/TestPausableMarket.t.sol @@ -102,6 +102,28 @@ contract TestPausableMarket is TestSetup { } } + function testBorrowPauseCheckSkipped() public { + // Deprecate a market. + morpho.setIsBorrowPaused(aDai, true); + morpho.setIsDeprecated(aDai, true); + (, bool isBorrowPaused, , , , , bool isDeprecated) = morpho.marketPauseStatus(aDai); + + assertTrue(isBorrowPaused); + assertTrue(isDeprecated); + + morpho.setIsPausedForAllMarkets(false); + (, isBorrowPaused, , , , , isDeprecated) = morpho.marketPauseStatus(aDai); + + assertTrue(isBorrowPaused); + assertTrue(isDeprecated); + + morpho.setIsPausedForAllMarkets(true); + (, isBorrowPaused, , , , , isDeprecated) = morpho.marketPauseStatus(aDai); + + assertTrue(isBorrowPaused); + assertTrue(isDeprecated); + } + function testPauseSupply() public { uint256 amount = 10_000 ether; morpho.setIsSupplyPaused(aDai, true); diff --git a/test/aave-v2/TestRatesLens.t.sol b/test/aave-v2/TestRatesLens.t.sol index f42b32bee..f92365f16 100644 --- a/test/aave-v2/TestRatesLens.t.sol +++ b/test/aave-v2/TestRatesLens.t.sol @@ -8,6 +8,8 @@ contract TestRatesLens is TestSetup { using SafeTransferLib for ERC20; function testGetRatesPerYear() public { + supplier1.aaveSupply(dai, 1 ether); // Update pool rates. + hevm.roll(block.number + 1_000); ( uint256 p2pSupplyRate, @@ -174,7 +176,7 @@ contract TestRatesLens is TestSetup { uint256 totalBalance ) = lens.getNextUserSupplyRatePerYear(aDai, address(supplier1), 0); - assertEq(supplyRatePerYear, 0, "non zero supply rate per block"); + assertEq(supplyRatePerYear, 0, "non zero supply rate per year"); assertEq(balanceOnPool, 0, "non zero pool balance"); assertEq(balanceInP2P, 0, "non zero p2p balance"); assertEq(totalBalance, 0, "non zero total balance"); @@ -188,7 +190,7 @@ contract TestRatesLens is TestSetup { uint256 totalBalance ) = lens.getNextUserBorrowRatePerYear(aDai, address(borrower1), 0); - assertEq(borrowRatePerYear, 0, "non zero borrow rate per block"); + assertEq(borrowRatePerYear, 0, "non zero borrow rate per year"); assertEq(balanceOnPool, 0, "non zero pool balance"); assertEq(balanceInP2P, 0, "non zero p2p balance"); assertEq(totalBalance, 0, "non zero total balance"); @@ -219,8 +221,8 @@ contract TestRatesLens is TestSetup { uint256 expectedTotalBalance ) = lens.getCurrentSupplyBalanceInOf(aDai, address(supplier1)); - assertGt(supplyRatePerYear, 0, "zero supply rate per block"); - assertEq(supplyRatePerYear, expectedSupplyRatePerYear, "unexpected supply rate per block"); + assertGt(supplyRatePerYear, 0, "zero supply rate per year"); + assertEq(supplyRatePerYear, expectedSupplyRatePerYear, "unexpected supply rate per year"); assertEq(balanceOnPool, expectedBalanceOnPool, "unexpected pool balance"); assertEq(balanceInP2P, expectedBalanceInP2P, "unexpected p2p balance"); assertEq(totalBalance, expectedTotalBalance, "unexpected total balance"); @@ -252,8 +254,8 @@ contract TestRatesLens is TestSetup { uint256 expectedTotalBalance ) = lens.getCurrentBorrowBalanceInOf(aDai, address(borrower1)); - assertGt(borrowRatePerYear, 0, "zero borrow rate per block"); - assertEq(borrowRatePerYear, expectedBorrowRatePerYear, "unexpected borrow rate per block"); + assertGt(borrowRatePerYear, 0, "zero borrow rate per year"); + assertEq(borrowRatePerYear, expectedBorrowRatePerYear, "unexpected borrow rate per year"); assertEq(balanceOnPool, expectedBalanceOnPool, "unexpected pool balance"); assertEq(balanceInP2P, expectedBalanceInP2P, "unexpected p2p balance"); assertEq(totalBalance, expectedTotalBalance, "unexpected total balance"); @@ -273,12 +275,12 @@ contract TestRatesLens is TestSetup { uint256 expectedSupplyRatePerYear = reserve.currentLiquidityRate; uint256 poolSupplyIndex = pool.getReserveNormalizedIncome(dai); - assertGt(supplyRatePerYear, 0, "zero supply rate per block"); + assertGt(supplyRatePerYear, 0, "zero supply rate per year"); assertApproxEqAbs( supplyRatePerYear, expectedSupplyRatePerYear, - 1, - "unexpected supply rate per block" + 1e22, + "unexpected supply rate per year" ); assertEq( balanceOnPool, @@ -306,12 +308,12 @@ contract TestRatesLens is TestSetup { DataTypes.ReserveData memory reserve = pool.getReserveData(dai); uint256 expectedBorrowRatePerYear = reserve.currentVariableBorrowRate; - assertGt(borrowRatePerYear, 0, "zero borrow rate per block"); + assertGt(borrowRatePerYear, 0, "zero borrow rate per year"); assertApproxEqAbs( borrowRatePerYear, expectedBorrowRatePerYear, - 1, - "unexpected borrow rate per block" + 1e22, + "unexpected borrow rate per year" ); assertApproxEqAbs(balanceOnPool, amount, 1, "unexpected pool balance"); assertEq(balanceInP2P, 0, "unexpected p2p balance"); @@ -341,12 +343,12 @@ contract TestRatesLens is TestSetup { uint256 expectedBalanceInP2P = amount.rayDiv(p2pSupplyIndex).rayMul(p2pSupplyIndex); - assertGt(supplyRatePerYear, 0, "zero supply rate per block"); + assertGt(supplyRatePerYear, 0, "zero supply rate per year"); assertApproxEqAbs( supplyRatePerYear, p2pSupplyRatePerYear, - 1, - "unexpected supply rate per block" + 1e22, + "unexpected supply rate per year" ); assertEq(balanceOnPool, 0, "unexpected pool balance"); assertEq(balanceInP2P, expectedBalanceInP2P, "unexpected p2p balance"); @@ -375,12 +377,12 @@ contract TestRatesLens is TestSetup { uint256 expectedBalanceInP2P = amount.rayDiv(p2pBorrowIndex).rayMul(p2pBorrowIndex); - assertGt(borrowRatePerYear, 0, "zero borrow rate per block"); + assertGt(borrowRatePerYear, 0, "zero borrow rate per year"); assertApproxEqAbs( borrowRatePerYear, p2pBorrowRatePerYear, - 1, - "unexpected borrow rate per block" + 1e22, + "unexpected borrow rate per year" ); assertApproxEqAbs(balanceOnPool, 0, 1, "unexpected pool balance"); assertEq(balanceInP2P, expectedBalanceInP2P, "unexpected p2p balance"); @@ -413,12 +415,12 @@ contract TestRatesLens is TestSetup { ); uint256 expectedBalanceInP2P = (amount / 2).rayDiv(p2pSupplyIndex).rayMul(p2pSupplyIndex); - assertGt(supplyRatePerYear, 0, "zero supply rate per block"); + assertGt(supplyRatePerYear, 0, "zero supply rate per year"); assertApproxEqAbs( supplyRatePerYear, (p2pSupplyRatePerYear + poolSupplyRatePerYear) / 2, - 1, - "unexpected supply rate per block" + 1e22, + "unexpected supply rate per year" ); assertEq(balanceOnPool, expectedBalanceOnPool, "unexpected pool balance"); assertEq(balanceInP2P, expectedBalanceInP2P, "unexpected p2p balance"); @@ -454,12 +456,12 @@ contract TestRatesLens is TestSetup { ); uint256 expectedBalanceInP2P = (amount / 2).rayDiv(p2pBorrowIndex).rayMul(p2pBorrowIndex); - assertGt(borrowRatePerYear, 0, "zero borrow rate per block"); + assertGt(borrowRatePerYear, 0, "zero borrow rate per year"); assertApproxEqAbs( borrowRatePerYear, (p2pBorrowRatePerYear + poolBorrowRatePerYear) / 2, - 1, - "unexpected borrow rate per block" + 1e22, + "unexpected borrow rate per year" ); assertApproxEqAbs(balanceOnPool, expectedBalanceOnPool, 1, "unexpected pool balance"); assertApproxEqAbs(balanceInP2P, expectedBalanceInP2P, 1, "unexpected p2p balance"); @@ -496,8 +498,8 @@ contract TestRatesLens is TestSetup { assertApproxEqAbs( supplyRatePerYear, expectedPoolSupplyRate, - 1, - "unexpected supply rate per block" + 1e22, + "unexpected supply rate per year" ); assertEq( balanceOnPool, @@ -535,8 +537,8 @@ contract TestRatesLens is TestSetup { assertApproxEqAbs( borrowRatePerYear, expectedBorrowRatePerYear, - 1, - "unexpected borrow rate per block" + 1e22, + "unexpected borrow rate per year" ); assertApproxEqAbs(balanceOnPool, amount, 1, "unexpected pool balance"); assertEq(balanceInP2P, 0, "unexpected p2p balance"); @@ -565,12 +567,12 @@ contract TestRatesLens is TestSetup { uint256 p2pSupplyIndex = morpho.p2pSupplyIndex(aDai); uint256 expectedBalanceInP2P = amount.rayDiv(p2pSupplyIndex).rayMul(p2pSupplyIndex); - assertGt(supplyRatePerYear, 0, "zero supply rate per block"); + assertGt(supplyRatePerYear, 0, "zero supply rate per year"); assertApproxEqAbs( supplyRatePerYear, p2pSupplyRatePerYear, - 1, - "unexpected supply rate per block" + 1e22, + "unexpected supply rate per year" ); assertEq(balanceOnPool, 0, "unexpected pool balance"); assertApproxEqAbs(balanceInP2P, expectedBalanceInP2P, 1, "unexpected p2p balance"); @@ -599,12 +601,12 @@ contract TestRatesLens is TestSetup { uint256 p2pBorrowIndex = morpho.p2pBorrowIndex(aDai); uint256 expectedBalanceInP2P = amount.rayDiv(p2pBorrowIndex).rayMul(p2pBorrowIndex); - assertGt(borrowRatePerYear, 0, "zero borrow rate per block"); + assertGt(borrowRatePerYear, 0, "zero borrow rate per year"); assertApproxEqAbs( borrowRatePerYear, p2pBorrowRatePerYear, - 1, - "unexpected borrow rate per block" + 1e21, + "unexpected borrow rate per year" ); assertApproxEqAbs(balanceOnPool, 0, 1, "unexpected pool balance"); assertApproxEqAbs(balanceInP2P, expectedBalanceInP2P, 1, "unexpected p2p balance"); @@ -639,12 +641,12 @@ contract TestRatesLens is TestSetup { uint256 p2pSupplyIndex = morpho.p2pSupplyIndex(aDai); uint256 expectedBalanceInP2P = amount.rayDiv(p2pSupplyIndex).rayMul(p2pSupplyIndex); - assertGt(supplyRatePerYear, 0, "zero supply rate per block"); + assertGt(supplyRatePerYear, 0, "zero supply rate per year"); assertApproxEqAbs( supplyRatePerYear, p2pSupplyRatePerYear, - 1, - "unexpected supply rate per block" + 1e22, + "unexpected supply rate per year" ); assertEq(balanceOnPool, 0, "unexpected pool balance"); assertEq(balanceInP2P, expectedBalanceInP2P, "unexpected p2p balance"); @@ -680,12 +682,12 @@ contract TestRatesLens is TestSetup { uint256 p2pBorrowIndex = morpho.p2pBorrowIndex(aDai); uint256 expectedBalanceInP2P = amount.rayDiv(p2pBorrowIndex).rayMul(p2pBorrowIndex); - assertGt(borrowRatePerYear, 0, "zero borrow rate per block"); + assertGt(borrowRatePerYear, 0, "zero borrow rate per year"); assertApproxEqAbs( borrowRatePerYear, p2pBorrowRatePerYear, - 1, - "unexpected borrow rate per block" + 1e22, + "unexpected borrow rate per year" ); assertEq(balanceOnPool, 0, "unexpected pool balance"); assertEq(balanceInP2P, expectedBalanceInP2P, "unexpected p2p balance"); @@ -727,12 +729,12 @@ contract TestRatesLens is TestSetup { ); uint256 expectedBalanceInP2P = (amount / 2).rayDiv(p2pSupplyIndex).rayMul(p2pSupplyIndex); - assertGt(supplyRatePerYear, 0, "zero supply rate per block"); + assertGt(supplyRatePerYear, 0, "zero supply rate per year"); assertApproxEqAbs( supplyRatePerYear, (p2pSupplyRatePerYear + poolSupplyRatePerYear) / 2, - 1, - "unexpected supply rate per block" + 1e22, + "unexpected supply rate per year" ); assertApproxEqAbs(balanceOnPool, expectedBalanceOnPool, 1, "unexpected pool balance"); assertApproxEqAbs(balanceInP2P, expectedBalanceInP2P, 1, "unexpected p2p balance"); @@ -779,12 +781,12 @@ contract TestRatesLens is TestSetup { ); uint256 expectedBalanceInP2P = (amount / 2).rayDiv(p2pBorrowIndex).rayMul(p2pBorrowIndex); - assertGt(borrowRatePerYear, 0, "zero borrow rate per block"); + assertGt(borrowRatePerYear, 0, "zero borrow rate per year"); assertApproxEqAbs( borrowRatePerYear, (p2pBorrowRatePerYear + poolBorrowRatePerYear) / 2, - 1, - "unexpected borrow rate per block" + 1e22, + "unexpected borrow rate per year" ); assertApproxEqAbs(balanceOnPool, expectedBalanceOnPool, 1, "unexpected pool balance"); assertApproxEqAbs(balanceInP2P, expectedBalanceInP2P, 1, "unexpected p2p balance"); @@ -1044,30 +1046,53 @@ contract TestRatesLens is TestSetup { function testRatesWithInvertedSpreadAndHalfSupplyDelta() public { // Full p2p on the DAI market, with a 50% supply delta. uint256 supplyAmount = 1 ether; - uint256 repayAmount = 0.5 ether; + uint256 repayAmount = supplyAmount / 2; + supplier1.approve(dai, supplyAmount); supplier1.supply(aDai, supplyAmount); + borrower1.approve(aave, supplyAmount); borrower1.supply(aAave, supplyAmount); borrower1.borrow(aDai, supplyAmount); + setDefaultMaxGasForMatchingHelper(0, 0, 0, 0); borrower1.approve(dai, repayAmount); borrower1.repay(aDai, repayAmount); - (uint256 p2pSupplyDelta, , , ) = morpho.deltas(aDai); - assertEq(p2pSupplyDelta, repayAmount.rayDiv(pool.getReserveNormalizedIncome(dai))); - // Invert spreads on DAI. - (uint256 poolSupplyRate, uint256 poolBorrowRate) = _invertPoolSpreadWithStorageManipulation( - dai + DataTypes.ReserveData memory reserve = pool.getReserveData(dai); + reserve.currentLiquidityRate = 0.03e27; + reserve.currentVariableBorrowRate = 0.02e27; + reserve.currentStableBorrowRate = 0.04e27; + vm.mockCall( + reserve.interestRateStrategyAddress, + abi.encodeWithSelector(IReserveInterestRateStrategy.calculateInterestRates.selector), + abi.encode( + reserve.currentLiquidityRate, + reserve.currentStableBorrowRate, + reserve.currentVariableBorrowRate + ) + ); + vm.mockCall( + address(pool), + abi.encodeWithSelector(ILendingPool.getReserveData.selector), + abi.encode(reserve) ); (uint256 avgSupplyRate, , ) = lens.getAverageSupplyRatePerYear(aDai); (uint256 avgBorrowRate, , ) = lens.getAverageBorrowRatePerYear(aDai); (uint256 p2pSupplyRate, uint256 p2pBorrowRate, , ) = lens.getRatesPerYear(aDai); - assertApproxEqAbs(avgSupplyRate, (poolBorrowRate + poolSupplyRate) / 2, 1e7); - assertEq(avgBorrowRate, poolBorrowRate); - assertApproxEqAbs(p2pSupplyRate, (poolBorrowRate + poolSupplyRate) / 2, 1e7); - assertEq(p2pBorrowRate, poolBorrowRate); + assertEq( + avgSupplyRate, + (reserve.currentVariableBorrowRate + reserve.currentLiquidityRate) / 2, + "avg supply rate" + ); + assertEq(avgBorrowRate, reserve.currentVariableBorrowRate, "avg borrow rate"); + assertEq( + p2pSupplyRate, + (reserve.currentVariableBorrowRate + reserve.currentLiquidityRate) / 2, + "p2p supply rate" + ); + assertEq(p2pBorrowRate, reserve.currentVariableBorrowRate, "p2p borrow rate"); } } diff --git a/test/aave-v2/TestSupply.t.sol b/test/aave-v2/TestSupply.t.sol index 97910e513..30e6e86ee 100644 --- a/test/aave-v2/TestSupply.t.sol +++ b/test/aave-v2/TestSupply.t.sol @@ -5,6 +5,7 @@ import "./setup/TestSetup.sol"; contract TestSupply is TestSetup { using stdStorage for StdStorage; + using ReserveConfiguration for DataTypes.ReserveConfigurationMap; using WadRayMath for uint256; // There are no available borrowers: all of the supplied amount is supplied to the pool and set `onPool`. diff --git a/test/aave-v2/helpers/User.sol b/test/aave-v2/helpers/User.sol index 8f304bca1..b948c4129 100644 --- a/test/aave-v2/helpers/User.sol +++ b/test/aave-v2/helpers/User.sol @@ -10,12 +10,10 @@ contract User { Morpho internal morpho; ILendingPool public pool; - IAaveIncentivesController public aaveIncentivesController; constructor(Morpho _morpho) { morpho = _morpho; pool = _morpho.pool(); - aaveIncentivesController = _morpho.aaveIncentivesController(); } receive() external payable {} @@ -122,10 +120,6 @@ contract User { pool.borrow(_underlyingToken, _amount, 2, 0, address(this)); // 2 : variable rate | 0 : no refferal code } - function aaveClaimRewards(address[] memory assets) external { - aaveIncentivesController.claimRewards(assets, type(uint256).max, address(this)); - } - function liquidate( address _poolTokenBorrowed, address _poolTokenCollateral, @@ -145,13 +139,6 @@ contract User { morpho.setDefaultMaxGasForMatching(_maxGasForMatching); } - function claimRewards(address[] calldata _assets, bool _toSwap) - external - returns (uint256 claimedAmount) - { - return morpho.claimRewards(_assets, _toSwap); - } - function setTreasuryVault(address _newTreasuryVault) external { morpho.setTreasuryVault(_newTreasuryVault); } @@ -179,10 +166,4 @@ contract User { function setIsLiquidateBorrowPaused(address _poolToken, bool _isPaused) external { morpho.setIsLiquidateBorrowPaused(_poolToken, _isPaused); } - - function setMorphoAddresses(Morpho _morpho) public { - morpho = _morpho; - pool = _morpho.pool(); - aaveIncentivesController = _morpho.aaveIncentivesController(); - } } diff --git a/test/aave-v2/setup/TestSetup.sol b/test/aave-v2/setup/TestSetup.sol index 204cbeeac..5b7465cf9 100644 --- a/test/aave-v2/setup/TestSetup.sol +++ b/test/aave-v2/setup/TestSetup.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.0; -import "src/aave-v2/interfaces/aave/IAaveIncentivesController.sol"; +import "src/aave-v2/interfaces/aave/IReserveInterestRateStrategy.sol"; import "src/aave-v2/interfaces/aave/IVariableDebtToken.sol"; import "src/aave-v2/interfaces/aave/IAToken.sol"; import "src/aave-v2/interfaces/IMorpho.sol"; @@ -14,7 +14,6 @@ import "@openzeppelin/contracts/utils/Strings.sol"; import "src/aave-v2/libraries/Types.sol"; import {InterestRatesManager} from "src/aave-v2/InterestRatesManager.sol"; -import {IncentivesVault} from "src/aave-v2/IncentivesVault.sol"; import {MatchingEngine} from "src/aave-v2/MatchingEngine.sol"; import {EntryPositionsManager} from "src/aave-v2/EntryPositionsManager.sol"; import {ExitPositionsManager} from "src/aave-v2/ExitPositionsManager.sol"; @@ -93,7 +92,6 @@ contract TestSetup is Config, Utils { treasuryVault = new User(morpho); morpho.setTreasuryVault(address(treasuryVault)); - morpho.setAaveIncentivesController(address(aaveIncentivesController)); /// Create markets /// @@ -105,19 +103,10 @@ contract TestSetup is Config, Utils { hevm.warp(block.timestamp + 100); - /// Create Morpho token, deploy Incentives Vault and activate rewards /// + /// Create Morpho token /// morphoToken = new MorphoToken(address(this)); dumbOracle = new DumbOracle(); - incentivesVault = new IncentivesVault( - IMorpho(address(morpho)), - morphoToken, - ERC20(REWARD_TOKEN), - address(treasuryVault), - dumbOracle - ); - morphoToken.transfer(address(incentivesVault), 1_000_000 ether); - morpho.setIncentivesVault(incentivesVault); lensImplV1 = new Lens(address(morpho)); lensProxy = new TransparentUpgradeableProxy(address(lensImplV1), address(proxyAdmin), ""); @@ -174,13 +163,11 @@ contract TestSetup is Config, Utils { function setContractsLabels() internal { hevm.label(address(morpho), "Morpho"); hevm.label(address(morphoToken), "MorphoToken"); - hevm.label(address(aaveIncentivesController), "AaveIncentivesController"); hevm.label(address(poolAddressesProvider), "PoolAddressesProvider"); hevm.label(address(pool), "Pool"); hevm.label(address(oracle), "AaveOracle"); hevm.label(address(treasuryVault), "TreasuryVault"); hevm.label(address(interestRatesManager), "InterestRatesManager"); - hevm.label(address(incentivesVault), "IncentivesVault"); } function createSigners(uint256 _nbOfSigners) internal { diff --git a/test/compound/TestGovernance.t.sol b/test/compound/TestGovernance.t.sol index 6fedc5b2a..f37bf2d1e 100644 --- a/test/compound/TestGovernance.t.sol +++ b/test/compound/TestGovernance.t.sol @@ -20,7 +20,7 @@ contract TestGovernance is TestSetup { function testShouldRevertWhenCreatingMarketWithAnImproperMarket() public { Types.MarketParameters memory marketParams = Types.MarketParameters(3_333, 0); - hevm.expectRevert(abi.encodeWithSignature("MarketCreationFailedOnCompound()")); + hevm.expectRevert(abi.encodeWithSignature("MarketCreationFailedOnCompound(uint256)", 9)); morpho.createMarket(address(supplier1), marketParams); } @@ -170,23 +170,6 @@ contract TestGovernance is TestSetup { assertEq(address(morpho.interestRatesManager()), address(interestRatesV2)); } - function testOnlyOwnerShouldSetIncentivesVault() public { - IIncentivesVault incentivesVaultV2 = new IncentivesVault( - comptroller, - IMorpho(address(morpho)), - morphoToken, - address(1), - dumbOracle - ); - - hevm.prank(address(0)); - hevm.expectRevert("Ownable: caller is not the owner"); - morpho.setIncentivesVault(incentivesVaultV2); - - morpho.setIncentivesVault(incentivesVaultV2); - assertEq(address(morpho.incentivesVault()), address(incentivesVaultV2)); - } - function testOnlyOwnerShouldSetDustThreshold() public { hevm.prank(address(0)); hevm.expectRevert("Ownable: caller is not the owner"); @@ -230,6 +213,8 @@ contract TestGovernance is TestSetup { } function testOnlyOwnerShouldSetDeprecatedMarket() public { + morpho.setIsBorrowPaused(cDai, true); + hevm.prank(address(supplier1)); hevm.expectRevert("Ownable: caller is not the owner"); morpho.setIsDeprecated(cDai, true); @@ -447,4 +432,18 @@ contract TestGovernance is TestSetup { function testFailCallIncreaseP2PDeltasFromImplementation() public { positionsManager.increaseP2PDeltasLogic(cDai, 0); } + + function testDeprecateCycle() public { + hevm.expectRevert(abi.encodeWithSignature("BorrowNotPaused()")); + morpho.setIsDeprecated(cDai, true); + + morpho.setIsBorrowPaused(cDai, true); + morpho.setIsDeprecated(cDai, true); + + hevm.expectRevert(abi.encodeWithSignature("MarketIsDeprecated()")); + morpho.setIsBorrowPaused(cDai, false); + + morpho.setIsDeprecated(cDai, false); + morpho.setIsBorrowPaused(cDai, false); + } } diff --git a/test/compound/TestIncentivesVault.t.sol b/test/compound/TestIncentivesVault.t.sol deleted file mode 100644 index ff53b46e0..000000000 --- a/test/compound/TestIncentivesVault.t.sol +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./setup/TestSetup.sol"; -import "@morpho-dao/morpho-utils/math/PercentageMath.sol"; - -contract TestIncentivesVault is TestSetup { - using SafeTransferLib for ERC20; - - function testShouldNotSetBonusAboveMaxBasisPoints() public { - uint256 moreThanMaxBasisPoints = PercentageMath.PERCENTAGE_FACTOR + 1; - hevm.expectRevert(abi.encodeWithSelector(IncentivesVault.ExceedsMaxBasisPoints.selector)); - incentivesVault.setBonus(moreThanMaxBasisPoints); - } - - function testOnlyOwnerShouldSetBonus() public { - uint256 bonusToSet = 1; - - hevm.prank(address(0)); - hevm.expectRevert("Ownable: caller is not the owner"); - incentivesVault.setBonus(bonusToSet); - - incentivesVault.setBonus(bonusToSet); - assertEq(incentivesVault.bonus(), bonusToSet); - } - - function testOnlyOwnerShouldSetIncentivesTreasuryVault() public { - address incentivesTreasuryVault = address(1); - - hevm.prank(address(0)); - hevm.expectRevert("Ownable: caller is not the owner"); - incentivesVault.setIncentivesTreasuryVault(incentivesTreasuryVault); - - incentivesVault.setIncentivesTreasuryVault(incentivesTreasuryVault); - assertEq(incentivesVault.incentivesTreasuryVault(), incentivesTreasuryVault); - } - - function testOnlyOwnerShouldSetOracle() public { - IOracle oracle = IOracle(address(1)); - - hevm.prank(address(0)); - hevm.expectRevert("Ownable: caller is not the owner"); - incentivesVault.setOracle(oracle); - - incentivesVault.setOracle(oracle); - assertEq(address(incentivesVault.oracle()), address(oracle)); - } - - function testOnlyOwnerShouldSetPauseStatus() public { - hevm.prank(address(0)); - hevm.expectRevert("Ownable: caller is not the owner"); - incentivesVault.setPauseStatus(true); - - incentivesVault.setPauseStatus(true); - assertTrue(incentivesVault.isPaused()); - - incentivesVault.setPauseStatus(false); - assertFalse(incentivesVault.isPaused()); - } - - function testOnlyOwnerShouldTransferTokensToDao() public { - hevm.prank(address(0)); - hevm.expectRevert("Ownable: caller is not the owner"); - incentivesVault.transferTokensToDao(address(morphoToken), 1); - - incentivesVault.transferTokensToDao(address(morphoToken), 1); - assertEq(ERC20(morphoToken).balanceOf(address(treasuryVault)), 1); - } - - function testFailWhenContractNotActive() public { - incentivesVault.setPauseStatus(true); - - hevm.prank(address(morpho)); - incentivesVault.tradeCompForMorphoTokens(address(1), 0); - } - - function testOnlyMorphoShouldTriggerCompConvertFunction() public { - incentivesVault.setIncentivesTreasuryVault(address(1)); - uint256 amount = 100; - deal(comp, address(morpho), amount); - - hevm.prank(address(morpho)); - ERC20(comp).safeApprove(address(incentivesVault), amount); - - hevm.expectRevert(abi.encodeWithSignature("OnlyMorpho()")); - incentivesVault.tradeCompForMorphoTokens(address(2), amount); - - hevm.prank(address(morpho)); - incentivesVault.tradeCompForMorphoTokens(address(2), amount); - } - - function testShouldGiveTheRightAmountOfRewards() public { - incentivesVault.setIncentivesTreasuryVault(address(1)); - uint256 toApprove = 1_000 ether; - deal(comp, address(morpho), toApprove); - - hevm.prank(address(morpho)); - ERC20(comp).safeApprove(address(incentivesVault), toApprove); - uint256 amount = 100; - - // O% bonus. - uint256 balanceBefore = ERC20(morphoToken).balanceOf(address(2)); - hevm.prank(address(morpho)); - incentivesVault.tradeCompForMorphoTokens(address(2), amount); - uint256 balanceAfter = ERC20(morphoToken).balanceOf(address(2)); - assertEq(balanceAfter - balanceBefore, 100); - - // 10% bonus. - incentivesVault.setBonus(1_000); - balanceBefore = ERC20(morphoToken).balanceOf(address(2)); - hevm.prank(address(morpho)); - incentivesVault.tradeCompForMorphoTokens(address(2), amount); - balanceAfter = ERC20(morphoToken).balanceOf(address(2)); - assertEq(balanceAfter - balanceBefore, 110); - } -} diff --git a/test/compound/TestInterestRates.t.sol b/test/compound/TestInterestRates.t.sol index 483bc6d16..110ef16e0 100644 --- a/test/compound/TestInterestRates.t.sol +++ b/test/compound/TestInterestRates.t.sol @@ -11,8 +11,8 @@ contract TestInterestRates is InterestRatesManager, Test { uint256 public p2pBorrowIndexTest = 1 * WAD; uint256 public poolSupplyIndexTest = 2 * WAD; uint256 public poolBorrowIndexTest = 3 * WAD; - uint256 public lastPoolSupplyIndexTest = 1 * WAD; - uint256 public lastPoolBorrowIndexTest = 1 * WAD; + Types.LastPoolIndexes public lastPoolIndexesTest = + Types.LastPoolIndexes(uint32(block.number), uint112(1 * WAD), uint112(1 * WAD)); uint256 public reserveFactor0PerCentTest = 0; uint256 public reserveFactor50PerCentTest = 5_000; uint256 public p2pIndexCursorTest = 3_333; @@ -23,15 +23,15 @@ contract TestInterestRates is InterestRatesManager, Test { pure returns (uint256 p2pSupplyIndex_, uint256 p2pBorrowIndex_) { - uint256 poolSupplyGrowthFactor = ((_params.poolSupplyIndex * WAD) / _params.lastPoolSupplyIndex); - uint256 poolBorrowGrowthFactor = ((_params.poolBorrowIndex * WAD) / _params.lastPoolBorrowIndex); + uint256 poolSupplyGrowthFactor = ((_params.poolSupplyIndex * WAD) / _params.lastPoolIndexes.lastSupplyPoolIndex); + uint256 poolBorrowGrowthFactor = ((_params.poolBorrowIndex * WAD) / _params.lastPoolIndexes.lastBorrowPoolIndex); uint256 p2pGrowthFactor = ((PercentageMath.PERCENTAGE_FACTOR - _params.p2pIndexCursor) * poolSupplyGrowthFactor + _params.p2pIndexCursor * poolBorrowGrowthFactor) / PercentageMath.PERCENTAGE_FACTOR; uint256 shareOfTheSupplyDelta = _params.delta.p2pBorrowAmount > 0 - ? (((_params.delta.p2pSupplyDelta * _params.lastPoolSupplyIndex) / WAD) * WAD) / + ? (((_params.delta.p2pSupplyDelta * _params.lastPoolIndexes.lastSupplyPoolIndex) / WAD) * WAD) / ((_params.delta.p2pSupplyAmount * _params.lastP2PSupplyIndex) / WAD) : 0; uint256 shareOfTheBorrowDelta = _params.delta.p2pSupplyAmount > 0 - ? (((_params.delta.p2pBorrowDelta * _params.lastPoolBorrowIndex) / WAD) * WAD) / + ? (((_params.delta.p2pBorrowDelta * _params.lastPoolIndexes.lastBorrowPoolIndex) / WAD) * WAD) / ((_params.delta.p2pBorrowAmount * _params.lastP2PBorrowIndex) / WAD) : 0; if (poolSupplyGrowthFactor <= poolBorrowGrowthFactor) { @@ -63,8 +63,7 @@ contract TestInterestRates is InterestRatesManager, Test { p2pBorrowIndexTest, poolSupplyIndexTest, poolBorrowIndexTest, - lastPoolSupplyIndexTest, - lastPoolBorrowIndexTest, + lastPoolIndexesTest, reserveFactor0PerCentTest, p2pIndexCursorTest, Types.Delta(0, 0, 0, 0) @@ -82,8 +81,7 @@ contract TestInterestRates is InterestRatesManager, Test { p2pBorrowIndexTest, poolSupplyIndexTest, poolBorrowIndexTest, - lastPoolSupplyIndexTest, - lastPoolBorrowIndexTest, + lastPoolIndexesTest, reserveFactor50PerCentTest, p2pIndexCursorTest, Types.Delta(0, 0, 0, 0) @@ -101,8 +99,7 @@ contract TestInterestRates is InterestRatesManager, Test { p2pBorrowIndexTest, poolSupplyIndexTest, poolBorrowIndexTest, - lastPoolSupplyIndexTest, - lastPoolBorrowIndexTest, + lastPoolIndexesTest, reserveFactor0PerCentTest, p2pIndexCursorTest, Types.Delta(1 * WAD, 1 * WAD, 4 * WAD, 6 * WAD) @@ -120,8 +117,7 @@ contract TestInterestRates is InterestRatesManager, Test { p2pBorrowIndexTest, poolSupplyIndexTest, poolBorrowIndexTest, - lastPoolSupplyIndexTest, - lastPoolBorrowIndexTest, + lastPoolIndexesTest, reserveFactor50PerCentTest, p2pIndexCursorTest, Types.Delta(1 * WAD, 1 * WAD, 4 * WAD, 6 * WAD) @@ -139,8 +135,7 @@ contract TestInterestRates is InterestRatesManager, Test { p2pBorrowIndexTest, poolBorrowIndexTest * 2, poolBorrowIndexTest, - lastPoolSupplyIndexTest, - lastPoolBorrowIndexTest, + lastPoolIndexesTest, reserveFactor50PerCentTest, p2pIndexCursorTest, Types.Delta(0, 0, 0, 0) @@ -158,8 +153,7 @@ contract TestInterestRates is InterestRatesManager, Test { p2pBorrowIndexTest, poolBorrowIndexTest * 2, poolBorrowIndexTest, - lastPoolSupplyIndexTest, - lastPoolBorrowIndexTest, + lastPoolIndexesTest, reserveFactor50PerCentTest, p2pIndexCursorTest, Types.Delta(1 * WAD, 1 * WAD, 4 * WAD, 6 * WAD) diff --git a/test/compound/TestLens.t.sol b/test/compound/TestLens.t.sol index 046989a36..c08944941 100644 --- a/test/compound/TestLens.t.sol +++ b/test/compound/TestLens.t.sol @@ -7,10 +7,10 @@ contract TestLens is TestSetup { using CompoundMath for uint256; struct UserBalanceStates { - uint256 collateralValue; - uint256 debtValue; - uint256 maxDebtValue; - uint256 liquidationValue; + uint256 collateralUsd; + uint256 debtUsd; + uint256 maxDebtUsd; + uint256 liquidationUsd; } struct UserBalance { @@ -32,9 +32,9 @@ contract TestLens is TestSetup { assertEq(assetData.collateralFactor, collateralFactor); assertEq(assetData.underlyingPrice, underlyingPrice); - assertEq(assetData.collateralValue, 0); - assertEq(assetData.maxDebtValue, 0); - assertEq(assetData.debtValue, 0); + assertEq(assetData.collateralUsd, 0); + assertEq(assetData.maxDebtUsd, 0); + assertEq(assetData.debtUsd, 0); } function testUserLiquidityDataForAssetWithSupply() public { @@ -59,9 +59,9 @@ contract TestLens is TestSetup { assertEq(assetData.collateralFactor, collateralFactor, "collateralFactor"); assertEq(assetData.underlyingPrice, underlyingPrice, "underlyingPrice"); - assertEq(assetData.collateralValue, collateralValue, "collateralValue"); - assertEq(assetData.maxDebtValue, maxDebtValue, "maxDebtValue"); - assertEq(assetData.debtValue, 0, "debtValue"); + assertEq(assetData.collateralUsd, collateralValue, "collateralValue"); + assertEq(assetData.maxDebtUsd, maxDebtValue, "maxDebtValue"); + assertEq(assetData.debtUsd, 0, "debtValue"); } struct Indexes { @@ -107,9 +107,9 @@ contract TestLens is TestSetup { uint256 debtValue = toBorrow.div(p2pBorrowIndex).mul(p2pBorrowIndex).mul(underlyingPrice); assertEq(assetData.underlyingPrice, underlyingPrice, "underlyingPrice"); - assertEq(assetData.collateralValue, collateralValue, "collateralValue"); - assertEq(assetData.maxDebtValue, maxDebtValue, "maxDebtValue"); - assertEq(assetData.debtValue, debtValue, "debtValue"); + assertEq(assetData.collateralUsd, collateralValue, "collateralValue"); + assertEq(assetData.maxDebtUsd, maxDebtValue, "maxDebtValue"); + assertEq(assetData.debtUsd, debtValue, "debtValue"); } function testUserLiquidityDataForAssetWithSupplyAndBorrowWithMultipleAssets() public { @@ -138,7 +138,7 @@ contract TestLens is TestSetup { Types.AssetLiquidityData memory expectedDataCUsdc; expectedDataCUsdc.underlyingPrice = oracle.getUnderlyingPrice(cUsdc); - expectedDataCUsdc.debtValue = getBalanceOnCompound(toBorrow, ICToken(cUsdc).borrowIndex()) + expectedDataCUsdc.debtUsd = getBalanceOnCompound(toBorrow, ICToken(cUsdc).borrowIndex()) .mul(expectedDataCUsdc.underlyingPrice); assertEq( @@ -146,9 +146,9 @@ contract TestLens is TestSetup { expectedDataCUsdc.underlyingPrice, "underlyingPriceUsdc" ); - assertEq(assetDataCUsdc.collateralValue, 0, "collateralValue"); - assertEq(assetDataCUsdc.maxDebtValue, 0, "maxDebtValue"); - assertEq(assetDataCUsdc.debtValue, expectedDataCUsdc.debtValue, "debtValueUsdc"); + assertEq(assetDataCUsdc.collateralUsd, 0, "collateralValue"); + assertEq(assetDataCUsdc.maxDebtUsd, 0, "maxDebtValue"); + assertEq(assetDataCUsdc.debtUsd, expectedDataCUsdc.debtUsd, "debtValueUsdc"); // Avoid stack too deep error. Types.AssetLiquidityData memory expectedDataCDai; @@ -156,11 +156,11 @@ contract TestLens is TestSetup { (, expectedDataCDai.collateralFactor, ) = comptroller.markets(cDai); expectedDataCDai.underlyingPrice = oracle.getUnderlyingPrice(cDai); - expectedDataCDai.collateralValue = getBalanceOnCompound( + expectedDataCDai.collateralUsd = getBalanceOnCompound( amount, ICToken(cDai).exchangeRateStored() ).mul(expectedDataCDai.underlyingPrice); - expectedDataCDai.maxDebtValue = expectedDataCDai.collateralValue.mul( + expectedDataCDai.maxDebtUsd = expectedDataCDai.collateralUsd.mul( expectedDataCDai.collateralFactor ); @@ -175,13 +175,9 @@ contract TestLens is TestSetup { "underlyingPriceDai" ); - assertEq( - assetDataCDai.collateralValue, - expectedDataCDai.collateralValue, - "collateralValueDai" - ); - assertEq(assetDataCDai.maxDebtValue, expectedDataCDai.maxDebtValue, "maxDebtValueDai"); - assertEq(assetDataCDai.debtValue, 0, "debtValueDai"); + assertEq(assetDataCDai.collateralUsd, expectedDataCDai.collateralUsd, "collateralValueDai"); + assertEq(assetDataCDai.maxDebtUsd, expectedDataCDai.maxDebtUsd, "maxDebtValueDai"); + assertEq(assetDataCDai.debtUsd, 0, "debtValueDai"); } function testMaxCapacitiesWithNothing() public { @@ -214,10 +210,10 @@ contract TestLens is TestSetup { oracle ); - uint256 expectedBorrowableUsdc = assetDataCUsdc.maxDebtValue.div( + uint256 expectedBorrowableUsdc = assetDataCUsdc.maxDebtUsd.div( assetDataCUsdc.underlyingPrice ); - uint256 expectedBorrowableDai = assetDataCUsdc.maxDebtValue.div( + uint256 expectedBorrowableDai = assetDataCUsdc.maxDebtUsd.div( assetDataCDai.underlyingPrice ); @@ -476,8 +472,9 @@ contract TestLens is TestSetup { (uint256 withdrawableUsdc, ) = lens.getUserMaxCapacitiesForAsset(address(borrower1), cUsdc); (, uint256 borrowableUsdt) = lens.getUserMaxCapacitiesForAsset(address(borrower1), cUsdt); - uint256 expectedBorrowableUsdt = (assetDataCDai.maxDebtValue + assetDataCUsdc.maxDebtValue) - .div(assetDataCUsdt.underlyingPrice); + uint256 expectedBorrowableUsdt = (assetDataCDai.maxDebtUsd + assetDataCUsdc.maxDebtUsd).div( + assetDataCUsdt.underlyingPrice + ); assertEq( withdrawableUsdc, @@ -516,7 +513,7 @@ contract TestLens is TestSetup { UserBalanceStates memory states; UserBalanceStates memory expectedStates; - (states.collateralValue, states.debtValue, states.maxDebtValue) = lens.getUserBalanceStates( + (states.collateralUsd, states.debtUsd, states.maxDebtUsd) = lens.getUserBalanceStates( address(borrower1), new address[](0) ); @@ -526,19 +523,19 @@ contract TestLens is TestSetup { // DAI data (, uint256 collateralFactor, ) = comptroller.markets(cDai); uint256 underlyingPriceDai = oracle.getUnderlyingPrice(cDai); - expectedStates.collateralValue = getBalanceOnCompound( + expectedStates.collateralUsd = getBalanceOnCompound( amount, ICToken(cDai).exchangeRateStored() ).mul(underlyingPriceDai); - expectedStates.debtValue = getBalanceOnCompound(toBorrow, ICToken(cUsdc).borrowIndex()).mul( + expectedStates.debtUsd = getBalanceOnCompound(toBorrow, ICToken(cUsdc).borrowIndex()).mul( underlyingPriceUsdc ); - expectedStates.maxDebtValue = expectedStates.collateralValue.mul(collateralFactor); + expectedStates.maxDebtUsd = expectedStates.collateralUsd.mul(collateralFactor); - assertEq(states.collateralValue, expectedStates.collateralValue, "Collateral Value"); - assertEq(states.maxDebtValue, expectedStates.maxDebtValue, "Max Debt Value"); - assertEq(states.debtValue, expectedStates.debtValue, "Debt Value"); + assertEq(states.collateralUsd, expectedStates.collateralUsd, "Collateral Value"); + assertEq(states.maxDebtUsd, expectedStates.maxDebtUsd, "Max Debt Value"); + assertEq(states.debtUsd, expectedStates.debtUsd, "Debt Value"); } function testUserBalanceStatesWithSupplyAndBorrowWithMultipleAssets() public { @@ -562,36 +559,36 @@ contract TestLens is TestSetup { to6Decimals(amount), ICToken(cUsdc).exchangeRateStored() ).mul(oracle.getUnderlyingPrice(cUsdc)); - expectedStates.collateralValue += collateralValueToAdd; + expectedStates.collateralUsd += collateralValueToAdd; (, uint256 collateralFactor, ) = comptroller.markets(cUsdc); - expectedStates.maxDebtValue += collateralValueToAdd.mul(collateralFactor); + expectedStates.maxDebtUsd += collateralValueToAdd.mul(collateralFactor); // DAI data collateralValueToAdd = getBalanceOnCompound(amount, ICToken(cDai).exchangeRateStored()).mul( oracle.getUnderlyingPrice(cDai) ); - expectedStates.collateralValue += collateralValueToAdd; + expectedStates.collateralUsd += collateralValueToAdd; (, collateralFactor, ) = comptroller.markets(cDai); - expectedStates.maxDebtValue += collateralValueToAdd.mul(collateralFactor); + expectedStates.maxDebtUsd += collateralValueToAdd.mul(collateralFactor); // BAT - expectedStates.debtValue += getBalanceOnCompound(toBorrow, ICToken(cBat).borrowIndex()).mul( + expectedStates.debtUsd += getBalanceOnCompound(toBorrow, ICToken(cBat).borrowIndex()).mul( oracle.getUnderlyingPrice(cBat) ); // USDT - expectedStates.debtValue += getBalanceOnCompound( + expectedStates.debtUsd += getBalanceOnCompound( to6Decimals(toBorrow), ICToken(cUsdt).borrowIndex() ).mul(oracle.getUnderlyingPrice(cUsdt)); - (states.collateralValue, states.debtValue, states.maxDebtValue) = lens.getUserBalanceStates( + (states.collateralUsd, states.debtUsd, states.maxDebtUsd) = lens.getUserBalanceStates( address(borrower1), new address[](0) ); - assertEq(states.collateralValue, expectedStates.collateralValue, "Collateral Value"); - assertEq(states.debtValue, expectedStates.debtValue, "Debt Value"); - assertEq(states.maxDebtValue, expectedStates.maxDebtValue, "Max Debt Value"); + assertEq(states.collateralUsd, expectedStates.collateralUsd, "Collateral Value"); + assertEq(states.debtUsd, expectedStates.debtUsd, "Debt Value"); + assertEq(states.maxDebtUsd, expectedStates.maxDebtUsd, "Max Debt Value"); } /// This test is to check that a call to getUserLiquidityDataForAsset with USDT doesn't end @@ -657,7 +654,7 @@ contract TestLens is TestSetup { UserBalanceStates memory states; UserBalanceStates memory expectedStates; - (states.collateralValue, states.debtValue, states.maxDebtValue) = lens.getUserBalanceStates( + (states.collateralUsd, states.debtUsd, states.maxDebtUsd) = lens.getUserBalanceStates( address(borrower1), new address[](0) ); @@ -679,27 +676,29 @@ contract TestLens is TestSetup { uint256 underlyingPrice = oracle.getUnderlyingPrice(cUsdt); uint256 collateralValueToAdd = total.mul(underlyingPrice); - expectedStates.collateralValue += collateralValueToAdd; - expectedStates.maxDebtValue += collateralValueToAdd.mul(collateralFactor); + expectedStates.collateralUsd += collateralValueToAdd; + expectedStates.maxDebtUsd += collateralValueToAdd.mul(collateralFactor); // DAI data (, collateralFactor, ) = comptroller.markets(cDai); collateralValueToAdd = getBalanceOnCompound(amount, ICToken(cDai).exchangeRateCurrent()) .mul(oracle.getUnderlyingPrice(cDai)); - expectedStates.collateralValue += collateralValueToAdd; - expectedStates.maxDebtValue += collateralValueToAdd.mul(collateralFactor); + expectedStates.collateralUsd += collateralValueToAdd; + expectedStates.maxDebtUsd += collateralValueToAdd.mul(collateralFactor); // USDC data - expectedStates.debtValue += getBalanceOnCompound(toBorrow, ICToken(cUsdc).borrowIndex()) - .mul(oracle.getUnderlyingPrice(cUsdc)); + expectedStates.debtUsd += getBalanceOnCompound(toBorrow, ICToken(cUsdc).borrowIndex()).mul( + oracle.getUnderlyingPrice(cUsdc) + ); // USDT data - expectedStates.debtValue += getBalanceOnCompound(toBorrow, ICToken(cUsdt).borrowIndex()) - .mul(oracle.getUnderlyingPrice(cUsdt)); + expectedStates.debtUsd += getBalanceOnCompound(toBorrow, ICToken(cUsdt).borrowIndex()).mul( + oracle.getUnderlyingPrice(cUsdt) + ); - assertEq(states.collateralValue, expectedStates.collateralValue, "Collateral Value"); - assertEq(states.debtValue, expectedStates.debtValue, "Debt Value"); - assertEq(states.maxDebtValue, expectedStates.maxDebtValue, "Max Debt Value"); + assertEq(states.collateralUsd, expectedStates.collateralUsd, "Collateral Value"); + assertEq(states.debtUsd, expectedStates.debtUsd, "Debt Value"); + assertEq(states.maxDebtUsd, expectedStates.maxDebtUsd, "Max Debt Value"); } function testUserHypotheticalBalanceStatesUnenteredMarket() public { @@ -1460,6 +1459,7 @@ contract TestLens is TestSetup { assertFalse(lens.isLiquidatable(address(borrower1), cUsdc, new address[](0))); + morpho.setIsBorrowPaused(cUsdc, true); morpho.setIsDeprecated(cUsdc, true); assertTrue(lens.isLiquidatable(address(borrower1), cUsdc, new address[](0))); @@ -1685,6 +1685,7 @@ contract TestLens is TestSetup { } function testGetMarketPauseStatusesDeprecatedMarket() public { + morpho.setIsBorrowPaused(cDai, true); morpho.setIsDeprecated(cDai, true); assertTrue(lens.getMarketPauseStatus(cDai).isDeprecated); } diff --git a/test/compound/TestLiquidate.t.sol b/test/compound/TestLiquidate.t.sol index df33462c3..3a64379ea 100644 --- a/test/compound/TestLiquidate.t.sol +++ b/test/compound/TestLiquidate.t.sol @@ -25,16 +25,22 @@ contract TestLiquidate is TestSetup { liquidator.liquidate(cDai, cUsdc, address(borrower1), toRepay); } + function testShouldNotLiquidateZero() public { + hevm.expectRevert(abi.encodeWithSignature("AmountIsZero()")); + borrower2.liquidate(cDai, cUsdc, address(borrower1), 0); + } + function testLiquidateWhenMarketDeprecated() public { uint256 amount = 10_000 ether; uint256 collateral = to6Decimals(3 * amount); - morpho.setIsDeprecated(cDai, true); - borrower1.approve(usdc, address(morpho), collateral); borrower1.supply(cUsdc, collateral); borrower1.borrow(cDai, amount); + morpho.setIsBorrowPaused(cDai, true); + morpho.setIsDeprecated(cDai, true); + moveOneBlockForwardBorrowRepay(); (, uint256 supplyOnPoolBefore) = morpho.supplyBalanceInOf(cUsdc, address(borrower1)); diff --git a/test/compound/TestMorphoGetters.t.sol b/test/compound/TestMorphoGetters.t.sol index b446f0af4..eede3905a 100644 --- a/test/compound/TestMorphoGetters.t.sol +++ b/test/compound/TestMorphoGetters.t.sol @@ -113,6 +113,7 @@ contract TestMorphoGetters is TestSetup { borrower1.supply(cUsdc, to6Decimals(10 ether)); assertEq(morpho.enteredMarkets(address(borrower1), 0), cDai); + assertEq(IMorpho(address(morpho)).enteredMarkets(address(borrower1), 0), cDai); // test the interface assertEq(morpho.enteredMarkets(address(borrower1), 1), cUsdc); // Borrower1 withdraw, USDC should be the first in enteredMarkets. diff --git a/test/compound/TestPausableMarket.t.sol b/test/compound/TestPausableMarket.t.sol index ab325267f..a39c65896 100644 --- a/test/compound/TestPausableMarket.t.sol +++ b/test/compound/TestPausableMarket.t.sol @@ -74,6 +74,28 @@ contract TestPausableMarket is TestSetup { } } + function testBorrowPauseCheckSkipped() public { + // Deprecate a market. + morpho.setIsBorrowPaused(cDai, true); + morpho.setIsDeprecated(cDai, true); + (, bool isBorrowPaused, , , , , bool isDeprecated) = morpho.marketPauseStatus(cDai); + + assertTrue(isBorrowPaused); + assertTrue(isDeprecated); + + morpho.setIsPausedForAllMarkets(false); + (, isBorrowPaused, , , , , isDeprecated) = morpho.marketPauseStatus(cDai); + + assertTrue(isBorrowPaused); + assertTrue(isDeprecated); + + morpho.setIsPausedForAllMarkets(true); + (, isBorrowPaused, , , , , isDeprecated) = morpho.marketPauseStatus(cDai); + + assertTrue(isBorrowPaused); + assertTrue(isDeprecated); + } + function testOnlyOwnerShouldDisableSupply() public { uint256 amount = 10_000 ether; diff --git a/test/compound/TestRatesLens.t.sol b/test/compound/TestRatesLens.t.sol index 47823daf8..d79d55a21 100644 --- a/test/compound/TestRatesLens.t.sol +++ b/test/compound/TestRatesLens.t.sol @@ -7,6 +7,8 @@ contract TestRatesLens is TestSetup { using CompoundMath for uint256; function testGetRatesPerBlock() public { + supplier1.compoundSupply(cDai, 1 ether); // Update pool rates. + hevm.roll(block.number + 1_000); ( uint256 p2pSupplyRate, @@ -303,7 +305,7 @@ contract TestRatesLens is TestSetup { assertApproxEqAbs( supplyRatePerBlock, expectedSupplyRatePerBlock, - 1, + 1e6, "unexpected supply rate per block" ); assertEq( @@ -335,7 +337,7 @@ contract TestRatesLens is TestSetup { assertApproxEqAbs( borrowRatePerBlock, expectedBorrowRatePerBlock, - 1, + 1e6, "unexpected borrow rate per block" ); assertApproxEqAbs(balanceOnPool, amount, 1, "unexpected pool balance"); @@ -370,7 +372,7 @@ contract TestRatesLens is TestSetup { assertApproxEqAbs( supplyRatePerBlock, p2pSupplyRatePerBlock, - 1, + 1e6, "unexpected supply rate per block" ); assertEq(balanceOnPool, 0, "unexpected pool balance"); @@ -404,7 +406,7 @@ contract TestRatesLens is TestSetup { assertApproxEqAbs( borrowRatePerBlock, p2pBorrowRatePerBlock, - 1, + 1e6, "unexpected borrow rate per block" ); assertApproxEqAbs(balanceOnPool, 0, 1e6, "unexpected pool balance"); // compound rounding error at supply @@ -440,7 +442,7 @@ contract TestRatesLens is TestSetup { assertApproxEqAbs( supplyRatePerBlock, (p2pSupplyRatePerBlock + poolSupplyRatePerBlock) / 2, - 1, + 1e6, "unexpected supply rate per block" ); assertEq(balanceOnPool, expectedBalanceOnPool, "unexpected pool balance"); @@ -516,7 +518,7 @@ contract TestRatesLens is TestSetup { assertApproxEqAbs( supplyRatePerBlock, expectedSupplyRatePerBlock, - 1, + 1e6, "unexpected supply rate per block" ); assertEq( @@ -554,7 +556,7 @@ contract TestRatesLens is TestSetup { assertApproxEqAbs( borrowRatePerBlock, expectedBorrowRatePerBlock, - 1, + 1e6, "unexpected borrow rate per block" ); assertApproxEqAbs(balanceOnPool, amount, 1, "unexpected pool balance"); @@ -588,7 +590,7 @@ contract TestRatesLens is TestSetup { assertApproxEqAbs( supplyRatePerBlock, p2pSupplyRatePerBlock, - 1, + 1e6, "unexpected supply rate per block" ); assertEq(balanceOnPool, 0, "unexpected pool balance"); @@ -622,7 +624,7 @@ contract TestRatesLens is TestSetup { assertApproxEqAbs( borrowRatePerBlock, p2pBorrowRatePerBlock, - 1, + 1e6, "unexpected borrow rate per block" ); assertApproxEqAbs(balanceOnPool, 0, 1e9, "unexpected pool balance"); // compound rounding errors @@ -662,7 +664,7 @@ contract TestRatesLens is TestSetup { assertApproxEqAbs( supplyRatePerBlock, p2pSupplyRatePerBlock, - 1, + 1e6, "unexpected supply rate per block" ); assertEq(balanceOnPool, 0, "unexpected pool balance"); @@ -703,7 +705,7 @@ contract TestRatesLens is TestSetup { assertApproxEqAbs( borrowRatePerBlock, p2pBorrowRatePerBlock, - 1, + 1e6, "unexpected borrow rate per block" ); assertEq(balanceOnPool, 0, "unexpected pool balance"); diff --git a/test/compound/TestRepay.t.sol b/test/compound/TestRepay.t.sol index 7e8702a56..f89925ab6 100644 --- a/test/compound/TestRepay.t.sol +++ b/test/compound/TestRepay.t.sol @@ -418,7 +418,7 @@ contract TestRepay is TestSetup { function testDeltaRepay() public { // Allows only 10 unmatch suppliers. - setDefaultMaxGasForMatchingHelper(3e6, 3e6, 3e6, 1e6); + setDefaultMaxGasForMatchingHelper(3e6, 3e6, 3e6, 0.9e6); uint256 suppliedAmount = 1 ether; uint256 borrowedAmount = 20 * suppliedAmount; @@ -607,7 +607,7 @@ contract TestRepay is TestSetup { function testDeltaRepayAll() public { // Allows only 10 unmatch suppliers. - setDefaultMaxGasForMatchingHelper(3e6, 3e6, 3e6, 1e6); + setDefaultMaxGasForMatchingHelper(3e6, 3e6, 3e6, 0.9e6); uint256 suppliedAmount = 1 ether; uint256 borrowedAmount = 20 * suppliedAmount + 1e12; diff --git a/test/compound/TestRewards.t.sol b/test/compound/TestRewards.t.sol index c0b4ec1cf..f64ff50fa 100644 --- a/test/compound/TestRewards.t.sol +++ b/test/compound/TestRewards.t.sol @@ -314,35 +314,6 @@ contract TestRewards is TestSetup { supplier3.borrow(cUsdc, toBorrow); } - function testShouldClaimRewardsAndTradeForMorpkoTokens() public { - // 10% bonus. - incentivesVault.setBonus(1_000); - - uint256 toSupply = 100 ether; - supplier1.approve(dai, toSupply); - supplier1.supply(cDai, toSupply); - - (, uint256 onPool) = morpho.supplyBalanceInOf(cDai, address(supplier1)); - uint256 userIndex = rewardsManager.compSupplierIndex(cDai, address(supplier1)); - uint256 rewardBalanceBefore = supplier1.balanceOf(comp); - - address[] memory cTokens = new address[](1); - cTokens[0] = cDai; - - hevm.roll(block.number + 1_000); - uint256 claimedAmount = supplier1.claimRewards(cTokens, true); - - uint256 index = comptroller.compSupplyState(cDai).index; - uint256 expectedClaimed = (onPool * (index - userIndex)) / 1e36; - uint256 expectedMorphoTokens = (expectedClaimed * 11_000) / 10_000; // 10% bonus with a dumb oracle 1:1 exchange from COMP to MORPHO. - - uint256 morphoBalance = supplier1.balanceOf(address(morphoToken)); - uint256 rewardBalanceAfter = supplier1.balanceOf(comp); - assertEq(claimedAmount, expectedClaimed); - testEquality(morphoBalance, expectedMorphoTokens); - testEquality(rewardBalanceBefore, rewardBalanceAfter); - } - function testShouldClaimTheSameAmountOfRewards() public { uint256 smallAmount = 1 ether; uint256 bigAmount = 10_000 ether; diff --git a/test/compound/TestUpgradeable.t.sol b/test/compound/TestUpgradeable.t.sol index eadee9c2a..b60bc17b5 100644 --- a/test/compound/TestUpgradeable.t.sol +++ b/test/compound/TestUpgradeable.t.sol @@ -65,15 +65,18 @@ contract TestUpgradeable is TestSetup { /// Lens /// function testUpgradeLens() public { - _testUpgradeProxy(lensProxy, address(new Lens(address(morpho)))); + _testUpgradeProxy(lensProxy, address(new Lens(address(lensExtension)))); } function testOnlyProxyOwnerCanUpgradeLens() public { - _testOnlyProxyOwnerCanUpgradeProxy(lensProxy, address(new Lens(address(morpho)))); + _testOnlyProxyOwnerCanUpgradeProxy(lensProxy, address(new Lens(address(lensExtension)))); } function testOnlyProxyOwnerCanUpgradeAndCallLens() public { - _testOnlyProxyOwnerCanUpgradeAndCallProxy(lensProxy, address(new Lens(address(morpho)))); + _testOnlyProxyOwnerCanUpgradeAndCallProxy( + lensProxy, + address(new Lens(address(lensExtension))) + ); } /// INTERNAL /// diff --git a/test/compound/helpers/IncentivesVault.sol b/test/compound/helpers/IncentivesVault.sol deleted file mode 100644 index bfc354e11..000000000 --- a/test/compound/helpers/IncentivesVault.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.8.0; - -import "./DumbOracle.sol"; - -import "@rari-capital/solmate/src/utils/SafeTransferLib.sol"; - -contract IncentivesVault { - using SafeTransferLib for ERC20; - - address public immutable morphoToken; - address public immutable morpho; - address public immutable oracle; - address public constant COMP = 0xc00e94Cb662C3520282E6f5717214004A7f26888; - - uint256 public constant MAX_BASIS_POINTS = 10_000; - uint256 public constant BONUS = 1_000; - - constructor( - address _morpho, - address _morphoToken, - address _oracle - ) { - morpho = _morpho; - morphoToken = _morphoToken; - oracle = _oracle; - } - - function convertCompToMorphoTokens(address _to, uint256 _amount) external { - require(msg.sender == morpho, "!morpho"); - ERC20(COMP).safeTransferFrom(msg.sender, address(this), _amount); - uint256 amountOut = (IOracle(oracle).consult(_amount) * (MAX_BASIS_POINTS + BONUS)) / - MAX_BASIS_POINTS; - ERC20(morphoToken).transfer(_to, amountOut); - } -} diff --git a/test/compound/helpers/User.sol b/test/compound/helpers/User.sol index 40e7b22ec..6dfc9441e 100644 --- a/test/compound/helpers/User.sol +++ b/test/compound/helpers/User.sol @@ -4,19 +4,16 @@ pragma solidity ^0.8.0; import "src/compound/interfaces/IRewardsManager.sol"; import "src/compound/Morpho.sol"; -import "src/compound/InterestRatesManager.sol"; contract User { using SafeTransferLib for ERC20; Morpho internal morpho; - InterestRatesManager internal interestRatesManager; IRewardsManager internal rewardsManager; IComptroller internal comptroller; constructor(Morpho _morpho) { morpho = _morpho; - interestRatesManager = InterestRatesManager(address(_morpho.interestRatesManager())); rewardsManager = _morpho.rewardsManager(); comptroller = morpho.comptroller(); } diff --git a/test/compound/setup/TestSetup.sol b/test/compound/setup/TestSetup.sol index 2f1003c17..1b1ec1684 100644 --- a/test/compound/setup/TestSetup.sol +++ b/test/compound/setup/TestSetup.sol @@ -4,10 +4,9 @@ pragma solidity ^0.8.0; import "src/compound/interfaces/IMorpho.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; -import "src/compound/libraries/CompoundMath.sol"; +import "@morpho-dao/morpho-utils/math/CompoundMath.sol"; import "@rari-capital/solmate/src/utils/SafeTransferLib.sol"; -import {IncentivesVault} from "src/compound/IncentivesVault.sol"; import {PositionsManager} from "src/compound/PositionsManager.sol"; import {InterestRatesManager} from "src/compound/InterestRatesManager.sol"; import "../../common/helpers/MorphoToken.sol"; @@ -96,15 +95,6 @@ contract TestSetup is Config, Utils { morphoToken = new MorphoToken(address(this)); dumbOracle = new DumbOracle(); - incentivesVault = new IncentivesVault( - comptroller, - IMorpho(address(morpho)), - morphoToken, - address(treasuryVault), - dumbOracle - ); - morphoToken.transfer(address(incentivesVault), 1_000_000 ether); - morpho.setIncentivesVault(incentivesVault); rewardsManagerImplV1 = new RewardsManager(); rewardsManagerProxy = new TransparentUpgradeableProxy( @@ -117,7 +107,8 @@ contract TestSetup is Config, Utils { morpho.setRewardsManager(rewardsManager); - lensImplV1 = new Lens(address(morpho)); + lensExtension = new LensExtension(address(morpho)); + lensImplV1 = new Lens(address(lensExtension)); lensProxy = new TransparentUpgradeableProxy(address(lensImplV1), address(proxyAdmin), ""); lens = Lens(address(lensProxy)); } @@ -182,7 +173,6 @@ contract TestSetup is Config, Utils { hevm.label(address(comptroller), "Comptroller"); hevm.label(address(oracle), "CompoundOracle"); hevm.label(address(dumbOracle), "DumbOracle"); - hevm.label(address(incentivesVault), "IncentivesVault"); hevm.label(address(treasuryVault), "TreasuryVault"); hevm.label(address(lens), "Lens"); } diff --git a/test/compound/setup/Utils.sol b/test/compound/setup/Utils.sol index 02d76d424..29a7d19b6 100644 --- a/test/compound/setup/Utils.sol +++ b/test/compound/setup/Utils.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.0; -import "src/compound/libraries/CompoundMath.sol"; +import "@morpho-dao/morpho-utils/math/CompoundMath.sol"; import "@forge-std/Test.sol"; diff --git a/test/prod/aave-v2/TestDeltas.t.sol b/test/prod/aave-v2/TestDeltas.t.sol index b5c710d3c..a39911a13 100644 --- a/test/prod/aave-v2/TestDeltas.t.sol +++ b/test/prod/aave-v2/TestDeltas.t.sol @@ -59,7 +59,7 @@ contract TestDeltas is TestSetup { address(morpho) ); - vm.prank(proxyAdmin.owner()); + vm.prank(morpho.owner()); morpho.increaseP2PDeltas(test.market.poolToken, type(uint256).max); ( @@ -108,14 +108,14 @@ contract TestDeltas is TestSetup { assertApproxEqAbs( p2pSupplyUnderlying - supplyDeltaUnderlyingBefore, IAToken(test.market.poolToken).balanceOf(address(morpho)) - test.morphoSupplyBefore, - 3, + 10, "morpho pool supply" ); assertApproxEqAbs( p2pBorrowUnderlying - borrowDeltaUnderlyingBefore, IVariableDebtToken(test.market.debtToken).balanceOf(address(morpho)) - test.morphoBorrowBefore, - 3, + 10, "morpho pool borrow" ); } @@ -150,7 +150,7 @@ contract TestDeltas is TestSetup { p2pBorrowUnderlying > borrowDeltaUnderlyingBefore ) continue; - vm.prank(proxyAdmin.owner()); + vm.prank(morpho.owner()); vm.expectRevert(PositionsManagerUtils.AmountIsZero.selector); morpho.increaseP2PDeltas(test.market.poolToken, type(uint256).max); } diff --git a/test/prod/aave-v2/TestLifecycle.t.sol b/test/prod/aave-v2/TestLifecycle.t.sol index e51c9008e..906640115 100644 --- a/test/prod/aave-v2/TestLifecycle.t.sol +++ b/test/prod/aave-v2/TestLifecycle.t.sol @@ -37,7 +37,6 @@ contract TestLifecycle is TestSetup { uint256 scaledPoolBalance; // MorphoPosition position; - uint256 unclaimedRewardsBefore; } function _initMarketSideTest(TestMarket memory _market, uint256 _amount) @@ -121,21 +120,6 @@ contract TestLifecycle is TestSetup { ); } - address[] memory poolTokens = new address[](1); - poolTokens[0] = supply.market.poolToken; - if (address(rewardsManager) != address(0)) { - supply.unclaimedRewardsBefore = rewardsManager.getUserUnclaimedRewards( - poolTokens, - address(user) - ); - - assertEq( - supply.unclaimedRewardsBefore, - 0, - string.concat(supply.market.symbol, " unclaimed rewards") - ); - } - assertEq( ERC20(supply.market.underlying).balanceOf(address(morpho)), supply.morphoUnderlyingBalanceBefore, @@ -158,17 +142,6 @@ contract TestLifecycle is TestSetup { (supply.position.p2p, supply.position.pool, supply.position.total) = lens .getCurrentSupplyBalanceInOf(supply.market.poolToken, address(user)); - - if ( - supply.position.pool > 0 && - address(rewardsManager) != address(0) && - block.timestamp < aaveIncentivesController.DISTRIBUTION_END() - ) - assertGt( - rewardsManager.getUserUnclaimedRewards(poolTokens, address(user)), - supply.unclaimedRewardsBefore, - string.concat(supply.market.symbol, " unclaimed rewards after supply") - ); } function _borrow(TestMarket memory _market, uint256 _amount) @@ -231,15 +204,6 @@ contract TestLifecycle is TestSetup { ); } - address[] memory borrowedPoolTokens = new address[](1); - borrowedPoolTokens[0] = borrow.market.poolToken; - if (address(rewardsManager) != address(0)) { - borrow.unclaimedRewardsBefore = rewardsManager.getUserUnclaimedRewards( - borrowedPoolTokens, - address(user) - ); - } - assertEq( ERC20(borrow.market.underlying).balanceOf(address(morpho)), borrow.morphoUnderlyingBalanceBefore, @@ -262,17 +226,6 @@ contract TestLifecycle is TestSetup { (borrow.position.p2p, borrow.position.pool, borrow.position.total) = lens .getCurrentBorrowBalanceInOf(borrow.market.poolToken, address(user)); - - if ( - borrow.position.pool > 0 && - address(rewardsManager) != address(0) && - block.timestamp < aaveIncentivesController.DISTRIBUTION_END() - ) - assertGt( - rewardsManager.getUserUnclaimedRewards(borrowedPoolTokens, address(user)), - borrow.unclaimedRewardsBefore, - string.concat(borrow.market.symbol, " unclaimed rewards after borrow") - ); } function _repay(MarketSideTest memory borrow) internal virtual { @@ -357,6 +310,10 @@ contract TestLifecycle is TestSetup { supplyMarketIndex < collateralMarkets.length; ++supplyMarketIndex ) { + TestMarket memory supplyMarket = collateralMarkets[supplyMarketIndex]; + + if (supplyMarket.status.isSupplyPaused) continue; + for ( uint256 borrowMarketIndex; borrowMarketIndex < borrowableMarkets.length; @@ -364,19 +321,13 @@ contract TestLifecycle is TestSetup { ) { _revert(); - TestMarket memory supplyMarket = collateralMarkets[supplyMarketIndex]; TestMarket memory borrowMarket = borrowableMarkets[borrowMarketIndex]; - if (supplyMarket.status.isSupplyPaused) continue; - - uint256 borrowAmount = _boundBorrowAmount( - borrowMarket, - _amount, - oracle.getAssetPrice(borrowMarket.underlying) - ); + uint256 borrowedPrice = oracle.getAssetPrice(borrowMarket.underlying); + uint256 borrowAmount = _boundBorrowAmount(borrowMarket, _amount, borrowedPrice); uint256 supplyAmount = _getMinimumCollateralAmount( borrowAmount, - oracle.getAssetPrice(borrowMarket.underlying), + borrowedPrice, borrowMarket.decimals, oracle.getAssetPrice(supplyMarket.underlying), supplyMarket.decimals, @@ -410,6 +361,10 @@ contract TestLifecycle is TestSetup { supplyMarketIndex < collateralMarkets.length; ++supplyMarketIndex ) { + TestMarket memory supplyMarket = collateralMarkets[supplyMarketIndex]; + + if (supplyMarket.status.isSupplyPaused) continue; + for ( uint256 borrowMarketIndex; borrowMarketIndex < borrowableMarkets.length; @@ -417,20 +372,15 @@ contract TestLifecycle is TestSetup { ) { _revert(); - TestMarket memory supplyMarket = collateralMarkets[supplyMarketIndex]; TestMarket memory borrowMarket = borrowableMarkets[borrowMarketIndex]; - if (supplyMarket.status.isSupplyPaused || borrowMarket.status.isBorrowPaused) - continue; + if (borrowMarket.status.isBorrowPaused) continue; - uint256 borrowAmount = _boundBorrowAmount( - borrowMarket, - _amount, - oracle.getAssetPrice(borrowMarket.underlying) - ); + uint256 borrowedPrice = oracle.getAssetPrice(borrowMarket.underlying); + uint256 borrowAmount = _boundBorrowAmount(borrowMarket, _amount, borrowedPrice); uint256 supplyAmount = _getMinimumCollateralAmount( borrowAmount, - oracle.getAssetPrice(borrowMarket.underlying), + borrowedPrice, borrowMarket.decimals, oracle.getAssetPrice(supplyMarket.underlying), supplyMarket.decimals, diff --git a/test/prod/aave-v2/TestUpgradeLens.t.sol b/test/prod/aave-v2/TestUpgradeLens.t.sol index 240d01a31..1822c6c6f 100644 --- a/test/prod/aave-v2/TestUpgradeLens.t.sol +++ b/test/prod/aave-v2/TestUpgradeLens.t.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.0; import "./setup/TestSetup.sol"; contract TestUpgradeLens is TestSetup { + using WadRayMath for uint256; + function testShouldPreserveIndexes() public { Types.Indexes[] memory expectedIndexes = new Types.Indexes[](markets.length); @@ -42,4 +44,74 @@ contract TestUpgradeLens is TestSetup { ); } } + + function testNextRateShouldMatchRateAfterInteraction(uint96 _amount) public { + _upgrade(); + + for ( + uint256 supplyMarketIndex; + supplyMarketIndex < collateralMarkets.length; + ++supplyMarketIndex + ) { + TestMarket memory supplyMarket = collateralMarkets[supplyMarketIndex]; + + if (supplyMarket.status.isSupplyPaused) continue; + + for ( + uint256 borrowMarketIndex; + borrowMarketIndex < borrowableMarkets.length; + ++borrowMarketIndex + ) { + _revert(); + + TestMarket memory borrowMarket = borrowableMarkets[borrowMarketIndex]; + + uint256 borrowedPrice = oracle.getAssetPrice(borrowMarket.underlying); + uint256 borrowAmount = _boundBorrowAmount(borrowMarket, _amount, borrowedPrice); + uint256 supplyAmount = _getMinimumCollateralAmount( + borrowAmount, + borrowedPrice, + borrowMarket.decimals, + oracle.getAssetPrice(supplyMarket.underlying), + supplyMarket.decimals, + supplyMarket.ltv + ).wadMul(1.001 ether); + + (uint256 expectedSupplyRate, , , ) = lens.getNextUserSupplyRatePerYear( + supplyMarket.poolToken, + address(user), + supplyAmount + ); + + _tip(supplyMarket.underlying, address(user), supplyAmount); + + user.approve(supplyMarket.underlying, supplyAmount); + user.supply(supplyMarket.poolToken, address(user), supplyAmount, 1_000); // Only perform 1 matching loop, as simulated in getNextUserSupplyRatePerYear. + + assertApproxEqAbs( + lens.getCurrentUserSupplyRatePerYear(supplyMarket.poolToken, address(user)), + expectedSupplyRate, + 1e15, + string.concat(supplyMarket.symbol, " supply rate") + ); + + if (borrowMarket.status.isBorrowPaused) continue; + + (uint256 expectedBorrowRate, , , ) = lens.getNextUserBorrowRatePerYear( + borrowMarket.poolToken, + address(user), + borrowAmount + ); + + user.borrow(borrowMarket.poolToken, borrowAmount, 1_000); // Only perform 1 matching loop, as simulated in getNextUserBorrowRatePerYear. + + assertApproxEqAbs( + lens.getCurrentUserBorrowRatePerYear(borrowMarket.poolToken, address(user)), + expectedBorrowRate, + 1e15, + string.concat(borrowMarket.symbol, " borrow rate") + ); + } + } + } } diff --git a/test/prod/aave-v2/setup/TestSetup.sol b/test/prod/aave-v2/setup/TestSetup.sol index 49374643d..1df1c8263 100644 --- a/test/prod/aave-v2/setup/TestSetup.sol +++ b/test/prod/aave-v2/setup/TestSetup.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.0; -import "src/aave-v2/interfaces/aave/IAaveIncentivesController.sol"; import "src/aave-v2/interfaces/aave/IVariableDebtToken.sol"; import "src/aave-v2/interfaces/aave/IAToken.sol"; import "src/aave-v2/interfaces/lido/ILido.sol"; @@ -13,7 +12,6 @@ import "@rari-capital/solmate/src/utils/SafeTransferLib.sol"; import "@morpho-dao/morpho-utils/math/Math.sol"; import {InterestRatesManager} from "src/aave-v2/InterestRatesManager.sol"; -import {IncentivesVault} from "src/aave-v2/IncentivesVault.sol"; import {MatchingEngine} from "src/aave-v2/MatchingEngine.sol"; import {PositionsManagerUtils} from "src/aave-v2/PositionsManagerUtils.sol"; import {EntryPositionsManager} from "src/aave-v2/EntryPositionsManager.sol"; @@ -73,7 +71,6 @@ contract TestSetup is Config, Test { function initContracts() internal { lens = Lens(address(lensProxy)); morpho = Morpho(payable(morphoProxy)); - incentivesVault = morpho.incentivesVault(); entryPositionsManager = morpho.entryPositionsManager(); exitPositionsManager = morpho.exitPositionsManager(); interestRatesManager = morpho.interestRatesManager(); @@ -112,14 +109,12 @@ contract TestSetup is Config, Test { function setContractsLabels() internal { vm.label(address(poolAddressesProvider), "PoolAddressesProvider"); - vm.label(address(aaveIncentivesController), "IncentivesController"); vm.label(address(pool), "LendingPool"); vm.label(address(proxyAdmin), "ProxyAdmin"); vm.label(address(morphoImplV1), "MorphoImplV1"); vm.label(address(morpho), "Morpho"); vm.label(address(interestRatesManager), "InterestRatesManager"); vm.label(address(oracle), "Oracle"); - vm.label(address(incentivesVault), "IncentivesVault"); vm.label(address(lens), "Lens"); vm.label(address(aave), "AAVE"); diff --git a/test/prod/compound/TestDeltas.t.sol b/test/prod/compound/TestDeltas.t.sol index 57bf74cfa..0eaf438fc 100644 --- a/test/prod/compound/TestDeltas.t.sol +++ b/test/prod/compound/TestDeltas.t.sol @@ -63,7 +63,7 @@ contract TestDeltas is TestSetup { address(morpho) ); - vm.prank(proxyAdmin.owner()); + vm.prank(morpho.owner()); morpho.increaseP2PDeltas(test.market.poolToken, type(uint256).max); ( @@ -98,7 +98,7 @@ contract TestDeltas is TestSetup { assertApproxEqAbs( test.avgSupplyRatePerBlock, ICToken(test.market.poolToken).supplyRatePerBlock(), - 1, + 10, "avg supply rate per year" ); assertApproxEqAbs( @@ -154,9 +154,11 @@ contract TestDeltas is TestSetup { if ( p2pSupplyUnderlying > supplyDeltaUnderlyingBefore && p2pBorrowUnderlying > borrowDeltaUnderlyingBefore - ) continue; + ) { + continue; + } - vm.prank(proxyAdmin.owner()); + vm.prank(morpho.owner()); vm.expectRevert(PositionsManager.AmountIsZero.selector); morpho.increaseP2PDeltas(test.market.poolToken, type(uint256).max); } diff --git a/test/prod/compound/TestLifecycle.t.sol b/test/prod/compound/TestLifecycle.t.sol index e75e73c0b..669b4c116 100644 --- a/test/prod/compound/TestLifecycle.t.sol +++ b/test/prod/compound/TestLifecycle.t.sol @@ -381,6 +381,10 @@ contract TestLifecycle is TestSetup { supplyMarketIndex < collateralMarkets.length; ++supplyMarketIndex ) { + TestMarket memory supplyMarket = collateralMarkets[supplyMarketIndex]; + + if (supplyMarket.status.isSupplyPaused) continue; + for ( uint256 borrowMarketIndex; borrowMarketIndex < borrowableMarkets.length; @@ -388,19 +392,13 @@ contract TestLifecycle is TestSetup { ) { _revert(); - TestMarket memory supplyMarket = collateralMarkets[supplyMarketIndex]; TestMarket memory borrowMarket = borrowableMarkets[borrowMarketIndex]; - if (supplyMarket.status.isSupplyPaused) continue; - - uint256 borrowAmount = _boundBorrowAmount( - borrowMarket, - _amount, - oracle.getUnderlyingPrice(borrowMarket.poolToken) - ); + uint256 borrowedPrice = oracle.getUnderlyingPrice(borrowMarket.poolToken); + uint256 borrowAmount = _boundBorrowAmount(borrowMarket, _amount, borrowedPrice); uint256 supplyAmount = _getMinimumCollateralAmount( borrowAmount, - oracle.getUnderlyingPrice(borrowMarket.poolToken), + borrowedPrice, oracle.getUnderlyingPrice(supplyMarket.poolToken), supplyMarket.collateralFactor ).mul(1.001 ether); @@ -432,6 +430,10 @@ contract TestLifecycle is TestSetup { supplyMarketIndex < collateralMarkets.length; ++supplyMarketIndex ) { + TestMarket memory supplyMarket = collateralMarkets[supplyMarketIndex]; + + if (supplyMarket.status.isSupplyPaused) continue; + for ( uint256 borrowMarketIndex; borrowMarketIndex < borrowableMarkets.length; @@ -439,11 +441,9 @@ contract TestLifecycle is TestSetup { ) { _revert(); - TestMarket memory supplyMarket = collateralMarkets[supplyMarketIndex]; TestMarket memory borrowMarket = borrowableMarkets[borrowMarketIndex]; - if (supplyMarket.status.isSupplyPaused || borrowMarket.status.isBorrowPaused) - continue; + if (borrowMarket.status.isBorrowPaused) continue; uint256 borrowAmount = _boundBorrowAmount( borrowMarket, diff --git a/test/prod/compound/TestUpgradeLens.t.sol b/test/prod/compound/TestUpgradeLens.t.sol index ba22a933d..563a35a7f 100644 --- a/test/prod/compound/TestUpgradeLens.t.sol +++ b/test/prod/compound/TestUpgradeLens.t.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.0; import "./setup/TestSetup.sol"; contract TestUpgradeLens is TestSetup { + using CompoundMath for uint256; + function testShouldPreserveOutdatedIndexes() public { Types.Indexes[] memory expectedIndexes = new Types.Indexes[](markets.length); @@ -77,4 +79,72 @@ contract TestUpgradeLens is TestSetup { ); } } + + function testNextRateShouldMatchRateAfterInteraction(uint96 _amount) public { + _upgrade(); + + for ( + uint256 supplyMarketIndex; + supplyMarketIndex < collateralMarkets.length; + ++supplyMarketIndex + ) { + TestMarket memory supplyMarket = collateralMarkets[supplyMarketIndex]; + + if (supplyMarket.status.isSupplyPaused) continue; + + for ( + uint256 borrowMarketIndex; + borrowMarketIndex < borrowableMarkets.length; + ++borrowMarketIndex + ) { + _revert(); + + TestMarket memory borrowMarket = borrowableMarkets[borrowMarketIndex]; + + uint256 borrowedPrice = oracle.getUnderlyingPrice(borrowMarket.poolToken); + uint256 borrowAmount = _boundBorrowAmount(borrowMarket, _amount, borrowedPrice); + uint256 supplyAmount = _getMinimumCollateralAmount( + borrowAmount, + borrowedPrice, + oracle.getUnderlyingPrice(supplyMarket.poolToken), + supplyMarket.collateralFactor + ).mul(1.001 ether); + + (uint256 expectedSupplyRate, , , ) = lens.getNextUserSupplyRatePerBlock( + supplyMarket.poolToken, + address(user), + supplyAmount + ); + + _tip(supplyMarket.underlying, address(user), supplyAmount); + + user.approve(supplyMarket.underlying, supplyAmount); + user.supply(supplyMarket.poolToken, address(user), supplyAmount, 1_000); // Only perform 1 matching loop, as simulated in getNextUserSupplyRatePerYear. + + assertApproxEqAbs( + lens.getCurrentUserSupplyRatePerBlock(supplyMarket.poolToken, address(user)), + expectedSupplyRate, + 1, + string.concat(supplyMarket.symbol, " supply rate") + ); + + if (borrowMarket.status.isBorrowPaused) continue; + + (uint256 expectedBorrowRate, , , ) = lens.getNextUserBorrowRatePerBlock( + borrowMarket.poolToken, + address(user), + borrowAmount + ); + + user.borrow(borrowMarket.poolToken, borrowAmount, 1_000); // Only perform 1 matching loop, as simulated in getNextUserBorrowRatePerBlock. + + assertApproxEqAbs( + lens.getCurrentUserBorrowRatePerBlock(borrowMarket.poolToken, address(user)), + expectedBorrowRate, + 1, + string.concat(borrowMarket.symbol, " borrow rate") + ); + } + } + } } diff --git a/test/prod/compound/setup/TestSetup.sol b/test/prod/compound/setup/TestSetup.sol index 16161bc62..377c83891 100644 --- a/test/prod/compound/setup/TestSetup.sol +++ b/test/prod/compound/setup/TestSetup.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.0; -import {CompoundMath} from "src/compound/libraries/CompoundMath.sol"; +import {CompoundMath} from "@morpho-dao/morpho-utils/math/CompoundMath.sol"; import {PercentageMath} from "@morpho-dao/morpho-utils/math/PercentageMath.sol"; import {SafeTransferLib, ERC20} from "@rari-capital/solmate/src/utils/SafeTransferLib.sol"; import {Math} from "@morpho-dao/morpho-utils/math/Math.sol"; @@ -61,7 +61,6 @@ contract TestSetup is Config, Test { lens = Lens(address(lensProxy)); morpho = Morpho(payable(morphoProxy)); rewardsManager = RewardsManager(address(morpho.rewardsManager())); - incentivesVault = morpho.incentivesVault(); positionsManager = morpho.positionsManager(); interestRatesManager = morpho.interestRatesManager(); @@ -100,7 +99,6 @@ contract TestSetup is Config, Test { vm.label(address(rewardsManager), "RewardsManager"); vm.label(address(comptroller), "Comptroller"); vm.label(address(oracle), "Oracle"); - vm.label(address(incentivesVault), "IncentivesVault"); vm.label(address(lens), "Lens"); vm.label(address(aave), "AAVE"); @@ -126,7 +124,7 @@ contract TestSetup is Config, Test { vm.label(address(cUsdc), "cUSDC"); vm.label(address(cUsdt), "cUSDT"); vm.label(address(cWbtc2), "cWBTC"); - vm.label(address(cEth), "cWETH"); + vm.label(address(cEth), "cETH"); vm.label(address(cComp), "cCOMP"); vm.label(address(cBat), "cBAT"); vm.label(address(cTusd), "cTUSD"); @@ -263,7 +261,9 @@ contract TestSetup is Config, Test { proxyAdmin.upgrade(morphoProxy, morphoImplV2); vm.label(morphoImplV2, "MorphoImplV2"); - address lensImplV2 = address(new Lens(address(morpho))); + lensExtension = new LensExtension(address(morpho)); + + address lensImplV2 = address(new Lens(address(lensExtension))); proxyAdmin.upgrade(lensProxy, lensImplV2); vm.label(lensImplV2, "LensImplV2");