Puzzle #7
CurtaLending
Author
0xb49bf876be26435b6fae1ef42c3c82c5867fa149
chainlight.io
SoliditySolidity's logo.Puzzle
Curtacallsverify()
1
// SPDX-License-Identifier: UNLICENSED
2
pragma solidity ^0.8.20;
3
4
import "@openzeppelin/contracts/proxy/Clones.sol";
5
import "@openzeppelin/contracts/access/Ownable.sol";
6
import "@openzeppelin-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol";
7
import "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol";
8
9
import "../lib/curta/src/interfaces/IPuzzle.sol";
10
11
contract FailedLendingMarket is IPuzzle {
12
ChallFactory public challFactory;
13
14
mapping(uint256 => Challenge) public instances;
15
address public owner;
16
17
constructor() {
18
owner = msg.sender;
19
challFactory = new ChallFactory(address(this));
20
}
21
22
function name() external pure returns (string memory) {
23
return "CurtaLending";
24
}
25
26
function generate(address solver) public pure returns (uint256) {
27
return uint256(keccak256(abi.encode(solver)));
28
}
29
30
function verify(uint256 seed, uint256) external view returns (bool) {
31
return instances[seed].isSolved();
32
}
33
34
function deploy() external returns (address) {
35
uint256 seed = generate(msg.sender);
36
instances[seed] = Challenge(challFactory.createChallenge(seed, msg.sender));
37
38
return address(instances[seed]);
39
}
40
}
41
42
contract ChallFactory is Ownable {
43
address immutable tokenImplementation;
44
address immutable rebasingTokenImplementation;
45
address immutable oracleImplementation;
46
address immutable curtaLendingImplementation;
47
address immutable challengeImplementation;
48
49
constructor(address _curta) Ownable(_curta) {
50
tokenImplementation = address(new CurtaToken());
51
rebasingTokenImplementation = address(new CurtaRebasingToken());
52
oracleImplementation = address(new Oracle());
53
curtaLendingImplementation = address(new CurtaLending());
54
challengeImplementation = address(new Challenge());
55
}
56
57
function createChallenge(uint256 seed, address player) external onlyOwner returns (address) {
58
address usdClone = Clones.clone(tokenImplementation);
59
address wethClone = Clones.clone(tokenImplementation);
60
address rebasingWETHClone = Clones.clone(rebasingTokenImplementation);
61
address oracleClone = Clones.clone(oracleImplementation);
62
address curtaLendingClone = Clones.clone(curtaLendingImplementation);
63
address challClone = Clones.clone(challengeImplementation);
64
65
CurtaToken(usdClone).initialize("CurtaUSD", "USD", address(this));
66
CurtaToken(wethClone).initialize("CurtaWETH", "WETH", address(this));
67
CurtaRebasingToken(rebasingWETHClone).initialize("CurtaRebasingWETH", "RebasingWETH", wethClone, address(this));
68
Oracle(oracleClone).initialize(address(this));
69
CurtaLending(curtaLendingClone).initialize(address(this), oracleClone);
70
Challenge(challClone).initialize(address(this), usdClone, wethClone, rebasingWETHClone, curtaLendingClone, seed);
71
72
Oracle(oracleClone).setPrice(usdClone, 1e18);
73
Oracle(oracleClone).setPrice(wethClone, 3000e18);
74
Oracle(oracleClone).setPrice(rebasingWETHClone, 3100e18);
75
76
CurtaLending(curtaLendingClone).setAsset(usdClone, true, 500, 0.8 ether, 0.9 ether, 0.05 ether);
77
CurtaLending(curtaLendingClone).setAsset(wethClone, true, 300, 0.7 ether, 0.8 ether, 0.05 ether);
78
CurtaLending(curtaLendingClone).setAsset(rebasingWETHClone, true, 300, 0.7 ether, 0.8 ether, 0.05 ether);
79
80
CurtaToken(usdClone).mint(address(this), 10000 ether);
81
CurtaToken(wethClone).mint(address(this), 20000 ether);
82
CurtaToken(wethClone).approve(rebasingWETHClone, 10000 ether);
83
CurtaRebasingToken(rebasingWETHClone).deposit(10000 ether);
84
85
CurtaToken(usdClone).mint(player, 10000 ether);
86
CurtaToken(wethClone).mint(player, 10000 ether);
87
88
CurtaToken(usdClone).approve(curtaLendingClone, 10000 ether);
89
CurtaLending(curtaLendingClone).depositLiquidity(usdClone, 10000 ether);
90
CurtaToken(wethClone).approve(curtaLendingClone, 10000 ether);
91
CurtaLending(curtaLendingClone).depositLiquidity(wethClone, 10000 ether);
92
CurtaRebasingToken(rebasingWETHClone).approve(curtaLendingClone, 10000 ether);
93
CurtaLending(curtaLendingClone).depositLiquidity(rebasingWETHClone, 10000 ether);
94
95
return challClone;
96
}
97
}
98
99
contract CurtaToken is ERC20Upgradeable, OwnableUpgradeable {
100
constructor() {
101
_disableInitializers();
102
}
103
104
function initialize(string memory _name, string memory _symbol, address _initialOwner) external initializer {
105
__ERC20_init(_name, _symbol);
106
__Ownable_init(_initialOwner);
107
}
108
109
function mint(address to, uint256 amount) external onlyOwner {
110
_mint(to, amount);
111
}
112
}
113
114
contract CurtaRebasingToken is ERC20Upgradeable, OwnableUpgradeable {
115
IERC20 public underlyingToken;
116
uint256 public unavaliableLiquidity;
117
118
constructor() {
119
_disableInitializers();
120
}
121
122
function initialize(string memory _name, string memory _symbol, address _underlyingToken, address _initialOwner)
123
external
124
initializer
125
{
126
__ERC20_init(_name, _symbol);
127
__Ownable_init(_initialOwner);
128
129
underlyingToken = IERC20(_underlyingToken);
130
}
131
132
function deposit(uint256 amount) external {
133
_mint(msg.sender, amount * 1e18 / getExchangeRate());
134
require(IERC20(underlyingToken).transferFrom(msg.sender, address(this), amount));
135
}
136
137
function withdraw(uint256 amount) external {
138
require(super.balanceOf(msg.sender) >= amount);
139
require(IERC20(underlyingToken).transfer(msg.sender, amount * getExchangeRate() / 1e18));
140
_burn(msg.sender, amount);
141
}
142
143
function addYield(uint256 amount) external onlyOwner {
144
require(IERC20(underlyingToken).transferFrom(msg.sender, address(this), amount));
145
}
146
147
function invest(uint256 amount) external onlyOwner {
148
require(IERC20(underlyingToken).transfer(msg.sender, amount));
149
unavaliableLiquidity += amount;
150
}
151
152
function payback(uint256 amount) external onlyOwner {
153
require(IERC20(underlyingToken).transferFrom(msg.sender, address(this), amount));
154
unavaliableLiquidity -= amount;
155
}
156
157
function balanceOf(address account) public view override returns (uint256) {
158
return super.balanceOf(account) * getExchangeRate() / 1e18;
159
}
160
161
function shareBalanceOf(address account) public view returns (uint256) {
162
return super.balanceOf(account);
163
}
164
165
function getExchangeRate() public view returns (uint256) {
166
if (super.totalSupply() == 0) {
167
return 1e18;
168
}
169
return (underlyingToken.balanceOf(address(this)) + unavaliableLiquidity) * 1e18 / super.totalSupply();
170
}
171
172
function totalSupply() public view override returns (uint256) {
173
return super.totalSupply() * getExchangeRate() / 1e18;
174
}
175
}
176
177
struct UserInfo {
178
address borrowAsset;
179
uint256 liquidityAmount;
180
uint256 collateralAmount;
181
uint256 liquidityIndex;
182
uint256 borrowIndex;
183
uint256 claimableReward;
184
uint256 totalDebt;
185
uint256 principal;
186
}
187
188
struct AssetInfo {
189
bool isAsset;
190
uint256 totalLiquidity;
191
uint256 avaliableLiquidity;
192
uint256 totalDebt;
193
uint256 totalPrincipal;
194
uint256 interestRate;
195
uint256 avaliableClaimableReward;
196
uint256 borrowLTV;
197
uint256 liquidationLTV;
198
uint256 liquidationBonus;
199
uint256 globalIndex;
200
uint256 lastUpdateBlock;
201
}
202
203
contract CurtaLending is OwnableUpgradeable {
204
mapping(address => mapping(address => UserInfo)) public userInfo;
205
mapping(address => AssetInfo) public assetInfo;
206
207
Oracle public oracle;
208
209
constructor() {
210
_disableInitializers();
211
}
212
213
function initialize(address _initialOwner, address _oracle) external initializer {
214
oracle = Oracle(_oracle);
215
__Ownable_init(_initialOwner);
216
}
217
218
function setAsset(
219
address _asset,
220
bool _isAsset,
221
uint256 _interestRate,
222
uint256 _borrowLTV,
223
uint256 _liquidationLTV,
224
uint256 _liquidationBonus
225
) external onlyOwner {
226
assetInfo[_asset].isAsset = _isAsset;
227
assetInfo[_asset].interestRate = _interestRate;
228
assetInfo[_asset].borrowLTV = _borrowLTV;
229
assetInfo[_asset].liquidationLTV = _liquidationLTV;
230
assetInfo[_asset].liquidationBonus = _liquidationBonus;
231
assetInfo[_asset].lastUpdateBlock = block.number;
232
}
233
234
function depositCollateral(address asset, uint256 amount) external {
235
require(assetInfo[asset].isAsset);
236
accrueInterest(msg.sender, asset);
237
238
UserInfo storage _userInfo = userInfo[msg.sender][asset];
239
AssetInfo storage _assetInfo = assetInfo[asset];
240
241
uint256 liquidityAmount = _userInfo.liquidityAmount;
242
243
if (_userInfo.liquidityAmount < amount) {
244
_userInfo.collateralAmount += amount;
245
_userInfo.liquidityAmount = 0;
246
_assetInfo.totalLiquidity -= liquidityAmount;
247
_assetInfo.avaliableLiquidity -= liquidityAmount;
248
require(IERC20(asset).transferFrom(msg.sender, address(this), amount - liquidityAmount));
249
} else {
250
_userInfo.collateralAmount += amount;
251
_userInfo.liquidityAmount -= amount;
252
_assetInfo.totalLiquidity -= amount;
253
_assetInfo.avaliableLiquidity -= amount;
254
}
255
}
256
257
function withdrawCollateral(address asset, uint256 amount) external {
258
accrueInterest(msg.sender, asset);
259
260
UserInfo storage _userInfo = userInfo[msg.sender][asset];
261
AssetInfo storage _assetInfo = assetInfo[asset];
262
263
uint256 collateralValue = (_userInfo.collateralAmount - amount) * oracle.getPrice(asset);
264
uint256 borrowValue = _userInfo.totalDebt * oracle.getPrice(_userInfo.borrowAsset);
265
require(collateralValue * _assetInfo.borrowLTV >= borrowValue * 1e18);
266
267
if (amount == 0) {
268
_userInfo.liquidityAmount += _userInfo.collateralAmount;
269
_assetInfo.totalLiquidity += _userInfo.collateralAmount;
270
_assetInfo.avaliableLiquidity += _userInfo.collateralAmount;
271
_userInfo.collateralAmount = 0;
272
} else {
273
require(_userInfo.collateralAmount >= amount);
274
_userInfo.liquidityAmount += amount;
275
_userInfo.collateralAmount -= amount;
276
_assetInfo.totalLiquidity += amount;
277
_assetInfo.avaliableLiquidity += amount;
278
}
279
}
280
281
function depositLiquidity(address asset, uint256 amount) external {
282
require(assetInfo[asset].isAsset);
283
accrueInterest(msg.sender, asset);
284
285
UserInfo storage _userInfo = userInfo[msg.sender][asset];
286
AssetInfo storage _assetInfo = assetInfo[asset];
287
288
if (_userInfo.liquidityIndex == 0) {
289
_userInfo.liquidityIndex = _assetInfo.globalIndex;
290
}
291
292
uint256 beforeBalance = IERC20(asset).balanceOf(address(this));
293
require(IERC20(asset).transferFrom(msg.sender, address(this), amount));
294
uint256 afterBalance = IERC20(asset).balanceOf(address(this)) - beforeBalance;
295
296
_userInfo.liquidityAmount += afterBalance;
297
_assetInfo.totalLiquidity += afterBalance;
298
_assetInfo.avaliableLiquidity += afterBalance;
299
}
300
301
function withdrawLiquidity(address asset, uint256 amount) external {
302
accrueInterest(msg.sender, asset);
303
304
UserInfo storage _userInfo = userInfo[msg.sender][asset];
305
AssetInfo storage _assetInfo = assetInfo[asset];
306
307
require(_assetInfo.avaliableLiquidity >= amount);
308
309
_userInfo.liquidityAmount -= amount;
310
_assetInfo.totalLiquidity -= amount;
311
_assetInfo.avaliableLiquidity -= amount;
312
313
require(IERC20(asset).transfer(msg.sender, amount));
314
}
315
316
function borrow(address collateral, address borrowAsset, uint256 amount) external {
317
require(assetInfo[borrowAsset].isAsset);
318
UserInfo storage _userInfo = userInfo[msg.sender][collateral];
319
require(_userInfo.borrowAsset == address(0) || _userInfo.borrowAsset == borrowAsset);
320
321
if (_userInfo.borrowAsset == address(0)) {
322
_userInfo.borrowAsset = borrowAsset;
323
}
324
325
accrueInterest(msg.sender, collateral);
326
327
AssetInfo storage _assetInfo = assetInfo[borrowAsset];
328
require(_assetInfo.avaliableLiquidity >= amount);
329
330
if (_userInfo.borrowIndex == 0) {
331
_userInfo.borrowIndex = _assetInfo.globalIndex;
332
}
333
334
uint256 collateralValue = _userInfo.collateralAmount * oracle.getPrice(collateral);
335
uint256 borrowValue = amount * oracle.getPrice(borrowAsset);
336
require(collateralValue * assetInfo[collateral].borrowLTV >= borrowValue * 1e18);
337
338
_userInfo.totalDebt += amount;
339
_userInfo.principal += amount;
340
_assetInfo.totalDebt += amount;
341
_assetInfo.totalPrincipal += amount;
342
_assetInfo.avaliableLiquidity -= amount;
343
344
require(IERC20(borrowAsset).transfer(msg.sender, amount));
345
}
346
347
function repay(address collateral, uint256 amount) external {
348
accrueInterest(msg.sender, collateral);
349
UserInfo storage _userInfo = userInfo[msg.sender][collateral];
350
AssetInfo storage _assetInfo = assetInfo[_userInfo.borrowAsset];
351
352
require(_userInfo.borrowAsset != address(0));
353
354
uint256 borrowInterest = _userInfo.totalDebt - _userInfo.principal;
355
356
if (_userInfo.totalDebt < amount) {
357
amount = _userInfo.totalDebt;
358
}
359
360
_userInfo.totalDebt -= amount;
361
_assetInfo.totalDebt -= amount;
362
363
if (borrowInterest < amount) {
364
_userInfo.principal -= amount - borrowInterest;
365
_assetInfo.totalPrincipal -= amount - borrowInterest;
366
_assetInfo.avaliableClaimableReward += borrowInterest;
367
_assetInfo.avaliableLiquidity += amount - borrowInterest;
368
} else {
369
_assetInfo.avaliableClaimableReward += amount;
370
}
371
372
require(IERC20(_userInfo.borrowAsset).transferFrom(msg.sender, address(this), amount));
373
}
374
375
function liquidate(address user, address collateral, uint256 amount) external {
376
accrueInterest(user, collateral);
377
378
UserInfo storage _userInfo = userInfo[msg.sender][collateral];
379
380
address asset = _userInfo.borrowAsset;
381
382
if (_userInfo.totalDebt * 5 < amount * 10) {
383
amount = _userInfo.totalDebt / 2;
384
}
385
386
uint256 collateralValue = _userInfo.collateralAmount * oracle.getPrice(collateral);
387
uint256 borrowValue = _userInfo.totalDebt * oracle.getPrice(asset);
388
require(collateralValue * assetInfo[collateral].liquidationLTV <= borrowValue * 1e18);
389
390
AssetInfo storage _assetInfo = assetInfo[_userInfo.borrowAsset];
391
392
uint256 refundCollateral = amount * oracle.getPrice(asset) / oracle.getPrice(collateral)
393
+ amount * oracle.getPrice(asset) / oracle.getPrice(collateral) * _assetInfo.liquidationBonus / 1e18;
394
395
if (refundCollateral > _userInfo.collateralAmount) {
396
refundCollateral = _userInfo.collateralAmount;
397
}
398
399
_userInfo.collateralAmount -= refundCollateral;
400
401
uint256 borrowInterest = _userInfo.totalDebt - _userInfo.principal;
402
403
_userInfo.totalDebt -= amount;
404
_assetInfo.totalDebt -= amount;
405
406
if (borrowInterest < amount) {
407
_userInfo.principal -= amount - borrowInterest;
408
_assetInfo.totalPrincipal -= amount - borrowInterest;
409
_assetInfo.avaliableClaimableReward += borrowInterest;
410
_assetInfo.avaliableLiquidity += amount - borrowInterest;
411
} else {
412
_assetInfo.avaliableClaimableReward += amount;
413
}
414
415
require(IERC20(asset).transferFrom(msg.sender, address(this), amount));
416
require(IERC20(collateral).transfer(msg.sender, refundCollateral));
417
}
418
419
function resetBorrowAsset(address collateral) external {
420
accrueInterest(msg.sender, collateral);
421
422
UserInfo storage _userInfo = userInfo[msg.sender][collateral];
423
require(_userInfo.borrowAsset != address(0));
424
require(_userInfo.principal == 0 && _userInfo.totalDebt == 0);
425
426
_userInfo.borrowAsset = address(0);
427
_userInfo.borrowIndex = 0;
428
}
429
430
function burnBadDebt(address user, address collateral) external {
431
accrueInterest(user, collateral);
432
433
UserInfo storage _userInfo = userInfo[user][collateral];
434
require(_userInfo.collateralAmount == 0 && _userInfo.totalDebt != 0);
435
436
AssetInfo storage _assetInfo = assetInfo[_userInfo.borrowAsset];
437
438
require(IERC20(_userInfo.borrowAsset).transferFrom(msg.sender, address(this), _userInfo.totalDebt));
439
440
_assetInfo.totalDebt -= _userInfo.totalDebt;
441
_assetInfo.totalPrincipal -= _userInfo.principal;
442
_assetInfo.avaliableClaimableReward += _userInfo.totalDebt - _userInfo.principal;
443
_assetInfo.avaliableLiquidity += _userInfo.principal;
444
_userInfo.totalDebt = 0;
445
_userInfo.principal = 0;
446
}
447
448
function claimReward(address asset, uint256 amount) external {
449
require(assetInfo[asset].avaliableClaimableReward >= amount);
450
accrueInterest(msg.sender, asset);
451
452
UserInfo storage _userInfo = userInfo[msg.sender][asset];
453
require(_userInfo.claimableReward >= amount * 1e18);
454
455
_userInfo.claimableReward -= amount * 1e18;
456
assetInfo[asset].avaliableClaimableReward -= amount;
457
458
require(IERC20(asset).transfer(msg.sender, amount));
459
}
460
461
function accrueInterest(address user, address asset) public {
462
UserInfo storage _userInfo = userInfo[user][asset];
463
464
if (_userInfo.liquidityIndex == 0 && _userInfo.borrowIndex == 0) {
465
return;
466
}
467
468
address borrowAsset = _userInfo.borrowAsset;
469
470
updateAsset(asset);
471
updateAsset(borrowAsset);
472
473
AssetInfo memory _assetInfo = assetInfo[asset];
474
AssetInfo memory _borrowAssetInfo = assetInfo[borrowAsset];
475
476
if (_userInfo.liquidityIndex != 0) {
477
uint256 pending = _assetInfo.globalIndex - _userInfo.liquidityIndex;
478
_userInfo.claimableReward += pending * 1e18 * _userInfo.liquidityAmount / _assetInfo.totalLiquidity;
479
_userInfo.liquidityIndex = _assetInfo.globalIndex;
480
}
481
482
if (_userInfo.borrowIndex != 0) {
483
uint256 pending = _borrowAssetInfo.globalIndex - _userInfo.borrowIndex;
484
_userInfo.totalDebt += pending * _userInfo.principal / _borrowAssetInfo.totalPrincipal;
485
_userInfo.borrowIndex = _borrowAssetInfo.globalIndex;
486
487
if ((pending * _userInfo.principal) % _borrowAssetInfo.totalPrincipal != 0) {
488
_userInfo.totalDebt += 1;
489
_borrowAssetInfo.totalDebt += 1;
490
}
491
}
492
}
493
494
function updateAsset(address asset) public {
495
AssetInfo storage _assetInfo = assetInfo[asset];
496
497
if (block.number == _assetInfo.lastUpdateBlock) {
498
return;
499
}
500
501
_assetInfo.globalIndex +=
502
(block.number - _assetInfo.lastUpdateBlock) * _assetInfo.totalPrincipal * _assetInfo.interestRate / 10000;
503
_assetInfo.totalDebt +=
504
(block.number - _assetInfo.lastUpdateBlock) * _assetInfo.totalPrincipal * _assetInfo.interestRate / 10000;
505
_assetInfo.lastUpdateBlock = block.number;
506
}
507
}
508
509
contract Oracle is OwnableUpgradeable {
510
mapping(address => uint256) private prices;
511
512
constructor() {
513
_disableInitializers();
514
}
515
516
function initialize(address _initialOwner) external initializer {
517
__Ownable_init(_initialOwner);
518
}
519
520
function setPrice(address asset, uint256 price) external onlyOwner {
521
prices[asset] = price;
522
}
523
524
function getPrice(address asset) external view returns (uint256) {
525
return prices[asset];
526
}
527
}
528
529
// SPDX-License-Identifier: UNLICENSED
530
pragma solidity ^0.8.13;
531
532
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
533
import "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol";
534
535
import "./Oracle.sol";
536
537
struct UserInfo {
538
address borrowAsset;
539
uint256 liquidityAmount;
540
uint256 collateralAmount;
541
uint256 liquidityIndex;
542
uint256 borrowIndex;
543
uint256 claimableReward;
544
uint256 totalDebt;
545
uint256 principal;
546
}
547
548
struct AssetInfo {
549
bool isAsset;
550
uint256 totalLiquidity;
551
uint256 avaliableLiquidity;
552
uint256 totalDebt;
553
uint256 totalPrincipal;
554
uint256 interestRate;
555
uint256 avaliableClaimableReward;
556
uint256 borrowLTV;
557
uint256 liquidationLTV;
558
uint256 liquidationBonus;
559
uint256 globalIndex;
560
uint256 lastUpdateBlock;
561
}
562
563
contract CurtaLending is OwnableUpgradeable {
564
mapping(address => mapping(address => UserInfo)) public userInfo;
565
mapping(address => AssetInfo) public assetInfo;
566
567
Oracle public oracle;
568
569
constructor() {
570
_disableInitializers();
571
}
572
573
function initialize(address _initialOwner, address _oracle) external initializer {
574
oracle = Oracle(_oracle);
575
__Ownable_init(_initialOwner);
576
}
577
578
function setAsset(
579
address _asset,
580
bool _isAsset,
581
uint256 _interestRate,
582
uint256 _borrowLTV,
583
uint256 _liquidationLTV,
584
uint256 _liquidationBonus
585
) external onlyOwner {
586
assetInfo[_asset].isAsset = _isAsset;
587
assetInfo[_asset].interestRate = _interestRate;
588
assetInfo[_asset].borrowLTV = _borrowLTV;
589
assetInfo[_asset].liquidationLTV = _liquidationLTV;
590
assetInfo[_asset].liquidationBonus = _liquidationBonus;
591
assetInfo[_asset].lastUpdateBlock = block.number;
592
}
593
594
function depositCollateral(address asset, uint256 amount) external {
595
require(assetInfo[asset].isAsset);
596
accrueInterest(msg.sender, asset);
597
598
UserInfo storage _userInfo = userInfo[msg.sender][asset];
599
AssetInfo storage _assetInfo = assetInfo[asset];
600
601
uint256 liquidityAmount = _userInfo.liquidityAmount;
602
603
if (_userInfo.liquidityAmount < amount) {
604
_userInfo.collateralAmount += amount;
605
_userInfo.liquidityAmount = 0;
606
_assetInfo.totalLiquidity -= liquidityAmount;
607
_assetInfo.avaliableLiquidity -= liquidityAmount;
608
require(IERC20(asset).transferFrom(msg.sender, address(this), amount - liquidityAmount));
609
} else {
610
_userInfo.collateralAmount += amount;
611
_userInfo.liquidityAmount -= amount;
612
_assetInfo.totalLiquidity -= amount;
613
_assetInfo.avaliableLiquidity -= amount;
614
}
615
}
616
617
function withdrawCollateral(address asset, uint256 amount) external {
618
accrueInterest(msg.sender, asset);
619
620
UserInfo storage _userInfo = userInfo[msg.sender][asset];
621
AssetInfo storage _assetInfo = assetInfo[asset];
622
623
uint256 collateralValue = (_userInfo.collateralAmount - amount) * oracle.getPrice(asset);
624
uint256 borrowValue = _userInfo.totalDebt * oracle.getPrice(_userInfo.borrowAsset);
625
require(collateralValue * _assetInfo.borrowLTV >= borrowValue * 1e18);
626
627
if (amount == 0) {
628
_userInfo.liquidityAmount += _userInfo.collateralAmount;
629
_assetInfo.totalLiquidity += _userInfo.collateralAmount;
630
_assetInfo.avaliableLiquidity += _userInfo.collateralAmount;
631
_userInfo.collateralAmount = 0;
632
} else {
633
require(_userInfo.collateralAmount >= amount);
634
_userInfo.liquidityAmount += amount;
635
_userInfo.collateralAmount -= amount;
636
_assetInfo.totalLiquidity += amount;
637
_assetInfo.avaliableLiquidity += amount;
638
}
639
}
640
641
function depositLiquidity(address asset, uint256 amount) external {
642
require(assetInfo[asset].isAsset);
643
accrueInterest(msg.sender, asset);
644
645
UserInfo storage _userInfo = userInfo[msg.sender][asset];
646
AssetInfo storage _assetInfo = assetInfo[asset];
647
648
if (_userInfo.liquidityIndex == 0) {
649
_userInfo.liquidityIndex = _assetInfo.globalIndex;
650
}
651
652
uint256 beforeBalance = IERC20(asset).balanceOf(address(this));
653
require(IERC20(asset).transferFrom(msg.sender, address(this), amount));
654
uint256 afterBalance = IERC20(asset).balanceOf(address(this)) - beforeBalance;
655
656
_userInfo.liquidityAmount += afterBalance;
657
_assetInfo.totalLiquidity += afterBalance;
658
_assetInfo.avaliableLiquidity += afterBalance;
659
}
660
661
function withdrawLiquidity(address asset, uint256 amount) external {
662
accrueInterest(msg.sender, asset);
663
664
UserInfo storage _userInfo = userInfo[msg.sender][asset];
665
AssetInfo storage _assetInfo = assetInfo[asset];
666
667
require(_assetInfo.avaliableLiquidity >= amount);
668
669
_userInfo.liquidityAmount -= amount;
670
_assetInfo.totalLiquidity -= amount;
671
_assetInfo.avaliableLiquidity -= amount;
672
673
require(IERC20(asset).transfer(msg.sender, amount));
674
}
675
676
function borrow(address collateral, address borrowAsset, uint256 amount) external {
677
require(assetInfo[borrowAsset].isAsset);
678
UserInfo storage _userInfo = userInfo[msg.sender][collateral];
679
require(_userInfo.borrowAsset == address(0) || _userInfo.borrowAsset == borrowAsset);
680
681
if (_userInfo.borrowAsset == address(0)) {
682
_userInfo.borrowAsset = borrowAsset;
683
}
684
685
accrueInterest(msg.sender, collateral);
686
687
AssetInfo storage _assetInfo = assetInfo[borrowAsset];
688
require(_assetInfo.avaliableLiquidity >= amount);
689
690
if (_userInfo.borrowIndex == 0) {
691
_userInfo.borrowIndex = _assetInfo.globalIndex;
692
}
693
694
uint256 collateralValue = _userInfo.collateralAmount * oracle.getPrice(collateral);
695
uint256 borrowValue = amount * oracle.getPrice(borrowAsset);
696
require(collateralValue * assetInfo[collateral].borrowLTV >= borrowValue * 1e18);
697
698
_userInfo.totalDebt += amount;
699
_userInfo.principal += amount;
700
_assetInfo.totalDebt += amount;
701
_assetInfo.totalPrincipal += amount;
702
_assetInfo.avaliableLiquidity -= amount;
703
704
require(IERC20(borrowAsset).transfer(msg.sender, amount));
705
}
706
707
function repay(address collateral, uint256 amount) external {
708
accrueInterest(msg.sender, collateral);
709
UserInfo storage _userInfo = userInfo[msg.sender][collateral];
710
AssetInfo storage _assetInfo = assetInfo[_userInfo.borrowAsset];
711
712
require(_userInfo.borrowAsset != address(0));
713
714
uint256 borrowInterest = _userInfo.totalDebt - _userInfo.principal;
715
716
if (_userInfo.totalDebt < amount) {
717
amount = _userInfo.totalDebt;
718
}
719
720
_userInfo.totalDebt -= amount;
721
_assetInfo.totalDebt -= amount;
722
723
if (borrowInterest < amount) {
724
_userInfo.principal -= amount - borrowInterest;
725
_assetInfo.totalPrincipal -= amount - borrowInterest;
726
_assetInfo.avaliableClaimableReward += borrowInterest;
727
_assetInfo.avaliableLiquidity += amount - borrowInterest;
728
} else {
729
_assetInfo.avaliableClaimableReward += amount;
730
}
731
732
require(IERC20(_userInfo.borrowAsset).transferFrom(msg.sender, address(this), amount));
733
}
734
735
function liquidate(address user, address collateral, uint256 amount) external {
736
accrueInterest(user, collateral);
737
738
UserInfo storage _userInfo = userInfo[msg.sender][collateral];
739
740
address asset = _userInfo.borrowAsset;
741
742
if (_userInfo.totalDebt * 5 < amount * 10) {
743
amount = _userInfo.totalDebt / 2;
744
}
745
746
uint256 collateralValue = _userInfo.collateralAmount * oracle.getPrice(collateral);
747
uint256 borrowValue = _userInfo.totalDebt * oracle.getPrice(asset);
748
require(collateralValue * assetInfo[collateral].liquidationLTV <= borrowValue * 1e18);
749
750
AssetInfo storage _assetInfo = assetInfo[_userInfo.borrowAsset];
751
752
uint256 refundCollateral = amount * oracle.getPrice(asset) / oracle.getPrice(collateral)
753
+ amount * oracle.getPrice(asset) / oracle.getPrice(collateral) * _assetInfo.liquidationBonus / 1e18;
754
755
if (refundCollateral > _userInfo.collateralAmount) {
756
refundCollateral = _userInfo.collateralAmount;
757
}
758
759
_userInfo.collateralAmount -= refundCollateral;
760
761
uint256 borrowInterest = _userInfo.totalDebt - _userInfo.principal;
762
763
_userInfo.totalDebt -= amount;
764
_assetInfo.totalDebt -= amount;
765
766
if (borrowInterest < amount) {
767
_userInfo.principal -= amount - borrowInterest;
768
_assetInfo.totalPrincipal -= amount - borrowInterest;
769
_assetInfo.avaliableClaimableReward += borrowInterest;
770
_assetInfo.avaliableLiquidity += amount - borrowInterest;
771
} else {
772
_assetInfo.avaliableClaimableReward += amount;
773
}
774
775
require(IERC20(asset).transferFrom(msg.sender, address(this), amount));
776
require(IERC20(collateral).transfer(msg.sender, refundCollateral));
777
}
778
779
function resetBorrowAsset(address collateral) external {
780
accrueInterest(msg.sender, collateral);
781
782
UserInfo storage _userInfo = userInfo[msg.sender][collateral];
783
require(_userInfo.borrowAsset != address(0));
784
require(_userInfo.principal == 0 && _userInfo.totalDebt == 0);
785
786
_userInfo.borrowAsset = address(0);
787
_userInfo.borrowIndex = 0;
788
}
789
790
function burnBadDebt(address user, address collateral) external {
791
accrueInterest(user, collateral);
792
793
UserInfo storage _userInfo = userInfo[user][collateral];
794
require(_userInfo.collateralAmount == 0 && _userInfo.totalDebt != 0);
795
796
AssetInfo storage _assetInfo = assetInfo[_userInfo.borrowAsset];
797
798
require(IERC20(_userInfo.borrowAsset).transferFrom(msg.sender, address(this), _userInfo.totalDebt));
799
800
_assetInfo.totalDebt -= _userInfo.totalDebt;
801
_assetInfo.totalPrincipal -= _userInfo.principal;
802
_assetInfo.avaliableClaimableReward += _userInfo.totalDebt - _userInfo.principal;
803
_assetInfo.avaliableLiquidity += _userInfo.principal;
804
_userInfo.totalDebt = 0;
805
_userInfo.principal = 0;
806
}
807
808
function claimReward(address asset, uint256 amount) external {
809
require(assetInfo[asset].avaliableClaimableReward >= amount);
810
accrueInterest(msg.sender, asset);
811
812
UserInfo storage _userInfo = userInfo[msg.sender][asset];
813
require(_userInfo.claimableReward >= amount * 1e18);
814
815
_userInfo.claimableReward -= amount * 1e18;
816
assetInfo[asset].avaliableClaimableReward -= amount;
817
818
require(IERC20(asset).transfer(msg.sender, amount));
819
}
820
821
function accrueInterest(address user, address asset) public {
822
UserInfo storage _userInfo = userInfo[user][asset];
823
824
if (_userInfo.liquidityIndex == 0 && _userInfo.borrowIndex == 0) {
825
return;
826
}
827
828
address borrowAsset = _userInfo.borrowAsset;
829
830
updateAsset(asset);
831
updateAsset(borrowAsset);
832
833
AssetInfo memory _assetInfo = assetInfo[asset];
834
AssetInfo memory _borrowAssetInfo = assetInfo[borrowAsset];
835
836
if (_userInfo.liquidityIndex != 0) {
837
uint256 pending = _assetInfo.globalIndex - _userInfo.liquidityIndex;
838
_userInfo.claimableReward += pending * 1e18 * _userInfo.liquidityAmount / _assetInfo.totalLiquidity;
839
_userInfo.liquidityIndex = _assetInfo.globalIndex;
840
}
841
842
if (_userInfo.borrowIndex != 0) {
843
uint256 pending = _borrowAssetInfo.globalIndex - _userInfo.borrowIndex;
844
_userInfo.totalDebt += pending * _userInfo.principal / _borrowAssetInfo.totalPrincipal;
845
_userInfo.borrowIndex = _borrowAssetInfo.globalIndex;
846
847
if ((pending * _userInfo.principal) % _borrowAssetInfo.totalPrincipal != 0) {
848
_userInfo.totalDebt += 1;
849
_borrowAssetInfo.totalDebt += 1;
850
}
851
}
852
}
853
854
function updateAsset(address asset) public {
855
AssetInfo storage _assetInfo = assetInfo[asset];
856
857
if (block.number == _assetInfo.lastUpdateBlock) {
858
return;
859
}
860
861
_assetInfo.globalIndex +=
862
(block.number - _assetInfo.lastUpdateBlock) * _assetInfo.totalPrincipal * _assetInfo.interestRate / 10000;
863
_assetInfo.totalDebt +=
864
(block.number - _assetInfo.lastUpdateBlock) * _assetInfo.totalPrincipal * _assetInfo.interestRate / 10000;
865
_assetInfo.lastUpdateBlock = block.number;
866
}
867
}
868
869
contract Challenge is OwnableUpgradeable {
870
CurtaToken public curtaUSD;
871
CurtaToken public curtaWETH;
872
CurtaRebasingToken public curtaRebasingETH;
873
CurtaLending public curtaLending;
874
875
uint256 public seed;
876
877
constructor() {
878
_disableInitializers();
879
}
880
881
function initialize(
882
address _initialOwner,
883
address _curtaUSD,
884
address _curtaWETH,
885
address _curtaRebasingETH,
886
address _curtaLending,
887
uint256 _seed
888
) external initializer {
889
__Ownable_init(_initialOwner);
890
891
curtaUSD = CurtaToken(_curtaUSD);
892
curtaWETH = CurtaToken(_curtaWETH);
893
curtaRebasingETH = CurtaRebasingToken(_curtaRebasingETH);
894
curtaLending = CurtaLending(_curtaLending);
895
896
seed = _seed;
897
}
898
899
function isSolved() external view returns (bool) {
900
require(
901
curtaUSD.balanceOf(address(uint160(seed))) == 20000 ether
902
&& curtaWETH.balanceOf(address(uint160(seed))) == 30000 ether
903
);
904
return true;
905
}
906
}
First Blood
windowhan.eth
03:23:58
10
Time Left

Solve locally (WIP)

  1. Clone GitHub repo + install deps
git clone https://github.com/waterfall-mkt/curta-puzzles.git && cd curta-puzzles && forge install
  1. Set RPC_URL_MAINNET in .env
.env
RPC_URL_MAINNET=""
  1. Write solution + run script
forge script <PATH_TO_PUZZLE> -f mainnet -vvv
This is still WIP.
Waterfall