-
Notifications
You must be signed in to change notification settings - Fork 93
Add Morpho Aave strategy #1207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add Morpho Aave strategy #1207
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
97d50e8
checkpoint
shahthepro c39655e
Revert dep on BaseCompoundStrategy
shahthepro 3e64c20
Update tests
shahthepro ce2e43d
prettier
shahthepro 7bb200d
Use migration ID 046
shahthepro 5602bf9
lint
shahthepro fd4dc02
lint
shahthepro ebaad03
Address review comments
shahthepro 9ed49b4
Also, update `_checkBalance` method
shahthepro f5f0689
Remove unused import
shahthepro 7f6fc5f
Merge branch 'master' into shah/morpho-aave
shahthepro 5e06e72
Update migration ID
shahthepro 19009a4
Deploy 47 - Morpho Aave strategy (#1209)
shahthepro File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,240 @@ | ||
| // SPDX-License-Identifier: agpl-3.0 | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| /** | ||
| * @title OUSD Morpho Aave Strategy | ||
| * @notice Investment strategy for investing stablecoins via Morpho (Aave) | ||
| * @author Origin Protocol Inc | ||
| */ | ||
| import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
| import { IERC20, InitializableAbstractStrategy } from "../utils/InitializableAbstractStrategy.sol"; | ||
| import { IMorpho } from "../interfaces/morpho/IMorpho.sol"; | ||
| import { ILens } from "../interfaces/morpho/ILens.sol"; | ||
| import { StableMath } from "../utils/StableMath.sol"; | ||
|
|
||
| contract MorphoAaveStrategy is InitializableAbstractStrategy { | ||
| address public constant MORPHO = 0x777777c9898D384F785Ee44Acfe945efDFf5f3E0; | ||
| address public constant LENS = 0x507fA343d0A90786d86C7cd885f5C49263A91FF4; | ||
shahthepro marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| using SafeERC20 for IERC20; | ||
| using StableMath for uint256; | ||
|
|
||
| /** | ||
| * @dev Initialize function, to set up initial internal state | ||
| * @param _vaultAddress Address of the Vault | ||
| * @param _rewardTokenAddresses Address of reward token for platform | ||
| * @param _assets Addresses of initial supported assets | ||
| * @param _pTokens Platform Token corresponding addresses | ||
| */ | ||
| function initialize( | ||
| address _vaultAddress, | ||
| address[] calldata _rewardTokenAddresses, | ||
| address[] calldata _assets, | ||
| address[] calldata _pTokens | ||
| ) external onlyGovernor initializer { | ||
| super._initialize( | ||
| MORPHO, | ||
| _vaultAddress, | ||
| _rewardTokenAddresses, | ||
| _assets, | ||
| _pTokens | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Approve the spending of all assets by main Morpho contract, | ||
| * if for some reason is it necessary. | ||
| */ | ||
| function safeApproveAllTokens() | ||
| external | ||
| override | ||
| onlyGovernor | ||
| nonReentrant | ||
| { | ||
| uint256 assetCount = assetsMapped.length; | ||
| for (uint256 i = 0; i < assetCount; i++) { | ||
| address asset = assetsMapped[i]; | ||
|
|
||
| // Safe approval | ||
| IERC20(asset).safeApprove(MORPHO, 0); | ||
| IERC20(asset).safeApprove(MORPHO, type(uint256).max); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @dev Internal method to respond to the addition of new asset | ||
| * We need to approve and allow Morpho to move them | ||
| * @param _asset Address of the asset to approve | ||
| * @param _pToken The pToken for the approval | ||
| */ | ||
| // solhint-disable-next-line no-unused-vars | ||
| function _abstractSetPToken(address _asset, address _pToken) | ||
| internal | ||
| override | ||
| { | ||
| IERC20(_asset).safeApprove(MORPHO, 0); | ||
| IERC20(_asset).safeApprove(MORPHO, type(uint256).max); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Collect accumulated rewards and send them to Harvester. | ||
| */ | ||
| function collectRewardTokens() | ||
| external | ||
| override | ||
| onlyHarvester | ||
| nonReentrant | ||
| { | ||
| // Morpho Aave-v2 doesn't distribute reward tokens | ||
DanielVF marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // solhint-disable-next-line max-line-length | ||
| // Ref: https://developers.morpho.xyz/interact-with-morpho/get-started/interact-with-morpho/claim-rewards#morpho-aave-v2 | ||
| } | ||
|
|
||
| /** | ||
| * @dev Get the amount of rewards pending to be collected from the protocol | ||
| */ | ||
| function getPendingRewards() external view returns (uint256 balance) { | ||
| // Morpho Aave-v2 doesn't distribute reward tokens | ||
| // solhint-disable-next-line max-line-length | ||
| // Ref: https://developers.morpho.xyz/interact-with-morpho/get-started/interact-with-morpho/claim-rewards#morpho-aave-v2 | ||
| return 0; | ||
| } | ||
|
|
||
| /** | ||
| * @dev Deposit asset into Morpho | ||
| * @param _asset Address of asset to deposit | ||
| * @param _amount Amount of asset to deposit | ||
| */ | ||
| function deposit(address _asset, uint256 _amount) | ||
| external | ||
| override | ||
| onlyVault | ||
| nonReentrant | ||
| { | ||
| _deposit(_asset, _amount); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Deposit asset into Morpho | ||
| * @param _asset Address of asset to deposit | ||
| * @param _amount Amount of asset to deposit | ||
| */ | ||
| function _deposit(address _asset, uint256 _amount) internal { | ||
| require(_amount > 0, "Must deposit something"); | ||
|
|
||
| address pToken = address(_getPTokenFor(_asset)); | ||
|
|
||
| IMorpho(MORPHO).supply( | ||
| pToken, | ||
| address(this), // the address of the user you want to supply on behalf of | ||
| _amount | ||
| ); | ||
| emit Deposit(_asset, pToken, _amount); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Deposit the entire balance of any supported asset into Morpho | ||
| */ | ||
| function depositAll() external override onlyVault nonReentrant { | ||
| for (uint256 i = 0; i < assetsMapped.length; i++) { | ||
| uint256 balance = IERC20(assetsMapped[i]).balanceOf(address(this)); | ||
| if (balance > 0) { | ||
| _deposit(assetsMapped[i], balance); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @dev Withdraw asset from Morpho | ||
| * @param _recipient Address to receive withdrawn asset | ||
| * @param _asset Address of asset to withdraw | ||
| * @param _amount Amount of asset to withdraw | ||
| */ | ||
| function withdraw( | ||
| address _recipient, | ||
| address _asset, | ||
| uint256 _amount | ||
| ) external override onlyVault nonReentrant { | ||
| _withdraw(_recipient, _asset, _amount); | ||
| } | ||
|
|
||
| function _withdraw( | ||
| address _recipient, | ||
| address _asset, | ||
| uint256 _amount | ||
| ) internal { | ||
| require(_amount > 0, "Must withdraw something"); | ||
| require(_recipient != address(0), "Must specify recipient"); | ||
|
|
||
| address pToken = address(_getPTokenFor(_asset)); | ||
|
|
||
| IMorpho(MORPHO).withdraw(pToken, _amount); | ||
DanielVF marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| emit Withdrawal(_asset, pToken, _amount); | ||
| IERC20(_asset).safeTransfer(_recipient, _amount); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Remove all assets from platform and send them to Vault contract. | ||
| */ | ||
| function withdrawAll() external override onlyVaultOrGovernor nonReentrant { | ||
| for (uint256 i = 0; i < assetsMapped.length; i++) { | ||
| uint256 balance = _checkBalance(assetsMapped[i]); | ||
| if (balance > 0) { | ||
| _withdraw(vaultAddress, assetsMapped[i], balance); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @dev Return total value of an asset held in the platform | ||
| * @param _asset Address of the asset | ||
| * @return balance Total value of the asset in the platform | ||
| */ | ||
| function checkBalance(address _asset) | ||
| external | ||
| view | ||
| override | ||
| returns (uint256 balance) | ||
| { | ||
| return _checkBalance(_asset); | ||
| } | ||
|
|
||
| function _checkBalance(address _asset) | ||
| internal | ||
| view | ||
| returns (uint256 balance) | ||
| { | ||
| address pToken = address(_getPTokenFor(_asset)); | ||
|
|
||
| // Total value represented by decimal position of underlying token | ||
| (, , balance) = ILens(LENS).getCurrentSupplyBalanceInOf( | ||
| pToken, | ||
| address(this) | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Retuns bool indicating whether asset is supported by strategy | ||
| * @param _asset Address of the asset | ||
| */ | ||
| function supportsAsset(address _asset) | ||
shahthepro marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| external | ||
| view | ||
| override | ||
| returns (bool) | ||
| { | ||
| return assetToPToken[_asset] != address(0); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Get the pToken wrapped in the IERC20 interface for this asset. | ||
| * Fails if the pToken doesn't exist in our mappings. | ||
| * @param _asset Address of the asset | ||
| * @return pToken Corresponding pToken to this asset | ||
| */ | ||
| function _getPTokenFor(address _asset) internal view returns (IERC20) { | ||
| address pToken = assetToPToken[_asset]; | ||
| require(pToken != address(0), "pToken does not exist"); | ||
| return IERC20(pToken); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| const { deploymentWithProposal } = require("../utils/deploy"); | ||
|
|
||
| module.exports = deploymentWithProposal( | ||
| { | ||
| deployName: "047_morpho_aave_strategy", | ||
| forceDeploy: false, | ||
| proposalId: 43, | ||
| }, | ||
| async ({ | ||
| assetAddresses, | ||
| deployWithConfirmation, | ||
| ethers, | ||
| getTxOpts, | ||
| withConfirmation, | ||
| }) => { | ||
| const { deployerAddr, governorAddr } = await getNamedAccounts(); | ||
| const sDeployer = await ethers.provider.getSigner(deployerAddr); | ||
|
|
||
| // Current contracts | ||
| const cVaultProxy = await ethers.getContract("VaultProxy"); | ||
| const cVaultAdmin = await ethers.getContractAt( | ||
| "VaultAdmin", | ||
| cVaultProxy.address | ||
| ); | ||
|
|
||
| // Deployer Actions | ||
| // ---------------- | ||
|
|
||
| // 1. Deploy new proxy | ||
| // New strategy will be living at a clean address | ||
| const dMorphoAaveStrategyProxy = await deployWithConfirmation( | ||
| "MorphoAaveStrategyProxy" | ||
| ); | ||
| const cMorphoAaveStrategyProxy = await ethers.getContractAt( | ||
| "MorphoAaveStrategyProxy", | ||
| dMorphoAaveStrategyProxy.address | ||
| ); | ||
|
|
||
| // 2. Deploy new implementation | ||
| const dMorphoAaveStrategyImpl = await deployWithConfirmation( | ||
| "MorphoAaveStrategy" | ||
| ); | ||
| const cMorphoAaveStrategy = await ethers.getContractAt( | ||
| "MorphoAaveStrategy", | ||
| dMorphoAaveStrategyProxy.address | ||
| ); | ||
|
|
||
| const cHarvesterProxy = await ethers.getContract("HarvesterProxy"); | ||
| const cHarvester = await ethers.getContractAt( | ||
| "Harvester", | ||
| cHarvesterProxy.address | ||
| ); | ||
|
|
||
| // 3. Init the proxy to point at the implementation | ||
| await withConfirmation( | ||
| cMorphoAaveStrategyProxy | ||
| .connect(sDeployer) | ||
| ["initialize(address,address,bytes)"]( | ||
| dMorphoAaveStrategyImpl.address, | ||
| deployerAddr, | ||
| [], | ||
| await getTxOpts() | ||
| ) | ||
| ); | ||
|
|
||
| // 4. Init and configure new Morpho strategy | ||
| const initFunction = "initialize(address,address[],address[],address[])"; | ||
| await withConfirmation( | ||
| cMorphoAaveStrategy.connect(sDeployer)[initFunction]( | ||
| cVaultProxy.address, | ||
| [], // reward token addresses | ||
| [assetAddresses.DAI, assetAddresses.USDC, assetAddresses.USDT], // asset token addresses | ||
| [assetAddresses.aDAI, assetAddresses.aUSDC, assetAddresses.aUSDT], // platform tokens addresses | ||
| await getTxOpts() | ||
| ) | ||
| ); | ||
|
|
||
| // 5. Transfer governance | ||
| await withConfirmation( | ||
| cMorphoAaveStrategy | ||
| .connect(sDeployer) | ||
| .transferGovernance(governorAddr, await getTxOpts()) | ||
| ); | ||
|
|
||
| console.log("Morpho Aave strategy address: ", cMorphoAaveStrategy.address); | ||
| // Governance Actions | ||
| // ---------------- | ||
| return { | ||
| name: "Deploy new Morpho Aave strategy", | ||
| actions: [ | ||
| // 1. Accept governance of new MorphoAaveStrategy | ||
| { | ||
| contract: cMorphoAaveStrategy, | ||
| signature: "claimGovernance()", | ||
| args: [], | ||
| }, | ||
| // 2. Add new Morpho strategy to vault | ||
| { | ||
| contract: cVaultAdmin, | ||
| signature: "approveStrategy(address)", | ||
| args: [cMorphoAaveStrategy.address], | ||
| }, | ||
| // 3. Set supported strategy on Harvester | ||
| { | ||
| contract: cHarvester, | ||
| signature: "setSupportedStrategy(address,bool)", | ||
| args: [dMorphoAaveStrategyProxy.address, true], | ||
| }, | ||
| // 4. Set harvester address | ||
| { | ||
| contract: cMorphoAaveStrategy, | ||
| signature: "setHarvesterAddress(address)", | ||
| args: [cHarvesterProxy.address], | ||
| }, | ||
| ], | ||
| }; | ||
| } | ||
| ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.