1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.20;3
4import "@openzeppelin/contracts/proxy/Clones.sol";5import "@openzeppelin/contracts/access/Ownable.sol";6import "@openzeppelin-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol";7import "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol";8
9import "../lib/curta/src/interfaces/IPuzzle.sol";10
11contract 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
42contract 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
99contract 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
114contract 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 external124 initializer125 {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
177struct 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
188struct 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
203contract 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 _liquidationBonus225 ) 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
509contract 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: UNLICENSED530pragma solidity ^0.8.13;531
532import "@openzeppelin/contracts/token/ERC20/IERC20.sol";533import "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol";534
535import "./Oracle.sol";536
537struct 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
548struct 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
563contract 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 _liquidationBonus585 ) 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
869contract 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 _seed888 ) 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 ether902 && curtaWETH.balanceOf(address(uint160(seed))) == 30000 ether903 );904 return true;905 }906}
Time Left
Solve locally (WIP)
- Clone GitHub repo + install deps
git clone https://github.com/waterfall-mkt/curta-puzzles.git && cd curta-puzzles && forge install
- Set
RPC_URL_MAINNET
in.env
.env
RPC_URL_MAINNET=""
- Write solution + run script
forge script <PATH_TO_PUZZLE> -f mainnet -vvv
This is still WIP.