Puzzle #7
CurtaLending
Author
First Blood
Solve Time
Total Solves
Time Left
Phase 0
Phase 1
Phase 2
Solutions revealed
Phase 3
Submissions closed
123456789101112131415161718192021222324252627282930313233343536373839401// 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}git clone https://github.com/waterfall-mkt/curta-puzzles.git && cd curta-puzzles && forge installRPC_URL_MAINNET in .envRPC_URL_MAINNET=""forge script <PATH_TO_PUZZLE> -f mainnet -vvv