Skip to content

Commit 5885f8e

Browse files
authored
Add Morpho Aave strategy (#1207)
* checkpoint * Revert dep on BaseCompoundStrategy * Update tests * prettier * Use migration ID 046 * lint * lint * Address review comments * Also, update `_checkBalance` method * Remove unused import * Update migration ID * Deploy 47 - Morpho Aave strategy (#1209) * Deploy Morpho Aave strategy * Update deployment files * prettify
1 parent e550ea2 commit 5885f8e

17 files changed

+2768
-7
lines changed

brownie/world.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def load_contract(name, address):
4545
comp_strat = load_contract('comp_strat', COMP_STRAT)
4646
convex_strat = load_contract('convex_strat', CONVEX_STRAT)
4747
ousd_meta_strat = load_contract('ousd_metastrat', OUSD_METASTRAT)
48-
morpho_comp_strat = load_contract('comp_strat', MORPHO_COMP_STRAT)
48+
morpho_comp_strat = load_contract('morpho_strat', MORPHO_COMP_STRAT)
4949

5050
aave_incentives_controller = load_contract('aave_incentives_controller', '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5')
5151
stkaave = load_contract('stkaave', '0x4da27a545c0c5B758a6BA100e3a049001de870f5')

contracts/contracts/proxies/Proxies.sol

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,10 @@ contract ConvexBUSDMetaStrategyProxy is InitializeGovernedUpgradeabilityProxy {
114114
contract ConvexLUSDMetaStrategyProxy is InitializeGovernedUpgradeabilityProxy {
115115

116116
}
117+
118+
/**
119+
* @notice MorphoAaveStrategyProxy delegates calls to a MorphoCompoundStrategy implementation
120+
*/
121+
contract MorphoAaveStrategyProxy is InitializeGovernedUpgradeabilityProxy {
122+
123+
}
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
// SPDX-License-Identifier: agpl-3.0
2+
pragma solidity ^0.8.0;
3+
4+
/**
5+
* @title OUSD Morpho Aave Strategy
6+
* @notice Investment strategy for investing stablecoins via Morpho (Aave)
7+
* @author Origin Protocol Inc
8+
*/
9+
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
10+
import { IERC20, InitializableAbstractStrategy } from "../utils/InitializableAbstractStrategy.sol";
11+
import { IMorpho } from "../interfaces/morpho/IMorpho.sol";
12+
import { ILens } from "../interfaces/morpho/ILens.sol";
13+
import { StableMath } from "../utils/StableMath.sol";
14+
15+
contract MorphoAaveStrategy is InitializableAbstractStrategy {
16+
address public constant MORPHO = 0x777777c9898D384F785Ee44Acfe945efDFf5f3E0;
17+
address public constant LENS = 0x507fA343d0A90786d86C7cd885f5C49263A91FF4;
18+
19+
using SafeERC20 for IERC20;
20+
using StableMath for uint256;
21+
22+
/**
23+
* @dev Initialize function, to set up initial internal state
24+
* @param _vaultAddress Address of the Vault
25+
* @param _rewardTokenAddresses Address of reward token for platform
26+
* @param _assets Addresses of initial supported assets
27+
* @param _pTokens Platform Token corresponding addresses
28+
*/
29+
function initialize(
30+
address _vaultAddress,
31+
address[] calldata _rewardTokenAddresses,
32+
address[] calldata _assets,
33+
address[] calldata _pTokens
34+
) external onlyGovernor initializer {
35+
super._initialize(
36+
MORPHO,
37+
_vaultAddress,
38+
_rewardTokenAddresses,
39+
_assets,
40+
_pTokens
41+
);
42+
}
43+
44+
/**
45+
* @dev Approve the spending of all assets by main Morpho contract,
46+
* if for some reason is it necessary.
47+
*/
48+
function safeApproveAllTokens()
49+
external
50+
override
51+
onlyGovernor
52+
nonReentrant
53+
{
54+
uint256 assetCount = assetsMapped.length;
55+
for (uint256 i = 0; i < assetCount; i++) {
56+
address asset = assetsMapped[i];
57+
58+
// Safe approval
59+
IERC20(asset).safeApprove(MORPHO, 0);
60+
IERC20(asset).safeApprove(MORPHO, type(uint256).max);
61+
}
62+
}
63+
64+
/**
65+
* @dev Internal method to respond to the addition of new asset
66+
* We need to approve and allow Morpho to move them
67+
* @param _asset Address of the asset to approve
68+
* @param _pToken The pToken for the approval
69+
*/
70+
// solhint-disable-next-line no-unused-vars
71+
function _abstractSetPToken(address _asset, address _pToken)
72+
internal
73+
override
74+
{
75+
IERC20(_asset).safeApprove(MORPHO, 0);
76+
IERC20(_asset).safeApprove(MORPHO, type(uint256).max);
77+
}
78+
79+
/**
80+
* @dev Collect accumulated rewards and send them to Harvester.
81+
*/
82+
function collectRewardTokens()
83+
external
84+
override
85+
onlyHarvester
86+
nonReentrant
87+
{
88+
// Morpho Aave-v2 doesn't distribute reward tokens
89+
// solhint-disable-next-line max-line-length
90+
// Ref: https://developers.morpho.xyz/interact-with-morpho/get-started/interact-with-morpho/claim-rewards#morpho-aave-v2
91+
}
92+
93+
/**
94+
* @dev Get the amount of rewards pending to be collected from the protocol
95+
*/
96+
function getPendingRewards() external view returns (uint256 balance) {
97+
// Morpho Aave-v2 doesn't distribute reward tokens
98+
// solhint-disable-next-line max-line-length
99+
// Ref: https://developers.morpho.xyz/interact-with-morpho/get-started/interact-with-morpho/claim-rewards#morpho-aave-v2
100+
return 0;
101+
}
102+
103+
/**
104+
* @dev Deposit asset into Morpho
105+
* @param _asset Address of asset to deposit
106+
* @param _amount Amount of asset to deposit
107+
*/
108+
function deposit(address _asset, uint256 _amount)
109+
external
110+
override
111+
onlyVault
112+
nonReentrant
113+
{
114+
_deposit(_asset, _amount);
115+
}
116+
117+
/**
118+
* @dev Deposit asset into Morpho
119+
* @param _asset Address of asset to deposit
120+
* @param _amount Amount of asset to deposit
121+
*/
122+
function _deposit(address _asset, uint256 _amount) internal {
123+
require(_amount > 0, "Must deposit something");
124+
125+
address pToken = address(_getPTokenFor(_asset));
126+
127+
IMorpho(MORPHO).supply(
128+
pToken,
129+
address(this), // the address of the user you want to supply on behalf of
130+
_amount
131+
);
132+
emit Deposit(_asset, pToken, _amount);
133+
}
134+
135+
/**
136+
* @dev Deposit the entire balance of any supported asset into Morpho
137+
*/
138+
function depositAll() external override onlyVault nonReentrant {
139+
for (uint256 i = 0; i < assetsMapped.length; i++) {
140+
uint256 balance = IERC20(assetsMapped[i]).balanceOf(address(this));
141+
if (balance > 0) {
142+
_deposit(assetsMapped[i], balance);
143+
}
144+
}
145+
}
146+
147+
/**
148+
* @dev Withdraw asset from Morpho
149+
* @param _recipient Address to receive withdrawn asset
150+
* @param _asset Address of asset to withdraw
151+
* @param _amount Amount of asset to withdraw
152+
*/
153+
function withdraw(
154+
address _recipient,
155+
address _asset,
156+
uint256 _amount
157+
) external override onlyVault nonReentrant {
158+
_withdraw(_recipient, _asset, _amount);
159+
}
160+
161+
function _withdraw(
162+
address _recipient,
163+
address _asset,
164+
uint256 _amount
165+
) internal {
166+
require(_amount > 0, "Must withdraw something");
167+
require(_recipient != address(0), "Must specify recipient");
168+
169+
address pToken = address(_getPTokenFor(_asset));
170+
171+
IMorpho(MORPHO).withdraw(pToken, _amount);
172+
emit Withdrawal(_asset, pToken, _amount);
173+
IERC20(_asset).safeTransfer(_recipient, _amount);
174+
}
175+
176+
/**
177+
* @dev Remove all assets from platform and send them to Vault contract.
178+
*/
179+
function withdrawAll() external override onlyVaultOrGovernor nonReentrant {
180+
for (uint256 i = 0; i < assetsMapped.length; i++) {
181+
uint256 balance = _checkBalance(assetsMapped[i]);
182+
if (balance > 0) {
183+
_withdraw(vaultAddress, assetsMapped[i], balance);
184+
}
185+
}
186+
}
187+
188+
/**
189+
* @dev Return total value of an asset held in the platform
190+
* @param _asset Address of the asset
191+
* @return balance Total value of the asset in the platform
192+
*/
193+
function checkBalance(address _asset)
194+
external
195+
view
196+
override
197+
returns (uint256 balance)
198+
{
199+
return _checkBalance(_asset);
200+
}
201+
202+
function _checkBalance(address _asset)
203+
internal
204+
view
205+
returns (uint256 balance)
206+
{
207+
address pToken = address(_getPTokenFor(_asset));
208+
209+
// Total value represented by decimal position of underlying token
210+
(, , balance) = ILens(LENS).getCurrentSupplyBalanceInOf(
211+
pToken,
212+
address(this)
213+
);
214+
}
215+
216+
/**
217+
* @dev Retuns bool indicating whether asset is supported by strategy
218+
* @param _asset Address of the asset
219+
*/
220+
function supportsAsset(address _asset)
221+
external
222+
view
223+
override
224+
returns (bool)
225+
{
226+
return assetToPToken[_asset] != address(0);
227+
}
228+
229+
/**
230+
* @dev Get the pToken wrapped in the IERC20 interface for this asset.
231+
* Fails if the pToken doesn't exist in our mappings.
232+
* @param _asset Address of the asset
233+
* @return pToken Corresponding pToken to this asset
234+
*/
235+
function _getPTokenFor(address _asset) internal view returns (IERC20) {
236+
address pToken = assetToPToken[_asset];
237+
require(pToken != address(0), "pToken does not exist");
238+
return IERC20(pToken);
239+
}
240+
}

contracts/deploy/044_morpho_strategy.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
const { deploymentWithProposal } = require("../utils/deploy");
2-
const addresses = require("../utils/addresses");
32

43
module.exports = deploymentWithProposal(
54
{ deployName: "044_morpho_strategy", forceDeploy: false, proposalId: 39 },
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
const { deploymentWithProposal } = require("../utils/deploy");
2+
3+
module.exports = deploymentWithProposal(
4+
{
5+
deployName: "047_morpho_aave_strategy",
6+
forceDeploy: false,
7+
proposalId: 43,
8+
},
9+
async ({
10+
assetAddresses,
11+
deployWithConfirmation,
12+
ethers,
13+
getTxOpts,
14+
withConfirmation,
15+
}) => {
16+
const { deployerAddr, governorAddr } = await getNamedAccounts();
17+
const sDeployer = await ethers.provider.getSigner(deployerAddr);
18+
19+
// Current contracts
20+
const cVaultProxy = await ethers.getContract("VaultProxy");
21+
const cVaultAdmin = await ethers.getContractAt(
22+
"VaultAdmin",
23+
cVaultProxy.address
24+
);
25+
26+
// Deployer Actions
27+
// ----------------
28+
29+
// 1. Deploy new proxy
30+
// New strategy will be living at a clean address
31+
const dMorphoAaveStrategyProxy = await deployWithConfirmation(
32+
"MorphoAaveStrategyProxy"
33+
);
34+
const cMorphoAaveStrategyProxy = await ethers.getContractAt(
35+
"MorphoAaveStrategyProxy",
36+
dMorphoAaveStrategyProxy.address
37+
);
38+
39+
// 2. Deploy new implementation
40+
const dMorphoAaveStrategyImpl = await deployWithConfirmation(
41+
"MorphoAaveStrategy"
42+
);
43+
const cMorphoAaveStrategy = await ethers.getContractAt(
44+
"MorphoAaveStrategy",
45+
dMorphoAaveStrategyProxy.address
46+
);
47+
48+
const cHarvesterProxy = await ethers.getContract("HarvesterProxy");
49+
const cHarvester = await ethers.getContractAt(
50+
"Harvester",
51+
cHarvesterProxy.address
52+
);
53+
54+
// 3. Init the proxy to point at the implementation
55+
await withConfirmation(
56+
cMorphoAaveStrategyProxy
57+
.connect(sDeployer)
58+
["initialize(address,address,bytes)"](
59+
dMorphoAaveStrategyImpl.address,
60+
deployerAddr,
61+
[],
62+
await getTxOpts()
63+
)
64+
);
65+
66+
// 4. Init and configure new Morpho strategy
67+
const initFunction = "initialize(address,address[],address[],address[])";
68+
await withConfirmation(
69+
cMorphoAaveStrategy.connect(sDeployer)[initFunction](
70+
cVaultProxy.address,
71+
[], // reward token addresses
72+
[assetAddresses.DAI, assetAddresses.USDC, assetAddresses.USDT], // asset token addresses
73+
[assetAddresses.aDAI, assetAddresses.aUSDC, assetAddresses.aUSDT], // platform tokens addresses
74+
await getTxOpts()
75+
)
76+
);
77+
78+
// 5. Transfer governance
79+
await withConfirmation(
80+
cMorphoAaveStrategy
81+
.connect(sDeployer)
82+
.transferGovernance(governorAddr, await getTxOpts())
83+
);
84+
85+
console.log("Morpho Aave strategy address: ", cMorphoAaveStrategy.address);
86+
// Governance Actions
87+
// ----------------
88+
return {
89+
name: "Deploy new Morpho Aave strategy",
90+
actions: [
91+
// 1. Accept governance of new MorphoAaveStrategy
92+
{
93+
contract: cMorphoAaveStrategy,
94+
signature: "claimGovernance()",
95+
args: [],
96+
},
97+
// 2. Add new Morpho strategy to vault
98+
{
99+
contract: cVaultAdmin,
100+
signature: "approveStrategy(address)",
101+
args: [cMorphoAaveStrategy.address],
102+
},
103+
// 3. Set supported strategy on Harvester
104+
{
105+
contract: cHarvester,
106+
signature: "setSupportedStrategy(address,bool)",
107+
args: [dMorphoAaveStrategyProxy.address, true],
108+
},
109+
// 4. Set harvester address
110+
{
111+
contract: cMorphoAaveStrategy,
112+
signature: "setHarvesterAddress(address)",
113+
args: [cHarvesterProxy.address],
114+
},
115+
],
116+
};
117+
}
118+
);

contracts/deployments/mainnet/.migrations.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,6 @@
4141
"043_convex_OUSD_meta_strategy": 1667562899,
4242
"044_morpho_strategy": 1668204653,
4343
"045_convex_lusd_meta_strategy": 1671543402,
44-
"046_vault_value_checker": 1669212530
44+
"046_vault_value_checker": 1669212530,
45+
"047_morpho_aave_strategy": 1672817148
4546
}

0 commit comments

Comments
 (0)