-
Notifications
You must be signed in to change notification settings - Fork 1
/
ArbitrageVault.sol
318 lines (281 loc) · 10.5 KB
/
ArbitrageVault.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
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {GhoToken} from "./GhoToken.sol";
import {BurraNFT} from "./BurraNFT.sol";
import {PriceConsumerV3} from "./PriceConsumerV3.sol";
import "./aaveLibraries/WadRayMath.sol";
import "./aaveLibraries/MathUtils.sol";
/**
@notice Ideally the users who mint here are advantaged because they have a different interest rate.
I want to try to make up an InterestStrategy to incetivize people to mintwhen gho market price is >1$ and burn when gho market price is < 1$
Inspired by the discount on interest rate for stkAAVE holders
*/
contract ArbitrageVault is ERC4626 {
struct InterestStrategy {
uint256 start_block;
uint256 rate;
}
mapping(address => uint256) private usersToDeposit;
mapping(address => uint256) private listedBurra;
mapping(address => uint256) private usersToShares;
mapping(address => InterestStrategy) private userToInterestStrategy;
GhoToken public gho;
PriceConsumerV3 public priceConsumerV3;
event GHOBorrowed(
address indexed borrower,
uint256 amount,
uint256 discountStrategy
);
event GHORepaid(
address indexed borrower,
uint256 amount,
uint256 totalPaidInDollars
);
event BurraListed(
address indexed owner,
uint256 amount
);
constructor(
GhoToken _gho,
IERC20 _underlying,
address aggregator
) ERC4626(_underlying) ERC20("Burra", "bu") {
gho = _gho;
priceConsumerV3 = new PriceConsumerV3(aggregator);
}
/**
@dev should:
1. deposit collateral (underlying asset defined in the vault)
1a. give the borrower vault shares
2. set a discountRate (or interest rate dky) basing on gho market price
2a.check if holder has burraNFT, if so, user has more advantage
3. mint new gho
this should give as much gho as much deposit is put in the vault
this should have a disadvantaging dicount strategy if gho price < 1
this should have a advantaging dicount strategy if gho price > 1
*/
function borrowGho(uint256 depositAmount) public {
//deposit and give shares
uint256 shares = previewDeposit(depositAmount);
_deposit(msg.sender, msg.sender, depositAmount, depositAmount);
usersToShares[msg.sender] = usersToShares[msg.sender] + shares;
// select interest strategy and mint GHO
uint256 ghoMarketPrice = getGHOMarketPrice();
uint256 interestRate = _determineInterestRate(ghoMarketPrice);
userToInterestStrategy[msg.sender] = InterestStrategy({
start_block: block.timestamp,
rate: 1 * interestRate
});
_mintGHO(depositAmount);
emit GHOBorrowed(msg.sender, depositAmount, interestRate);
}
/**
@dev this function should repay a gho debt applying the interestStrategy when user borrowed GHO
revert if user doesn't have a deposit position in this vault
gives back collateral to user
*/
function repayGHO(uint256 nominalAmount) public {
uint256 totalDeposit = usersToDeposit[msg.sender];
require(totalDeposit > 0, "user has not a borrow position");
require(totalDeposit >= nominalAmount, "user want to repay more?");
uint256 debitPlusInterest = getDebtToPay(msg.sender, nominalAmount);
_withdraw(
msg.sender,
msg.sender,
address(this),
nominalAmount,
nominalAmount //should change to shares
);
_burnGHO(debitPlusInterest);
usersToDeposit[msg.sender] = totalDeposit - debitPlusInterest;
emit GHORepaid(msg.sender, nominalAmount, debitPlusInterest);
}
/**
@notice calculate the API using MathUtils.calculateLinearInterest from aave
*/
function calculateAPY(
uint256 amount,
InterestStrategy memory strategy
) public view returns (uint256) {
uint256 rate = WadRayMath.wadToRay(strategy.rate);
uint256 i = WadRayMath.rayToWad(
MathUtils.calculateLinearInterest(
rate,
uint40(strategy.start_block)
)
);
return WadRayMath.wadMul((i - WadRayMath.WAD), amount) + amount;
}
/**
@dev need to override that to move the funds accounting to who's buying the shares of the vault, same for transferfrom
*/
function transfer(
address to,
uint256 value
) public override(IERC20, ERC20) returns (bool) {
address owner = _msgSender();
uint256 shares = convertToShares(value);
_transfer(owner, to, value);
gho.transferFrom(owner, to, value);
usersToDeposit[to] = usersToDeposit[to] + value;
usersToDeposit[_msgSender()] = usersToDeposit[_msgSender()] - value;
usersToShares[to] = usersToShares[to] + shares;
usersToShares[_msgSender()] = usersToShares[_msgSender()] - shares;
return true;
}
/**
@dev need to override that to move the funds accounting to who's buying the shares of the vault, same for transferfrom
*/
function transferFrom(
address from,
address to,
uint256 value
) public override(IERC20, ERC20) returns (bool) {
address spender = _msgSender();
uint256 shares = convertToShares(value);
_spendAllowance(from, spender, value);
_transfer(from, to, value);
gho.transferFrom(from, to, value);
usersToDeposit[to] = usersToDeposit[to] + value;
usersToDeposit[from] = usersToDeposit[from] - value;
usersToShares[to] = usersToShares[to] + shares;
usersToShares[from] = usersToShares[from] - shares;
return true;
}
/**
@dev list burra for sales. part of the deposit is put in sales
*/
function listBurra(uint256 amount) public {
uint256 totalDeposit = usersToDeposit[msg.sender];
require(totalDeposit > 0, "user has not a borrow position");
listedBurra[msg.sender] = listedBurra[msg.sender] + amount;
emit BurraListed(msg.sender,amount);
}
////////////////////////// internal functions //////////////////////////
function _mintGHO(uint256 amount) internal {
gho.mint(msg.sender, amount);
}
/**
@notice interest rate: this is a stable interest rate that get rebalanced depending on gho market price
Stable rate parameters:
my formula is
Interest Rate=Base Interest Rate+(Market Price−Threshold)×Sensitivity
*/
function _determineInterestRate(
uint256 ghoMarketPrice
) internal pure returns (uint256) {
uint256 sensitivity = 17000000000000000;
uint256 rate;
// Interest Rate=Base Interest Rate+(Market Price−Threshold)×Sensitivity
if (ghoMarketPrice >= 1000000000000000000) {
uint256 baseRate = 15000000000000000; //1.5%
uint256 threshold;
unchecked {
threshold = ghoMarketPrice - 1000000000000000000;
}
rate = WadRayMath.wadDiv(
baseRate + WadRayMath.wadMul(threshold, sensitivity),
WadRayMath.WAD
);
} else {
uint256 baseRate = 30000000000000000; //3%
uint256 threshold;
unchecked {
threshold = 1000000000000000000 - ghoMarketPrice;
}
rate = WadRayMath.wadDiv(
baseRate + WadRayMath.wadMul(threshold, sensitivity),
WadRayMath.WAD
);
}
return rate;
}
/**
@dev overridden from ERC4626
*/
function _deposit(
address caller,
address receiver,
uint256 assets,
uint256 shares
) internal override {
IERC20 _asset = IERC20(asset());
SafeERC20.safeTransferFrom(_asset, caller, address(this), assets);
_mint(receiver, shares);
usersToDeposit[msg.sender] = usersToDeposit[msg.sender] + assets;
emit Deposit(caller, receiver, assets, shares);
}
/**
@dev send back the collateral to the borrower before burning the gho tokens
*/
function _withdraw(
address caller,
address receiver,
address owner,
uint256 assets,
uint256 shares
) internal override {
IERC20 _asset = IERC20(asset());
_burn(msg.sender, shares);
SafeERC20.safeTransfer(_asset, receiver, assets);
emit Withdraw(caller, receiver, owner, assets, shares);
}
function _burnGHO(uint256 amount) internal {
gho.transferFrom(msg.sender, address(this), amount);
gho.burn(amount);
}
////////////////////////// getters //////////////////////////
function getGHOMarketPrice() public view returns (uint256) {
return uint256(priceConsumerV3.getLatestPrice() * 10000000000); // chainlink return 8 decimals
}
function getDepositForUser(address user) public view returns (uint256) {
return usersToDeposit[user];
}
function getInterestStrategyForUser(
address user
) public view returns (InterestStrategy memory) {
return userToInterestStrategy[user];
}
function getDebtToPay(
address borrower,
uint256 nominalAmount
)
public
view
returns (
// uint256 timestamp
uint256
)
{
uint256 debitPlusInterest = calculateAPY(
nominalAmount,
userToInterestStrategy[borrower]
);
return debitPlusInterest;
}
function getBorrowRate() public view returns (uint256) {
uint256 ghoMarketPrice = getGHOMarketPrice();
return _determineInterestRate(ghoMarketPrice);
}
function getShareToken() public view returns (string memory) {
return name();
}
function getInterestRate() public view returns (uint256) {
uint256 ghoPrice = getGHOMarketPrice();
return _determineInterestRate(ghoPrice);
}
function getInterestRateAtPrice(
uint256 ghoPrice
) public pure returns (uint256) {
return _determineInterestRate(ghoPrice);
}
function getListedBurra(
address owner
) public view returns (uint256) {
return listedBurra[owner];
}
}