-
Notifications
You must be signed in to change notification settings - Fork 0
/
StrategyInsurance.sol
247 lines (211 loc) · 7.5 KB
/
StrategyInsurance.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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
// SPDX-License-Identifier: AGPL-3.0
// Feel free to change the license, but this is what we use
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import {
SafeERC20,
SafeMath,
IERC20,
Address
} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import {Math} from "@openzeppelin/contracts/math/Math.sol";
import {VaultAPI, StrategyAPI} from "@yearnvaults/contracts/BaseStrategy.sol";
interface StrategyAPIExt is StrategyAPI {
function strategist() external view returns (address);
function insurance() external view returns (address);
}
interface IStrategyInsurance {
function reportProfit(uint256 _totalDebt, uint256 _profit)
external
returns (uint256 _payment, uint256 _compensation);
function reportLoss(uint256 _totalDebt, uint256 _loss)
external
returns (uint256 _compensation);
function migrateInsurance(address newInsurance) external;
}
/**
* @title Strategy Generic Insurrance
* @author Robovault
* @notice
* StrategyInsurance provides an issurrance fund for strategy losses
* A portion of all profits are sent to the insurrance fund untill
* it reaches its target insurrance percentage. When a loss is realised
* by the strategy the inssurance fund will return the funds to the
* strategy to fully compensate or soften the loss.
*/
contract StrategyInsurance {
using SafeERC20 for IERC20;
using Address for address;
using SafeMath for uint256;
StrategyAPIExt public strategy;
IERC20 want;
uint256 constant BPS_MAX = 10000;
uint256 public lossSum = 0;
event InsurancePayment(
uint256 indexed strategyDebt,
uint256 indexed harvestProfit,
uint256 indexed wantPayment
);
event InsurancePayout(uint256 indexed wantPayout);
// Bips - Proportion of totalDebt the inssurance fund is targeting to grow
uint256 public targetFundSize = 50; // 0.5% default
// Rate of the profits that go to insurrance while it's below target
uint256 public profitTakeRate = 1000; // 10% default
// The maximum compensation rate the insurrance fund will return funds to the strategy
// proportional to the TotalDebt of the strategy
uint256 public maximumCompenstionRate = 5; // 5 bips per harvest default
function _onlyAuthorized() internal {
require(
msg.sender == strategy.strategist() || msg.sender == governance()
);
}
function _onlyGovernance() internal {
require(msg.sender == governance());
}
function _onlyStrategy() internal {
require(msg.sender == address(strategy));
}
constructor(address _strategy) public {
strategy = StrategyAPIExt(_strategy);
want = IERC20(strategy.want());
}
function setTargetFundSize(uint256 _targetFundSize) external {
_onlyAuthorized();
require(_targetFundSize < 500); // Must be less than 5%
targetFundSize = _targetFundSize;
}
function setProfitTakeRate(uint256 _profitTakeRate) external {
_onlyAuthorized();
require(_profitTakeRate < 4000); // Must be less than 40%
profitTakeRate = _profitTakeRate;
}
function setmaximumCompenstionRate(uint256 _maximumCompenstionRate)
external
{
_onlyAuthorized();
require(_maximumCompenstionRate < 50); // Must be less than 0.5%
maximumCompenstionRate = _maximumCompenstionRate;
}
/**
* @notice
* Strategy reports profits to the insurrance find and informs the strategy
* of how much want is requested for insurrance.
* @param _totalDebt Debt the strategy has with the vault.
* @param _profit The profit the strategy is reporting this harvest
* @return _payment amount requested for insurrance
* @return _compensation amount paid out in latent insurance
*/
function reportProfit(uint256 _totalDebt, uint256 _profit)
external
returns (uint256 _payment, uint256 _compensation)
{
_onlyStrategy();
// if there has been a loss that is yet to be paid fully compensated, continue
// to compensate
if (lossSum > _profit) {
lossSum = lossSum.sub(_profit);
_compensation = compensate(_totalDebt);
return (0, _compensation);
}
// no pending losses to pay out
lossSum = 0;
// Has the insurrance hit the insurrance target
uint256 balance = want.balanceOf(address(this));
uint256 targetBalance = _totalDebt.mul(targetFundSize).div(BPS_MAX);
if (balance >= targetBalance) {
return (0, 0);
}
_payment = _profit.mul(profitTakeRate).div(BPS_MAX);
emit InsurancePayment(_totalDebt, _profit, _payment);
}
/**
* @notice
* Strategy reports loss. The insurrance fund will decide weather or not to
* send want back to the strategy to soften the loss
* @param _totalDebt Debt the strategy has with the vault.
* @param _loss The loss realised by the this harvest
* @return _compensation amount sent back to the strategy.
*/
function reportLoss(uint256 _totalDebt, uint256 _loss)
external
returns (uint256 _compensation)
{
_onlyStrategy();
lossSum = lossSum.add(_loss);
_compensation = compensate(_totalDebt);
}
/**
* @notice
* Processes insurance payouot
* @param _totalDebt Debt the strategy has with the vault.
* @return _compensation amount sent back to the strategy.
*/
function compensate(uint256 _totalDebt)
internal
returns (uint256 _compensation)
{
uint256 balance = want.balanceOf(address(this));
// Reserves are empties, we cannot compensate
if (balance == 0) {
lossSum = 0;
return 0;
}
// Calculat what the payout will be
uint256 maxComp = maximumCompenstionRate.mul(_totalDebt).div(BPS_MAX);
_compensation = Math.min(Math.min(balance, lossSum), maxComp);
if (_compensation > 0) {
SafeERC20.safeTransfer(want, address(strategy), _compensation);
emit InsurancePayout(_compensation);
}
lossSum = lossSum.sub(_compensation);
}
function governance() public view returns (address) {
return VaultAPI(strategy.vault()).governance();
}
/**
* @notice
* Sends balance to gov for the purpose of migrating to a new strategy at the
* disgression of governance.
*/
function withdraw() external {
_onlyGovernance();
SafeERC20.safeTransfer(
want,
governance(),
want.balanceOf(address(this))
);
}
/**
* @notice
* Sets the lossSum. Adds some flexibility with payouts to cover edge-case
* scenarios
*/
function setLossSum(uint256 newLossSum) external {
_onlyGovernance();
lossSum = newLossSum;
}
/**
* @notice
* called by the strategy when updating the insurance contract
*/
function migrateInsurance(address newInsurance) external {
_onlyStrategy();
SafeERC20.safeTransfer(
want,
newInsurance,
want.balanceOf(address(this))
);
}
/**
* @notice
* Called by goverannace when updating the strategy
*/
function migrateStrategy(address newStrategy) external {
_onlyGovernance();
SafeERC20.safeTransfer(
want,
StrategyAPIExt(newStrategy).insurance(),
want.balanceOf(address(this))
);
}
}