/
SupplyHarvestVault.sol
192 lines (151 loc) · 8.07 KB
/
SupplyHarvestVault.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// SPDX-License-Identifier: GNU AGPLv3
pragma solidity ^0.8.0;
import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
import "@aave/core-v3/contracts/protocol/libraries/math/PercentageMath.sol";
import "./SupplyVaultUpgradeable.sol";
/// @title SupplyHarvestVault.
/// @author Morpho Labs.
/// @custom:contact security@morpho.xyz
/// @notice ERC4626-upgradeable Tokenized Vault implementation for Morpho-Compound, which can harvest accrued COMP rewards, swap them and re-supply them through Morpho-Compound.
contract SupplyHarvestVault is SupplyVaultUpgradeable {
using SafeTransferLib for ERC20;
using PercentageMath for uint256;
/// EVENTS ///
/// @notice Emitted when an harvest is done.
/// @param harvester The address of the harvester receiving the fee.
/// @param rewardsAmount The amount of rewards in underlying asset which is supplied to Morpho.
/// @param rewardsFee The amount of underlying asset sent to the harvester.
event Harvested(address indexed harvester, uint256 rewardsAmount, uint256 rewardsFee);
/// @notice Emitted when the fee for swapping comp for WETH is set.
/// @param newCompSwapFee The new comp swap fee (in UniswapV3 fee unit).
event CompSwapFeeSet(uint24 newCompSwapFee);
/// @notice Emitted when the fee for swapping WETH for the underlying asset is set.
/// @param newAssetSwapFee The new asset swap fee (in UniswapV3 fee unit).
event AssetSwapFeeSet(uint24 newAssetSwapFee);
/// @notice Emitted when the fee for harvesting is set.
/// @param newHarvestingFee The new harvesting fee.
event HarvestingFeeSet(uint16 newHarvestingFee);
/// @notice Emitted when the maximum slippage for harvesting is set.
/// @param newMaxHarvestingSlippage The new maximum slippage allowed when swapping rewards for the underlying token (in bps).
event MaxHarvestingSlippageSet(uint16 newMaxHarvestingSlippage);
/// ERRORS ///
/// @notice Thrown when the input is above the maximum basis points value (100%).
error ExceedsMaxBasisPoints();
/// @notice Thrown when the input is above the maximum UniswapV3 pool fee value (100%).
error ExceedsMaxUniswapV3Fee();
/// STRUCTS ///
struct HarvestConfig {
uint24 compSwapFee; // The fee taken by the UniswapV3Pool for swapping COMP rewards for WETH (in UniswapV3 fee unit).
uint24 assetSwapFee; // The fee taken by the UniswapV3Pool for swapping WETH for the underlying asset (in UniswapV3 fee unit).
uint16 harvestingFee; // The fee taken by the claimer when harvesting the vault (in bps).
}
/// STORAGE ///
uint16 public constant MAX_BASIS_POINTS = 10_000; // 100% in basis points.
uint24 public constant MAX_UNISWAP_FEE = 1_000_000; // 100% in UniswapV3 fee units.
ISwapRouter public constant SWAP_ROUTER =
ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564); // The address of UniswapV3SwapRouter.
bool public isEth; // Whether the underlying asset is WETH.
address public wEth; // The address of WETH token.
address public cComp; // The address of cCOMP token.
HarvestConfig public harvestConfig; // The configuration of the swap on Uniswap V3.
/// UPGRADE ///
/// @notice Initializes the vault.
/// @param _morpho The address of the main Morpho contract.
/// @param _poolToken The address of the pool token corresponding to the market to supply through this vault.
/// @param _name The name of the ERC20 token associated to this tokenized vault.
/// @param _symbol The symbol of the ERC20 token associated to this tokenized vault.
/// @param _initialDeposit The amount of the initial deposit used to prevent pricePerShare manipulation.
/// @param _harvestConfig The swap config to set.
function initialize(
address _morpho,
address _poolToken,
string calldata _name,
string calldata _symbol,
uint256 _initialDeposit,
HarvestConfig calldata _harvestConfig,
address _cComp
) external initializer {
(isEth, wEth) = __SupplyVaultUpgradeable_init(
_morpho,
_poolToken,
_name,
_symbol,
_initialDeposit
);
harvestConfig = _harvestConfig;
cComp = _cComp;
comp.safeApprove(address(SWAP_ROUTER), type(uint256).max);
}
/// GOVERNANCE ///
/// @notice Sets the fee taken by the UniswapV3Pool for swapping COMP rewards for WETH.
/// @param _newCompSwapFee The new comp swap fee (in UniswapV3 fee unit).
function setCompSwapFee(uint24 _newCompSwapFee) external onlyOwner {
if (_newCompSwapFee > MAX_UNISWAP_FEE) revert ExceedsMaxUniswapV3Fee();
harvestConfig.compSwapFee = _newCompSwapFee;
emit CompSwapFeeSet(_newCompSwapFee);
}
/// @notice Sets the fee taken by the UniswapV3Pool for swapping WETH for the underlying asset.
/// @param _newAssetSwapFee The new asset swap fee (in UniswapV3 fee unit).
function setAssetSwapFee(uint24 _newAssetSwapFee) external onlyOwner {
if (_newAssetSwapFee > MAX_UNISWAP_FEE) revert ExceedsMaxUniswapV3Fee();
harvestConfig.assetSwapFee = _newAssetSwapFee;
emit AssetSwapFeeSet(_newAssetSwapFee);
}
/// @notice Sets the fee taken by the claimer from the total amount of COMP rewards when harvesting the vault.
/// @param _newHarvestingFee The new harvesting fee to set (in bps).
function setHarvestingFee(uint16 _newHarvestingFee) external onlyOwner {
if (_newHarvestingFee > MAX_BASIS_POINTS) revert ExceedsMaxBasisPoints();
harvestConfig.harvestingFee = _newHarvestingFee;
emit HarvestingFeeSet(_newHarvestingFee);
}
/// EXTERNAL ///
/// @notice Harvests the vault: claims rewards from the underlying pool, swaps them for the underlying asset and supply them through Morpho.
/// @return rewardsAmount The amount of rewards claimed, swapped then supplied through Morpho (in underlying).
/// @return rewardsFee The amount of fees taken by the claimer (in underlying).
function harvest() external returns (uint256 rewardsAmount, uint256 rewardsFee) {
address assetMem = asset();
address poolTokenMem = poolToken;
address compMem = address(comp);
HarvestConfig memory harvestConfigMem = harvestConfig;
address[] memory poolTokens = new address[](1);
poolTokens[0] = poolTokenMem;
// Note: Uniswap pairs are considered to have enough market depth.
// The amount swapped is considered low enough to avoid relying on any oracle.
if (assetMem != compMem) {
rewardsAmount = SWAP_ROUTER.exactInput(
ISwapRouter.ExactInputParams({
path: isEth
? abi.encodePacked(compMem, harvestConfigMem.compSwapFee, wEth)
: abi.encodePacked(
compMem,
harvestConfigMem.compSwapFee,
wEth,
harvestConfigMem.assetSwapFee,
assetMem
),
recipient: address(this),
deadline: block.timestamp,
amountIn: morpho.claimRewards(poolTokens, false),
amountOutMinimum: 0
})
);
} else rewardsAmount = morpho.claimRewards(poolTokens, false);
if (harvestConfigMem.harvestingFee > 0) {
rewardsFee = rewardsAmount.percentMul(harvestConfigMem.harvestingFee);
rewardsAmount -= rewardsFee;
}
morpho.supply(poolTokenMem, address(this), rewardsAmount);
if (rewardsFee > 0) ERC20(assetMem).safeTransfer(msg.sender, rewardsFee);
emit Harvested(msg.sender, rewardsAmount, rewardsFee);
}
/// GETTERS ///
function compSwapFee() external view returns (uint24) {
return harvestConfig.compSwapFee;
}
function assetSwapFee() external view returns (uint24) {
return harvestConfig.assetSwapFee;
}
function harvestingFee() external view returns (uint16) {
return harvestConfig.harvestingFee;
}
}