/
SupplyHarvestVault.sol
164 lines (132 loc) · 6.3 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
// SPDX-License-Identifier: GNU AGPLv3
pragma solidity ^0.8.0;
import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
import "../interfaces/ISwapper.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-Aave, which can harvest accrued COMP rewards, swap them and re-supply them through Morpho-Rewardsound.
contract SupplyHarvestVault is SupplyVaultUpgradeable {
using SafeTransferLib for ERC20;
using PercentageMath for uint256;
using WadRayMath for uint256;
/// EVENTS ///
/// @notice Emitted when an harvest is done.
/// @param harvester The address of the harvester receiving the fee.
/// @param rewardToken The address of the reward token swapped.
/// @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,
address indexed rewardToken,
uint256 rewardsAmount,
uint256 rewardsFee
);
/// @notice Emitted when the fee for harvesting is set.
/// @param newHarvestingFee The new harvesting fee.
event HarvestingFeeSet(uint16 newHarvestingFee);
/// @notice Emitted when the swapper is set.
/// @param newSwapper The new swapper contract.
event SwapperSet(address newSwapper);
/// ERRORS ///
/// @notice Thrown when the input is above the maximum basis points value (100%).
error ExceedsMaxBasisPoints();
/// STORAGE ///
uint16 public constant MAX_BASIS_POINTS = 10_000; // 100% in basis points.
uint16 public harvestingFee; // The fee taken by the claimer when harvesting the vault (in bps).
ISwapper public swapper; // Swapper contract to swap reward tokens for underlying asset.
/// 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 _harvestingFee The fee taken by the claimer when harvesting the vault (in bps).
function initialize(
address _morpho,
address _poolToken,
string calldata _name,
string calldata _symbol,
uint256 _initialDeposit,
uint16 _harvestingFee,
address _swapper
) external initializer {
__SupplyVaultUpgradeable_init(_morpho, _poolToken, _name, _symbol, _initialDeposit);
harvestingFee = _harvestingFee;
swapper = ISwapper(_swapper);
}
/// GOVERNANCE ///
/// @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();
harvestingFee = _newHarvestingFee;
emit HarvestingFeeSet(_newHarvestingFee);
}
/// @notice Sets the swapper contract to swap reward tokens for underlying asset.
/// @param _swapper The new swapper to set.
function setSwapper(address _swapper) external onlyOwner {
swapper = ISwapper(_swapper);
emit SwapperSet(_swapper);
}
/// EXTERNAL ///
/// @notice Harvests the vault: claims rewards from the underlying pool, swaps them for the underlying asset and supply them through Morpho.
/// @return rewardTokens The addresses of reward tokens claimed.
/// @return rewardsAmounts The amount of rewards claimed for each reward token (in underlying).
/// @return rewardsFees The amount of fees taken by the claimer for each reward token (in underlying).
function harvest()
external
returns (
address[] memory rewardTokens,
uint256[] memory rewardsAmounts,
uint256[] memory rewardsFees
)
{
address poolTokenMem = poolToken;
{
address[] memory poolTokens = new address[](1);
poolTokens[0] = poolTokenMem;
(rewardTokens, rewardsAmounts) = morpho.claimRewards(poolTokens, false);
}
address assetMem = asset();
ISwapper swapperMem = swapper;
uint16 harvestingFeeMem = harvestingFee;
uint256 nbRewardTokens = rewardTokens.length;
uint256 toSupply;
rewardsFees = new uint256[](nbRewardTokens);
for (uint256 i; i < nbRewardTokens; ) {
uint256 rewardsAmount = rewardsAmounts[i];
if (rewardsAmount > 0) {
ERC20 rewardToken = ERC20(rewardTokens[i]);
// 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 != address(rewardToken)) {
rewardToken.safeTransfer(address(swapperMem), rewardsAmount);
rewardsAmount = swapperMem.executeSwap(
address(rewardToken),
rewardsAmount,
assetMem,
address(this)
);
}
uint256 rewardsFee;
if (harvestingFeeMem > 0) {
rewardsFee = rewardsAmount.percentMul(harvestingFeeMem);
rewardsFees[i] = rewardsFee;
rewardsAmount -= rewardsFee;
ERC20(assetMem).safeTransfer(msg.sender, rewardsFee);
}
rewardsAmounts[i] = rewardsAmount;
toSupply += rewardsAmount;
emit Harvested(msg.sender, address(rewardToken), rewardsAmount, rewardsFee);
}
unchecked {
++i;
}
}
morpho.supply(poolTokenMem, address(this), toSupply);
}
}