From d9a02e5ba5b533d46a662a1cadb1baf37968b41d Mon Sep 17 00:00:00 2001 From: sherlock-admin Date: Tue, 11 Feb 2025 13:53:06 +0000 Subject: [PATCH 1/2] Fix Review --- Numa/contracts/NumaProtocol/NumaOracle.sol | 20 +- Numa/contracts/NumaProtocol/NumaPrinter.sol | 8 +- Numa/contracts/NumaProtocol/NumaVault.sol | 112 +++- .../NumaProtocol/USDToEthConverter.sol | 109 ++++ Numa/contracts/NumaProtocol/VaultManager.sol | 45 +- Numa/contracts/Test/Lending.t.sol | 429 ++++++++++--- Numa/contracts/Test/Printer.t.sol | 580 +++++------------- Numa/contracts/Test/VaultBuySellFee.t.sol | 37 +- Numa/contracts/Test/VaultMigrations.t.sol | 4 +- Numa/contracts/Test/utils/ConstantsTest.sol | 3 + Numa/contracts/Test/utils/SetupBase.sol | 23 +- .../Test/utils/Setup_ArbitrumFork.sol | 2 +- Numa/contracts/deployment/utils.sol | 8 +- Numa/contracts/deployment/vaultV2Deployer.sol | 4 +- Numa/contracts/interfaces/INumaVault.sol | 4 + Numa/contracts/interfaces/IVaultManager.sol | 1 + Numa/contracts/lending/CNumaToken.sol | 109 +++- Numa/contracts/lending/CToken.sol | 72 ++- Numa/contracts/lending/CTokenInterfaces.sol | 9 +- .../lending/ComptrollerInterface.sol | 3 +- Numa/contracts/lending/ErrorReporter.sol | 3 + Numa/contracts/lending/Lens/CompoundLens.sol | 5 +- Numa/contracts/lending/NumaComptroller.sol | 389 +++++++----- Numa/contracts/libraries/OracleUtils2.sol | 266 ++++++++ Numa/contracts/nuAssets/nuAssetManager2.sol | 295 +++++++++ Numa/contracts/utils/constants.sol | 2 + Numa/scripts/DeployLending.sol | 9 +- 27 files changed, 1775 insertions(+), 776 deletions(-) create mode 100644 Numa/contracts/NumaProtocol/USDToEthConverter.sol create mode 100644 Numa/contracts/libraries/OracleUtils2.sol create mode 100644 Numa/contracts/nuAssets/nuAssetManager2.sol diff --git a/Numa/contracts/NumaProtocol/NumaOracle.sol b/Numa/contracts/NumaProtocol/NumaOracle.sol index eb1efd6..b7a7c41 100644 --- a/Numa/contracts/NumaProtocol/NumaOracle.sol +++ b/Numa/contracts/NumaProtocol/NumaOracle.sol @@ -23,14 +23,14 @@ contract NumaOracle is Ownable2Step, INumaOracle { //uint maxSpotOffsetBps = 145;//1.45% //uint maxSpotOffsetSqrtBps = 1204;//sqrt(1.45%) - uint160 maxSpotOffsetPlus1SqrtBps = 10072; - uint160 maxSpotOffsetMinus1SqrtBps = 9927; + uint160 public maxSpotOffsetPlus1SqrtBps = 10072; + uint160 public maxSpotOffsetMinus1SqrtBps = 9927; nuAssetManager public nuAManager; event IntervalShort(uint32 _intervalShort); event IntervalLong(uint32 _intervalLong); - event MaxSpotOffsetBps(uint _maxSpotOffsetBps); + event MaxSpotOffset(uint _maxSpotOffset); constructor( address _token, uint32 _intervalShort, @@ -69,20 +69,18 @@ contract NumaOracle is Ownable2Step, INumaOracle { /** * - * @param _maxSpotOffsetBps offset percentage variable (cf doc) + * @param _maxSpotOffset offset percentage variable (cf doc) */ - function setMaxSpotOffsetBps(uint _maxSpotOffsetBps) external onlyOwner { - require(_maxSpotOffsetBps < 10000, "percentage must be less than 100"); + function setMaxSpotOffset(uint _maxSpotOffset) external onlyOwner { + require(_maxSpotOffset < 1 ether, "percentage must be less than 100"); maxSpotOffsetPlus1SqrtBps = - 100 * - uint160(Math.sqrt(10000 + _maxSpotOffsetBps)); + uint160(Math.sqrt(1 ether + _maxSpotOffset))/1e5; maxSpotOffsetMinus1SqrtBps = - 100 * - uint160(Math.sqrt(10000 - _maxSpotOffsetBps)); + uint160(Math.sqrt(1 ether - _maxSpotOffset))/1e5; - emit MaxSpotOffsetBps(_maxSpotOffsetBps); + emit MaxSpotOffset(_maxSpotOffset); } /** diff --git a/Numa/contracts/NumaProtocol/NumaPrinter.sol b/Numa/contracts/NumaProtocol/NumaPrinter.sol index 82ba164..0276cd5 100644 --- a/Numa/contracts/NumaProtocol/NumaPrinter.sol +++ b/Numa/contracts/NumaProtocol/NumaPrinter.sol @@ -68,10 +68,12 @@ contract NumaPrinter is Pausable, Ownable2Step { uint256 _amountReceived ); + // sherlock issue 41 + // CF warning can be bypassed modifier notInWarningCF() { + _; uint currentCF = vaultManager.getGlobalCF(); require(currentCF > vaultManager.getWarningCF(), "minting forbidden"); - _; } constructor( @@ -449,7 +451,7 @@ contract NumaPrinter is Pausable, Ownable2Step { (uint scaleSynthBurn, , , ) = vaultManager.getSynthScaling(); // apply scale - costWithoutFee = (costWithoutFee * scaleSynthBurn) / BASE_1000; + costWithoutFee = (costWithoutFee * scaleSynthBurn) / BASE_SCALE; // burn fee uint256 amountToBurn = computeFeeAmountIn( costWithoutFee, @@ -493,7 +495,7 @@ contract NumaPrinter is Pausable, Ownable2Step { uint256 nuAssetIn = oracle.ethToNuAssetRoundUp(_nuAsset, ethAmount); (uint scaleSynthBurn, , , ) = vaultManager.getSynthScaling(); // apply scale - nuAssetIn = (nuAssetIn * BASE_1000) / scaleSynthBurn; + nuAssetIn = (nuAssetIn * BASE_SCALE) / scaleSynthBurn; return (nuAssetIn, amountWithFee - _numaAmount); } diff --git a/Numa/contracts/NumaProtocol/NumaVault.sol b/Numa/contracts/NumaProtocol/NumaVault.sol index 7b5005d..37fd95c 100644 --- a/Numa/contracts/NumaProtocol/NumaVault.sol +++ b/Numa/contracts/NumaProtocol/NumaVault.sol @@ -14,10 +14,11 @@ import "../interfaces/INumaVault.sol"; import "./NumaMinter.sol"; import "../lending/CNumaToken.sol"; - +import "../lending/NumaComptroller.sol"; import "@openzeppelin/contracts_5.0.2/utils/structs/EnumerableSet.sol"; import "../utils/constants.sol"; + /// @title Numa vault to mint/burn Numa to lst token contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { using EnumerableSet for EnumerableSet.AddressSet; @@ -172,7 +173,7 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { } /** - * @dev minimum reth borrow balance needed to allow partial liquidations + * @dev minimum lst borrow balance needed to allow partial liquidations */ function setMinBorrowAmountAllowPartialLiquidation( uint _minBorrowAmountAllowPartialLiquidation @@ -180,6 +181,39 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { minBorrowAmountAllowPartialLiquidation = _minBorrowAmountAllowPartialLiquidation; } + /** + * @dev minimum borrow balance needed to allow partial liquidations in numa or in lst + */ + function getMinBorrowAmountAllowPartialLiquidation(address _cBorrowToken) external view returns (uint) { + if (_cBorrowToken == address(cNuma) ) + { + // numa borrower + // min amount in numa + ( + , + , + uint criticalScaleForNumaPriceAndSellFee, + ) = vaultManager.getSynthScaling(); + + uint minBorrowAmountAllowPartialLiquidationNuma = vaultManager + .tokenToNuma( + minBorrowAmountAllowPartialLiquidation, + last_lsttokenvalueWei, + decimals, + criticalScaleForNumaPriceAndSellFee + ); + return minBorrowAmountAllowPartialLiquidationNuma; + + } + else + { + return minBorrowAmountAllowPartialLiquidation; + + } + + + } + /** * @dev set the IVaultOracle address (used to compute token price in Eth) */ @@ -855,11 +889,12 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { function startLiquidation() internal - returns (uint criticalScaleForNumaPriceAndSellFee) + returns (uint criticalScaleForNumaPriceAndSellFee, uint sell_fee) { ( , criticalScaleForNumaPriceAndSellFee, + sell_fee ) = updateVaultAndUpdateDebasing(); // lock numa supply @@ -975,7 +1010,7 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { "invalid param" ); - uint criticalScaleForNumaPriceAndSellFee = startLiquidation(); + (uint criticalScaleForNumaPriceAndSellFee,) = startLiquidation(); uint numaAmount = _numaAmount; @@ -985,19 +1020,6 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { // AUDITV2FIX: handle max liquidations if (_numaAmount == type(uint256).max) { numaAmount = borrowAmount; - } else { - // min liquidation amount - // convert minimum amount for partial liquidations in numa - uint minBorrowAmountAllowPartialLiquidationNuma = vaultManager - .tokenToNuma( - minBorrowAmountAllowPartialLiquidation, - last_lsttokenvalueWei, - decimals, - criticalScaleForNumaPriceAndSellFee - ); - uint minAmount = minBorrowAmountAllowPartialLiquidationNuma; - if (borrowAmount < minAmount) minAmount = borrowAmount; - require(numaAmount >= minAmount, "min liquidation"); } if (_flashloan) { @@ -1015,7 +1037,7 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { // liquidate numa.approve(address(cNuma), numaAmount); - cNuma.liquidateBorrow( + (,uint badDebt) = cNuma.liquidateBorrow( _borrower, numaAmount, CTokenInterface(address(cLstToken)) @@ -1051,9 +1073,15 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { decimals, criticalScaleForNumaPriceAndSellFee ); + // sherlock 86 42 + // for numa borrow, numa <-> lst conversions should use buyPrice + maxNumaProfitForLiquidations = (maxNumaProfitForLiquidations * vaultManager.getBuyFee()) / 1 ether; // cap profit - if (numaLiquidatorProfit > maxNumaProfitForLiquidations) + // sherlock 101 153 + // cap only if there is no bad debt, because if we are in bad debt it means this is a partial liquidation which + // should not be capped + if ((badDebt == 0) && (numaLiquidatorProfit > maxNumaProfitForLiquidations)) numaLiquidatorProfit = maxNumaProfitForLiquidations; uint numaToSend = numaLiquidatorProfit; @@ -1078,6 +1106,9 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { decimals, criticalScaleForNumaPriceAndSellFee ); + // sherlock 86 42 + // for numa borrow, numa <-> lst conversions should use buyPrice + lstProvidedEstimate = (lstProvidedEstimate * 1 ether) / vaultManager.getBuyFee(); uint lstLiquidatorProfit; // we don't revert if liquidation is not profitable because it might be profitable @@ -1086,8 +1117,13 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { lstLiquidatorProfit = receivedlst - lstProvidedEstimate; } + + // cap profit + // sherlock 101 153 + // cap only if there is no bad debt, because if we are in bad debt it means this is a partial liquidation which + // should not be capped uint vaultProfit; - if (lstLiquidatorProfit > maxLstProfitForLiquidations) { + if ((badDebt == 0) && (lstLiquidatorProfit > maxLstProfitForLiquidations)) { vaultProfit = lstLiquidatorProfit - maxLstProfitForLiquidations; } @@ -1132,12 +1168,9 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { lstAmount = borrowAmount; } - uint minAmount = minBorrowAmountAllowPartialLiquidation; - if (borrowAmount < minAmount) minAmount = borrowAmount; - require(lstAmount >= minAmount, "min liquidation"); - uint criticalScaleForNumaPriceAndSellFee = startLiquidation(); + (uint criticalScaleForNumaPriceAndSellFee,uint sellfee) = startLiquidation(); if (!_flashloan) { // user supplied funds @@ -1151,7 +1184,7 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { // liquidate IERC20(lstToken).approve(address(cLstToken), lstAmount); - cLstToken.liquidateBorrow( + (,uint badDebt) = cLstToken.liquidateBorrow( _borrower, lstAmount, CTokenInterface(address(cNuma)) @@ -1176,7 +1209,10 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { uint lstLiquidatorProfit = lstReceived - lstAmount; // cap profit - if (lstLiquidatorProfit > maxLstProfitForLiquidations) + // sherlock 101 153 + // cap only if there is no bad debt, because if we are in bad debt it means this is a partial liquidation which + // should not be capped + if ((badDebt == 0) && (lstLiquidatorProfit > maxLstProfitForLiquidations)) lstLiquidatorProfit = maxLstProfitForLiquidations; uint lstToSend = lstLiquidatorProfit; @@ -1193,12 +1229,20 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { decimals, criticalScaleForNumaPriceAndSellFee ); + // sherlock 86 42 + // for lst borrow, numa <-> lst conversions should use sellPrice + numaProvidedEstimate = (numaProvidedEstimate * 1 ether) / sellfee; + uint maxNumaProfitForLiquidations = vaultManager.tokenToNuma( maxLstProfitForLiquidations, last_lsttokenvalueWei, decimals, criticalScaleForNumaPriceAndSellFee ); + // sherlock 86 42 + // for lst borrow, numa <-> lst conversions should use sellPrice + maxNumaProfitForLiquidations = (maxNumaProfitForLiquidations * 1 ether) / sellfee; + uint numaLiquidatorProfit; // we don't revert if liquidation is not profitable because it might be profitable @@ -1208,7 +1252,11 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { } uint vaultProfit; - if (numaLiquidatorProfit > maxNumaProfitForLiquidations) { + // cap profit + // sherlock 101 153 + // cap only if there is no bad debt, because if we are in bad debt it means this is a partial liquidation which + // should not be capped + if ((badDebt == 0) && (numaLiquidatorProfit > maxNumaProfitForLiquidations)) { vaultProfit = numaLiquidatorProfit - maxNumaProfitForLiquidations; @@ -1318,4 +1366,14 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { } return (extSize > 0); } + + + function borrowAllowed(address _ctokenAddress) external view returns (bool allowed) + { + allowed = true; + if (_ctokenAddress == address(cNuma)) + { + allowed = vaultManager.numaBorrowAllowed(); + } + } } diff --git a/Numa/contracts/NumaProtocol/USDToEthConverter.sol b/Numa/contracts/NumaProtocol/USDToEthConverter.sol new file mode 100644 index 0000000..9ed3a26 --- /dev/null +++ b/Numa/contracts/NumaProtocol/USDToEthConverter.sol @@ -0,0 +1,109 @@ +//SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import "../interfaces/INumaTokenToEthConverter.sol"; +import "../libraries/OracleUtils.sol"; + +contract USDToEthConverter is INumaTokenToEthConverter, OracleUtils { + + address public immutable pricefeedETH_USD; + uint128 immutable chainlink_heartbeatETH_USD; + + //uint decimals; + + constructor( + address _pricefeedETH_USD, + uint128 _chainlink_heartbeatETH_USD, + address _uptimeFeedAddress //, + ) + //uint _decimals + OracleUtils(_uptimeFeedAddress) + { + + pricefeedETH_USD = _pricefeedETH_USD; + chainlink_heartbeatETH_USD = _chainlink_heartbeatETH_USD; + //decimals = _decimals; + } + + /** + * @dev eth to pool token using 2 oracles + */ + function convertEthToToken( + uint256 _ethAmount + ) public view checkSequencerActive returns (uint256 tokenAmount) { + + ( + uint80 roundID2, + int256 price2, + , + uint256 timeStamp2, + uint80 answeredInRound2 + ) = AggregatorV3Interface(pricefeedETH_USD).latestRoundData(); + + // heartbeat check + require( + timeStamp2 >= block.timestamp - chainlink_heartbeatETH_USD, + "Stale pricefeed" + ); + + // minAnswer/maxAnswer check + IChainlinkAggregator aggregator2 = IChainlinkAggregator( + IChainlinkPriceFeed(pricefeedETH_USD).aggregator() + ); + require( + ((price2 > int256(aggregator2.minAnswer())) && + (price2 < int256(aggregator2.maxAnswer()))), + "min/max reached" + ); + require(answeredInRound2 >= roundID2, "Answer given before round"); + + // compose oracles + + // Chainlink ETH/USD price feed has 8 decimals; ethAmount is in wei (18 decimals) + // To convert: + // (ethAmount in wei) * (ethPriceUsd) / 10^(decimals difference) + // = ethAmount * ethPriceUsd / 10^8 + tokenAmount = (_ethAmount * uint256(price2)) / 1e8; + } + + /** + * @dev pool token to eth using 2 oracles + */ + function convertTokenToEth( + uint256 _tokenAmount + ) public view checkSequencerActive returns (uint256 ethValue) { + + ( + uint80 roundID2, + int256 price2, + , + uint256 timeStamp2, + uint80 answeredInRound2 + ) = AggregatorV3Interface(pricefeedETH_USD).latestRoundData(); + + // heartbeat check + require( + timeStamp2 >= block.timestamp - chainlink_heartbeatETH_USD, + "Stale pricefeed" + ); + + // minAnswer/maxAnswer check + IChainlinkAggregator aggregator2 = IChainlinkAggregator( + IChainlinkPriceFeed(pricefeedETH_USD).aggregator() + ); + require( + ((price2 > int256(aggregator2.minAnswer())) && + (price2 < int256(aggregator2.maxAnswer()))), + "min/max reached" + ); + + require(answeredInRound2 >= roundID2, "Answer given before round"); + + + // Chainlink ETH/USD price feed has 8 decimals; usdAmount is in 18 decimals + // To convert: + // (usdAmount in 18 decimals) * 10^(decimals difference) / ethPriceUsd + // = usdAmount * 10^8 / ethPriceUsd + ethValue = (_tokenAmount * 1e8) / uint256(price2); + } +} diff --git a/Numa/contracts/NumaProtocol/VaultManager.sol b/Numa/contracts/NumaProtocol/VaultManager.sol index 09f9035..01dc4a2 100644 --- a/Numa/contracts/NumaProtocol/VaultManager.sol +++ b/Numa/contracts/NumaProtocol/VaultManager.sol @@ -14,6 +14,7 @@ import "../interfaces/INumaPrinter.sol"; import "../utils/constants.sol"; + contract VaultManager is IVaultManager, Ownable2Step { using EnumerableSet for EnumerableSet.AddressSet; EnumerableSet.AddressSet vaultsList; @@ -63,13 +64,14 @@ contract VaultManager is IVaultManager, Ownable2Step { uint public cf_critical = 1100; uint public cf_severe = 1500; uint public cf_warning = 1700; - uint public debaseValue = 20; //base 1000 - uint public rebaseValue = 30; //base 1000 - uint public minimumScale = 500; + // + uint public debaseValue = 0.02 ether; + uint public rebaseValue = 0.03 ether; + uint public minimumScale = 0.5 ether; uint public criticalDebaseMult = 1100; //base 1000 uint public deltaRebase = 24 hours; uint public deltaDebase = 24 hours; - uint lastSynthPID = BASE_1000; + uint lastSynthPID = BASE_SCALE; uint lastBlockTime; // the amount by which buyFee_PID increments/decrements at each event @@ -444,12 +446,12 @@ contract VaultManager is IVaultManager, Ownable2Step { // we use criticalScaleForNumaPriceAndSellFee because we want to use this scale in our sell_fee only when cf_critical is reached (, , uint criticalScaleForNumaPriceAndSellFee, ) = getSynthScaling(); - uint sell_fee_increaseCriticalCF = ((BASE_1000 - - criticalScaleForNumaPriceAndSellFee) * 1 ether) / BASE_1000; + uint sell_fee_increaseCriticalCF = ((BASE_SCALE - + criticalScaleForNumaPriceAndSellFee) * 1 ether) / BASE_SCALE; // add a multiplier on top sell_fee_increaseCriticalCF = (sell_fee_increaseCriticalCF * sell_fee_criticalMultiplier) / - 1000; + BASE_1000; // here we use original fee value increase by this factor uint sell_fee_criticalCF; @@ -524,7 +526,7 @@ contract VaultManager is IVaultManager, Ownable2Step { } else syntheticsCurrentPID = minimumScale; } } else { - if (syntheticsCurrentPID < BASE_1000) { + if (syntheticsCurrentPID < BASE_SCALE) { // rebase linearly uint nrebase = ((blockTime - lastBlockTime) * rebaseValue) / (deltaRebase); @@ -535,26 +537,26 @@ contract VaultManager is IVaultManager, Ownable2Step { } else { syntheticsCurrentPID = syntheticsCurrentPID + nrebase; - if (syntheticsCurrentPID > BASE_1000) - syntheticsCurrentPID = BASE_1000; + if (syntheticsCurrentPID > BASE_SCALE) + syntheticsCurrentPID = BASE_SCALE; } } } } // apply scale to synth burn price uint scaleSynthBurn = syntheticsCurrentPID; // PID - uint criticalScaleForNumaPriceAndSellFee = BASE_1000; + uint criticalScaleForNumaPriceAndSellFee = BASE_SCALE; // CRITICAL_CF if (currentCF < cf_critical) { // scale such that currentCF = cf_critical - uint criticalDebaseFactor = (currentCF * BASE_1000) / cf_critical; + uint criticalDebaseFactor = (currentCF * BASE_SCALE) / cf_critical; // when reaching CF_CRITICAL, we use that criticalDebaseFactor in numa price so that numa price is clipped by this lower limit criticalScaleForNumaPriceAndSellFee = criticalDebaseFactor; // we apply this multiplier on the factor for when it's used on synthetics burning price criticalDebaseFactor = - (criticalDebaseFactor * BASE_1000) / + (criticalDebaseFactor * 1000) / criticalDebaseMult; // for burning price we take the min between PID and criticalDebaseFactor @@ -652,7 +654,7 @@ contract VaultManager is IVaultManager, Ownable2Step { ); uint synthValueInEth = getTotalSynthValueEth(); - synthValueInEth = (synthValueInEth * _synthScaling) / BASE_1000; + synthValueInEth = (synthValueInEth * _synthScaling) / BASE_SCALE; uint circulatingNuma = getNumaSupply(); uint result; @@ -702,7 +704,7 @@ contract VaultManager is IVaultManager, Ownable2Step { uint synthValueInEth = getTotalSynthValueEth(); - synthValueInEth = (synthValueInEth * _synthScaling) / BASE_1000; + synthValueInEth = (synthValueInEth * _synthScaling) / BASE_SCALE; uint circulatingNuma = getNumaSupply(); @@ -943,4 +945,17 @@ contract VaultManager is IVaultManager, Ownable2Step { return MAX_CF; } } + + function numaBorrowAllowed() external view returns (bool allowed) + { + allowed = true; + + // is numa borrow allowed + uint currentCF = getGlobalCF(); + + if (currentCF < cf_severe) + { + allowed = false; + } + } } diff --git a/Numa/contracts/Test/Lending.t.sol b/Numa/contracts/Test/Lending.t.sol index 0523b9d..335eff2 100644 --- a/Numa/contracts/Test/Lending.t.sol +++ b/Numa/contracts/Test/Lending.t.sol @@ -16,8 +16,15 @@ import {NumaLeverageLPSwap} from "../Test/mocks/NumaLeverageLPSwap.sol"; import "../lending/ExponentialNoError.sol"; import "../lending/INumaLeverageStrategy.sol"; import "../lending/CToken.sol"; + +import {TokenErrorReporter} from "../lending/ErrorReporter.sol"; import {Setup} from "./utils/SetupDeployNuma_Arbitrum.sol"; contract LendingTest is Setup, ExponentialNoError { + + uint constant strategyIndex1 = 1; + + uint strategyindex; + uint providedAmount = 10 ether; uint leverageAmount = 40 ether; @@ -134,10 +141,18 @@ contract LendingTest is Setup, ExponentialNoError { numa.approve(address(cReth), providedAmount); // call strategy - uint strategyindex = 0; + strategyindex = 0; + // slippage check + uint amountInExpected = cReth.getAmountIn( + leverageAmount, + false, + strategyindex + ); + cReth.leverageStrategy( providedAmount, leverageAmount, + amountInExpected, cNuma, strategyindex ); @@ -150,7 +165,7 @@ contract LendingTest is Setup, ExponentialNoError { uint mintTokens = div_(totalCollateral, exchangeRate); - assertEq(cNumaBal, mintTokens); + assertEq(cNumaBal, mintTokens - 1000);// first depositor has 1000 less // numa stored in cnuma contract uint numaBal = numa.balanceOf(address(cNuma)); @@ -161,7 +176,7 @@ contract LendingTest is Setup, ExponentialNoError { - (, uint ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + (,,,, uint ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log("ltv before"); console2.log(ltv); // vault borrow (should be the same) @@ -171,7 +186,7 @@ contract LendingTest is Setup, ExponentialNoError { deal({token: address(rEth), to: address(vault), give: newRethBalance}); // check states - (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log("ltv after"); console2.log(ltv); @@ -188,9 +203,9 @@ contract LendingTest is Setup, ExponentialNoError { ); cNuma.approve(address(cReth), cnumaAmount); - cReth.closeLeverageStrategy(cNuma, borrowrEThBalance, strategyindex); + cReth.closeLeverageStrategy(cNuma, borrowrEThBalance,swapAmountIn, strategyindex); - (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log("ltv after close"); console2.log(ltv); @@ -248,10 +263,19 @@ contract LendingTest is Setup, ExponentialNoError { numa.approve(address(cReth), providedAmount2); // call strategy - uint strategyindex = 0; + strategyindex = 0; + + // slippage check + uint amountInExpected = cReth.getAmountIn( + leverageAmount2, + false, + strategyindex + ); + cReth.leverageStrategy( providedAmount2, leverageAmount2, + amountInExpected, cNuma, strategyindex ); @@ -266,7 +290,7 @@ contract LendingTest is Setup, ExponentialNoError { uint mintTokens = div_(totalCollateral, exchangeRate); - assertEq(cNumaBal, mintTokens); + //assertEq(cNumaBal, mintTokens); // numa stored in cnuma contract uint numaBal = numa.balanceOf(address(cNuma)); @@ -277,7 +301,7 @@ contract LendingTest is Setup, ExponentialNoError { console2.log(borrowrEThBalance); - (, uint ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + (,,,, uint ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log("ltv"); console2.log(ltv); @@ -289,9 +313,17 @@ contract LendingTest is Setup, ExponentialNoError { numa.approve(address(cReth), providedAmount2); // call strategy + // slippage check + amountInExpected = cReth.getAmountIn( + leverageAmount2, + false, + strategyindex + ); + cReth.leverageStrategy( providedAmount2, leverageAmount2, + amountInExpected, cNuma, strategyindex ); @@ -306,7 +338,7 @@ contract LendingTest is Setup, ExponentialNoError { console2.log(borrowrEThBalance); - (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log("ltv"); console2.log(ltv); @@ -318,9 +350,17 @@ contract LendingTest is Setup, ExponentialNoError { numa.approve(address(cReth), providedAmount2); // call strategy + // slippage check + amountInExpected = cReth.getAmountIn( + leverageAmount2, + false, + strategyindex + ); + cReth.leverageStrategy( providedAmount2, leverageAmount2, + amountInExpected, cNuma, strategyindex ); @@ -335,7 +375,7 @@ contract LendingTest is Setup, ExponentialNoError { console2.log(borrowrEThBalance); - (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log("ltv"); console2.log(ltv); @@ -347,9 +387,17 @@ contract LendingTest is Setup, ExponentialNoError { numa.approve(address(cReth), providedAmount2); // call strategy + // slippage check + amountInExpected = cReth.getAmountIn( + leverageAmount2, + false, + strategyindex + ); + cReth.leverageStrategy( providedAmount2, leverageAmount2, + amountInExpected, cNuma, strategyindex ); @@ -364,7 +412,7 @@ contract LendingTest is Setup, ExponentialNoError { console2.log(borrowrEThBalance); - (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log("ltv"); console2.log(ltv); @@ -378,9 +426,15 @@ contract LendingTest is Setup, ExponentialNoError { numa.approve(address(cReth), providedAmount2); // call strategy + amountInExpected = cReth.getAmountIn( + leverageAmount2, + false, + strategyindex + ); cReth.leverageStrategy( providedAmount2, leverageAmount2, + amountInExpected, cNuma, strategyindex ); @@ -395,7 +449,7 @@ contract LendingTest is Setup, ExponentialNoError { console2.log(borrowrEThBalance); - (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log("ltv"); console2.log(ltv); @@ -403,7 +457,7 @@ contract LendingTest is Setup, ExponentialNoError { console2.log("vault balance 5", rEth.balanceOf(address(vault))); console2.log("vault debt 5", vault.getDebt()); console2.log("lst borrow rate per block 5", cReth.borrowRatePerBlock()); - (, uint liquidity, uint shortfall, uint badDebt) = comptroller + (, uint liquidity, uint shortfall, uint badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log("liquidity:", liquidity); console2.log("shortfall:", shortfall); @@ -416,7 +470,7 @@ contract LendingTest is Setup, ExponentialNoError { vm.startPrank(deployer); vaultManager.setSellFee(0.85 ether); - (, liquidity, shortfall, badDebt) = comptroller + (, liquidity, shortfall, badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log("liquidity:", liquidity); console2.log("shortfall:", shortfall); @@ -448,12 +502,18 @@ contract LendingTest is Setup, ExponentialNoError { numa.approve(address(cReth), providedAmount); // call strategy - uint strategyindex = 1; + + uint amountInExpected = cReth.getAmountIn( + leverageAmount, + false, + strategyIndex1 + ); cReth.leverageStrategy( providedAmount, leverageAmount, + amountInExpected, cNuma, - strategyindex + strategyIndex1 ); // check balances @@ -466,7 +526,7 @@ contract LendingTest is Setup, ExponentialNoError { uint mintTokens = div_(totalCollateral, exchangeRate); - assertEq(cNumaBal, mintTokens); + assertEq(cNumaBal, mintTokens - 1000);// first depositor has 1000 less // numa stored in cnuma contract uint numaBal = numa.balanceOf(address(cNuma)); @@ -477,7 +537,7 @@ contract LendingTest is Setup, ExponentialNoError { console2.log(borrowrEThBalance); - (, uint ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + (,,,, uint ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log("ltv before"); console2.log(ltv); //vault borrow (should be the same) @@ -507,7 +567,7 @@ contract LendingTest is Setup, ExponentialNoError { vm.stopPrank(); vm.startPrank(userA); // check states - (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log("ltv after"); console2.log(ltv); @@ -516,16 +576,16 @@ contract LendingTest is Setup, ExponentialNoError { (uint cnumaAmount, uint swapAmountIn) = cReth.closeLeverageAmount( cNuma, borrowrEThBalance, - strategyindex + strategyIndex1 ); cNuma.approve(address(cReth), cnumaAmount); - cReth.closeLeverageStrategy(cNuma, borrowrEThBalance, strategyindex); + cReth.closeLeverageStrategy(cNuma, borrowrEThBalance,swapAmountIn, strategyIndex1); borrowrEThBalance = cReth.borrowBalanceCurrent(userA); console2.log("borrow amount after"); console2.log(borrowrEThBalance); - (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log("ltv after close"); console2.log(ltv); @@ -568,7 +628,7 @@ contract LendingTest is Setup, ExponentialNoError { console2.log(cReth.getAmountIn(leverageAmount, false, 1)); // call strategy - uint strategyindex = 0; + strategyindex = 0; if ( cReth.getAmountIn(leverageAmount, false, 1) < @@ -581,6 +641,7 @@ contract LendingTest is Setup, ExponentialNoError { cReth.leverageStrategy( providedAmount, leverageAmount, + cReth.getAmountIn(leverageAmount, false, strategyindex), cNuma, strategyindex ); @@ -595,7 +656,7 @@ contract LendingTest is Setup, ExponentialNoError { uint mintTokens = div_(totalCollateral, exchangeRate); - assertEq(cNumaBal, mintTokens); + assertEq(cNumaBal, mintTokens - 1000);// first depositor has 1000 less // numa stored in cnuma contract uint numaBal = numa.balanceOf(address(cNuma)); @@ -606,7 +667,7 @@ contract LendingTest is Setup, ExponentialNoError { console2.log(borrowrEThBalance); - (, uint ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + (,,,, uint ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log("ltv before"); console2.log(ltv); //vault borrow (should be the same) @@ -636,7 +697,7 @@ contract LendingTest is Setup, ExponentialNoError { vm.stopPrank(); vm.startPrank(userA); // check states - (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log("ltv after"); console2.log(ltv); @@ -658,12 +719,12 @@ contract LendingTest is Setup, ExponentialNoError { ); cNuma.approve(address(cReth), cnumaAmount); - cReth.closeLeverageStrategy(cNuma, borrowrEThBalance, strategyindex); + cReth.closeLeverageStrategy(cNuma, borrowrEThBalance, swapAmountIn,strategyindex); borrowrEThBalance = cReth.borrowBalanceCurrent(userA); console2.log("borrow amount after"); console2.log(borrowrEThBalance); - (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log("ltv after close"); console2.log(ltv); @@ -714,7 +775,7 @@ contract LendingTest is Setup, ExponentialNoError { console2.log(cReth.getAmountIn(leverageAmount, false, 1)); // call strategy - uint strategyindex = 0; + strategyindex = 0; if ( cReth.getAmountIn(leverageAmount, false, 1) < @@ -724,9 +785,16 @@ contract LendingTest is Setup, ExponentialNoError { console2.log("strategy for open"); console2.log(strategyindex); assertEq(strategyindex, 0); + + uint amountInExpected = cReth.getAmountIn( + leverageAmount, + false, + strategyindex + ); cReth.leverageStrategy( providedAmount, leverageAmount, + amountInExpected, cNuma, strategyindex ); @@ -741,7 +809,7 @@ contract LendingTest is Setup, ExponentialNoError { uint mintTokens = div_(totalCollateral, exchangeRate); - assertEq(cNumaBal, mintTokens); + assertEq(cNumaBal, mintTokens - 1000);// first depositor has 1000 less // numa stored in cnuma contract uint numaBal = numa.balanceOf(address(cNuma)); @@ -752,7 +820,7 @@ contract LendingTest is Setup, ExponentialNoError { console2.log(borrowrEThBalance); - (, uint ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + (,,,, uint ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log("ltv before"); console2.log(ltv); //vault borrow (should be the same) @@ -782,7 +850,7 @@ contract LendingTest is Setup, ExponentialNoError { vm.stopPrank(); vm.startPrank(userA); // check states - (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log("ltv after"); console2.log(ltv); @@ -806,12 +874,12 @@ contract LendingTest is Setup, ExponentialNoError { ); cNuma.approve(address(cReth), cnumaAmount); - cReth.closeLeverageStrategy(cNuma, borrowrEThBalance, strategyindex); + cReth.closeLeverageStrategy(cNuma, borrowrEThBalance,swapAmountIn, strategyindex); borrowrEThBalance = cReth.borrowBalanceCurrent(userA); console2.log("borrow amount after"); console2.log(borrowrEThBalance); - (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log("ltv after close"); console2.log(ltv); @@ -853,10 +921,16 @@ contract LendingTest is Setup, ExponentialNoError { rEth.approve(address(cNuma), providedAmount); // call strategy - uint strategyindex = 0; + strategyindex = 0; + uint amountInExpected = cReth.getAmountIn( + leverageAmount, + false, + strategyindex + ); cNuma.leverageStrategy( providedAmount, leverageAmount, + amountInExpected, cReth, strategyindex ); @@ -882,7 +956,7 @@ contract LendingTest is Setup, ExponentialNoError { // console2.log(borrowrEThBalance); - // (, uint ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + // (,,,, uint ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); // console2.log("ltv before"); // console2.log(ltv); // // vault borrow (should be the same) @@ -892,7 +966,7 @@ contract LendingTest is Setup, ExponentialNoError { // deal({token: address(rEth), to: address(vault), give: newRethBalance}); // // check states - // (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + // (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); // console2.log("ltv after"); // console2.log(ltv); @@ -911,7 +985,7 @@ contract LendingTest is Setup, ExponentialNoError { // cNuma.approve(address(cReth), cnumaAmount); // cReth.closeLeverageStrategy(cNuma, borrowrEThBalance, strategyindex); - // (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); + // (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); // console2.log("ltv after close"); // console2.log(ltv); @@ -984,7 +1058,7 @@ contract LendingTest is Setup, ExponentialNoError { numa.approve(address(cNuma), depositAmount); cNuma.mint(depositAmount); assertEq(numaBalBefore - numa.balanceOf(userA), depositAmount); - assertEq(cNuma.balanceOf(userA), (depositAmount * 50 * 1e8) / 1 ether); + assertEq(cNuma.balanceOf(userA), (depositAmount * 50 * 1e8) / 1 ether - 1000);// first depositor has 1000 less // borrow reth // should revert @@ -1024,13 +1098,13 @@ contract LendingTest is Setup, ExponentialNoError { //assertApproxEqAbs(borrowBalanceAfter - borrowAmount,((baseRatePerYear+ (borrowAmount*multiplierPerYear)/rethAmount)*borrowAmount)/1 ether,0.0000001 ether); // make it liquiditable, check shortfall - (, uint liquidity, uint shortfall, uint badDebt) = comptroller + (, uint liquidity, uint shortfall, uint badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log(liquidity); console2.log(shortfall); console2.log(badDebt); cReth.borrow((borrowAmount * 8) / 10); - (, liquidity, shortfall, badDebt) = comptroller + (, liquidity, shortfall, badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log(liquidity); console2.log(shortfall); @@ -1056,7 +1130,7 @@ contract LendingTest is Setup, ExponentialNoError { prepare_LstBorrowSuppliedLst_JRV4(); vm.roll(block.number + blocksPerYear / 4); cReth.accrueInterest(); - (, uint liquidity, uint shortfall, uint badDebt) = comptroller + (, uint liquidity, uint shortfall, uint badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log(liquidity); console2.log(shortfall); @@ -1074,11 +1148,22 @@ contract LendingTest is Setup, ExponentialNoError { vm.startPrank(deployer); comptroller._setSeizePaused(false); vm.startPrank(userC); + + + // test revert on partial + uint minAmountPartial = vault.getMinBorrowAmountAllowPartialLiquidation(address(cReth)); + console2.log("min amount for partial",minAmountPartial); + uint borrowBalance = cReth.borrowBalanceCurrent(userA); + console2.log("borrow balance",borrowBalance); + vm.expectRevert(); + vault.liquidateLstBorrower(userA, minAmountPartial - 1 ether, true, true); + + vault.liquidateLstBorrower(userA,minAmountPartial + 0.1 ether, true, true); vault.liquidateLstBorrower(userA, type(uint256).max, true, true); console2.log("liquidator profit:", rEth.balanceOf(userC) - balC); - (, liquidity, shortfall, badDebt) = comptroller + (, liquidity, shortfall, badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log(liquidity); console2.log(shortfall); @@ -1092,7 +1177,7 @@ contract LendingTest is Setup, ExponentialNoError { // make it liquiditable by changing vault fees vm.startPrank(deployer); vaultManager.setSellFee(0.90 ether); - (, uint liquidity, uint shortfall, uint badDebt) = comptroller + (, uint liquidity, uint shortfall, uint badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log(liquidity); console2.log(shortfall); @@ -1105,7 +1190,7 @@ contract LendingTest is Setup, ExponentialNoError { vault.liquidateLstBorrower(userA, type(uint256).max, true, false); console2.log("liquidator profit:", rEth.balanceOf(userC) - balC); - (, liquidity, shortfall, badDebt) = comptroller + (, liquidity, shortfall, badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log(liquidity); console2.log(shortfall); @@ -1119,7 +1204,7 @@ contract LendingTest is Setup, ExponentialNoError { // make it liquiditable by changing vault fees vm.startPrank(deployer); vaultManager.setSellFee(0.90 ether); - (, uint liquidity, uint shortfall, uint badDebt) = comptroller + (, uint liquidity, uint shortfall, uint badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log(liquidity); console2.log(shortfall); @@ -1131,7 +1216,7 @@ contract LendingTest is Setup, ExponentialNoError { vault.liquidateLstBorrower(userA, type(uint256).max, false, false); console2.log("liquidator profit + input:", numa.balanceOf(userC)); - (, liquidity, shortfall, badDebt) = comptroller + (, liquidity, shortfall, badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log(liquidity); console2.log(shortfall); @@ -1145,7 +1230,7 @@ contract LendingTest is Setup, ExponentialNoError { // make it liquiditable by changing vault fees vm.startPrank(deployer); vaultManager.setSellFee(0.85 ether); - (, uint liquidity, uint shortfall, uint badDebt) = comptroller + (, uint liquidity, uint shortfall, uint badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log(liquidity); console2.log(shortfall); @@ -1159,15 +1244,24 @@ contract LendingTest is Setup, ExponentialNoError { vault.liquidateLstBorrower(userA, type(uint256).max, false, false); vault.liquidateBadDebt(userA, 500, cNuma); - assertEq(numa.balanceOf(userC), 500 ether); - (, liquidity, shortfall, badDebt) = comptroller + // 1000 ctoken burnt for first depositor + // = 2x10^11 token + // 500x10^18 - 1x10^11 = 4999999900000000000 + //assertEq(numa.balanceOf(userC), 500 ether); + assertEq(numa.balanceOf(userC), 499999999900000000000); + + + + (, liquidity, shortfall, badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log(liquidity); console2.log(shortfall); console2.log(badDebt); } + + function prepare_numaBorrow_JRV4() public { vm.startPrank(deployer); vault.setMaxBorrow(0); @@ -1193,7 +1287,7 @@ contract LendingTest is Setup, ExponentialNoError { rEth.approve(address(cReth), depositAmount); cReth.mint(depositAmount); assertEq(rethBalBefore - rEth.balanceOf(userA), depositAmount); - assertEq(cReth.balanceOf(userA), (depositAmount * 50 * 1e8) / 1 ether); + assertEq(cReth.balanceOf(userA), (depositAmount * 50 * 1e8) / 1 ether - 1000);// 1000 ctoken burnt for first depositor // borrow numa // should revert @@ -1204,6 +1298,45 @@ contract LendingTest is Setup, ExponentialNoError { // around 50% UR uint borrowAmount = (depositAmount * rEthCollateralFactor) / (2 * 1 ether); + + + // test block numa borrow when CF < cf_severe + vm.stopPrank(); + vm.startPrank(deployer); + vaultManager.setScalingParameters( + vaultManager.cf_critical(), + vaultManager.cf_warning(), + 100001,// so that we are below severe (if there are no synth CF is 100000) + vaultManager.debaseValue(), + vaultManager.rebaseValue(), + 1 hours, + 2 hours, + vaultManager.minimumScale(), + vaultManager.criticalDebaseMult() + ); + vm.stopPrank(); + + vm.startPrank(userA); + vm.expectRevert(TokenErrorReporter.BorrowNotAllowed.selector); + //vm.expectRevert(); + cNuma.borrow(borrowAmount); + vm.stopPrank(); + // + vm.startPrank(deployer); + vaultManager.setScalingParameters( + vaultManager.cf_critical(), + vaultManager.cf_warning(), + 0,// we don't care, it's not used + vaultManager.debaseValue(), + vaultManager.rebaseValue(), + 1 hours, + 2 hours, + vaultManager.minimumScale(), + vaultManager.criticalDebaseMult() + ); + vm.stopPrank(); + // + vm.startPrank(userA); cNuma.borrow(borrowAmount); assertEq(numa.balanceOf(userA) - numaBalBefore, borrowAmount); @@ -1226,13 +1359,13 @@ contract LendingTest is Setup, ExponentialNoError { //assertApproxEqAbs(borrowBalanceAfter - borrowAmount,((baseRatePerYear+ (borrowAmount*multiplierPerYear)/rethAmount)*borrowAmount)/1 ether,0.0000001 ether); // make it liquiditable, check shortfall - (, uint liquidity, uint shortfall, uint badDebt) = comptroller + (, uint liquidity, uint shortfall, uint badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cReth, cNuma); console2.log(liquidity); console2.log(shortfall); console2.log(badDebt); cNuma.borrow((borrowAmount * 8) / 10); - (, liquidity, shortfall, badDebt) = comptroller + (, liquidity, shortfall, badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cReth, cNuma); console2.log(liquidity); console2.log(shortfall); @@ -1243,7 +1376,7 @@ contract LendingTest is Setup, ExponentialNoError { prepare_numaBorrow_JRV4(); vm.roll(block.number + blocksPerYear / 4); cNuma.accrueInterest(); - (, uint liquidity, uint shortfall, uint badDebt) = comptroller + (, uint liquidity, uint shortfall, uint badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cReth, cNuma); console2.log(liquidity); console2.log(shortfall); @@ -1253,21 +1386,38 @@ contract LendingTest is Setup, ExponentialNoError { vm.startPrank(userC); uint balC = numa.balanceOf(userC); + + // test revert on partial + uint minAmountPartial = vault.getMinBorrowAmountAllowPartialLiquidation(address(cNuma)); + console2.log("min amount for partial",minAmountPartial); + uint borrowBalance = cNuma.borrowBalanceCurrent(userA); + console2.log("borrow balance",borrowBalance); + vm.expectRevert(); + vault.liquidateNumaBorrower(userA, minAmountPartial - 1 ether, true, true); + + vault.liquidateNumaBorrower(userA,minAmountPartial + 0.1 ether, true, true); + + vault.liquidateNumaBorrower(userA, type(uint256).max, true, true); console2.log("liquidator profit:", numa.balanceOf(userC) - balC); - (, liquidity, shortfall, badDebt) = comptroller + (, liquidity, shortfall, badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cReth, cNuma); console2.log(liquidity); console2.log(shortfall); console2.log(badDebt); } + function test_prepare() public + { + prepare_numaBorrow_JRV4(); + } + function test_NumaBorrow_JRV4_liquidateSwap() public { prepare_numaBorrow_JRV4(); vm.roll(block.number + blocksPerYear / 4); cNuma.accrueInterest(); - (, uint liquidity, uint shortfall, uint badDebt) = comptroller + (, uint liquidity, uint shortfall, uint badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cReth, cNuma); console2.log(liquidity); console2.log(shortfall); @@ -1285,7 +1435,7 @@ contract LendingTest is Setup, ExponentialNoError { vault.liquidateNumaBorrower(userA, type(uint256).max, true, false); console2.log("liquidator profit:", numa.balanceOf(userC) - balC); - (, liquidity, shortfall, badDebt) = comptroller + (, liquidity, shortfall, badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cReth, cNuma); console2.log(liquidity); console2.log(shortfall); @@ -1296,7 +1446,7 @@ contract LendingTest is Setup, ExponentialNoError { prepare_numaBorrow_JRV4(); vm.roll(block.number + blocksPerYear / 4); cNuma.accrueInterest(); - (, uint liquidity, uint shortfall, uint badDebt) = comptroller + (, uint liquidity, uint shortfall, uint badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cReth, cNuma); console2.log(liquidity); console2.log(shortfall); @@ -1314,7 +1464,7 @@ contract LendingTest is Setup, ExponentialNoError { vault.liquidateNumaBorrower(userA, type(uint256).max, false, false); console2.log("liquidator profit:", rEth.balanceOf(userC) - balC); - (, liquidity, shortfall, badDebt) = comptroller + (, liquidity, shortfall, badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cReth, cNuma); console2.log(liquidity); console2.log(shortfall); @@ -1329,7 +1479,7 @@ contract LendingTest is Setup, ExponentialNoError { vm.startPrank(deployer); vaultManager.setBuyFee(0.85 ether); - (, uint liquidity, uint shortfall, uint badDebt) = comptroller + (, uint liquidity, uint shortfall, uint badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cReth, cNuma); console2.log(liquidity); console2.log(shortfall); @@ -1350,7 +1500,7 @@ contract LendingTest is Setup, ExponentialNoError { vault.liquidateBadDebt(userA, 1000, cReth); console2.log("liquidator profit:", rEth.balanceOf(userC) - balC); - (, liquidity, shortfall, badDebt) = comptroller + (, liquidity, shortfall, badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cReth, cNuma); console2.log(liquidity); console2.log(shortfall); @@ -1374,7 +1524,7 @@ contract LendingTest is Setup, ExponentialNoError { numa.approve(address(cNuma), depositAmount); cNuma.mint(depositAmount); assertEq(numaBalBefore - numa.balanceOf(userA), depositAmount); - assertEq(cNuma.balanceOf(userA), (depositAmount * 50 * 1e8) / 1 ether); + assertEq(cNuma.balanceOf(userA), (depositAmount * 50 * 1e8) / 1 ether - 1000);// first depositor has 1000 less // borrow reth // should revert not enough collat @@ -1424,13 +1574,13 @@ contract LendingTest is Setup, ExponentialNoError { //assertApproxEqAbs(borrowBalanceAfter - borrowAmount,((baseRatePerYear+ (borrowAmount*multiplierPerYear)/rethAmount)*borrowAmount)/1 ether,0.0000001 ether); // make it liquiditable, check shortfall - (, uint liquidity, uint shortfall, uint badDebt) = comptroller + (, uint liquidity, uint shortfall, uint badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log(liquidity); console2.log(shortfall); console2.log(badDebt); cReth.borrow((borrowAmount * 8) / 10); - (, liquidity, shortfall, badDebt) = comptroller + (, liquidity, shortfall, badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log(liquidity); console2.log(shortfall); @@ -1449,7 +1599,7 @@ contract LendingTest is Setup, ExponentialNoError { vm.roll(block.number + blocksPerYear / 4); cReth.accrueInterest(); - (, uint liquidity, uint shortfall, uint badDebt) = comptroller + (, uint liquidity, uint shortfall, uint badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); @@ -1463,7 +1613,7 @@ contract LendingTest is Setup, ExponentialNoError { vault.liquidateLstBorrower(userA, type(uint256).max, true, true); console2.log("liquidator profit:", rEth.balanceOf(userC) - balC); - (, liquidity, shortfall, badDebt) = comptroller + (, liquidity, shortfall, badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log(liquidity); console2.log(shortfall); @@ -1477,7 +1627,7 @@ contract LendingTest is Setup, ExponentialNoError { // make it liquiditable by changing vault fees vm.startPrank(deployer); vaultManager.setSellFee(0.90 ether); - (, uint liquidity, uint shortfall, uint badDebt) = comptroller + (, uint liquidity, uint shortfall, uint badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log(liquidity); console2.log(shortfall); @@ -1490,7 +1640,7 @@ contract LendingTest is Setup, ExponentialNoError { vault.liquidateLstBorrower(userA, type(uint256).max, true, false); console2.log("liquidator profit:", rEth.balanceOf(userC) - balC); - (, liquidity, shortfall, badDebt) = comptroller + (, liquidity, shortfall, badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log(liquidity); console2.log(shortfall); @@ -1504,7 +1654,7 @@ contract LendingTest is Setup, ExponentialNoError { // make it liquiditable by changing vault fees vm.startPrank(deployer); vaultManager.setSellFee(0.90 ether); - (, uint liquidity, uint shortfall, uint badDebt) = comptroller + (, uint liquidity, uint shortfall, uint badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log(liquidity); console2.log(shortfall); @@ -1516,7 +1666,7 @@ contract LendingTest is Setup, ExponentialNoError { vault.liquidateLstBorrower(userA, type(uint256).max, false, false); console2.log("liquidator profit + input:", numa.balanceOf(userC)); - (, liquidity, shortfall, badDebt) = comptroller + (, liquidity, shortfall, badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log(liquidity); console2.log(shortfall); @@ -1525,12 +1675,15 @@ contract LendingTest is Setup, ExponentialNoError { function test_LstBorrowLstVault_JRV4_liquidateBadDebt() public { prepare_LstBorrowLstVault_JRV4(); + + + //vm.roll(block.number + blocksPerYear/4); //cReth.accrueInterest(); // make it liquiditable by changing vault fees vm.startPrank(deployer); vaultManager.setSellFee(0.85 ether); - (, uint liquidity, uint shortfall, uint badDebt) = comptroller + (, uint liquidity, uint shortfall, uint badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log(liquidity); console2.log(shortfall); @@ -1544,9 +1697,13 @@ contract LendingTest is Setup, ExponentialNoError { vault.liquidateLstBorrower(userA, type(uint256).max, false, false); vault.liquidateBadDebt(userA, 500, cNuma); - assertEq(numa.balanceOf(userC), 500 ether); + // 1000 cnuma burnt for first depositor + // = 2x10^11 numa + // 500x10^18 - 1x10^11 = 4999999900000000000 + //assertEq(numa.balanceOf(userC), 500 ether); + assertEq(numa.balanceOf(userC), 499999999900000000000); - (, liquidity, shortfall, badDebt) = comptroller + (, liquidity, shortfall, badDebt,) = comptroller .getAccountLiquidityIsolate(userA, cNuma, cReth); console2.log(liquidity); console2.log(shortfall); @@ -1572,7 +1729,7 @@ contract LendingTest is Setup, ExponentialNoError { numa.approve(address(cNuma), depositAmount); cNuma.mint(depositAmount); assertEq(numaBalBefore - numa.balanceOf(userA), depositAmount); - assertEq(cNuma.balanceOf(userA), (depositAmount * 50 * 1e8) / 1 ether); + assertEq(cNuma.balanceOf(userA), (depositAmount * 50 * 1e8) / 1 ether - 1000);// first depositor has 1000 less // borrow reth // should revert not enough collat @@ -1755,4 +1912,126 @@ contract LendingTest is Setup, ExponentialNoError { } + + // partial liquidate borrow when bad debt + function test_NumaBorrow_JRV4_liquidateBadDebtPartial() public { + prepare_numaBorrow_JRV4(); + // make it bad debt + // vm.roll(block.number + blocksPerYear/4); + // cNuma.accrueInterest(); + vm.startPrank(deployer); + vaultManager.setBuyFee(0.8 ether); + comptroller._setLtvThresholds(0.98 ether,1.05 ether); + vault.setMinBorrowAmountAllowPartialLiquidation(1000000000 ether); + (, uint liquidity, uint shortfall, uint badDebt,uint ltv) = comptroller + .getAccountLiquidityIsolate(userA, cReth, cNuma); + console2.log("liquidity",liquidity); + console2.log("shortfall",shortfall); + console2.log("badDebt",badDebt); + console2.log("ltv",ltv); + // liquidate + + vm.startPrank(userC); + + uint numaAmountBuy = 1000 ether; + rEth.approve(address(vault), 2 * numaAmountBuy); + vault.buy(2 * numaAmountBuy, numaAmountBuy, userC); + + uint balC = rEth.balanceOf(userC); + numa.approve(address(vault), numa.balanceOf(userC)); + + uint borrowBalance = cNuma.borrowBalanceCurrent(userA); + // revert because we are in bad debt + vm.expectRevert(); + vault.liquidateNumaBorrower(userA, borrowBalance, false, false); + + vault.liquidateNumaBorrower(userA, borrowBalance/2, false, false); + + + (, liquidity, shortfall, badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cReth, cNuma); + console2.log(liquidity); + console2.log(shortfall); + console2.log(badDebt); + } + + function test_NumaBorrow_JRV4_liquidatePartialImpossible() public { + prepare_numaBorrow_JRV4(); + + vm.startPrank(deployer); + // make it liquiditable + vaultManager.setBuyFee(0.868 ether); + comptroller._setLtvThresholds(0.98 ether,1.05 ether); + vault.setMinBorrowAmountAllowPartialLiquidation(1000000000 ether); + (, uint liquidity, uint shortfall, uint badDebt,uint ltv) = comptroller + .getAccountLiquidityIsolate(userA, cReth, cNuma); + console2.log("liquidity",liquidity); + console2.log("shortfall",shortfall); + console2.log("badDebt",badDebt); + console2.log("ltv",ltv); + // liquidate + + vm.startPrank(userC); + + uint numaAmountBuy = 1000 ether; + rEth.approve(address(vault), 2 * numaAmountBuy); + vault.buy(2 * numaAmountBuy, numaAmountBuy, userC); + + uint balC = rEth.balanceOf(userC); + numa.approve(address(vault), numa.balanceOf(userC)); + + uint borrowBalance = cNuma.borrowBalanceCurrent(userA); + + vm.expectRevert(); + vault.liquidateNumaBorrower(userA, borrowBalance/2, false, false); + + vault.liquidateNumaBorrower(userA, borrowBalance, false, false); + + (, liquidity, shortfall, badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cReth, cNuma); + console2.log(liquidity); + console2.log(shortfall); + console2.log(badDebt); + } + + + function test_require_principal_revert_bug() public { + vm.startPrank(userA); + + // Supplying funds to cNuma market for us to borrow during leverageStrategy + // Supplier can be anybody need not to be userA + numa.approve(address(cNuma),50 ether); + cNuma.mint(50 ether); + + // + address[] memory t = new address[](2); + t[0] = address(cNuma); + t[1] = address(cReth); + comptroller.enterMarkets(t); + + // 1. Supply to numa market + rEth.approve(address(cReth),50 ether); + cReth.mint(50 ether); + + // 2. User's previous borrow + cNuma.borrow(10 ether); + + // 3. Rolling blocks so that interest will be accrued on user's previous borrow + vm.roll(block.number + 24 hours); + + + rEth.approve(address(cNuma), 10 ether); + // leverageStrategy will revert due to interest accurual + // vm.expectRevert("borrow ko"); + cNuma.leverageStrategy( + 1 ether, + 4 ether, + 5 ether,// max borrow + cReth, + 0 + ); + + } + + } diff --git a/Numa/contracts/Test/Printer.t.sol b/Numa/contracts/Test/Printer.t.sol index 4eab2bd..54918c2 100644 --- a/Numa/contracts/Test/Printer.t.sol +++ b/Numa/contracts/Test/Printer.t.sol @@ -11,6 +11,9 @@ import "./uniV3Interfaces/ISwapRouter.sol"; // import {Setup, FEE_LOW} from "./utils/SetupDeployNuma_Arbitrum.sol"; +import "../NumaProtocol/USDToEthConverter.sol"; +import {NuAsset2} from "../nuAssets/nuAsset2.sol"; +import "../utils/constants.sol"; contract PrinterTest is Setup { uint numaPriceVault; @@ -76,13 +79,17 @@ contract PrinterTest is Setup { nuAssetMgr.addNuAsset( address(nuUSD), PRICEFEEDETHUSD_ARBI, - HEART_BEAT_CUSTOM + HEART_BEAT_CUSTOM, + true, + address(0) ); nuAssets = nuAssetMgr.getNuAssetList(); assertEq(nuAssets.length,2); nuAssetMgr.updateNuAsset(address(nuUSD), PRICEFEEDETHUSD_ARBI, - HEART_BEAT_CUSTOM + HEART_BEAT_CUSTOM, + true, + address(0) ); } @@ -201,6 +208,20 @@ contract PrinterTest is Setup { assertEq(numaNeeded2, numaNeeded, "fee ko"); } + + function test_maxSpotOffsetBps() external + { + assertEq(NumaOracle(address(numaOracle)).maxSpotOffsetPlus1SqrtBps(), 10072, "maxSpotOffsetPlus1SqrtBps ko"); + assertEq(NumaOracle(address(numaOracle)).maxSpotOffsetMinus1SqrtBps(), 9927, "maxSpotOffsetMinus1SqrtBps ko"); + + //NumaOracle(address(numaOracle)).setMaxSpotOffsetBps(145);//1.45% + NumaOracle(address(numaOracle)).setMaxSpotOffset(0.0145 ether);//1.45% + + assertEq(NumaOracle(address(numaOracle)).maxSpotOffsetPlus1SqrtBps(), 10072, "maxSpotOffsetPlus1SqrtBps ko"); + assertEq(NumaOracle(address(numaOracle)).maxSpotOffsetMinus1SqrtBps(), 9927, "maxSpotOffsetMinus1SqrtBps ko"); + } + + function test_getNbOfNumaFromAssetWithFee() external { vm.stopPrank(); vm.startPrank(userA); @@ -245,7 +266,7 @@ contract PrinterTest is Setup { // to compare, we scale amount with fees uint totalOut = numaOut + fee; - totalOut = (totalOut * 800) / 1000; + totalOut = (totalOut * (BASE_SCALE - 0.2 ether)) / BASE_SCALE; uint feeEstim2 = (totalOut * (moneyPrinter.burnAssetFeeBps())) / 10000; assertEq(numaOut2, totalOut - feeEstim2, "amount ko"); @@ -259,17 +280,17 @@ contract PrinterTest is Setup { ); // compute critical debase factor - uint criticalDebaseFactor = (vaultManager.getGlobalCF() * 1000) / + uint criticalDebaseFactor = (vaultManager.getGlobalCF() * (1 ether)) / vaultManager.cf_critical(); // we apply this multiplier on the factor for when it's used on synthetics burning price criticalDebaseFactor = (criticalDebaseFactor * 1000) / vaultManager.criticalDebaseMult(); - assertLt(criticalDebaseFactor, 1000); + assertLt(criticalDebaseFactor, 1 ether); (uint scaleSynthBurn2, ) = vaultManager.getSynthScalingUpdate(); assertEq(scaleSynthBurn2, criticalDebaseFactor); totalOut = numaOut + fee; - totalOut = (criticalDebaseFactor * totalOut) / 1000; + totalOut = (criticalDebaseFactor * totalOut) / 1 ether; feeEstim2 = (totalOut * (moneyPrinter.burnAssetFeeBps())) / 10000; assertEq(numaOut2, totalOut - feeEstim2, "amount ko"); @@ -397,6 +418,7 @@ contract PrinterTest is Setup { uint globalCF = vaultManager.getGlobalCF(); vm.startPrank(deployer); + uint warningCF = vaultManager.cf_warning(); vaultManager.setScalingParameters( vaultManager.cf_critical(), globalCF + 1, @@ -408,6 +430,8 @@ contract PrinterTest is Setup { vaultManager.minimumScale(), vaultManager.criticalDebaseMult() ); + + vm.startPrank(userA); vm.expectRevert("minting forbidden"); moneyPrinter.mintAssetFromNumaInput( @@ -420,7 +444,7 @@ contract PrinterTest is Setup { vm.startPrank(deployer); vaultManager.setScalingParameters( vaultManager.cf_critical(), - globalCF - 1, + warningCF, vaultManager.cf_severe(), vaultManager.debaseValue(), vaultManager.rebaseValue(), @@ -429,6 +453,7 @@ contract PrinterTest is Setup { vaultManager.minimumScale(), vaultManager.criticalDebaseMult() ); + vm.startPrank(userA); // slippage test vm.expectRevert("min amount"); @@ -444,6 +469,7 @@ contract PrinterTest is Setup { vm.startPrank(deployer); moneyPrinter.pause(); vm.stopPrank(); + vm.startPrank(userA); vm.expectRevert(bytes4(0xd93c0665)); moneyPrinter.mintAssetFromNumaInput( @@ -462,6 +488,7 @@ contract PrinterTest is Setup { uint balnuUSD = nuUSD.balanceOf(userA); uint balnuma = numa.balanceOf(userA); + moneyPrinter.mintAssetFromNumaInput( address(nuUSD), numaAmount, @@ -508,6 +535,7 @@ contract PrinterTest is Setup { uint globalCF = vaultManager.getGlobalCF(); vm.startPrank(deployer); + uint warningCF = vaultManager.cf_warning(); vaultManager.setScalingParameters( vaultManager.cf_critical(), globalCF + 1, @@ -531,7 +559,7 @@ contract PrinterTest is Setup { vm.startPrank(deployer); vaultManager.setScalingParameters( vaultManager.cf_critical(), - globalCF - 1, + warningCF, vaultManager.cf_severe(), vaultManager.debaseValue(), vaultManager.rebaseValue(), @@ -1118,189 +1146,99 @@ contract PrinterTest is Setup { assertGt(numaNeeded2, (numaOut2 * 15) / 10, "amount ko"); } - // function test_Converter() external { - // //uint usdcAmountIn = 1000000; - // uint usdcAmountIn = 100000000; - // uint ethAmount = usdcEthConverter.convertTokenToEth(usdcAmountIn); - // console2.log(ethAmount); - - // uint usdcAmount = usdcEthConverter.convertEthToToken(ethAmount); - // console2.log(usdcAmount); - - // // TODOTEST - // //assertEq(usdcAmountIn, usdcAmount); // it will fail because the code logic is not correct - // } - - // function test_Mint_Estimations() external { - // uint numaAmount = 1000e18; - - // // with 1000 NUMA "nuUSDAmount" will be minted - // (uint256 nuUSDAmount, uint fee) = moneyPrinter.getNbOfNuAssetFromNuma( - // address(nuUSD), - // numaAmount - // ); - - // // plug in the above numa amount to see they are identical! - // (uint numaNeeded, uint fee2) = moneyPrinter.getNbOfNumaNeededAndFee( - // address(nuUSD), - // nuUSDAmount - // ); - - // console2.log("nuUSD would be minted given NUMA: ", nuUSDAmount); - - // console2.log( - // "If the same nuUSD would be the input for reverse trade the NUMA amount: ", - // numaNeeded - // ); - - // // TODOTEST - // // refacto test - // // assertEq(numaAmount, numaNeeded); // it will fail because the code logic is not correct - // // assertEq(fee, fee2); // same as above. - // } - - // function test_Mint_EstimationsRefacto() external { - // // removing fees for now to see if it matches better - // vm.stopPrank(); - // vm.startPrank(deployer); - // moneyPrinter.setPrintAssetFeeBps(0); - // vm.stopPrank(); - // vm.startPrank(userA); - - // uint numaAmount = 1000e18; - - // (uint256 nuUSDAmount4, uint fee4) = moneyPrinter.getNbOfNuAssetFromNuma( - // address(nuUSD), - // numaAmount - // ); - // console2.log( - // "nuUSD would be minted given NUMA new fct: ", - // nuUSDAmount4 - // ); - - // (uint numaNeeded3, uint fee3) = moneyPrinter.getNbOfNumaNeededAndFee( - // address(nuUSD), - // nuUSDAmount4 - // ); - - // console2.log( - // "If the same nuUSD would be the input for reverse trade the NUMA amount new fct: ", - // numaNeeded3 - // ); - - // // TODOTEST - // // refacto test - // // assertEq(numaAmount, numaNeeded3); // it will fail because the code logic is not correct - // // assertEq(fee4, fee3); // same as above. - // } - - // function test_Burn() external { - // numa.approve(address(moneyPrinter), type(uint).max); - - // uint numaAmount = 10_000e18; - - // uint nuUSDMinted = moneyPrinter.mintAssetFromNumaInput( - // address(nuUSD), - // numaAmount, - // 0, - // deployer - // ); - - // console2.log("nuUSD minted", nuUSDMinted); - - // nuUSD.approve(address(moneyPrinter), type(uint).max); - - // uint numaMinted = moneyPrinter.burnAssetInputToNuma( - // address(nuUSD), - // nuUSDMinted, - // 0, - // deployer - // ); - - // console2.log("Numa minted", numaMinted); - // } - - // // gets - // function testFuzz_mintAssetEstimations(uint nuAssetAmount) public view { - // // we have 10000000 numa so we can not get more than 5000000 nuUSD - // // and there is a 5% print fee, so max is 4750000 - // vm.assume(nuAssetAmount <= 4750000 ether); - - // //vm.assume(nuAssetAmount == 16334);// KO - - // // test only reasonnable amounts - // vm.assume(nuAssetAmount > 0.000001 ether); - - // (uint cost, uint fee) = moneyPrinter.getNbOfNumaNeededAndFee( - // address(nuUSD), - // nuAssetAmount - // ); - - // // check the numbers - // uint256 amountToBurn = (cost * moneyPrinter.printAssetFeeBps()) / 10000; - // uint costWithoutFee = cost - amountToBurn; - // uint estimatedOutput = costWithoutFee; - - // console.log(cost); - // console.log(amountToBurn); - // assertEq(fee, amountToBurn, "fees estim ko"); - // assertApproxEqAbs( - // estimatedOutput, - // (nuAssetAmount * 1e18) / numaPricePoolL, - // 0.01 ether, - // "output estim ko" - // ); // epsilon is big... - - // // other direction - // (uint outAmount, uint fee2) = moneyPrinter.getNbOfNuAssetFromNuma( - // address(nuUSD), - // cost - // ); - // assertApproxEqAbs( - // outAmount, - // nuAssetAmount, - // 0.0000000001 ether, - // "nuAsset amount ko" - // ); - // assertEq(fee2, fee, "fee matching ko"); - // } - - // function testFuzz_mintAssetEstimationsVaultClipped(uint nuAssetAmount) public { - - // // we have 10000000 numa so we can not get more than 5000000 nuUSD - // // and there is a 5% print fee, so max is 4750000 - // vm.assume(nuAssetAmount <= 4750000 ether); - - // // as we use pool lowest price here, to be clipped by vault, we need to set vault price inferior - // // to do that we can mint some numa - // numa.mint(deployer,numaSupply/2); - - // uint numaPriceVault2 = vaultManager.numaToEth(1 ether,IVaultManager.PriceType.NoFeePrice); - // assertLt(numaPriceVault2,numaPriceVault,"new price ko"); - // assertLt(numaPriceVault2,numaPricePoolL,"new price ko"); - - // (uint cost,uint fee) = moneyPrinter.getNbOfNumaNeededAndFee(address(nuUSD),nuAssetAmount); - - // // check the numbers - // uint256 amountToBurn = (cost * moneyPrinter.printAssetFeeBps()) / 10000; - // uint costWithoutFee = cost - amountToBurn; - // uint estimatedOutput = costWithoutFee; - // assertEq(fee,amountToBurn,"fees estim ko"); - // assertEq(estimatedOutput,(nuAssetAmount*1e18)/numaPriceVault2,"output estim ko"); - - // // other direction - // (uint outAmount,uint fee2) = moneyPrinter.getNbOfNuAssetFromNuma(address(nuUSD),cost); - // assertEq(outAmount,nuAssetAmount,"nuAsset amount ko"); - // assertEq(fee2,fee,"fee matching ko"); - // } + + function test_priceFeedNoEth() external { + + USDToEthConverter converter = new USDToEthConverter(PRICEFEEDETHUSD_ARBI,HEART_BEAT_CUSTOM,UPTIME_FEED_ARBI); + NuAsset2 nuBTC2 = new NuAsset2("nuBTC2", "nuBTC2", deployer, deployer); + nuAssetMgr.addNuAsset( + address(nuBTC2), + PRICEFEEDBTCUSD_ARBI, + HEART_BEAT_CUSTOM, + false, + address(converter) + ); + + // set printer as a NuUSD minter + nuBTC2.grantRole(MINTER_ROLE, address(moneyPrinter)); + + uint numaAmount = 100000e18; + numa.transfer(userA, numaAmount); + + vm.stopPrank(); + vm.startPrank(userA); + + + + // TEST ESTIMATION + (uint256 nuBTCAmount, uint fee) = moneyPrinter.getNbOfNuAssetFromNuma( + address(nuBTC), + numaAmount + ); + + (uint256 nuBTCAmount2, uint fee2) = moneyPrinter.getNbOfNuAssetFromNuma( + address(nuBTC2), + numaAmount + ); + assertApproxEqAbs(nuBTCAmount, nuBTCAmount2,0.001 ether,"price ko"); + assertApproxEqAbs(fee, fee2, 0.00001 ether,"fee ko"); + + + // TEST ESTIMATION 2 (needed) + uint nuBtcAmount = 10e18; + + // compare getNbOfNuAssetFromNuma + (uint numaNeeded, uint fee3) = moneyPrinter.getNbOfNumaNeededAndFee( + address(nuBTC2), + nuBtcAmount + ); + (uint numaNeeded2, uint fee4) = moneyPrinter.getNbOfNumaNeededAndFee( + address(nuBTC), + nuBtcAmount + ); + assertApproxEqAbs(numaNeeded, numaNeeded2,5000 ether,"price ko"); + assertApproxEqAbs(fee, fee2, 50 ether,"fee ko"); + + // TEST TRANSACTION + uint balnuBTC2 = nuBTC2.balanceOf(userA); + uint balnuma = numa.balanceOf(userA); + + numa.approve(address(moneyPrinter),numaAmount); + moneyPrinter.mintAssetFromNumaInput( + address(nuBTC2), + numaAmount, + nuBTCAmount2, + userA + ); + uint balnuBTC2After = nuBTC2.balanceOf(userA); + uint balnumaAfter = numa.balanceOf(userA); + assertEq(balnuBTC2After - balnuBTC2, nuBTCAmount2); + assertEq(balnuma - balnumaAfter, numaAmount); + + // TEST TOTAL SYNTH VALUE + uint synthValue = nuAssetMgr.getTotalSynthValueEth(); + + + console2.log("nuBTCAmount2",nuBTCAmount2); + console2.log("(nuBTCAmount2*uint(btcusd))/(uint(ethusd))",(nuBTCAmount2*uint(btcusd))/(uint(ethusd))); + assertEq(synthValue, (nuBTCAmount2*uint(btcusd))/(uint(ethusd))); + + + } + + function forceSynthDebasing() public { uint globalCF = vaultManager.getGlobalCF(); - + console2.log("globalCF", globalCF); vm.startPrank(deployer); + // now we can not mint if after mint we become below warningCF. + // so I can not mint such that we become in cf_severe. + // simulate cf_severe can only be done by changing oracle prices + // or modifying cf_warning vaultManager.setScalingParameters( vaultManager.cf_critical(), - vaultManager.cf_warning(), + vaultManager.cf_warning() -1000, vaultManager.cf_severe(), vaultManager.debaseValue(), vaultManager.rebaseValue(), @@ -1310,6 +1248,18 @@ contract PrinterTest is Setup { vaultManager.criticalDebaseMult() ); + // vaultManager.setScalingParameters( + // vaultManager.cf_critical(), + // vaultManager.cf_warning(), + // vaultManager.cf_severe(), + // vaultManager.debaseValue(), + // vaultManager.rebaseValue(), + // 1 hours, + // 2 hours, + // vaultManager.minimumScale(), + // vaultManager.criticalDebaseMult() + // ); + numa.approve(address(moneyPrinter), 10000000 ether); // checking numa price @@ -1336,7 +1286,20 @@ contract PrinterTest is Setup { uint scaleSynthBurn2, uint criticalScaleForNumaPriceAndSellFee2 ) = vaultManager.getSynthScalingUpdate(); - assertEq(scaleSynthBurn2, 800); + + assertEq(scaleSynthBurn2, BASE_SCALE - 0.2 ether); + // put it back + vaultManager.setScalingParameters( + vaultManager.cf_critical(), + vaultManager.cf_warning() + 1000, + vaultManager.cf_severe(), + vaultManager.debaseValue(), + vaultManager.rebaseValue(), + 1 hours, + 2 hours, + vaultManager.minimumScale(), + vaultManager.criticalDebaseMult() + ); } function forceSynthDebasingCritical() public { @@ -1368,7 +1331,7 @@ contract PrinterTest is Setup { // //assertEq(scaleSynthBurn2,800); uint globalCF2 = vaultManager.getGlobalCF(); - console2.log(globalCF2); + console2.log("current CF",globalCF2); // critical_cf vaultManager.setScalingParameters( @@ -1383,247 +1346,4 @@ contract PrinterTest is Setup { vaultManager.criticalDebaseMult() ); } - - // function test_SynthScaling() public { - // uint globalCF = vaultManager.getGlobalCF(); - // assertGt(globalCF, vaultManager.cf_critical()); - // console2.log(globalCF); - // vm.startPrank(deployer); - // vaultManager.setScalingParameters( - // vaultManager.cf_critical(), - // vaultManager.cf_warning(), - // vaultManager.cf_severe(), - // vaultManager.debaseValue(), - // vaultManager.rebaseValue(), - // 1 hours, - // 2 hours, - // vaultManager.minimumScale(), - // vaultManager.criticalDebaseMult() - // ); - - // numa.approve(address(moneyPrinter), 10000000 ether); - - // moneyPrinter.mintAssetOutputFromNuma( - // address(nuUSD), - // 4500000 ether, - // 10000000 ether, - // deployer - // ); - - // uint globalCF2 = vaultManager.getGlobalCF(); - // console2.log(globalCF2); - // assertLt(globalCF2, globalCF); - - // (uint scaleSynthBurn,uint criticalScaleForNumaPriceAndSellFee) = vaultManager.getSynthScalingUpdate(); - // console2.log(scaleSynthBurn); - // console2.log(criticalScaleForNumaPriceAndSellFee); - // assertEq(scaleSynthBurn,1000); - // assertEq(criticalScaleForNumaPriceAndSellFee,1000); - // // test debase - // vm.warp(block.timestamp + 10 hours); - // (uint scaleSynthBurn2,uint criticalScaleForNumaPriceAndSellFee2) = vaultManager.getSynthScalingUpdate(); - // console2.log(scaleSynthBurn2); - // console2.log(criticalScaleForNumaPriceAndSellFee2); - // assertEq(scaleSynthBurn2,800); - // assertEq(criticalScaleForNumaPriceAndSellFee2,criticalScaleForNumaPriceAndSellFee); - - // vm.warp(block.timestamp + 10 hours); - // (scaleSynthBurn2,criticalScaleForNumaPriceAndSellFee2) = vaultManager.getSynthScalingUpdate(); - // console2.log(scaleSynthBurn2); - // console2.log(criticalScaleForNumaPriceAndSellFee2); - // assertEq(scaleSynthBurn2,600); - // assertEq(criticalScaleForNumaPriceAndSellFee2,criticalScaleForNumaPriceAndSellFee); - - // // min reached - // vm.warp(block.timestamp + 10 hours); - // (scaleSynthBurn2,criticalScaleForNumaPriceAndSellFee2) = vaultManager.getSynthScalingUpdate(); - // console2.log(scaleSynthBurn2); - // console2.log(criticalScaleForNumaPriceAndSellFee2); - // assertEq(scaleSynthBurn2,vaultManager.minimumScale()); - // assertEq(criticalScaleForNumaPriceAndSellFee2,criticalScaleForNumaPriceAndSellFee); - - // globalCF2 = vaultManager.getGlobalCF(); - // console2.log(globalCF2); - - // // critical_cf - // vaultManager.setScalingParameters( - // globalCF2 + 1, - // vaultManager.cf_warning(), - // vaultManager.cf_severe(), - // vaultManager.debaseValue(), - // vaultManager.rebaseValue(), - // 1 hours, - // 2 hours, - // vaultManager.minimumScale(), - // vaultManager.criticalDebaseMult() - // ); - // (uint scaleSynthBurn3,uint criticalScaleForNumaPriceAndSellFee3) = vaultManager.getSynthScalingUpdate(); - // console2.log(scaleSynthBurn3); - // console2.log(criticalScaleForNumaPriceAndSellFee3); - - // uint criticalDebaseFactor = (globalCF2 * 1000) / vaultManager.cf_critical(); - // console2.log("criticalDebaseFactor",criticalDebaseFactor); - - // uint scalePriceFee = criticalDebaseFactor; - - // // we apply this multiplier on the factor for when it's used on synthetics burning price - // criticalDebaseFactor = - // (criticalDebaseFactor * 1000) / - // vaultManager.criticalDebaseMult(); - - // console2.log("criticalDebaseFactorMult",criticalDebaseFactor); - - // console2.log("criticalScaleForNumaPriceAndSellFee",scalePriceFee); - - // // test that we use min for scaling - // assertEq(scaleSynthBurn3,vaultManager.minimumScale()); - // // for price and sell fee we use critical debase factor - // assertEq(criticalScaleForNumaPriceAndSellFee3,scalePriceFee); - - // // check critical scale value - // vaultManager.setScalingParameters( - // globalCF2 *3, - // vaultManager.cf_warning(), - // vaultManager.cf_severe(), - // vaultManager.debaseValue(), - // vaultManager.rebaseValue(), - // 1 hours, - // 2 hours, - // vaultManager.minimumScale(), - // vaultManager.criticalDebaseMult() - // ); - // (scaleSynthBurn3,criticalScaleForNumaPriceAndSellFee3) = vaultManager.getSynthScalingUpdate(); - // console2.log(scaleSynthBurn3); - // console2.log(criticalScaleForNumaPriceAndSellFee3); - - // criticalDebaseFactor = (globalCF2 * 1000) / vaultManager.cf_critical(); - // console2.log("criticalDebaseFactor",criticalDebaseFactor); - - // scalePriceFee = criticalDebaseFactor; - - // // we apply this multiplier on the factor for when it's used on synthetics burning price - // criticalDebaseFactor = - // (criticalDebaseFactor * 1000) / - // vaultManager.criticalDebaseMult(); - - // console2.log("criticalDebaseFactorMult",criticalDebaseFactor); - - // console2.log("criticalScaleForNumaPriceAndSellFee",scalePriceFee); - - // // test rebase - // vaultManager.setScalingParameters( - // 1100, - // vaultManager.cf_warning(), - // vaultManager.cf_severe(), - // vaultManager.debaseValue(), - // vaultManager.rebaseValue(), - // 1 hours, - // 2 hours, - // vaultManager.minimumScale(), - // vaultManager.criticalDebaseMult() - // ); - - // nuUSD.approve(address(moneyPrinter), 4500000 ether); - - // moneyPrinter.burnAssetInputToNuma( - // address(nuUSD), - // 4500000 ether, - // 0, - // userA - // ); - // (scaleSynthBurn2,criticalScaleForNumaPriceAndSellFee2) = vaultManager.getSynthScalingUpdate(); - // assertEq(scaleSynthBurn2,vaultManager.minimumScale()); - // assertEq(criticalScaleForNumaPriceAndSellFee2,criticalScaleForNumaPriceAndSellFee); - - // // rebase - // vm.warp(block.timestamp + 10 hours); - // (scaleSynthBurn2,criticalScaleForNumaPriceAndSellFee2) = vaultManager.getSynthScalingUpdate(); - // assertEq(scaleSynthBurn2,vaultManager.minimumScale()+150); - // assertEq(criticalScaleForNumaPriceAndSellFee2,criticalScaleForNumaPriceAndSellFee); - // // rebase again - // vm.warp(block.timestamp + 10 hours); - // (scaleSynthBurn2,criticalScaleForNumaPriceAndSellFee2) = vaultManager.getSynthScalingUpdate(); - // assertEq(scaleSynthBurn2,vaultManager.minimumScale()+300); - // assertEq(criticalScaleForNumaPriceAndSellFee2,criticalScaleForNumaPriceAndSellFee); - - // // test max - // vm.warp(block.timestamp + 30 hours); - // (scaleSynthBurn2,criticalScaleForNumaPriceAndSellFee2) = vaultManager.getSynthScalingUpdate(); - // assertEq(scaleSynthBurn2,1000); - // assertEq(criticalScaleForNumaPriceAndSellFee2,criticalScaleForNumaPriceAndSellFee); - - // } - // function testFuzz_mintAssetEstimationsSynthScaled( - // uint nuAssetAmount - // ) public { - // // we have 10000000 numa so we can not get more than 5000000 nuUSD - // // and there is a 5% print fee, so max is 4750000 - // vm.assume(nuAssetAmount <= 4750000 ether); - - // // - - // uint numaPriceVault2 = vaultManager.numaToEth( - // 1 ether, - // IVaultManager.PriceType.NoFeePrice - // ); - // assertLt(numaPriceVault2, numaPriceVault, "new price ko"); - // assertLt(numaPriceVault2, numaPricePoolL, "new price ko"); - - // (uint cost, uint fee) = moneyPrinter.getNbOfNumaNeededAndFee( - // address(nuUSD), - // nuAssetAmount - // ); - - // // check the numbers - // uint256 amountToBurn = (cost * moneyPrinter.printAssetFeeBps()) / 10000; - // uint costWithoutFee = cost - amountToBurn; - // uint estimatedOutput = costWithoutFee; - // assertEq(fee, amountToBurn, "fees estim ko"); - // assertEq( - // estimatedOutput, - // (nuAssetAmount * 1e18) / numaPriceVault2, - // "output estim ko" - // ); - - // // other direction - // (uint outAmount, uint fee2) = moneyPrinter.getNbOfNuAssetFromNuma( - // address(nuUSD), - // cost - // ); - // assertEq(outAmount, nuAssetAmount, "nuAsset amount ko"); - // assertEq(fee2, fee, "fee matching ko"); - // } - - // numa.approve(address(moneyPrinter),type(uint).max); - - // // check slippage test - // vm.expectRevert("min amount"); - // uint maxAmountReached = cost - 1; - // moneyPrinter.mintAssetOutputFromNuma(address(nuUSD),nuAssetAmount,maxAmountReached,deployer); - - // // check print - // uint numaBalBefore = numa.balanceOf(deployer); - // uint nuUSDBefore = nuUSD.balanceOf(deployer); - - // moneyPrinter.mintAssetOutputFromNuma(address(nuUSD),nuAssetAmount,cost,deployer); - // uint numaBalAfter = numa.balanceOf(deployer); - // uint nuUSDAfter = nuUSD.balanceOf(deployer); - - // assertEq(numaBalBefore - numaBalAfter,cost,"input amount ko"); - // assertEq(nuUSDAfter - nuUSDBefore,nuAssetAmount,"output amount ko"); - - // function testFuzz_SwapEstimations(uint nuUSDAmountIn) public { - - // // - // (uint nuBTCAmountOut,uint swapFee1) = moneyPrinter.getNbOfNuAssetFromNuAsset(address(nuUSD),address(nuBTC),nuUSDAmountIn); - // // check fee - // assertEq(swapFee1,(nuUSDAmountIn * moneyPrinter.swapAssetFeeBps()) / 10000); - // // - // (uint nuUSDCIn,uint swapFee2) = moneyPrinter.getNbOfNuAssetNeededForNuAsset(address(nuUSD),address(nuBTC),nuBTCAmountOut); - - // // check matching - // assertEq(nuUSDCIn,nuUSDAmountIn); - // // check fee - // assertEq(swapFee1,swapFee2); - // } } diff --git a/Numa/contracts/Test/VaultBuySellFee.t.sol b/Numa/contracts/Test/VaultBuySellFee.t.sol index 5518f52..836fec3 100644 --- a/Numa/contracts/Test/VaultBuySellFee.t.sol +++ b/Numa/contracts/Test/VaultBuySellFee.t.sol @@ -5,7 +5,7 @@ import "forge-std/console2.sol"; import {Setup} from "./utils/SetupDeployNuma_Arbitrum.sol"; import "../lending/ExponentialNoError.sol"; import "../interfaces/IVaultManager.sol"; - +import "../utils/constants.sol"; contract VaultBuySellFeeTest is Setup, ExponentialNoError { uint buy_fee_PID; function setUp() public virtual override { @@ -682,10 +682,24 @@ contract VaultBuySellFeeTest is Setup, ExponentialNoError { uint globalCF = vaultManager.getGlobalCF(); assertGt(globalCF, vaultManager.cf_critical()); - vm.prank(deployer); + vm.startPrank(deployer); numa.approve(address(moneyPrinter), 10000000 ether); - // function mintAssetOutputFromNuma( - vm.prank(deployer); + + // change warning cf so that we can mint enough + uint warningCF = vaultManager.cf_warning(); + vaultManager.setScalingParameters( + vaultManager.cf_critical(), + 0, + vaultManager.cf_severe(), + vaultManager.debaseValue(), + vaultManager.rebaseValue(), + 1 hours, + 2 hours, + vaultManager.minimumScale(), + vaultManager.criticalDebaseMult() + ); + + moneyPrinter.mintAssetOutputFromNuma( address(nuUSD), 4500000 ether, @@ -701,7 +715,7 @@ contract VaultBuySellFeeTest is Setup, ExponentialNoError { vm.startPrank(deployer); vaultManager.setScalingParameters( 1200, - vaultManager.cf_warning(), + warningCF, vaultManager.cf_severe(), vaultManager.debaseValue(), vaultManager.rebaseValue(), @@ -711,12 +725,14 @@ contract VaultBuySellFeeTest is Setup, ExponentialNoError { vaultManager.criticalDebaseMult() ); - uint criticalScaleForNumaPriceAndSellFee = (1000 * globalCF2) / + uint criticalScaleForNumaPriceAndSellFee = (BASE_SCALE * globalCF2) / vaultManager.cf_critical(); + console2.log("criticalScaleForNumaPriceAndSellFee TEST", criticalScaleForNumaPriceAndSellFee); - uint sell_fee_increaseCriticalCF = ((1000 - - criticalScaleForNumaPriceAndSellFee) * 1 ether) / 1000; + + uint sell_fee_increaseCriticalCF = ((BASE_SCALE - + criticalScaleForNumaPriceAndSellFee) * 1 ether) / BASE_SCALE; // add a multiplier on top sell_fee_increaseCriticalCF = (sell_fee_increaseCriticalCF * @@ -733,8 +749,9 @@ contract VaultBuySellFeeTest is Setup, ExponentialNoError { if (sell_fee_criticalCF < vaultManager.sell_fee_minimum_critical()) sell_fee_criticalCF = vaultManager.sell_fee_minimum_critical(); (sell_feePID, , ) = vaultManager.getSellFeeScaling(); - + console2.log("sell_fee_criticalCF", sell_fee_criticalCF); + console2.log("sell_feePID", sell_feePID); - assertEq(sell_feePID, sell_fee_criticalCF); + assertEq(sell_feePID, sell_fee_criticalCF,"sell fee critical"); } } diff --git a/Numa/contracts/Test/VaultMigrations.t.sol b/Numa/contracts/Test/VaultMigrations.t.sol index c4d9608..04bdef0 100644 --- a/Numa/contracts/Test/VaultMigrations.t.sol +++ b/Numa/contracts/Test/VaultMigrations.t.sol @@ -19,7 +19,7 @@ import "forge-std/console2.sol"; import {Setup} from "./utils/Setup_ArbitrumFork.sol"; import "../lending/ExponentialNoError.sol"; import "../interfaces/IVaultManager.sol"; -import {nuAssetManager} from "./../nuAssets/nuAssetManager.sol"; +import {nuAssetManager2} from "./../nuAssets/nuAssetManager2.sol"; import {NumaMinter} from "./../NumaProtocol/NumaMinter.sol"; import {VaultOracleSingle} from "./../NumaProtocol/VaultOracleSingle.sol"; import {VaultManager} from "./../NumaProtocol/VaultManager.sol"; @@ -39,7 +39,7 @@ contract VaultMigrationTest is Setup, ExponentialNoError { // uint sellResult; // - nuAssetManager nuAssetMgr2; + nuAssetManager2 nuAssetMgr2; NumaMinter numaMinter2; VaultOracleSingle vaultOracle2; VaultManager vaultManager2; diff --git a/Numa/contracts/Test/utils/ConstantsTest.sol b/Numa/contracts/Test/utils/ConstantsTest.sol index 72cddc8..3f75e52 100644 --- a/Numa/contracts/Test/utils/ConstantsTest.sol +++ b/Numa/contracts/Test/utils/ConstantsTest.sol @@ -62,6 +62,9 @@ contract ConstantsTest { // lending protocol uint blocksPerYear = 2102400; // TODO here eth values for test + uint borrowRateMaxMantissa = 0.0005e16;// TODO here eth values for test + + uint baseRatePerYear = 0.02 ether; // 2% uint multiplierPerYear = 0.01 ether; // 1% uint jumpMultiplierPerYear = 4 ether; //400% diff --git a/Numa/contracts/Test/utils/SetupBase.sol b/Numa/contracts/Test/utils/SetupBase.sol index fcfb4c8..75ec090 100644 --- a/Numa/contracts/Test/utils/SetupBase.sol +++ b/Numa/contracts/Test/utils/SetupBase.sol @@ -25,7 +25,7 @@ import {FakeNuma} from "../mocks/FakeNuma.sol"; import "../../interfaces/INuma.sol"; import {LstTokenMock} from "../mocks/LstTokenMock.sol"; -import {nuAssetManager} from "../../nuAssets/nuAssetManager.sol"; +import {nuAssetManager2} from "../../nuAssets/nuAssetManager2.sol"; import {NumaMinter} from "../../NumaProtocol/NumaMinter.sol"; import {VaultOracleSingle} from "../../NumaProtocol/VaultOracleSingle.sol"; import {VaultManager} from "../../NumaProtocol/VaultManager.sol"; @@ -61,7 +61,7 @@ contract SetupBase is ERC20 rEth; ERC20 usdc; // Vault - nuAssetManager nuAssetMgr; + nuAssetManager2 nuAssetMgr; NumaMinter numaMinter; VaultOracleSingle vaultOracle; VaultManager vaultManager; @@ -119,7 +119,7 @@ contract SetupBase is ) internal returns ( - nuAssetManager nuAM, + nuAssetManager2 nuAM, NumaMinter minter, VaultManager vaultm, VaultOracleSingle vo, @@ -141,7 +141,7 @@ contract SetupBase is address(rEth) ); - nuAM = new nuAssetManager(UPTIME_FEED_ARBI); + nuAM = new nuAssetManager2(UPTIME_FEED_ARBI); (nuAM, minter, vaultm, vo, v) = setupVaultAndAssetManager(parameters); @@ -217,19 +217,23 @@ contract SetupBase is address _printerAddress ) internal { // register nuAsset - nuAssetManager(_nuAssetMgrAddress).addNuAsset( + nuAssetManager2(_nuAssetMgrAddress).addNuAsset( address(nuUSD), PRICEFEEDETHUSD_ARBI, - HEART_BEAT_CUSTOM + HEART_BEAT_CUSTOM, + true, + address(0) ); // set printer as a NuUSD minter nuUSD.grantRole(MINTER_ROLE, _printerAddress); // owner is NuUSD deployer // register nuAsset - nuAssetManager(_nuAssetMgrAddress).addNuAsset( + nuAssetManager2(_nuAssetMgrAddress).addNuAsset( address(nuBTC), PRICEFEEDBTCETH_ARBI, - HEART_BEAT + HEART_BEAT, + false, + address(0) ); // set printer as a NuUSD minter nuBTC.grantRole(MINTER_ROLE, _printerAddress); // owner is NuUSD deployer @@ -327,6 +331,9 @@ contract SetupBase is address(_vault) ); + cReth._setBorrowRateMaxMantissa(borrowRateMaxMantissa); + cNuma._setBorrowRateMaxMantissa(borrowRateMaxMantissa); + _vault.setMaxBorrow(1000 ether); _vault.setCTokens(address(cNuma), address(cReth)); diff --git a/Numa/contracts/Test/utils/Setup_ArbitrumFork.sol b/Numa/contracts/Test/utils/Setup_ArbitrumFork.sol index 62c8f88..0e8d20c 100644 --- a/Numa/contracts/Test/utils/Setup_ArbitrumFork.sol +++ b/Numa/contracts/Test/utils/Setup_ArbitrumFork.sol @@ -62,7 +62,7 @@ contract Setup is SetupBase { function setUp() public virtual { numa_admin = NUMA_ADMIN; // setup fork - string memory ARBI_RPC_URL = vm.envString("URLARBI"); + string memory ARBI_RPC_URL = vm.envString("URL6"); uint256 arbitrumFork = vm.createFork(ARBI_RPC_URL); vm.selectFork(arbitrumFork); diff --git a/Numa/contracts/deployment/utils.sol b/Numa/contracts/deployment/utils.sol index 5b0bcd0..8bf3320 100644 --- a/Numa/contracts/deployment/utils.sol +++ b/Numa/contracts/deployment/utils.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.20; import "../interfaces/INuma.sol"; -import {nuAssetManager} from "../nuAssets/nuAssetManager.sol"; +import {nuAssetManager2} from "../nuAssets/nuAssetManager2.sol"; import {NumaMinter} from "../NumaProtocol/NumaMinter.sol"; import {VaultOracleSingle} from "../NumaProtocol/VaultOracleSingle.sol"; import {VaultManager} from "../NumaProtocol/VaultManager.sol"; @@ -30,7 +30,7 @@ contract deployUtils { ) public returns ( - nuAssetManager nuAM, + nuAssetManager2 nuAM, NumaMinter minter, VaultManager vaultm, VaultOracleSingle vo, @@ -39,9 +39,9 @@ contract deployUtils { { // nuAssetManager if (_parameters._existingAssetManager != address(0)) { - nuAM = nuAssetManager(_parameters._existingAssetManager); + nuAM = nuAssetManager2(_parameters._existingAssetManager); } else { - nuAM = new nuAssetManager(_parameters._uptimefeed); + nuAM = new nuAssetManager2(_parameters._uptimefeed); } diff --git a/Numa/contracts/deployment/vaultV2Deployer.sol b/Numa/contracts/deployment/vaultV2Deployer.sol index cc11db0..4747813 100644 --- a/Numa/contracts/deployment/vaultV2Deployer.sol +++ b/Numa/contracts/deployment/vaultV2Deployer.sol @@ -4,7 +4,7 @@ import "../interfaces/INuma.sol"; import "./utils.sol"; -import {nuAssetManager} from "../nuAssets/nuAssetManager.sol"; +import {nuAssetManager2} from "../nuAssets/nuAssetManager2.sol"; import {NumaMinter} from "../NumaProtocol/NumaMinter.sol"; import {VaultOracleSingle} from "../NumaProtocol/VaultOracleSingle.sol"; import {VaultManager} from "../NumaProtocol/VaultManager.sol"; @@ -27,7 +27,7 @@ contract vaultV2Deployer is deployUtils { address uptimefeed; // out - nuAssetManager public nuAssetMgr; + nuAssetManager2 public nuAssetMgr; NumaMinter public numaMinter; VaultOracleSingle public vaultOracle; VaultManager public vaultManager; diff --git a/Numa/contracts/interfaces/INumaVault.sol b/Numa/contracts/interfaces/INumaVault.sol index c220b90..0bc8461 100644 --- a/Numa/contracts/interfaces/INumaVault.sol +++ b/Numa/contracts/interfaces/INumaVault.sol @@ -18,4 +18,8 @@ interface INumaVault { function updateVault() external; function getcNumaAddress() external view returns (address); function getcLstAddress() external view returns (address); + + function getMinBorrowAmountAllowPartialLiquidation(address) external view returns (uint); + function borrowAllowed(address _ctokenAddress) external returns (bool); + } diff --git a/Numa/contracts/interfaces/IVaultManager.sol b/Numa/contracts/interfaces/IVaultManager.sol index b891e27..6f6ed97 100644 --- a/Numa/contracts/interfaces/IVaultManager.sol +++ b/Numa/contracts/interfaces/IVaultManager.sol @@ -50,4 +50,5 @@ interface IVaultManager { function getSynthScaling() external view returns (uint, uint, uint, uint); function getWarningCF() external view returns (uint); + function numaBorrowAllowed() external view returns (bool allowed); } diff --git a/Numa/contracts/lending/CNumaToken.sol b/Numa/contracts/lending/CNumaToken.sol index 318c58c..3b767bd 100644 --- a/Numa/contracts/lending/CNumaToken.sol +++ b/Numa/contracts/lending/CNumaToken.sol @@ -141,9 +141,20 @@ contract CNumaToken is CErc20Immutable { function leverageStrategy( uint _suppliedAmount, uint _borrowAmount, + uint _maxBorrowAmount, CNumaToken _collateral, uint _strategyIndex ) external { + + // Sherlock-issue 120 + require( + ( + ((address(this) == vault.getcLstAddress()) && (address(_collateral) == vault.getcNumaAddress())) + || + ((address(this) == vault.getcNumaAddress()) && (address(_collateral) == vault.getcLstAddress())) + ) + , "invalid collateral"); + // AUDITV2FIX if we don't do that, borrow balance might change when calling borrowinternal accrueInterest(); _collateral.accrueInterest(); @@ -179,24 +190,30 @@ contract CNumaToken is CErc20Immutable { ); // send collateral to sender - uint receivedtokens = balCtokenAfter - balCtokenBefore; - require(receivedtokens > 0, "no collateral"); + //uint receivedtokens = balCtokenAfter - balCtokenBefore; + require((balCtokenAfter - balCtokenBefore) > 0, "no collateral"); // transfer collateral to sender SafeERC20.safeTransfer( IERC20(address(_collateral)), msg.sender, - receivedtokens + (balCtokenAfter - balCtokenBefore) ); // how much to we need to borrow to repay vault uint borrowAmount = strat.getAmountIn(_borrowAmount, false); - // - uint accountBorrowBefore = accountBorrows[msg.sender].principal; + // sherlock issue-182 + require(borrowAmount <= _maxBorrowAmount); + + // Sherlock-issue 120 + //uint accountBorrowBefore = accountBorrows[msg.sender].principal; + uint accountBorrowBefore = borrowBalanceStored(msg.sender); + // borrow but do not transfer borrowed tokens borrowInternalNoTransfer(borrowAmount, msg.sender); - //uint accountBorrowAfter = accountBorrows[msg.sender].principal; + + // require( (accountBorrows[msg.sender].principal - accountBorrowBefore) == borrowAmount, @@ -263,6 +280,7 @@ contract CNumaToken is CErc20Immutable { function closeLeverageStrategy( CNumaToken _collateral, uint _borrowtorepay, + uint _minRedeemedAmount, uint _strategyIndex ) external { // AUDITV2FIX @@ -275,7 +293,6 @@ contract CNumaToken is CErc20Immutable { address underlyingCollateral = _collateral.underlying(); // get borrowed amount uint borrowAmountFull = borrowBalanceStored(msg.sender); - require(borrowAmountFull >= _borrowtorepay, "no borrow"); // clip to borrowed amount if (_borrowtorepay > borrowAmountFull) @@ -293,6 +310,8 @@ contract CNumaToken is CErc20Immutable { _borrowtorepay, _strategyIndex ); + // sherlock issue-182 + require(swapAmountIn >= _minRedeemedAmount); SafeERC20.safeTransferFrom( IERC20(address(_collateral)), @@ -346,17 +365,18 @@ contract CNumaToken is CErc20Immutable { * @param borrower The borrower of this cToken to be liquidated * @param repayAmount The amount of the underlying borrowed asset to repay * @param cTokenCollateral The market in which to seize collateral from the borrower - * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) - */ + * @return (uint error, uint badDebt) The error code and amount of bad debt */ function liquidateBorrow( address borrower, uint repayAmount, CTokenInterface cTokenCollateral - ) external override returns (uint) { + ) external override returns (uint,uint) { // only vault can liquidate require(msg.sender == address(vault), "vault only"); - liquidateBorrowInternal(borrower, repayAmount, cTokenCollateral); - return NO_ERROR; + // sherlock 101 153 returning bad debt so that vault can decide whether to clip + // profit or not + uint badDebt = liquidateBorrowInternal(borrower, repayAmount, cTokenCollateral); + return (NO_ERROR,badDebt); } function liquidateBadDebt( @@ -375,4 +395,69 @@ contract CNumaToken is CErc20Immutable { ); return NO_ERROR; } + + + /** + * @notice Users borrow assets from the protocol to their own address + * @param borrowAmount The amount of the underlying asset to borrow + */ + function borrowFreshNoTransfer( + address payable borrower, + uint borrowAmount + ) internal virtual override { + /* Fail if borrow not allowed */ + uint allowed = comptroller.borrowAllowed( + address(this), + borrower, + borrowAmount + ); + if (allowed != 0) { + revert BorrowComptrollerRejection(allowed); + } + + // check if vault allows borrows + // borrowing numa is not allowed when CF < CF_SEVERE + // only needed for numa borrows (lst borrows will go through CNumaLst::borrowFreshNoTransfer) + if (address(vault) != address(0)) { + if (!vault.borrowAllowed(address(this))) + { + revert BorrowNotAllowed(); + } + } + + + /* Verify market's block number equals current block number */ + if (accrualBlockNumber != getBlockNumber()) { + revert BorrowFreshnessCheck(); + } + + /* Fail gracefully if protocol has insufficient underlying cash */ + if (getCashPrior() < borrowAmount) { + revert BorrowCashNotAvailable(); + } + + /* + * We calculate the new borrower and total borrow balances, failing on overflow: + * accountBorrowNew = accountBorrow + borrowAmount + * totalBorrowsNew = totalBorrows + borrowAmount + */ + uint accountBorrowsPrev = borrowBalanceStoredInternal(borrower); + uint accountBorrowsNew = accountBorrowsPrev + borrowAmount; + uint totalBorrowsNew = totalBorrows + borrowAmount; + + ///////////////////////// + // EFFECTS & INTERACTIONS + // (No safe failures beyond this point) + + /* + * We write the previously calculated values into storage. + * Note: Avoid token reentrancy attacks by writing increased borrow before external transfer. + `*/ + accountBorrows[borrower].principal = accountBorrowsNew; + accountBorrows[borrower].interestIndex = borrowIndex; + totalBorrows = totalBorrowsNew; + + /* We emit a Borrow event */ + emit Borrow(borrower, borrowAmount, accountBorrowsNew, totalBorrowsNew); + } } diff --git a/Numa/contracts/lending/CToken.sol b/Numa/contracts/lending/CToken.sol index 4226745..fe3abe2 100644 --- a/Numa/contracts/lending/CToken.sol +++ b/Numa/contracts/lending/CToken.sol @@ -7,7 +7,7 @@ import "./ErrorReporter.sol"; import "./EIP20Interface.sol"; import "./InterestRateModel.sol"; import "./ExponentialNoError.sol"; -import "forge-std/console2.sol"; + /** * @title Compound's CToken Contract * @notice Abstract base for CTokens @@ -86,6 +86,9 @@ abstract contract CToken is address dst, uint tokens ) internal returns (uint) { + // sherlock issue 168 + // Before transferring CToken, the accrueInterest() function should be called first + accrueInterest(); /* Fail if transfer not allowed */ uint allowed = comptroller.transferAllowed( address(this), @@ -558,6 +561,17 @@ abstract contract CToken is uint mintTokens = div_(actualMintAmount, exchangeRate); + + + // sherlock issue-253 + // first depositor bug + if (totalSupply == 0) + { + totalSupply = 1000; + accountTokens[address(0)] = 1000; + mintTokens -= 1000; + } + /* * We calculate the new total supply of cTokens and minter token balance, checking for overflow: * totalSupplyNew = totalSupply + mintTokens @@ -646,7 +660,7 @@ abstract contract CToken is redeemer, redeemTokens ); - console2.log("redeem?",allowed); + if (allowed != 0) { revert RedeemComptrollerRejection(allowed); } @@ -897,12 +911,13 @@ abstract contract CToken is * @param borrower The borrower of this cToken to be liquidated * @param cTokenCollateral The market in which to seize collateral from the borrower * @param repayAmount The amount of the underlying borrowed asset to repay + * @return badDebt the amount of baddebt the position had */ function liquidateBorrowInternal( address borrower, uint repayAmount, CTokenInterface cTokenCollateral - ) internal nonReentrant { + ) internal nonReentrant returns (uint){ accrueInterest(); uint error = cTokenCollateral.accrueInterest(); @@ -912,7 +927,7 @@ abstract contract CToken is } // liquidateBorrowFresh emits borrow-specific logs on errors, so we don't need to - liquidateBorrowFresh( + return liquidateBorrowFresh( msg.sender, borrower, repayAmount, @@ -950,18 +965,18 @@ abstract contract CToken is * @param liquidator The address repaying the borrow and seizing collateral * @param cTokenCollateral The market in which to seize collateral from the borrower * @param repayAmount The amount of the underlying borrowed asset to repay + * @return badDebt the amount of baddebt the position had */ function liquidateBorrowFresh( address liquidator, address borrower, uint repayAmount, CTokenInterface cTokenCollateral - ) internal { + ) internal returns (uint) { /* Fail if liquidate not allowed */ - uint allowed = comptroller.liquidateBorrowAllowed( + (uint allowed,uint badDebt,uint restOfDebt) = comptroller.liquidateBorrowAllowed( address(this), address(cTokenCollateral), - liquidator, borrower, repayAmount ); @@ -1017,11 +1032,24 @@ abstract contract CToken is "LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED" ); - /* Revert if borrower collateral token balance < seizeTokens */ - require( - cTokenCollateral.balanceOf(borrower) >= seizeTokens, - "LIQUIDATE_SEIZE_TOO_MUCH" - ); + + + if (cTokenCollateral.balanceOf(borrower) < seizeTokens) { + // sherlock 101 153 + // not enough collateral to pay liquidator with incentives + // if we are not in bad debt territory, it will still be profitable to take all collateral + // but we do this only if it's a full liquidation, so that we don't become in bad debt after + if ((badDebt == 0) && (restOfDebt == 0)) { + // no bad debt + // full borrow balance liquidation + seizeTokens = cTokenCollateral.balanceOf(borrower); + } + else + { + revert("LIQUIDATE_SEIZE_TOO_MUCH") ; + } + + } // If this is also the collateral, run seizeInternal to avoid re-entrancy, otherwise make an external call if (address(cTokenCollateral) == address(this)) { @@ -1042,6 +1070,9 @@ abstract contract CToken is address(cTokenCollateral), seizeTokens ); + // sherlock 101 153 returning bad debt so that vault can decide whether to clip + // profit or not + return badDebt; } function liquidateBadDebtFresh( @@ -1542,6 +1573,22 @@ abstract contract CToken is } /** + * @notice change borrowRateMaxMantissa + * @dev needed because values should be adjustedbased on chain block time + * @param _borrowRateMaxMantissa the new value + */ + function _setBorrowRateMaxMantissa( + uint _borrowRateMaxMantissa + ) public override + { + if (msg.sender != admin) { + revert SetBorrowRateMaxMantissaOwnerCheck(); + } + borrowRateMaxMantissa = _borrowRateMaxMantissa; + } + + + /** * @notice accrues interest and updates the interest rate model using _setInterestRateModelFresh * @dev Admin function to accrue interest and update the interest rate model * @param newInterestRateModel the new interest rate model to use @@ -1555,6 +1602,7 @@ abstract contract CToken is return _setInterestRateModelFresh(newInterestRateModel); } + /** * @notice updates the interest rate model (*requires fresh interest accrual) * @dev Admin function to update the interest rate model diff --git a/Numa/contracts/lending/CTokenInterfaces.sol b/Numa/contracts/lending/CTokenInterfaces.sol index c858fca..7fd9dcb 100644 --- a/Numa/contracts/lending/CTokenInterfaces.sol +++ b/Numa/contracts/lending/CTokenInterfaces.sol @@ -28,7 +28,7 @@ contract CTokenStorage { uint8 public decimals; // Maximum borrow rate that can ever be applied (.0005% / block) - uint internal constant borrowRateMaxMantissa = 0.0005e16; + uint public borrowRateMaxMantissa = 0.0005e16; // Maximum fraction of interest that can be set aside for reserves uint internal constant reserveFactorMaxMantissa = 1e18; @@ -320,6 +320,11 @@ abstract contract CTokenInterface is CTokenStorage { function _setInterestRateModel( InterestRateModel newInterestRateModel ) external virtual returns (uint); + + function _setBorrowRateMaxMantissa( + uint _borrowRateMaxMantissa + ) external virtual; + } contract CErc20Storage { @@ -347,7 +352,7 @@ abstract contract CErc20Interface is CErc20Storage { address borrower, uint repayAmount, CTokenInterface cTokenCollateral - ) external virtual returns (uint); + ) external virtual returns (uint,uint); function liquidateBadDebt( address borrower, uint repayAmount, diff --git a/Numa/contracts/lending/ComptrollerInterface.sol b/Numa/contracts/lending/ComptrollerInterface.sol index 44996dd..70681af 100644 --- a/Numa/contracts/lending/ComptrollerInterface.sol +++ b/Numa/contracts/lending/ComptrollerInterface.sol @@ -74,10 +74,9 @@ abstract contract ComptrollerInterface { function liquidateBorrowAllowed( address cTokenBorrowed, address cTokenCollateral, - address liquidator, address borrower, uint repayAmount - ) external virtual returns (uint); + ) external virtual returns (uint,uint,uint); function liquidateBorrowVerify( address cTokenBorrowed, address cTokenCollateral, diff --git a/Numa/contracts/lending/ErrorReporter.sol b/Numa/contracts/lending/ErrorReporter.sol index 5221a34..faafee9 100644 --- a/Numa/contracts/lending/ErrorReporter.sol +++ b/Numa/contracts/lending/ErrorReporter.sol @@ -96,6 +96,8 @@ contract TokenErrorReporter { error BorrowFreshnessCheck(); error BorrowCashNotAvailable(); + error BorrowNotAllowed(); + error RepayBorrowComptrollerRejection(uint256 errorCode); error RepayBorrowFreshnessCheck(); @@ -129,5 +131,6 @@ contract TokenErrorReporter { error ReduceReservesCashValidation(); error SetInterestRateModelOwnerCheck(); + error SetBorrowRateMaxMantissaOwnerCheck(); error SetInterestRateModelFreshCheck(); } diff --git a/Numa/contracts/lending/Lens/CompoundLens.sol b/Numa/contracts/lending/Lens/CompoundLens.sol index 51fbad2..f0ed0e3 100644 --- a/Numa/contracts/lending/Lens/CompoundLens.sol +++ b/Numa/contracts/lending/Lens/CompoundLens.sol @@ -14,7 +14,7 @@ interface ComptrollerLensInterface { address, CToken, CToken - ) external view returns (uint, uint, uint, uint); + ) external view returns (uint, uint, uint, uint,uint); function getAssetsIn(address) external view returns (CToken[] memory); function borrowCaps(address) external view returns (uint); } @@ -222,7 +222,8 @@ contract CompoundLens { uint errorCode, uint liquidity, uint shortfall, - uint badDebt + uint badDebt, + ) = comptroller.getAccountLiquidityIsolate(account, collateral, borrow); require(errorCode == 0); diff --git a/Numa/contracts/lending/NumaComptroller.sol b/Numa/contracts/lending/NumaComptroller.sol index 1c3dccb..aa32888 100644 --- a/Numa/contracts/lending/NumaComptroller.sol +++ b/Numa/contracts/lending/NumaComptroller.sol @@ -110,6 +110,9 @@ contract NumaComptroller is // No collateralFactorMantissa may exceed this value uint internal constant collateralFactorMaxMantissa = 0.99e18; // 0.99 + uint ltvMinBadDebtLiquidations = 0.98 ether; + uint ltvMinPartialLiquidations = 1.1 ether; + constructor() { admin = msg.sender; } @@ -550,25 +553,22 @@ contract NumaComptroller is * @notice Checks if the liquidation should be allowed to occur * @param cTokenBorrowed Asset which was borrowed by the borrower * @param cTokenCollateral Asset which was used as collateral and will be seized - * @param liquidator The address repaying the borrow and seizing the collateral * @param borrower The address of the borrower * @param repayAmount The amount of underlying being repaid + * @return (error,baddebt,restOfDebt) */ function liquidateBorrowAllowed( address cTokenBorrowed, address cTokenCollateral, - address liquidator, address borrower, uint repayAmount - ) external view override returns (uint) { - // Shh - currently unused - liquidator; + ) external view override returns (uint,uint,uint) { require((cTokenBorrowed) != (cTokenCollateral), "not isolate"); if ( !markets[cTokenBorrowed].isListed || !markets[cTokenCollateral].isListed ) { - return uint(Error.MARKET_NOT_LISTED); + return (uint(Error.MARKET_NOT_LISTED),0,0); } uint borrowBalance = CToken(cTokenBorrowed).borrowBalanceStored( @@ -576,29 +576,62 @@ contract NumaComptroller is ); /* allow accounts to be liquidated if the market is deprecated */ + + ( + Error err, + , + uint shortfall, + uint badDebt, + uint ltv + + ) = getAccountLiquidityIsolateInternal( + borrower, + CNumaToken(cTokenCollateral), + CNumaToken(cTokenBorrowed) + ); + if (err != Error.NO_ERROR) { + return (uint(err),0,0); + } + + + // sherlock 101 153 + // min amount allowed for liquidations from vault + uint minAmount = 0; + bool badDebtLiquidationAllowed = false; + + // Min amount from vault. + // we should have a vault, if not, it will revert which is ok + minAmount = CNumaToken(cTokenBorrowed).vault().getMinBorrowAmountAllowPartialLiquidation(cTokenBorrowed); + + // if ltv > ltvMinPartialLiquidations, partial liquidations are enabled + if (ltv > ltvMinPartialLiquidations) + { + minAmount = 0; + // if ltv > ltvMinPartialLiquidations, it means we are in bad debt + // so we need to allow bad debt liquidation + badDebtLiquidationAllowed = true; + } + + if (borrowBalance < minAmount) minAmount = borrowBalance; + + require(repayAmount >= minAmount, "min liquidation"); + + if (isDeprecated(CToken(cTokenBorrowed))) { require( borrowBalance >= repayAmount, "Can not repay more than the total borrow" ); - } else { - /* The borrower must have shortfall in order to be liquidatable */ - ( - Error err, - , - uint shortfall, - uint badDebt - ) = getAccountLiquidityIsolateInternal( - borrower, - CNumaToken(cTokenCollateral), - CNumaToken(cTokenBorrowed) - ); - if (err != Error.NO_ERROR) { - return uint(err); + // sherlock issue 67. Even if deprecated we don't want that liquidation type if in bad debt + if (badDebt > 0 && (!badDebtLiquidationAllowed)) { + return (uint(Error.BAD_DEBT),badDebt,0); } + } else { - if (shortfall == 0) { - return uint(Error.INSUFFICIENT_SHORTFALL); + /* The borrower must have shortfall in order to be liquidatable */ + if (shortfall == 0) + { + return (uint(Error.INSUFFICIENT_SHORTFALL),badDebt,0); } /* The liquidator may not repay more than what is allowed by the closeFactor */ @@ -607,14 +640,16 @@ contract NumaComptroller is borrowBalance ); if (repayAmount > maxClose) { - return uint(Error.TOO_MUCH_REPAY); + return (uint(Error.TOO_MUCH_REPAY),badDebt,0); } /* revert if there is bad debt, specific bad debt liquidations functions should be called */ - if (badDebt > 0) { - return uint(Error.BAD_DEBT); + if (badDebt > 0 && (!badDebtLiquidationAllowed)) { + return (uint(Error.BAD_DEBT),badDebt,0); } } - return uint(Error.NO_ERROR); + // sherlock 101 153 returning badDebt and restOfDebt + // because we want to know if liquidation is partial and not in baddebt to allow taking all collateral + return (uint(Error.NO_ERROR),badDebt,borrowBalance - repayAmount); } function liquidateBadDebtAllowed( @@ -639,33 +674,38 @@ contract NumaComptroller is borrower ); + ( + Error err, + , + uint shortfall, + , + uint ltv + + ) = getAccountLiquidityIsolateInternal( + borrower, + CNumaToken(cTokenCollateral), + CNumaToken(cTokenBorrowed) + ); + if (err != Error.NO_ERROR) { + return uint(err); + } /* allow accounts to be liquidated if the market is deprecated */ if (isDeprecated(CToken(cTokenBorrowed))) { require( borrowBalance >= repayAmount, "Can not repay more than the total borrow" ); + // sherlock issue 67. Even if deprecated some bad debt is needed + if (ltv < ltvMinBadDebtLiquidations) { + return uint(Error.INSUFFICIENT_BADDEBT); + } } else { /* The borrower must have shortfall in order to be liquidatable */ - ( - Error err, - , - uint shortfall, - uint badDebt - ) = getAccountLiquidityIsolateInternal( - borrower, - CNumaToken(cTokenCollateral), - CNumaToken(cTokenBorrowed) - ); - if (err != Error.NO_ERROR) { - return uint(err); - } - if (shortfall == 0) { return uint(Error.INSUFFICIENT_SHORTFALL); } - - if (badDebt == 0) { + // bad debt liquidation allowed only above ltvMinBadDebtLiquidations + if (ltv < ltvMinBadDebtLiquidations) { return uint(Error.INSUFFICIENT_BADDEBT); } } @@ -860,28 +900,29 @@ contract NumaComptroller is address account, CNumaToken collateral, CNumaToken borrow - ) public view returns (uint, uint, uint, uint) { + ) public view returns (uint, uint, uint, uint,uint) { ( Error err, uint liquidity, uint shortfall, - uint badDebt + uint badDebt, + uint ltv ) = getAccountLiquidityIsolateInternal(account, collateral, borrow); - return (uint(err), liquidity, shortfall, badDebt); + return (uint(err), liquidity, shortfall, badDebt,ltv); } - function getAccountLTVIsolate( - address account, - CNumaToken collateral, - CNumaToken borrow - ) public view returns (uint, uint) { - (Error err, uint ltv) = getAccountLTVIsolateInternal( - account, - collateral, - borrow - ); - return (uint(err), ltv); - } + // function getAccountLTVIsolate( + // address account, + // CNumaToken collateral, + // CNumaToken borrow + // ) public view returns (uint, uint) { + // (Error err, uint ltv) = getAccountLTVIsolateInternal( + // account, + // collateral, + // borrow + // ); + // return (uint(err), ltv); + // } /** * @notice Determine the current account liquidity wrt collateral requirements @@ -893,7 +934,7 @@ contract NumaComptroller is address account, CNumaToken collateral, CNumaToken borrow - ) internal view returns (Error, uint, uint, uint) { + ) internal view returns (Error, uint, uint, uint,uint) { AccountLiquidityLocalVars memory vars; // Holds all our calculation results uint oErr; @@ -908,7 +949,7 @@ contract NumaComptroller is ) = collateral.getAccountSnapshot(account); if (oErr != 0) { // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades - return (Error.SNAPSHOT_ERROR, 0, 0, 0); + return (Error.SNAPSHOT_ERROR, 0, 0, 0,0); } vars.collateralFactor = Exp({ mantissa: markets[address(collateral)].collateralFactorMantissa @@ -919,7 +960,7 @@ contract NumaComptroller is vars.oraclePriceMantissaCollateral = oracle .getUnderlyingPriceAsCollateral(collateral); if (vars.oraclePriceMantissaCollateral == 0) { - return (Error.PRICE_ERROR, 0, 0, 0); + return (Error.PRICE_ERROR, 0, 0, 0,0); } vars.oraclePriceCollateral = Exp({ mantissa: vars.oraclePriceMantissaCollateral @@ -958,7 +999,7 @@ contract NumaComptroller is ) = borrow.getAccountSnapshot(account); if (oErr != 0) { // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades - return (Error.SNAPSHOT_ERROR, 0, 0, 0); + return (Error.SNAPSHOT_ERROR, 0, 0, 0,0); } // Get the normalized price of the asset vars.oraclePriceMantissaBorrowed = oracle.getUnderlyingPriceAsBorrowed( @@ -966,7 +1007,7 @@ contract NumaComptroller is ); if (vars.oraclePriceMantissaBorrowed == 0) { - return (Error.PRICE_ERROR, 0, 0, 0); + return (Error.PRICE_ERROR, 0, 0, 0,0); } //vars.oraclePriceCollateral = Exp({mantissa: vars.oraclePriceMantissaCollateral}); vars.oraclePriceBorrowed = Exp({ @@ -981,13 +1022,25 @@ contract NumaComptroller is vars.sumBorrowPlusEffects ); + uint ltv; + if (vars.sumCollateralNoCollateralFactor > 0) + { + ltv = (vars.sumBorrowPlusEffects * 1 ether) / vars.sumCollateralNoCollateralFactor; + } + else if (vars.sumBorrowPlusEffects > 0) + { + // no collateral but some borrow, ltv is infinite + ltv = type(uint).max; + + } // These are safe, as the underflow condition is checked first if (vars.sumCollateral > vars.sumBorrowPlusEffects) { return ( Error.NO_ERROR, vars.sumCollateral - vars.sumBorrowPlusEffects, 0, - 0 + 0, + ltv ); } else { if ( @@ -997,7 +1050,8 @@ contract NumaComptroller is Error.NO_ERROR, 0, vars.sumBorrowPlusEffects - vars.sumCollateral, - 0 + 0, + ltv ); // returning bad debt else @@ -1006,108 +1060,109 @@ contract NumaComptroller is 0, vars.sumBorrowPlusEffects - vars.sumCollateral, vars.sumBorrowPlusEffects - - vars.sumCollateralNoCollateralFactor + vars.sumCollateralNoCollateralFactor, + ltv ); } } - function getAccountLTVIsolateInternal( - address account, - CNumaToken collateral, - CNumaToken borrow - ) internal view returns (Error, uint) { - AccountLiquidityLocalVars memory vars; // Holds all our calculation results - uint oErr; - - // Here we only consider only 2 tokens: collateral token and borrow token which should be different - // collateral - // Read the balances and exchange rate from the cToken - ( - oErr, - vars.cTokenBalance, - vars.borrowBalance, - vars.exchangeRateMantissa - ) = collateral.getAccountSnapshot(account); - if (oErr != 0) { - // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades - return (Error.SNAPSHOT_ERROR, 0); - } - vars.collateralFactor = Exp({ - mantissa: markets[address(collateral)].collateralFactorMantissa - }); - vars.exchangeRate = Exp({mantissa: vars.exchangeRateMantissa}); - - // Get the normalized price of the asset - vars.oraclePriceMantissaCollateral = oracle - .getUnderlyingPriceAsCollateral(collateral); - if (vars.oraclePriceMantissaCollateral == 0) { - return (Error.PRICE_ERROR, 0); - } - vars.oraclePriceCollateral = Exp({ - mantissa: vars.oraclePriceMantissaCollateral - }); - - // Pre-compute a conversion factor from tokens -> ether (normalized price value) - vars.tokensToDenomCollateral = mul_( - mul_(vars.collateralFactor, vars.exchangeRate), - vars.oraclePriceCollateral - ); - vars.tokensToDenomCollateralNoCollateralFactor = mul_( - vars.exchangeRate, - vars.oraclePriceCollateral - ); - // sumCollateral += tokensToDenom * cTokenBalance + // function getAccountLTVIsolateInternal( + // address account, + // CNumaToken collateral, + // CNumaToken borrow + // ) internal view returns (Error, uint) { + // AccountLiquidityLocalVars memory vars; // Holds all our calculation results + // uint oErr; - // NUMALENDING: use collateral price - vars.sumCollateral = mul_ScalarTruncateAddUInt( - vars.tokensToDenomCollateral, - vars.cTokenBalance, - vars.sumCollateral - ); - vars.sumCollateralNoCollateralFactor = mul_ScalarTruncateAddUInt( - vars.tokensToDenomCollateralNoCollateralFactor, - vars.cTokenBalance, - vars.sumCollateralNoCollateralFactor - ); + // // Here we only consider only 2 tokens: collateral token and borrow token which should be different + // // collateral + // // Read the balances and exchange rate from the cToken + // ( + // oErr, + // vars.cTokenBalance, + // vars.borrowBalance, + // vars.exchangeRateMantissa + // ) = collateral.getAccountSnapshot(account); + // if (oErr != 0) { + // // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades + // return (Error.SNAPSHOT_ERROR, 0); + // } + // vars.collateralFactor = Exp({ + // mantissa: markets[address(collateral)].collateralFactorMantissa + // }); + // vars.exchangeRate = Exp({mantissa: vars.exchangeRateMantissa}); + + // // Get the normalized price of the asset + // vars.oraclePriceMantissaCollateral = oracle + // .getUnderlyingPriceAsCollateral(collateral); + // if (vars.oraclePriceMantissaCollateral == 0) { + // return (Error.PRICE_ERROR, 0); + // } + // vars.oraclePriceCollateral = Exp({ + // mantissa: vars.oraclePriceMantissaCollateral + // }); + + // // Pre-compute a conversion factor from tokens -> ether (normalized price value) + // vars.tokensToDenomCollateral = mul_( + // mul_(vars.collateralFactor, vars.exchangeRate), + // vars.oraclePriceCollateral + // ); + // vars.tokensToDenomCollateralNoCollateralFactor = mul_( + // vars.exchangeRate, + // vars.oraclePriceCollateral + // ); + // // sumCollateral += tokensToDenom * cTokenBalance - // borrow - // Read the balances and exchange rate from the cToken - ( - oErr, - vars.cTokenBalance, - vars.borrowBalance, - vars.exchangeRateMantissa - ) = borrow.getAccountSnapshot(account); - if (oErr != 0) { - // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades - return (Error.SNAPSHOT_ERROR, 0); - } - // Get the normalized price of the asset - vars.oraclePriceMantissaBorrowed = oracle.getUnderlyingPriceAsBorrowed( - borrow - ); + // // NUMALENDING: use collateral price + // vars.sumCollateral = mul_ScalarTruncateAddUInt( + // vars.tokensToDenomCollateral, + // vars.cTokenBalance, + // vars.sumCollateral + // ); + // vars.sumCollateralNoCollateralFactor = mul_ScalarTruncateAddUInt( + // vars.tokensToDenomCollateralNoCollateralFactor, + // vars.cTokenBalance, + // vars.sumCollateralNoCollateralFactor + // ); - if (vars.oraclePriceMantissaBorrowed == 0) { - return (Error.PRICE_ERROR, 0); - } - //vars.oraclePriceCollateral = Exp({mantissa: vars.oraclePriceMantissaCollateral}); - vars.oraclePriceBorrowed = Exp({ - mantissa: vars.oraclePriceMantissaBorrowed - }); + // // borrow + // // Read the balances and exchange rate from the cToken + // ( + // oErr, + // vars.cTokenBalance, + // vars.borrowBalance, + // vars.exchangeRateMantissa + // ) = borrow.getAccountSnapshot(account); + // if (oErr != 0) { + // // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades + // return (Error.SNAPSHOT_ERROR, 0); + // } + // // Get the normalized price of the asset + // vars.oraclePriceMantissaBorrowed = oracle.getUnderlyingPriceAsBorrowed( + // borrow + // ); - // sumBorrowPlusEffects += oraclePrice * borrowBalance - // NUMALENDING: use borrow price - vars.sumBorrowPlusEffects = mul_ScalarTruncateAddUInt( - vars.oraclePriceBorrowed, - vars.borrowBalance, - vars.sumBorrowPlusEffects - ); + // if (vars.oraclePriceMantissaBorrowed == 0) { + // return (Error.PRICE_ERROR, 0); + // } + // //vars.oraclePriceCollateral = Exp({mantissa: vars.oraclePriceMantissaCollateral}); + // vars.oraclePriceBorrowed = Exp({ + // mantissa: vars.oraclePriceMantissaBorrowed + // }); + + // // sumBorrowPlusEffects += oraclePrice * borrowBalance + // // NUMALENDING: use borrow price + // vars.sumBorrowPlusEffects = mul_ScalarTruncateAddUInt( + // vars.oraclePriceBorrowed, + // vars.borrowBalance, + // vars.sumBorrowPlusEffects + // ); - return ( - Error.NO_ERROR, - (vars.sumBorrowPlusEffects * 1000) / vars.sumCollateral - ); - } + // return ( + // Error.NO_ERROR, + // (vars.sumBorrowPlusEffects * 1 ether) / vars.sumCollateral + // ); + // } /** * @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed @@ -1513,6 +1568,26 @@ contract NumaComptroller is /*** Admin Functions ***/ + /** + * @notice Sets the ltv thresholds used when liquidating borrows + * @dev Admin function to set ltv thresholds + * @param _ltvMinBadDebt min ltv to allow bad debt liquidation + * @param _ltvMinPartialLiquidation min to allow partial liquidation + * @return uint 0=success, otherwise a failure + */ + function _setLtvThresholds( + uint _ltvMinBadDebt, + uint _ltvMinPartialLiquidation + ) external returns (uint) { + // Check caller is admin + require(msg.sender == admin, "only admin"); + ltvMinBadDebtLiquidations = _ltvMinBadDebt; + ltvMinPartialLiquidations = _ltvMinPartialLiquidation; + + return uint(Error.NO_ERROR); + } + + /** * @notice Sets a new price oracle for the comptroller * @dev Admin function to set a new price oracle diff --git a/Numa/contracts/libraries/OracleUtils2.sol b/Numa/contracts/libraries/OracleUtils2.sol new file mode 100644 index 0000000..10a8ec1 --- /dev/null +++ b/Numa/contracts/libraries/OracleUtils2.sol @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.20; +import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV2V3Interface.sol"; +import {IChainlinkAggregator} from "../interfaces/IChainlinkAggregator.sol"; +import {IChainlinkPriceFeed} from "../interfaces/IChainlinkPriceFeed.sol"; + +import "@uniswap/v3-core/contracts/libraries/FullMath.sol"; + +contract OracleUtils2 { + uint256 private constant GRACE_PERIOD_TIME = 3600; + + error SequencerDown(); + error GracePeriodNotOver(); + + address internal sequencerUptimeFeed; + constructor(address _uptimeFeedAddress) { + sequencerUptimeFeed = _uptimeFeedAddress; + } + + modifier checkSequencerActive() { + if (sequencerUptimeFeed != address(0)) { + ( + , + /*uint80 roundID*/ int256 answer, + uint256 startedAt /*uint256 updatedAt*/ /*uint80 answeredInRound*/, + , + + ) = AggregatorV2V3Interface(sequencerUptimeFeed).latestRoundData(); + + // Answer == 0: Sequencer is up + // Answer == 1: Sequencer is down + bool isSequencerUp = answer == 0; + if (!isSequencerUp) { + revert SequencerDown(); + } + + // Make sure the grace period has passed after the + // sequencer is back up. + uint256 timeSinceUp = block.timestamp - startedAt; + if (timeSinceUp <= GRACE_PERIOD_TIME) { + revert GracePeriodNotOver(); + } + } + _; + } + + /** + * @dev chainlink call to a pricefeed with any amount + */ + function refToToken( + uint256 _ethAmount, + address _pricefeed, + uint128 _chainlink_heartbeat, + uint256 _decimals, + bool _isLeft + ) public view checkSequencerActive returns (uint256 tokenAmount) { + ( + uint80 roundID, + int256 price, + , + uint256 timeStamp, + uint80 answeredInRound + ) = AggregatorV3Interface(_pricefeed).latestRoundData(); + + // heartbeat check + require( + timeStamp >= block.timestamp - _chainlink_heartbeat, + "Stale pricefeed" + ); + + // minAnswer/maxAnswer check + IChainlinkAggregator aggregator = IChainlinkAggregator( + IChainlinkPriceFeed(_pricefeed).aggregator() + ); + require( + ((price > int256(aggregator.minAnswer())) && + (price < int256(aggregator.maxAnswer()))), + "min/max reached" + ); + + require(answeredInRound >= roundID, "Answer given before round"); + + //if ref is on the left side of the fraction in the price feed + if (_isLeft) { + tokenAmount = FullMath.mulDiv( + _ethAmount, + uint256(price), + 10 ** AggregatorV3Interface(_pricefeed).decimals() + ); + + } else { + tokenAmount = FullMath.mulDiv( + _ethAmount, + 10 ** AggregatorV3Interface(_pricefeed).decimals(), + uint256(price) + ); + + } + + // audit fix + tokenAmount = tokenAmount * 10 ** (18 - _decimals); + } + + /** + * @dev chainlink call to a pricefeed with any amount + */ + function refToTokenRoundUp( + uint256 _ethAmount, + address _pricefeed, + uint128 _chainlink_heartbeat, + uint256 _decimals, + bool _isLeft + ) public view checkSequencerActive returns (uint256 tokenAmount) { + ( + uint80 roundID, + int256 price, + , + uint256 timeStamp, + uint80 answeredInRound + ) = AggregatorV3Interface(_pricefeed).latestRoundData(); + + // heartbeat check + require( + timeStamp >= block.timestamp - _chainlink_heartbeat, + "Stale pricefeed" + ); + + // minAnswer/maxAnswer check + IChainlinkAggregator aggregator = IChainlinkAggregator( + IChainlinkPriceFeed(_pricefeed).aggregator() + ); + require( + ((price > int256(aggregator.minAnswer())) && + (price < int256(aggregator.maxAnswer()))), + "min/max reached" + ); + + require(answeredInRound >= roundID, "Answer given before round"); + + //if ref is on the left side of the fraction in the price feed + if (_isLeft) { + tokenAmount = FullMath.mulDivRoundingUp( + _ethAmount, + uint256(price), + 10 ** AggregatorV3Interface(_pricefeed).decimals() + ); + } else { + tokenAmount = FullMath.mulDivRoundingUp( + _ethAmount, + 10 ** AggregatorV3Interface(_pricefeed).decimals(), + uint256(price) + ); + } + // audit fix + tokenAmount = tokenAmount * 10 ** (18 - _decimals); + } + + /** + * @dev chainlink call to a pricefeed with any amount + */ + function tokenToRef( + uint256 _amount, + address _pricefeed, + uint128 _chainlink_heartbeat, + uint256 _decimals, + bool _isLeft + ) public view checkSequencerActive returns (uint256 RefValue) { + ( + uint80 roundID, + int256 price, + , + uint256 timeStamp, + uint80 answeredInRound + ) = AggregatorV3Interface(_pricefeed).latestRoundData(); + + // heartbeat check + require( + timeStamp >= block.timestamp - _chainlink_heartbeat, + "Stale pricefeed" + ); + + // minAnswer/maxAnswer check + IChainlinkAggregator aggregator = IChainlinkAggregator( + IChainlinkPriceFeed(_pricefeed).aggregator() + ); + require( + ((price > int256(aggregator.minAnswer())) && + (price < int256(aggregator.maxAnswer()))), + "min/max reached" + ); + + require(answeredInRound >= roundID, "Answer given before round"); + + //if ref is on the left side of the fraction in the price feed + if (_isLeft) { + RefValue = FullMath.mulDiv( + _amount, + 10 ** AggregatorV3Interface(_pricefeed).decimals(), + uint256(price) + ); + } else { + RefValue = FullMath.mulDiv( + _amount, + uint256(price), + 10 ** AggregatorV3Interface(_pricefeed).decimals() + ); + } + + // audit fix + RefValue = RefValue * 10 ** (18 - _decimals); + } + + /** + * @dev chainlink call to a pricefeed with any amount + */ + function tokenToRefRoundUp( + uint256 _amount, + address _pricefeed, + uint128 _chainlink_heartbeat, + uint256 _decimals, + bool _isLeft + ) public view checkSequencerActive returns (uint256 RefValue) { + ( + uint80 roundID, + int256 price, + , + uint256 timeStamp, + uint80 answeredInRound + ) = AggregatorV3Interface(_pricefeed).latestRoundData(); + + // heartbeat check + require( + timeStamp >= block.timestamp - _chainlink_heartbeat, + "Stale pricefeed" + ); + + // minAnswer/maxAnswer check + IChainlinkAggregator aggregator = IChainlinkAggregator( + IChainlinkPriceFeed(_pricefeed).aggregator() + ); + require( + ((price > int256(aggregator.minAnswer())) && + (price < int256(aggregator.maxAnswer()))), + "min/max reached" + ); + + require(answeredInRound >= roundID, "Answer given before round"); + + //if ref is on the left side of the fraction in the price feed + if (_isLeft) { + RefValue = FullMath.mulDivRoundingUp( + _amount, + 10 ** AggregatorV3Interface(_pricefeed).decimals(), + uint256(price) + ); + } else { + RefValue = FullMath.mulDivRoundingUp( + _amount, + uint256(price), + 10 ** AggregatorV3Interface(_pricefeed).decimals() + ); + } + // audit fix + RefValue = RefValue * 10 ** (18 - _decimals); + } +} diff --git a/Numa/contracts/nuAssets/nuAssetManager2.sol b/Numa/contracts/nuAssets/nuAssetManager2.sol new file mode 100644 index 0000000..f243bbd --- /dev/null +++ b/Numa/contracts/nuAssets/nuAssetManager2.sol @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.20; + +import "@openzeppelin/contracts_5.0.2/token/ERC20/utils/SafeERC20.sol"; + +import "@openzeppelin/contracts_5.0.2/token/ERC20/extensions/IERC20Metadata.sol"; + +import "@openzeppelin/contracts_5.0.2/access/Ownable2Step.sol"; + +import "../libraries/OracleUtils2.sol"; +import "../interfaces/INuAssetManager.sol"; +import "../interfaces/INumaTokenToEthConverter.sol"; +// struct representing a nuAsset: index in list (starts at 1), and pricefeed address +struct nuAssetInfo { + address feed; + uint128 heartbeat; + uint index; + bool isLeft; + address converterAddress; +} + +/// @title nuAssets manager +/// @notice used to compute total synthetics value in Eth +contract nuAssetManager2 is INuAssetManager, OracleUtils2, Ownable2Step { + // nuAsset to nuAssetInfo mapping + mapping(address => nuAssetInfo) public nuAssetInfos; + // list of nuAssets + address[] public nuAssetList; + + // max number of nuAssets this contract can handle + uint constant max_nuasset = 200; + + bool public renounceAddingRemovingAssets = false; + + + + event AddedAsset(address _assetAddress, address _pricefeed); + event UpdatedAsset(address _assetAddress, address _pricefeed); + event RemovedAsset(address _assetAddress); + constructor(address _uptimeFeedAddress) Ownable(msg.sender) OracleUtils2(_uptimeFeedAddress) + { + + } + + /** + * @dev returns nuAssets list + */ + function getNuAssetList() external view returns (address[] memory) { + return nuAssetList; + } + + function getNuAssetInfo( + address _nuAsset + ) public view returns (nuAssetInfo memory) { + return nuAssetInfos[_nuAsset]; + } + + function renounceAddingRemoving() external onlyOwner { + renounceAddingRemovingAssets = true; + } + + /** + * @dev does a nuAsset belong to our list + */ + function contains(address _assetAddress) public view returns (bool) { + return (nuAssetInfos[_assetAddress].index != 0); + } + + /** + * @dev adds a newAsset to the list + */ + function addNuAsset( + address _assetAddress, + address _pricefeed, + uint128 _heartbeat, + bool _isLeft, + address _converterAddress + ) external onlyOwner { + require(!renounceAddingRemovingAssets, "adding nuAsset renounced"); + require(_assetAddress != address(0), "invalid nuasset address"); + require(_pricefeed != address(0), "invalid price feed address"); + require(!contains(_assetAddress), "already added"); + require(nuAssetList.length < max_nuasset, "too many nuAssets"); + + // add to list + nuAssetList.push(_assetAddress); + // add to mapping + nuAssetInfos[_assetAddress] = nuAssetInfo( + _pricefeed, + _heartbeat, + nuAssetList.length, + _isLeft, + _converterAddress + ); + emit AddedAsset(_assetAddress, _pricefeed); + } + + + + /** + * @dev removes a newAsset from the list + */ + function removeNuAsset(address _assetAddress) external onlyOwner { + require(!renounceAddingRemovingAssets, "adding nuAsset renounced"); + require(contains(_assetAddress), "not in list"); + // find out the index + uint256 index = nuAssetInfos[_assetAddress].index; + // moves last element to the place of the value + // so there are no free spaces in the array + address lastValue = nuAssetList[nuAssetList.length - 1]; + nuAssetList[index - 1] = lastValue; + nuAssetInfos[lastValue].index = index; + + // delete the index + delete nuAssetInfos[_assetAddress]; + + // deletes last element and reduces array size + nuAssetList.pop(); + emit RemovedAsset(_assetAddress); + } + + /** + * @dev updates a newAsset from the list + */ + function updateNuAsset( + address _assetAddress, + address _pricefeed, + uint128 _heartbeat, + bool _isLeft, + address _converterAddress + ) external onlyOwner { + require(_assetAddress != address(0), "invalid nuasset address"); + require(_pricefeed != address(0), "invalid price feed address"); + require(contains(_assetAddress), "not in list"); + // find out the index + uint256 index = nuAssetInfos[_assetAddress].index; + nuAssetInfos[_assetAddress] = nuAssetInfo( + _pricefeed, + _heartbeat, + index, + _isLeft, + _converterAddress + ); + + emit UpdatedAsset(_assetAddress, _pricefeed); + } + + /** + * @dev total synth value in Eth (in wei) + */ + function getTotalSynthValueEth() external view returns (uint256) { + uint result; + uint256 nbNuAssets = nuAssetList.length; + require(nbNuAssets <= max_nuasset, "too many nuAssets in list"); + for (uint256 i = 0; i < nbNuAssets; i++) { + address nuAsset = nuAssetList[i]; + uint256 totalSupply = IERC20(nuAsset).totalSupply(); + if (totalSupply > 0) { + nuAssetInfo memory info = nuAssetInfos[nuAsset]; + + (address priceFeed, uint128 heartbeat,bool isleft,address converter) = ( + info.feed, + info.heartbeat, + info.isLeft, + info.converterAddress + ); + + uint256 EthValue = tokenToRef( + totalSupply, + priceFeed, + heartbeat, + IERC20Metadata(nuAsset).decimals(), + isleft + ); + if (converter != address(0)) + { + // pricefeed was not in ETH + // we need to convert to ETH + EthValue = INumaTokenToEthConverter(converter).convertTokenToEth(EthValue); + } + + result += EthValue; + } + } + return result; + } + + function nuAssetToEth( + address _nuAsset, + uint256 _amount + ) public view returns (uint256 EthValue) { + require(contains(_nuAsset), "bad nuAsset"); + nuAssetInfo memory info = getNuAssetInfo(_nuAsset); + (address priceFeed, uint128 heartbeat,bool isleft,address converter) = ( + info.feed, + info.heartbeat, + info.isLeft, + info.converterAddress + ); + + EthValue = tokenToRef( + _amount, + priceFeed, + heartbeat, + IERC20Metadata(_nuAsset).decimals(), + isleft + ); + if (converter != address(0)) + { + // pricefeed was not in ETH + // we need to convert to ETH + EthValue = INumaTokenToEthConverter(converter).convertTokenToEth(EthValue); + } + } + + function nuAssetToEthRoundUp( + address _nuAsset, + uint256 _amount + ) public view returns (uint256 EthValue) { + require(contains(_nuAsset), "bad nuAsset"); + nuAssetInfo memory info = getNuAssetInfo(_nuAsset); + (address priceFeed, uint128 heartbeat,bool isleft,address converter) = ( + info.feed, + info.heartbeat, + info.isLeft, + info.converterAddress + ); + EthValue = + tokenToRefRoundUp( + _amount, + priceFeed, + heartbeat, + IERC20Metadata(_nuAsset).decimals(), + isleft + ); + if (converter != address(0)) + { + // pricefeed was not in ETH + // we need to convert to ETH + EthValue = INumaTokenToEthConverter(converter).convertTokenToEth(EthValue); + } + } + + function ethToNuAsset( + address _nuAsset, + uint256 _amount + ) public view returns (uint256 nuAssetValue) { + require(contains(_nuAsset), "bad nuAsset"); + nuAssetInfo memory info = getNuAssetInfo(_nuAsset); + (address priceFeed, uint128 heartbeat,bool isleft,address converter) = ( + info.feed, + info.heartbeat, + info.isLeft, + info.converterAddress + ); + if (converter != address(0)) + { + // pricefeed was not in ETH + // we need to convert to ETH + _amount = INumaTokenToEthConverter(converter).convertEthToToken(_amount); + + } + nuAssetValue = refToToken(_amount, priceFeed, heartbeat, 18,isleft); + + + } + + function ethToNuAssetRoundUp( + address _nuAsset, + uint256 _amount + ) public view returns (uint256 EthValue) { + require(contains(_nuAsset), "bad nuAsset"); + nuAssetInfo memory info = getNuAssetInfo(_nuAsset); + (address priceFeed, uint128 heartbeat,bool isleft,address converter) = ( + info.feed, + info.heartbeat, + info.isLeft, + info.converterAddress + ); + if (converter != address(0)) + { + // pricefeed was not in ETH + // we need to convert to ETH + _amount = INumaTokenToEthConverter(converter).convertEthToToken(_amount); + + } + return refToTokenRoundUp(_amount, priceFeed, heartbeat, 18,isleft); + } + + function changeSequencerUptimeFeedAddress( + address _newaddress + ) external onlyOwner { + sequencerUptimeFeed = _newaddress; + } +} diff --git a/Numa/contracts/utils/constants.sol b/Numa/contracts/utils/constants.sol index 5c207b3..5e41976 100644 --- a/Numa/contracts/utils/constants.sol +++ b/Numa/contracts/utils/constants.sol @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.20; + +uint constant BASE_SCALE = 1 ether; uint constant BASE_1000 = 1000; uint constant MAX_CF = 100000; diff --git a/Numa/scripts/DeployLending.sol b/Numa/scripts/DeployLending.sol index 5761120..47174b2 100644 --- a/Numa/scripts/DeployLending.sol +++ b/Numa/scripts/DeployLending.sol @@ -71,6 +71,10 @@ contract DeployLending is Script { // uint liquidationIncentive = 1.02 ether; // uint maxLiquidationProfit; + // 0.0005e16 for ethereum chain, should be 10 x less for arbitrum as blocktime is /10 + //uint borrowRateMaxMantissaARBI = 0.0005e16; + uint borrowRateMaxMantissaARBI = 0.00005e16; + // SEPOLIA address constant VAULT_ADMIN = 0xe8153Afbe4739D4477C1fF86a26Ab9085C4eDC69; address constant NUMA_ADMIN = 0xe8153Afbe4739D4477C1fF86a26Ab9085C4eDC69; @@ -214,7 +218,9 @@ contract DeployLending is Script { address(vault) ); - + // arbitrum values + cReth._setBorrowRateMaxMantissa(borrowRateMaxMantissaARBI); + cNuma._setBorrowRateMaxMantissa(borrowRateMaxMantissaARBI); vault.setMaxBorrow(1000 ether); vault.setCTokens(address(cNuma), address(cReth)); @@ -232,6 +238,7 @@ contract DeployLending is Script { // 100% liquidation close factor comptroller._setCloseFactor(closeFactor); comptroller._setLiquidationIncentive(liquidationIncentive); + comptroller._setLtvThresholds(0.98 ether,1.1 ether); vault.setMaxLiquidationsProfit(maxLiquidationProfit); // strategies From eb0403a202c5dea5d5d25fd4c6257ec27284ae32 Mon Sep 17 00:00:00 2001 From: sherlock-admin Date: Mon, 17 Feb 2025 13:14:55 +0000 Subject: [PATCH 2/2] Fix Review --- .../contracts/NumaProtocol/NumaOracle.sol.rej | 48 + .../NumaProtocol/NumaPrinter.sol.rej | 33 + Numa/contracts/NumaProtocol/NumaVault.sol | 10 +- Numa/contracts/NumaProtocol/NumaVault.sol.rej | 244 ++++ .../NumaProtocol/VaultManager.sol.rej | 121 ++ Numa/contracts/Test/Lending.t.sol.rej | 1023 +++++++++++++++++ Numa/contracts/Test/Printer.t.sol | 4 +- Numa/contracts/Test/Printer.t.sol.rej | 699 +++++++++++ Numa/contracts/Test/VaultBuySellFee.t.sol.rej | 77 ++ Numa/contracts/Test/VaultMigrations.t.sol.rej | 19 + .../Test/utils/ConstantsTest.sol.rej | 11 + Numa/contracts/Test/utils/SetupBase.sol.rej | 75 ++ .../Test/utils/Setup_ArbitrumFork.sol.rej | 10 + Numa/contracts/deployment/utils.sol.rej | 31 + .../deployment/vaultV2Deployer.sol.rej | 19 + Numa/contracts/interfaces/INumaVault.sol.rej | 10 + .../interfaces/IVaultManager.sol.rej | 7 + Numa/contracts/lending/CNumaToken.sol.rej | 193 ++++ Numa/contracts/lending/CToken.sol.rej | 164 +++ .../lending/CTokenInterfaces.sol.rej | 31 + .../lending/ComptrollerInterface.sol.rej | 13 + Numa/contracts/lending/ErrorReporter.sol.rej | 17 + .../lending/Lens/CompoundLens.sol.rej | 20 + Numa/contracts/lending/NumaComptroller.sol | 14 +- .../contracts/lending/NumaComptroller.sol.rej | 525 +++++++++ Numa/contracts/utils/constants.sol.rej | 9 + Numa/scripts/DeployLending.sol.rej | 31 + 27 files changed, 3453 insertions(+), 5 deletions(-) create mode 100644 Numa/contracts/NumaProtocol/NumaOracle.sol.rej create mode 100644 Numa/contracts/NumaProtocol/NumaPrinter.sol.rej create mode 100644 Numa/contracts/NumaProtocol/NumaVault.sol.rej create mode 100644 Numa/contracts/NumaProtocol/VaultManager.sol.rej create mode 100644 Numa/contracts/Test/Lending.t.sol.rej create mode 100644 Numa/contracts/Test/Printer.t.sol.rej create mode 100644 Numa/contracts/Test/VaultBuySellFee.t.sol.rej create mode 100644 Numa/contracts/Test/VaultMigrations.t.sol.rej create mode 100644 Numa/contracts/Test/utils/ConstantsTest.sol.rej create mode 100644 Numa/contracts/Test/utils/SetupBase.sol.rej create mode 100644 Numa/contracts/Test/utils/Setup_ArbitrumFork.sol.rej create mode 100644 Numa/contracts/deployment/utils.sol.rej create mode 100644 Numa/contracts/deployment/vaultV2Deployer.sol.rej create mode 100644 Numa/contracts/interfaces/INumaVault.sol.rej create mode 100644 Numa/contracts/interfaces/IVaultManager.sol.rej create mode 100644 Numa/contracts/lending/CNumaToken.sol.rej create mode 100644 Numa/contracts/lending/CToken.sol.rej create mode 100644 Numa/contracts/lending/CTokenInterfaces.sol.rej create mode 100644 Numa/contracts/lending/ComptrollerInterface.sol.rej create mode 100644 Numa/contracts/lending/ErrorReporter.sol.rej create mode 100644 Numa/contracts/lending/Lens/CompoundLens.sol.rej create mode 100644 Numa/contracts/lending/NumaComptroller.sol.rej create mode 100644 Numa/contracts/utils/constants.sol.rej create mode 100644 Numa/scripts/DeployLending.sol.rej diff --git a/Numa/contracts/NumaProtocol/NumaOracle.sol.rej b/Numa/contracts/NumaProtocol/NumaOracle.sol.rej new file mode 100644 index 0000000..80dbea2 --- /dev/null +++ b/Numa/contracts/NumaProtocol/NumaOracle.sol.rej @@ -0,0 +1,48 @@ +diff a/Numa/contracts/NumaProtocol/NumaOracle.sol b/Numa/contracts/NumaProtocol/NumaOracle.sol (rejected hunks) +@@ -23,14 +23,14 @@ contract NumaOracle is Ownable2Step, INumaOracle { + //uint maxSpotOffsetBps = 145;//1.45% + //uint maxSpotOffsetSqrtBps = 1204;//sqrt(1.45%) + +- uint160 maxSpotOffsetPlus1SqrtBps = 10072; +- uint160 maxSpotOffsetMinus1SqrtBps = 9927; ++ uint160 public maxSpotOffsetPlus1SqrtBps = 10072; ++ uint160 public maxSpotOffsetMinus1SqrtBps = 9927; + + nuAssetManager public nuAManager; + + event IntervalShort(uint32 _intervalShort); + event IntervalLong(uint32 _intervalLong); +- event MaxSpotOffsetBps(uint _maxSpotOffsetBps); ++ event MaxSpotOffset(uint _maxSpotOffset); + constructor( + address _token, + uint32 _intervalShort, +@@ -69,20 +69,18 @@ contract NumaOracle is Ownable2Step, INumaOracle { + + /** + * +- * @param _maxSpotOffsetBps offset percentage variable (cf doc) ++ * @param _maxSpotOffset offset percentage variable (cf doc) + */ +- function setMaxSpotOffsetBps(uint _maxSpotOffsetBps) external onlyOwner { +- require(_maxSpotOffsetBps < 10000, "percentage must be less than 100"); ++ function setMaxSpotOffset(uint _maxSpotOffset) external onlyOwner { ++ require(_maxSpotOffset < 1 ether, "percentage must be less than 100"); + +- maxSpotOffsetPlus1SqrtBps = +- 100 * +- uint160(Math.sqrt(10000 + _maxSpotOffsetBps)); ++ maxSpotOffsetPlus1SqrtBps = ++ uint160(Math.sqrt(1 ether + _maxSpotOffset))/1e5; + +- maxSpotOffsetMinus1SqrtBps = +- 100 * +- uint160(Math.sqrt(10000 - _maxSpotOffsetBps)); ++ maxSpotOffsetMinus1SqrtBps = ++ uint160(Math.sqrt(1 ether - _maxSpotOffset))/1e5; + +- emit MaxSpotOffsetBps(_maxSpotOffsetBps); ++ emit MaxSpotOffset(_maxSpotOffset); + } + + /** diff --git a/Numa/contracts/NumaProtocol/NumaPrinter.sol.rej b/Numa/contracts/NumaProtocol/NumaPrinter.sol.rej new file mode 100644 index 0000000..5c6cdb3 --- /dev/null +++ b/Numa/contracts/NumaProtocol/NumaPrinter.sol.rej @@ -0,0 +1,33 @@ +diff a/Numa/contracts/NumaProtocol/NumaPrinter.sol b/Numa/contracts/NumaProtocol/NumaPrinter.sol (rejected hunks) +@@ -68,10 +68,12 @@ contract NumaPrinter is Pausable, Ownable2Step { + uint256 _amountReceived + ); + ++ // sherlock issue 41 ++ // CF warning can be bypassed + modifier notInWarningCF() { ++ _; + uint currentCF = vaultManager.getGlobalCF(); + require(currentCF > vaultManager.getWarningCF(), "minting forbidden"); +- _; + } + + constructor( +@@ -449,7 +451,7 @@ contract NumaPrinter is Pausable, Ownable2Step { + + (uint scaleSynthBurn, , , ) = vaultManager.getSynthScaling(); + // apply scale +- costWithoutFee = (costWithoutFee * scaleSynthBurn) / BASE_1000; ++ costWithoutFee = (costWithoutFee * scaleSynthBurn) / BASE_SCALE; + // burn fee + uint256 amountToBurn = computeFeeAmountIn( + costWithoutFee, +@@ -493,7 +495,7 @@ contract NumaPrinter is Pausable, Ownable2Step { + uint256 nuAssetIn = oracle.ethToNuAssetRoundUp(_nuAsset, ethAmount); + (uint scaleSynthBurn, , , ) = vaultManager.getSynthScaling(); + // apply scale +- nuAssetIn = (nuAssetIn * BASE_1000) / scaleSynthBurn; ++ nuAssetIn = (nuAssetIn * BASE_SCALE) / scaleSynthBurn; + + return (nuAssetIn, amountWithFee - _numaAmount); + } diff --git a/Numa/contracts/NumaProtocol/NumaVault.sol b/Numa/contracts/NumaProtocol/NumaVault.sol index 37fd95c..0e02f44 100644 --- a/Numa/contracts/NumaProtocol/NumaVault.sol +++ b/Numa/contracts/NumaProtocol/NumaVault.sol @@ -690,8 +690,10 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { criticalScaleForNumaPriceAndSellFee ); numaAmount = (numaAmount * vaultManager.getBuyFee()) / 1 ether; - // using 1 ether here because numa token has 18 decimals - uint result = FullMath.mulDivRoundingUp(_amount, 1 ether, numaAmount); + + // sherlock 214 incorrect calculation + //uint result = FullMath.mulDivRoundingUp(_amount, 1 ether, numaAmount); + uint result = FullMath.mulDivRoundingUp(_amount, decimals, numaAmount); return result; } @@ -715,7 +717,9 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { ); (uint sellFee, , ) = vaultManager.getSellFeeScaling(); tokenAmount = (tokenAmount * sellFee) / 1 ether; - uint result = FullMath.mulDivRoundingUp(_amount, decimals, tokenAmount); + // sherlock 214 incorrect calculation + uint result = FullMath.mulDivRoundingUp(_amount, 1 ether, tokenAmount); + //uint result = FullMath.mulDivRoundingUp(_amount, decimals, tokenAmount); return result; } diff --git a/Numa/contracts/NumaProtocol/NumaVault.sol.rej b/Numa/contracts/NumaProtocol/NumaVault.sol.rej new file mode 100644 index 0000000..88b689c --- /dev/null +++ b/Numa/contracts/NumaProtocol/NumaVault.sol.rej @@ -0,0 +1,244 @@ +diff a/Numa/contracts/NumaProtocol/NumaVault.sol b/Numa/contracts/NumaProtocol/NumaVault.sol (rejected hunks) +@@ -14,10 +14,11 @@ import "../interfaces/INumaVault.sol"; + + import "./NumaMinter.sol"; + import "../lending/CNumaToken.sol"; +- ++import "../lending/NumaComptroller.sol"; + import "@openzeppelin/contracts_5.0.2/utils/structs/EnumerableSet.sol"; + import "../utils/constants.sol"; + ++ + /// @title Numa vault to mint/burn Numa to lst token + contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { + using EnumerableSet for EnumerableSet.AddressSet; +@@ -172,7 +173,7 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { + } + + /** +- * @dev minimum reth borrow balance needed to allow partial liquidations ++ * @dev minimum lst borrow balance needed to allow partial liquidations + */ + function setMinBorrowAmountAllowPartialLiquidation( + uint _minBorrowAmountAllowPartialLiquidation +@@ -180,6 +181,39 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { + minBorrowAmountAllowPartialLiquidation = _minBorrowAmountAllowPartialLiquidation; + } + ++ /** ++ * @dev minimum borrow balance needed to allow partial liquidations in numa or in lst ++ */ ++ function getMinBorrowAmountAllowPartialLiquidation(address _cBorrowToken) external view returns (uint) { ++ if (_cBorrowToken == address(cNuma) ) ++ { ++ // numa borrower ++ // min amount in numa ++ ( ++ , ++ , ++ uint criticalScaleForNumaPriceAndSellFee, ++ ) = vaultManager.getSynthScaling(); ++ ++ uint minBorrowAmountAllowPartialLiquidationNuma = vaultManager ++ .tokenToNuma( ++ minBorrowAmountAllowPartialLiquidation, ++ last_lsttokenvalueWei, ++ decimals, ++ criticalScaleForNumaPriceAndSellFee ++ ); ++ return minBorrowAmountAllowPartialLiquidationNuma; ++ ++ } ++ else ++ { ++ return minBorrowAmountAllowPartialLiquidation; ++ ++ } ++ ++ ++ } ++ + /** + * @dev set the IVaultOracle address (used to compute token price in Eth) + */ +@@ -855,11 +893,12 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { + + function startLiquidation() + internal +- returns (uint criticalScaleForNumaPriceAndSellFee) ++ returns (uint criticalScaleForNumaPriceAndSellFee, uint sell_fee) + { + ( + , + criticalScaleForNumaPriceAndSellFee, ++ sell_fee + + ) = updateVaultAndUpdateDebasing(); + // lock numa supply +@@ -975,7 +1014,7 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { + "invalid param" + ); + +- uint criticalScaleForNumaPriceAndSellFee = startLiquidation(); ++ (uint criticalScaleForNumaPriceAndSellFee,) = startLiquidation(); + + uint numaAmount = _numaAmount; + +@@ -985,20 +1024,7 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { + // AUDITV2FIX: handle max liquidations + if (_numaAmount == type(uint256).max) { + numaAmount = borrowAmount; +- } else { +- // min liquidation amount +- // convert minimum amount for partial liquidations in numa +- uint minBorrowAmountAllowPartialLiquidationNuma = vaultManager +- .tokenToNuma( +- minBorrowAmountAllowPartialLiquidation, +- last_lsttokenvalueWei, +- decimals, +- criticalScaleForNumaPriceAndSellFee +- ); +- uint minAmount = minBorrowAmountAllowPartialLiquidationNuma; +- if (borrowAmount < minAmount) minAmount = borrowAmount; +- require(numaAmount >= minAmount, "min liquidation"); +- } ++ } + + if (_flashloan) { + // mint +@@ -1015,7 +1041,7 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { + + // liquidate + numa.approve(address(cNuma), numaAmount); +- cNuma.liquidateBorrow( ++ (,uint badDebt) = cNuma.liquidateBorrow( + _borrower, + numaAmount, + CTokenInterface(address(cLstToken)) +@@ -1051,9 +1077,15 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { + decimals, + criticalScaleForNumaPriceAndSellFee + ); ++ // sherlock 86 42 ++ // for numa borrow, numa <-> lst conversions should use buyPrice ++ maxNumaProfitForLiquidations = (maxNumaProfitForLiquidations * vaultManager.getBuyFee()) / 1 ether; + + // cap profit +- if (numaLiquidatorProfit > maxNumaProfitForLiquidations) ++ // sherlock 101 153 ++ // cap only if there is no bad debt, because if we are in bad debt it means this is a partial liquidation which ++ // should not be capped ++ if ((badDebt == 0) && (numaLiquidatorProfit > maxNumaProfitForLiquidations)) + numaLiquidatorProfit = maxNumaProfitForLiquidations; + + uint numaToSend = numaLiquidatorProfit; +@@ -1078,6 +1110,9 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { + decimals, + criticalScaleForNumaPriceAndSellFee + ); ++ // sherlock 86 42 ++ // for numa borrow, numa <-> lst conversions should use buyPrice ++ lstProvidedEstimate = (lstProvidedEstimate * 1 ether) / vaultManager.getBuyFee(); + + uint lstLiquidatorProfit; + // we don't revert if liquidation is not profitable because it might be profitable +@@ -1086,8 +1121,13 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { + lstLiquidatorProfit = receivedlst - lstProvidedEstimate; + } + ++ ++ // cap profit ++ // sherlock 101 153 ++ // cap only if there is no bad debt, because if we are in bad debt it means this is a partial liquidation which ++ // should not be capped + uint vaultProfit; +- if (lstLiquidatorProfit > maxLstProfitForLiquidations) { ++ if ((badDebt == 0) && (lstLiquidatorProfit > maxLstProfitForLiquidations)) { + vaultProfit = lstLiquidatorProfit - maxLstProfitForLiquidations; + } + +@@ -1132,12 +1172,9 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { + lstAmount = borrowAmount; + } + +- uint minAmount = minBorrowAmountAllowPartialLiquidation; +- if (borrowAmount < minAmount) minAmount = borrowAmount; +- +- require(lstAmount >= minAmount, "min liquidation"); + +- uint criticalScaleForNumaPriceAndSellFee = startLiquidation(); ++ ++ (uint criticalScaleForNumaPriceAndSellFee,uint sellfee) = startLiquidation(); + + if (!_flashloan) { + // user supplied funds +@@ -1151,7 +1188,7 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { + + // liquidate + IERC20(lstToken).approve(address(cLstToken), lstAmount); +- cLstToken.liquidateBorrow( ++ (,uint badDebt) = cLstToken.liquidateBorrow( + _borrower, + lstAmount, + CTokenInterface(address(cNuma)) +@@ -1176,7 +1213,10 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { + uint lstLiquidatorProfit = lstReceived - lstAmount; + + // cap profit +- if (lstLiquidatorProfit > maxLstProfitForLiquidations) ++ // sherlock 101 153 ++ // cap only if there is no bad debt, because if we are in bad debt it means this is a partial liquidation which ++ // should not be capped ++ if ((badDebt == 0) && (lstLiquidatorProfit > maxLstProfitForLiquidations)) + lstLiquidatorProfit = maxLstProfitForLiquidations; + + uint lstToSend = lstLiquidatorProfit; +@@ -1193,12 +1233,20 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { + decimals, + criticalScaleForNumaPriceAndSellFee + ); ++ // sherlock 86 42 ++ // for lst borrow, numa <-> lst conversions should use sellPrice ++ numaProvidedEstimate = (numaProvidedEstimate * 1 ether) / sellfee; ++ + uint maxNumaProfitForLiquidations = vaultManager.tokenToNuma( + maxLstProfitForLiquidations, + last_lsttokenvalueWei, + decimals, + criticalScaleForNumaPriceAndSellFee + ); ++ // sherlock 86 42 ++ // for lst borrow, numa <-> lst conversions should use sellPrice ++ maxNumaProfitForLiquidations = (maxNumaProfitForLiquidations * 1 ether) / sellfee; ++ + + uint numaLiquidatorProfit; + // we don't revert if liquidation is not profitable because it might be profitable +@@ -1208,7 +1256,11 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { + } + + uint vaultProfit; +- if (numaLiquidatorProfit > maxNumaProfitForLiquidations) { ++ // cap profit ++ // sherlock 101 153 ++ // cap only if there is no bad debt, because if we are in bad debt it means this is a partial liquidation which ++ // should not be capped ++ if ((badDebt == 0) && (numaLiquidatorProfit > maxNumaProfitForLiquidations)) { + vaultProfit = + numaLiquidatorProfit - + maxNumaProfitForLiquidations; +@@ -1318,4 +1370,14 @@ contract NumaVault is Ownable2Step, ReentrancyGuard, Pausable, INumaVault { + } + return (extSize > 0); + } ++ ++ ++ function borrowAllowed(address _ctokenAddress) external view returns (bool allowed) ++ { ++ allowed = true; ++ if (_ctokenAddress == address(cNuma)) ++ { ++ allowed = vaultManager.numaBorrowAllowed(); ++ } ++ } + } diff --git a/Numa/contracts/NumaProtocol/VaultManager.sol.rej b/Numa/contracts/NumaProtocol/VaultManager.sol.rej new file mode 100644 index 0000000..a39ed1f --- /dev/null +++ b/Numa/contracts/NumaProtocol/VaultManager.sol.rej @@ -0,0 +1,121 @@ +diff a/Numa/contracts/NumaProtocol/VaultManager.sol b/Numa/contracts/NumaProtocol/VaultManager.sol (rejected hunks) +@@ -14,6 +14,7 @@ import "../interfaces/INumaPrinter.sol"; + + import "../utils/constants.sol"; + ++ + contract VaultManager is IVaultManager, Ownable2Step { + using EnumerableSet for EnumerableSet.AddressSet; + EnumerableSet.AddressSet vaultsList; +@@ -63,13 +64,14 @@ contract VaultManager is IVaultManager, Ownable2Step { + uint public cf_critical = 1100; + uint public cf_severe = 1500; + uint public cf_warning = 1700; +- uint public debaseValue = 20; //base 1000 +- uint public rebaseValue = 30; //base 1000 +- uint public minimumScale = 500; ++ // ++ uint public debaseValue = 0.02 ether; ++ uint public rebaseValue = 0.03 ether; ++ uint public minimumScale = 0.5 ether; + uint public criticalDebaseMult = 1100; //base 1000 + uint public deltaRebase = 24 hours; + uint public deltaDebase = 24 hours; +- uint lastSynthPID = BASE_1000; ++ uint lastSynthPID = BASE_SCALE; + uint lastBlockTime; + + // the amount by which buyFee_PID increments/decrements at each event +@@ -444,12 +446,12 @@ contract VaultManager is IVaultManager, Ownable2Step { + // we use criticalScaleForNumaPriceAndSellFee because we want to use this scale in our sell_fee only when cf_critical is reached + (, , uint criticalScaleForNumaPriceAndSellFee, ) = getSynthScaling(); + +- uint sell_fee_increaseCriticalCF = ((BASE_1000 - +- criticalScaleForNumaPriceAndSellFee) * 1 ether) / BASE_1000; ++ uint sell_fee_increaseCriticalCF = ((BASE_SCALE - ++ criticalScaleForNumaPriceAndSellFee) * 1 ether) / BASE_SCALE; + // add a multiplier on top + sell_fee_increaseCriticalCF = + (sell_fee_increaseCriticalCF * sell_fee_criticalMultiplier) / +- 1000; ++ BASE_1000; + + // here we use original fee value increase by this factor + uint sell_fee_criticalCF; +@@ -524,7 +526,7 @@ contract VaultManager is IVaultManager, Ownable2Step { + } else syntheticsCurrentPID = minimumScale; + } + } else { +- if (syntheticsCurrentPID < BASE_1000) { ++ if (syntheticsCurrentPID < BASE_SCALE) { + // rebase linearly + uint nrebase = ((blockTime - lastBlockTime) * rebaseValue) / + (deltaRebase); +@@ -535,26 +537,26 @@ contract VaultManager is IVaultManager, Ownable2Step { + } else { + syntheticsCurrentPID = syntheticsCurrentPID + nrebase; + +- if (syntheticsCurrentPID > BASE_1000) +- syntheticsCurrentPID = BASE_1000; ++ if (syntheticsCurrentPID > BASE_SCALE) ++ syntheticsCurrentPID = BASE_SCALE; + } + } + } + } + // apply scale to synth burn price + uint scaleSynthBurn = syntheticsCurrentPID; // PID +- uint criticalScaleForNumaPriceAndSellFee = BASE_1000; ++ uint criticalScaleForNumaPriceAndSellFee = BASE_SCALE; + // CRITICAL_CF + if (currentCF < cf_critical) { + // scale such that currentCF = cf_critical +- uint criticalDebaseFactor = (currentCF * BASE_1000) / cf_critical; ++ uint criticalDebaseFactor = (currentCF * BASE_SCALE) / cf_critical; + + // when reaching CF_CRITICAL, we use that criticalDebaseFactor in numa price so that numa price is clipped by this lower limit + criticalScaleForNumaPriceAndSellFee = criticalDebaseFactor; + + // we apply this multiplier on the factor for when it's used on synthetics burning price + criticalDebaseFactor = +- (criticalDebaseFactor * BASE_1000) / ++ (criticalDebaseFactor * 1000) / + criticalDebaseMult; + + // for burning price we take the min between PID and criticalDebaseFactor +@@ -652,7 +654,7 @@ contract VaultManager is IVaultManager, Ownable2Step { + ); + + uint synthValueInEth = getTotalSynthValueEth(); +- synthValueInEth = (synthValueInEth * _synthScaling) / BASE_1000; ++ synthValueInEth = (synthValueInEth * _synthScaling) / BASE_SCALE; + uint circulatingNuma = getNumaSupply(); + + uint result; +@@ -702,7 +704,7 @@ contract VaultManager is IVaultManager, Ownable2Step { + + uint synthValueInEth = getTotalSynthValueEth(); + +- synthValueInEth = (synthValueInEth * _synthScaling) / BASE_1000; ++ synthValueInEth = (synthValueInEth * _synthScaling) / BASE_SCALE; + + uint circulatingNuma = getNumaSupply(); + +@@ -943,4 +945,17 @@ contract VaultManager is IVaultManager, Ownable2Step { + return MAX_CF; + } + } ++ ++ function numaBorrowAllowed() external view returns (bool allowed) ++ { ++ allowed = true; ++ ++ // is numa borrow allowed ++ uint currentCF = getGlobalCF(); ++ ++ if (currentCF < cf_severe) ++ { ++ allowed = false; ++ } ++ } + } diff --git a/Numa/contracts/Test/Lending.t.sol.rej b/Numa/contracts/Test/Lending.t.sol.rej new file mode 100644 index 0000000..10a69d0 --- /dev/null +++ b/Numa/contracts/Test/Lending.t.sol.rej @@ -0,0 +1,1023 @@ +diff a/Numa/contracts/Test/Lending.t.sol b/Numa/contracts/Test/Lending.t.sol (rejected hunks) +@@ -16,8 +16,15 @@ import {NumaLeverageLPSwap} from "../Test/mocks/NumaLeverageLPSwap.sol"; + import "../lending/ExponentialNoError.sol"; + import "../lending/INumaLeverageStrategy.sol"; + import "../lending/CToken.sol"; ++ ++import {TokenErrorReporter} from "../lending/ErrorReporter.sol"; + import {Setup} from "./utils/SetupDeployNuma_Arbitrum.sol"; + contract LendingTest is Setup, ExponentialNoError { ++ ++ uint constant strategyIndex1 = 1; ++ ++ uint strategyindex; ++ + uint providedAmount = 10 ether; + uint leverageAmount = 40 ether; + +@@ -134,10 +141,18 @@ contract LendingTest is Setup, ExponentialNoError { + numa.approve(address(cReth), providedAmount); + + // call strategy +- uint strategyindex = 0; ++ strategyindex = 0; ++ // slippage check ++ uint amountInExpected = cReth.getAmountIn( ++ leverageAmount, ++ false, ++ strategyindex ++ ); ++ + cReth.leverageStrategy( + providedAmount, + leverageAmount, ++ amountInExpected, + cNuma, + strategyindex + ); +@@ -150,7 +165,7 @@ contract LendingTest is Setup, ExponentialNoError { + + uint mintTokens = div_(totalCollateral, exchangeRate); + +- assertEq(cNumaBal, mintTokens); ++ assertEq(cNumaBal, mintTokens - 1000);// first depositor has 1000 less + + // numa stored in cnuma contract + uint numaBal = numa.balanceOf(address(cNuma)); +@@ -161,7 +176,7 @@ contract LendingTest is Setup, ExponentialNoError { + + + +- (, uint ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ (,,,, uint ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log("ltv before"); + console2.log(ltv); + // vault borrow (should be the same) +@@ -171,7 +186,7 @@ contract LendingTest is Setup, ExponentialNoError { + deal({token: address(rEth), to: address(vault), give: newRethBalance}); + + // check states +- (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log("ltv after"); + console2.log(ltv); + +@@ -188,9 +203,9 @@ contract LendingTest is Setup, ExponentialNoError { + ); + + cNuma.approve(address(cReth), cnumaAmount); +- cReth.closeLeverageStrategy(cNuma, borrowrEThBalance, strategyindex); ++ cReth.closeLeverageStrategy(cNuma, borrowrEThBalance,swapAmountIn, strategyindex); + +- (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log("ltv after close"); + console2.log(ltv); + +@@ -248,10 +263,19 @@ contract LendingTest is Setup, ExponentialNoError { + numa.approve(address(cReth), providedAmount2); + + // call strategy +- uint strategyindex = 0; ++ strategyindex = 0; ++ ++ // slippage check ++ uint amountInExpected = cReth.getAmountIn( ++ leverageAmount2, ++ false, ++ strategyindex ++ ); ++ + cReth.leverageStrategy( + providedAmount2, + leverageAmount2, ++ amountInExpected, + cNuma, + strategyindex + ); +@@ -266,7 +290,7 @@ contract LendingTest is Setup, ExponentialNoError { + + uint mintTokens = div_(totalCollateral, exchangeRate); + +- assertEq(cNumaBal, mintTokens); ++ //assertEq(cNumaBal, mintTokens); + + // numa stored in cnuma contract + uint numaBal = numa.balanceOf(address(cNuma)); +@@ -277,7 +301,7 @@ contract LendingTest is Setup, ExponentialNoError { + + console2.log(borrowrEThBalance); + +- (, uint ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ (,,,, uint ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log("ltv"); + console2.log(ltv); + +@@ -289,9 +313,17 @@ contract LendingTest is Setup, ExponentialNoError { + numa.approve(address(cReth), providedAmount2); + + // call strategy ++ // slippage check ++ amountInExpected = cReth.getAmountIn( ++ leverageAmount2, ++ false, ++ strategyindex ++ ); ++ + cReth.leverageStrategy( + providedAmount2, + leverageAmount2, ++ amountInExpected, + cNuma, + strategyindex + ); +@@ -306,7 +338,7 @@ contract LendingTest is Setup, ExponentialNoError { + + console2.log(borrowrEThBalance); + +- (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log("ltv"); + console2.log(ltv); + +@@ -318,9 +350,17 @@ contract LendingTest is Setup, ExponentialNoError { + numa.approve(address(cReth), providedAmount2); + + // call strategy ++ // slippage check ++ amountInExpected = cReth.getAmountIn( ++ leverageAmount2, ++ false, ++ strategyindex ++ ); ++ + cReth.leverageStrategy( + providedAmount2, + leverageAmount2, ++ amountInExpected, + cNuma, + strategyindex + ); +@@ -335,7 +375,7 @@ contract LendingTest is Setup, ExponentialNoError { + + console2.log(borrowrEThBalance); + +- (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log("ltv"); + console2.log(ltv); + +@@ -347,9 +387,17 @@ contract LendingTest is Setup, ExponentialNoError { + numa.approve(address(cReth), providedAmount2); + + // call strategy ++ // slippage check ++ amountInExpected = cReth.getAmountIn( ++ leverageAmount2, ++ false, ++ strategyindex ++ ); ++ + cReth.leverageStrategy( + providedAmount2, + leverageAmount2, ++ amountInExpected, + cNuma, + strategyindex + ); +@@ -364,7 +412,7 @@ contract LendingTest is Setup, ExponentialNoError { + + console2.log(borrowrEThBalance); + +- (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log("ltv"); + console2.log(ltv); + +@@ -378,9 +426,15 @@ contract LendingTest is Setup, ExponentialNoError { + numa.approve(address(cReth), providedAmount2); + + // call strategy ++ amountInExpected = cReth.getAmountIn( ++ leverageAmount2, ++ false, ++ strategyindex ++ ); + cReth.leverageStrategy( + providedAmount2, + leverageAmount2, ++ amountInExpected, + cNuma, + strategyindex + ); +@@ -395,7 +449,7 @@ contract LendingTest is Setup, ExponentialNoError { + + console2.log(borrowrEThBalance); + +- (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log("ltv"); + console2.log(ltv); + +@@ -403,7 +457,7 @@ contract LendingTest is Setup, ExponentialNoError { + console2.log("vault balance 5", rEth.balanceOf(address(vault))); + console2.log("vault debt 5", vault.getDebt()); + console2.log("lst borrow rate per block 5", cReth.borrowRatePerBlock()); +- (, uint liquidity, uint shortfall, uint badDebt) = comptroller ++ (, uint liquidity, uint shortfall, uint badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log("liquidity:", liquidity); + console2.log("shortfall:", shortfall); +@@ -416,7 +470,7 @@ contract LendingTest is Setup, ExponentialNoError { + vm.startPrank(deployer); + vaultManager.setSellFee(0.85 ether); + +- (, liquidity, shortfall, badDebt) = comptroller ++ (, liquidity, shortfall, badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log("liquidity:", liquidity); + console2.log("shortfall:", shortfall); +@@ -448,12 +502,18 @@ contract LendingTest is Setup, ExponentialNoError { + numa.approve(address(cReth), providedAmount); + + // call strategy +- uint strategyindex = 1; ++ ++ uint amountInExpected = cReth.getAmountIn( ++ leverageAmount, ++ false, ++ strategyIndex1 ++ ); + cReth.leverageStrategy( + providedAmount, + leverageAmount, ++ amountInExpected, + cNuma, +- strategyindex ++ strategyIndex1 + ); + + // check balances +@@ -466,7 +526,7 @@ contract LendingTest is Setup, ExponentialNoError { + + uint mintTokens = div_(totalCollateral, exchangeRate); + +- assertEq(cNumaBal, mintTokens); ++ assertEq(cNumaBal, mintTokens - 1000);// first depositor has 1000 less + + // numa stored in cnuma contract + uint numaBal = numa.balanceOf(address(cNuma)); +@@ -477,7 +537,7 @@ contract LendingTest is Setup, ExponentialNoError { + + console2.log(borrowrEThBalance); + +- (, uint ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ (,,,, uint ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log("ltv before"); + console2.log(ltv); + //vault borrow (should be the same) +@@ -507,7 +567,7 @@ contract LendingTest is Setup, ExponentialNoError { + vm.stopPrank(); + vm.startPrank(userA); + // check states +- (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log("ltv after"); + console2.log(ltv); + +@@ -516,16 +576,16 @@ contract LendingTest is Setup, ExponentialNoError { + (uint cnumaAmount, uint swapAmountIn) = cReth.closeLeverageAmount( + cNuma, + borrowrEThBalance, +- strategyindex ++ strategyIndex1 + ); + + cNuma.approve(address(cReth), cnumaAmount); +- cReth.closeLeverageStrategy(cNuma, borrowrEThBalance, strategyindex); ++ cReth.closeLeverageStrategy(cNuma, borrowrEThBalance,swapAmountIn, strategyIndex1); + + borrowrEThBalance = cReth.borrowBalanceCurrent(userA); + console2.log("borrow amount after"); + console2.log(borrowrEThBalance); +- (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log("ltv after close"); + console2.log(ltv); + +@@ -568,7 +628,7 @@ contract LendingTest is Setup, ExponentialNoError { + console2.log(cReth.getAmountIn(leverageAmount, false, 1)); + + // call strategy +- uint strategyindex = 0; ++ strategyindex = 0; + + if ( + cReth.getAmountIn(leverageAmount, false, 1) < +@@ -581,6 +641,7 @@ contract LendingTest is Setup, ExponentialNoError { + cReth.leverageStrategy( + providedAmount, + leverageAmount, ++ cReth.getAmountIn(leverageAmount, false, strategyindex), + cNuma, + strategyindex + ); +@@ -595,7 +656,7 @@ contract LendingTest is Setup, ExponentialNoError { + + uint mintTokens = div_(totalCollateral, exchangeRate); + +- assertEq(cNumaBal, mintTokens); ++ assertEq(cNumaBal, mintTokens - 1000);// first depositor has 1000 less + + // numa stored in cnuma contract + uint numaBal = numa.balanceOf(address(cNuma)); +@@ -606,7 +667,7 @@ contract LendingTest is Setup, ExponentialNoError { + + console2.log(borrowrEThBalance); + +- (, uint ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ (,,,, uint ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log("ltv before"); + console2.log(ltv); + //vault borrow (should be the same) +@@ -636,7 +697,7 @@ contract LendingTest is Setup, ExponentialNoError { + vm.stopPrank(); + vm.startPrank(userA); + // check states +- (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log("ltv after"); + console2.log(ltv); + +@@ -658,12 +719,12 @@ contract LendingTest is Setup, ExponentialNoError { + ); + + cNuma.approve(address(cReth), cnumaAmount); +- cReth.closeLeverageStrategy(cNuma, borrowrEThBalance, strategyindex); ++ cReth.closeLeverageStrategy(cNuma, borrowrEThBalance, swapAmountIn,strategyindex); + + borrowrEThBalance = cReth.borrowBalanceCurrent(userA); + console2.log("borrow amount after"); + console2.log(borrowrEThBalance); +- (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log("ltv after close"); + console2.log(ltv); + +@@ -714,7 +775,7 @@ contract LendingTest is Setup, ExponentialNoError { + console2.log(cReth.getAmountIn(leverageAmount, false, 1)); + + // call strategy +- uint strategyindex = 0; ++ strategyindex = 0; + + if ( + cReth.getAmountIn(leverageAmount, false, 1) < +@@ -724,9 +785,16 @@ contract LendingTest is Setup, ExponentialNoError { + console2.log("strategy for open"); + console2.log(strategyindex); + assertEq(strategyindex, 0); ++ ++ uint amountInExpected = cReth.getAmountIn( ++ leverageAmount, ++ false, ++ strategyindex ++ ); + cReth.leverageStrategy( + providedAmount, + leverageAmount, ++ amountInExpected, + cNuma, + strategyindex + ); +@@ -741,7 +809,7 @@ contract LendingTest is Setup, ExponentialNoError { + + uint mintTokens = div_(totalCollateral, exchangeRate); + +- assertEq(cNumaBal, mintTokens); ++ assertEq(cNumaBal, mintTokens - 1000);// first depositor has 1000 less + + // numa stored in cnuma contract + uint numaBal = numa.balanceOf(address(cNuma)); +@@ -752,7 +820,7 @@ contract LendingTest is Setup, ExponentialNoError { + + console2.log(borrowrEThBalance); + +- (, uint ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ (,,,, uint ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log("ltv before"); + console2.log(ltv); + //vault borrow (should be the same) +@@ -782,7 +850,7 @@ contract LendingTest is Setup, ExponentialNoError { + vm.stopPrank(); + vm.startPrank(userA); + // check states +- (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log("ltv after"); + console2.log(ltv); + +@@ -806,12 +874,12 @@ contract LendingTest is Setup, ExponentialNoError { + ); + + cNuma.approve(address(cReth), cnumaAmount); +- cReth.closeLeverageStrategy(cNuma, borrowrEThBalance, strategyindex); ++ cReth.closeLeverageStrategy(cNuma, borrowrEThBalance,swapAmountIn, strategyindex); + + borrowrEThBalance = cReth.borrowBalanceCurrent(userA); + console2.log("borrow amount after"); + console2.log(borrowrEThBalance); +- (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log("ltv after close"); + console2.log(ltv); + +@@ -853,10 +921,16 @@ contract LendingTest is Setup, ExponentialNoError { + rEth.approve(address(cNuma), providedAmount); + + // call strategy +- uint strategyindex = 0; ++ strategyindex = 0; ++ uint amountInExpected = cReth.getAmountIn( ++ leverageAmount, ++ false, ++ strategyindex ++ ); + cNuma.leverageStrategy( + providedAmount, + leverageAmount, ++ amountInExpected, + cReth, + strategyindex + ); +@@ -882,7 +956,7 @@ contract LendingTest is Setup, ExponentialNoError { + + // console2.log(borrowrEThBalance); + +- // (, uint ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ // (,,,, uint ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + // console2.log("ltv before"); + // console2.log(ltv); + // // vault borrow (should be the same) +@@ -892,7 +966,7 @@ contract LendingTest is Setup, ExponentialNoError { + // deal({token: address(rEth), to: address(vault), give: newRethBalance}); + + // // check states +- // (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ // (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + // console2.log("ltv after"); + // console2.log(ltv); + +@@ -911,7 +985,7 @@ contract LendingTest is Setup, ExponentialNoError { + // cNuma.approve(address(cReth), cnumaAmount); + // cReth.closeLeverageStrategy(cNuma, borrowrEThBalance, strategyindex); + +- // (, ltv) = comptroller.getAccountLTVIsolate(userA, cNuma, cReth); ++ // (,,,, ltv) = comptroller.getAccountLiquidityIsolate(userA, cNuma, cReth); + // console2.log("ltv after close"); + // console2.log(ltv); + +@@ -984,7 +1058,7 @@ contract LendingTest is Setup, ExponentialNoError { + numa.approve(address(cNuma), depositAmount); + cNuma.mint(depositAmount); + assertEq(numaBalBefore - numa.balanceOf(userA), depositAmount); +- assertEq(cNuma.balanceOf(userA), (depositAmount * 50 * 1e8) / 1 ether); ++ assertEq(cNuma.balanceOf(userA), (depositAmount * 50 * 1e8) / 1 ether - 1000);// first depositor has 1000 less + + // borrow reth + // should revert +@@ -1024,13 +1098,13 @@ contract LendingTest is Setup, ExponentialNoError { + //assertApproxEqAbs(borrowBalanceAfter - borrowAmount,((baseRatePerYear+ (borrowAmount*multiplierPerYear)/rethAmount)*borrowAmount)/1 ether,0.0000001 ether); + + // make it liquiditable, check shortfall +- (, uint liquidity, uint shortfall, uint badDebt) = comptroller ++ (, uint liquidity, uint shortfall, uint badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log(liquidity); + console2.log(shortfall); + console2.log(badDebt); + cReth.borrow((borrowAmount * 8) / 10); +- (, liquidity, shortfall, badDebt) = comptroller ++ (, liquidity, shortfall, badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log(liquidity); + console2.log(shortfall); +@@ -1056,7 +1130,7 @@ contract LendingTest is Setup, ExponentialNoError { + prepare_LstBorrowSuppliedLst_JRV4(); + vm.roll(block.number + blocksPerYear / 4); + cReth.accrueInterest(); +- (, uint liquidity, uint shortfall, uint badDebt) = comptroller ++ (, uint liquidity, uint shortfall, uint badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log(liquidity); + console2.log(shortfall); +@@ -1074,11 +1148,22 @@ contract LendingTest is Setup, ExponentialNoError { + vm.startPrank(deployer); + comptroller._setSeizePaused(false); + vm.startPrank(userC); ++ ++ ++ // test revert on partial ++ uint minAmountPartial = vault.getMinBorrowAmountAllowPartialLiquidation(address(cReth)); ++ console2.log("min amount for partial",minAmountPartial); ++ uint borrowBalance = cReth.borrowBalanceCurrent(userA); ++ console2.log("borrow balance",borrowBalance); ++ vm.expectRevert(); ++ vault.liquidateLstBorrower(userA, minAmountPartial - 1 ether, true, true); ++ ++ vault.liquidateLstBorrower(userA,minAmountPartial + 0.1 ether, true, true); + vault.liquidateLstBorrower(userA, type(uint256).max, true, true); + + console2.log("liquidator profit:", rEth.balanceOf(userC) - balC); + +- (, liquidity, shortfall, badDebt) = comptroller ++ (, liquidity, shortfall, badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log(liquidity); + console2.log(shortfall); +@@ -1092,7 +1177,7 @@ contract LendingTest is Setup, ExponentialNoError { + // make it liquiditable by changing vault fees + vm.startPrank(deployer); + vaultManager.setSellFee(0.90 ether); +- (, uint liquidity, uint shortfall, uint badDebt) = comptroller ++ (, uint liquidity, uint shortfall, uint badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log(liquidity); + console2.log(shortfall); +@@ -1105,7 +1190,7 @@ contract LendingTest is Setup, ExponentialNoError { + vault.liquidateLstBorrower(userA, type(uint256).max, true, false); + console2.log("liquidator profit:", rEth.balanceOf(userC) - balC); + +- (, liquidity, shortfall, badDebt) = comptroller ++ (, liquidity, shortfall, badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log(liquidity); + console2.log(shortfall); +@@ -1119,7 +1204,7 @@ contract LendingTest is Setup, ExponentialNoError { + // make it liquiditable by changing vault fees + vm.startPrank(deployer); + vaultManager.setSellFee(0.90 ether); +- (, uint liquidity, uint shortfall, uint badDebt) = comptroller ++ (, uint liquidity, uint shortfall, uint badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log(liquidity); + console2.log(shortfall); +@@ -1131,7 +1216,7 @@ contract LendingTest is Setup, ExponentialNoError { + vault.liquidateLstBorrower(userA, type(uint256).max, false, false); + console2.log("liquidator profit + input:", numa.balanceOf(userC)); + +- (, liquidity, shortfall, badDebt) = comptroller ++ (, liquidity, shortfall, badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log(liquidity); + console2.log(shortfall); +@@ -1145,7 +1230,7 @@ contract LendingTest is Setup, ExponentialNoError { + // make it liquiditable by changing vault fees + vm.startPrank(deployer); + vaultManager.setSellFee(0.85 ether); +- (, uint liquidity, uint shortfall, uint badDebt) = comptroller ++ (, uint liquidity, uint shortfall, uint badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log(liquidity); + console2.log(shortfall); +@@ -1159,15 +1244,24 @@ contract LendingTest is Setup, ExponentialNoError { + vault.liquidateLstBorrower(userA, type(uint256).max, false, false); + vault.liquidateBadDebt(userA, 500, cNuma); + +- assertEq(numa.balanceOf(userC), 500 ether); + +- (, liquidity, shortfall, badDebt) = comptroller ++ // 1000 ctoken burnt for first depositor ++ // = 2x10^11 token ++ // 500x10^18 - 1x10^11 = 4999999900000000000 ++ //assertEq(numa.balanceOf(userC), 500 ether); ++ assertEq(numa.balanceOf(userC), 499999999900000000000); ++ ++ ++ ++ (, liquidity, shortfall, badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log(liquidity); + console2.log(shortfall); + console2.log(badDebt); + } + ++ ++ + function prepare_numaBorrow_JRV4() public { + vm.startPrank(deployer); + vault.setMaxBorrow(0); +@@ -1193,7 +1287,7 @@ contract LendingTest is Setup, ExponentialNoError { + rEth.approve(address(cReth), depositAmount); + cReth.mint(depositAmount); + assertEq(rethBalBefore - rEth.balanceOf(userA), depositAmount); +- assertEq(cReth.balanceOf(userA), (depositAmount * 50 * 1e8) / 1 ether); ++ assertEq(cReth.balanceOf(userA), (depositAmount * 50 * 1e8) / 1 ether - 1000);// 1000 ctoken burnt for first depositor + + // borrow numa + // should revert +@@ -1204,6 +1298,45 @@ contract LendingTest is Setup, ExponentialNoError { + // around 50% UR + uint borrowAmount = (depositAmount * rEthCollateralFactor) / + (2 * 1 ether); ++ ++ ++ // test block numa borrow when CF < cf_severe ++ vm.stopPrank(); ++ vm.startPrank(deployer); ++ vaultManager.setScalingParameters( ++ vaultManager.cf_critical(), ++ vaultManager.cf_warning(), ++ 100001,// so that we are below severe (if there are no synth CF is 100000) ++ vaultManager.debaseValue(), ++ vaultManager.rebaseValue(), ++ 1 hours, ++ 2 hours, ++ vaultManager.minimumScale(), ++ vaultManager.criticalDebaseMult() ++ ); ++ vm.stopPrank(); ++ ++ vm.startPrank(userA); ++ vm.expectRevert(TokenErrorReporter.BorrowNotAllowed.selector); ++ //vm.expectRevert(); ++ cNuma.borrow(borrowAmount); ++ vm.stopPrank(); ++ // ++ vm.startPrank(deployer); ++ vaultManager.setScalingParameters( ++ vaultManager.cf_critical(), ++ vaultManager.cf_warning(), ++ 0,// we don't care, it's not used ++ vaultManager.debaseValue(), ++ vaultManager.rebaseValue(), ++ 1 hours, ++ 2 hours, ++ vaultManager.minimumScale(), ++ vaultManager.criticalDebaseMult() ++ ); ++ vm.stopPrank(); ++ // ++ vm.startPrank(userA); + cNuma.borrow(borrowAmount); + assertEq(numa.balanceOf(userA) - numaBalBefore, borrowAmount); + +@@ -1226,13 +1359,13 @@ contract LendingTest is Setup, ExponentialNoError { + //assertApproxEqAbs(borrowBalanceAfter - borrowAmount,((baseRatePerYear+ (borrowAmount*multiplierPerYear)/rethAmount)*borrowAmount)/1 ether,0.0000001 ether); + + // make it liquiditable, check shortfall +- (, uint liquidity, uint shortfall, uint badDebt) = comptroller ++ (, uint liquidity, uint shortfall, uint badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cReth, cNuma); + console2.log(liquidity); + console2.log(shortfall); + console2.log(badDebt); + cNuma.borrow((borrowAmount * 8) / 10); +- (, liquidity, shortfall, badDebt) = comptroller ++ (, liquidity, shortfall, badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cReth, cNuma); + console2.log(liquidity); + console2.log(shortfall); +@@ -1243,7 +1376,7 @@ contract LendingTest is Setup, ExponentialNoError { + prepare_numaBorrow_JRV4(); + vm.roll(block.number + blocksPerYear / 4); + cNuma.accrueInterest(); +- (, uint liquidity, uint shortfall, uint badDebt) = comptroller ++ (, uint liquidity, uint shortfall, uint badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cReth, cNuma); + console2.log(liquidity); + console2.log(shortfall); +@@ -1253,21 +1386,38 @@ contract LendingTest is Setup, ExponentialNoError { + vm.startPrank(userC); + uint balC = numa.balanceOf(userC); + ++ ++ // test revert on partial ++ uint minAmountPartial = vault.getMinBorrowAmountAllowPartialLiquidation(address(cNuma)); ++ console2.log("min amount for partial",minAmountPartial); ++ uint borrowBalance = cNuma.borrowBalanceCurrent(userA); ++ console2.log("borrow balance",borrowBalance); ++ vm.expectRevert(); ++ vault.liquidateNumaBorrower(userA, minAmountPartial - 1 ether, true, true); ++ ++ vault.liquidateNumaBorrower(userA,minAmountPartial + 0.1 ether, true, true); ++ ++ + vault.liquidateNumaBorrower(userA, type(uint256).max, true, true); + console2.log("liquidator profit:", numa.balanceOf(userC) - balC); + +- (, liquidity, shortfall, badDebt) = comptroller ++ (, liquidity, shortfall, badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cReth, cNuma); + console2.log(liquidity); + console2.log(shortfall); + console2.log(badDebt); + } + ++ function test_prepare() public ++ { ++ prepare_numaBorrow_JRV4(); ++ } ++ + function test_NumaBorrow_JRV4_liquidateSwap() public { + prepare_numaBorrow_JRV4(); + vm.roll(block.number + blocksPerYear / 4); + cNuma.accrueInterest(); +- (, uint liquidity, uint shortfall, uint badDebt) = comptroller ++ (, uint liquidity, uint shortfall, uint badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cReth, cNuma); + console2.log(liquidity); + console2.log(shortfall); +@@ -1285,7 +1435,7 @@ contract LendingTest is Setup, ExponentialNoError { + vault.liquidateNumaBorrower(userA, type(uint256).max, true, false); + console2.log("liquidator profit:", numa.balanceOf(userC) - balC); + +- (, liquidity, shortfall, badDebt) = comptroller ++ (, liquidity, shortfall, badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cReth, cNuma); + console2.log(liquidity); + console2.log(shortfall); +@@ -1296,7 +1446,7 @@ contract LendingTest is Setup, ExponentialNoError { + prepare_numaBorrow_JRV4(); + vm.roll(block.number + blocksPerYear / 4); + cNuma.accrueInterest(); +- (, uint liquidity, uint shortfall, uint badDebt) = comptroller ++ (, uint liquidity, uint shortfall, uint badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cReth, cNuma); + console2.log(liquidity); + console2.log(shortfall); +@@ -1314,7 +1464,7 @@ contract LendingTest is Setup, ExponentialNoError { + vault.liquidateNumaBorrower(userA, type(uint256).max, false, false); + console2.log("liquidator profit:", rEth.balanceOf(userC) - balC); + +- (, liquidity, shortfall, badDebt) = comptroller ++ (, liquidity, shortfall, badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cReth, cNuma); + console2.log(liquidity); + console2.log(shortfall); +@@ -1329,7 +1479,7 @@ contract LendingTest is Setup, ExponentialNoError { + vm.startPrank(deployer); + vaultManager.setBuyFee(0.85 ether); + +- (, uint liquidity, uint shortfall, uint badDebt) = comptroller ++ (, uint liquidity, uint shortfall, uint badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cReth, cNuma); + console2.log(liquidity); + console2.log(shortfall); +@@ -1350,7 +1500,7 @@ contract LendingTest is Setup, ExponentialNoError { + vault.liquidateBadDebt(userA, 1000, cReth); + console2.log("liquidator profit:", rEth.balanceOf(userC) - balC); + +- (, liquidity, shortfall, badDebt) = comptroller ++ (, liquidity, shortfall, badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cReth, cNuma); + console2.log(liquidity); + console2.log(shortfall); +@@ -1374,7 +1524,7 @@ contract LendingTest is Setup, ExponentialNoError { + numa.approve(address(cNuma), depositAmount); + cNuma.mint(depositAmount); + assertEq(numaBalBefore - numa.balanceOf(userA), depositAmount); +- assertEq(cNuma.balanceOf(userA), (depositAmount * 50 * 1e8) / 1 ether); ++ assertEq(cNuma.balanceOf(userA), (depositAmount * 50 * 1e8) / 1 ether - 1000);// first depositor has 1000 less + + // borrow reth + // should revert not enough collat +@@ -1424,13 +1574,13 @@ contract LendingTest is Setup, ExponentialNoError { + //assertApproxEqAbs(borrowBalanceAfter - borrowAmount,((baseRatePerYear+ (borrowAmount*multiplierPerYear)/rethAmount)*borrowAmount)/1 ether,0.0000001 ether); + + // make it liquiditable, check shortfall +- (, uint liquidity, uint shortfall, uint badDebt) = comptroller ++ (, uint liquidity, uint shortfall, uint badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log(liquidity); + console2.log(shortfall); + console2.log(badDebt); + cReth.borrow((borrowAmount * 8) / 10); +- (, liquidity, shortfall, badDebt) = comptroller ++ (, liquidity, shortfall, badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log(liquidity); + console2.log(shortfall); +@@ -1449,7 +1599,7 @@ contract LendingTest is Setup, ExponentialNoError { + + vm.roll(block.number + blocksPerYear / 4); + cReth.accrueInterest(); +- (, uint liquidity, uint shortfall, uint badDebt) = comptroller ++ (, uint liquidity, uint shortfall, uint badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + + +@@ -1463,7 +1613,7 @@ contract LendingTest is Setup, ExponentialNoError { + vault.liquidateLstBorrower(userA, type(uint256).max, true, true); + console2.log("liquidator profit:", rEth.balanceOf(userC) - balC); + +- (, liquidity, shortfall, badDebt) = comptroller ++ (, liquidity, shortfall, badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log(liquidity); + console2.log(shortfall); +@@ -1477,7 +1627,7 @@ contract LendingTest is Setup, ExponentialNoError { + // make it liquiditable by changing vault fees + vm.startPrank(deployer); + vaultManager.setSellFee(0.90 ether); +- (, uint liquidity, uint shortfall, uint badDebt) = comptroller ++ (, uint liquidity, uint shortfall, uint badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log(liquidity); + console2.log(shortfall); +@@ -1490,7 +1640,7 @@ contract LendingTest is Setup, ExponentialNoError { + vault.liquidateLstBorrower(userA, type(uint256).max, true, false); + console2.log("liquidator profit:", rEth.balanceOf(userC) - balC); + +- (, liquidity, shortfall, badDebt) = comptroller ++ (, liquidity, shortfall, badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log(liquidity); + console2.log(shortfall); +@@ -1504,7 +1654,7 @@ contract LendingTest is Setup, ExponentialNoError { + // make it liquiditable by changing vault fees + vm.startPrank(deployer); + vaultManager.setSellFee(0.90 ether); +- (, uint liquidity, uint shortfall, uint badDebt) = comptroller ++ (, uint liquidity, uint shortfall, uint badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log(liquidity); + console2.log(shortfall); +@@ -1516,7 +1666,7 @@ contract LendingTest is Setup, ExponentialNoError { + vault.liquidateLstBorrower(userA, type(uint256).max, false, false); + console2.log("liquidator profit + input:", numa.balanceOf(userC)); + +- (, liquidity, shortfall, badDebt) = comptroller ++ (, liquidity, shortfall, badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log(liquidity); + console2.log(shortfall); +@@ -1525,12 +1675,15 @@ contract LendingTest is Setup, ExponentialNoError { + + function test_LstBorrowLstVault_JRV4_liquidateBadDebt() public { + prepare_LstBorrowLstVault_JRV4(); ++ ++ ++ + //vm.roll(block.number + blocksPerYear/4); + //cReth.accrueInterest(); + // make it liquiditable by changing vault fees + vm.startPrank(deployer); + vaultManager.setSellFee(0.85 ether); +- (, uint liquidity, uint shortfall, uint badDebt) = comptroller ++ (, uint liquidity, uint shortfall, uint badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log(liquidity); + console2.log(shortfall); +@@ -1544,9 +1697,13 @@ contract LendingTest is Setup, ExponentialNoError { + vault.liquidateLstBorrower(userA, type(uint256).max, false, false); + vault.liquidateBadDebt(userA, 500, cNuma); + +- assertEq(numa.balanceOf(userC), 500 ether); ++ // 1000 cnuma burnt for first depositor ++ // = 2x10^11 numa ++ // 500x10^18 - 1x10^11 = 4999999900000000000 ++ //assertEq(numa.balanceOf(userC), 500 ether); ++ assertEq(numa.balanceOf(userC), 499999999900000000000); + +- (, liquidity, shortfall, badDebt) = comptroller ++ (, liquidity, shortfall, badDebt,) = comptroller + .getAccountLiquidityIsolate(userA, cNuma, cReth); + console2.log(liquidity); + console2.log(shortfall); +@@ -1572,7 +1729,7 @@ contract LendingTest is Setup, ExponentialNoError { + numa.approve(address(cNuma), depositAmount); + cNuma.mint(depositAmount); + assertEq(numaBalBefore - numa.balanceOf(userA), depositAmount); +- assertEq(cNuma.balanceOf(userA), (depositAmount * 50 * 1e8) / 1 ether); ++ assertEq(cNuma.balanceOf(userA), (depositAmount * 50 * 1e8) / 1 ether - 1000);// first depositor has 1000 less + + // borrow reth + // should revert not enough collat +@@ -1755,4 +1912,126 @@ contract LendingTest is Setup, ExponentialNoError { + + } + ++ ++ // partial liquidate borrow when bad debt ++ function test_NumaBorrow_JRV4_liquidateBadDebtPartial() public { ++ prepare_numaBorrow_JRV4(); ++ // make it bad debt ++ // vm.roll(block.number + blocksPerYear/4); ++ // cNuma.accrueInterest(); ++ vm.startPrank(deployer); ++ vaultManager.setBuyFee(0.8 ether); ++ comptroller._setLtvThresholds(0.98 ether,1.05 ether); ++ vault.setMinBorrowAmountAllowPartialLiquidation(1000000000 ether); ++ (, uint liquidity, uint shortfall, uint badDebt,uint ltv) = comptroller ++ .getAccountLiquidityIsolate(userA, cReth, cNuma); ++ console2.log("liquidity",liquidity); ++ console2.log("shortfall",shortfall); ++ console2.log("badDebt",badDebt); ++ console2.log("ltv",ltv); ++ // liquidate ++ ++ vm.startPrank(userC); ++ ++ uint numaAmountBuy = 1000 ether; ++ rEth.approve(address(vault), 2 * numaAmountBuy); ++ vault.buy(2 * numaAmountBuy, numaAmountBuy, userC); ++ ++ uint balC = rEth.balanceOf(userC); ++ numa.approve(address(vault), numa.balanceOf(userC)); ++ ++ uint borrowBalance = cNuma.borrowBalanceCurrent(userA); ++ // revert because we are in bad debt ++ vm.expectRevert(); ++ vault.liquidateNumaBorrower(userA, borrowBalance, false, false); ++ ++ vault.liquidateNumaBorrower(userA, borrowBalance/2, false, false); ++ ++ ++ (, liquidity, shortfall, badDebt,) = comptroller ++ .getAccountLiquidityIsolate(userA, cReth, cNuma); ++ console2.log(liquidity); ++ console2.log(shortfall); ++ console2.log(badDebt); ++ } ++ ++ function test_NumaBorrow_JRV4_liquidatePartialImpossible() public { ++ prepare_numaBorrow_JRV4(); ++ ++ vm.startPrank(deployer); ++ // make it liquiditable ++ vaultManager.setBuyFee(0.868 ether); ++ comptroller._setLtvThresholds(0.98 ether,1.05 ether); ++ vault.setMinBorrowAmountAllowPartialLiquidation(1000000000 ether); ++ (, uint liquidity, uint shortfall, uint badDebt,uint ltv) = comptroller ++ .getAccountLiquidityIsolate(userA, cReth, cNuma); ++ console2.log("liquidity",liquidity); ++ console2.log("shortfall",shortfall); ++ console2.log("badDebt",badDebt); ++ console2.log("ltv",ltv); ++ // liquidate ++ ++ vm.startPrank(userC); ++ ++ uint numaAmountBuy = 1000 ether; ++ rEth.approve(address(vault), 2 * numaAmountBuy); ++ vault.buy(2 * numaAmountBuy, numaAmountBuy, userC); ++ ++ uint balC = rEth.balanceOf(userC); ++ numa.approve(address(vault), numa.balanceOf(userC)); ++ ++ uint borrowBalance = cNuma.borrowBalanceCurrent(userA); ++ ++ vm.expectRevert(); ++ vault.liquidateNumaBorrower(userA, borrowBalance/2, false, false); ++ ++ vault.liquidateNumaBorrower(userA, borrowBalance, false, false); ++ ++ (, liquidity, shortfall, badDebt,) = comptroller ++ .getAccountLiquidityIsolate(userA, cReth, cNuma); ++ console2.log(liquidity); ++ console2.log(shortfall); ++ console2.log(badDebt); ++ } ++ ++ ++ function test_require_principal_revert_bug() public { ++ vm.startPrank(userA); ++ ++ // Supplying funds to cNuma market for us to borrow during leverageStrategy ++ // Supplier can be anybody need not to be userA ++ numa.approve(address(cNuma),50 ether); ++ cNuma.mint(50 ether); ++ ++ // ++ address[] memory t = new address[](2); ++ t[0] = address(cNuma); ++ t[1] = address(cReth); ++ comptroller.enterMarkets(t); ++ ++ // 1. Supply to numa market ++ rEth.approve(address(cReth),50 ether); ++ cReth.mint(50 ether); ++ ++ // 2. User's previous borrow ++ cNuma.borrow(10 ether); ++ ++ // 3. Rolling blocks so that interest will be accrued on user's previous borrow ++ vm.roll(block.number + 24 hours); ++ ++ ++ rEth.approve(address(cNuma), 10 ether); ++ // leverageStrategy will revert due to interest accurual ++ // vm.expectRevert("borrow ko"); ++ cNuma.leverageStrategy( ++ 1 ether, ++ 4 ether, ++ 5 ether,// max borrow ++ cReth, ++ 0 ++ ); ++ ++ } ++ ++ + } diff --git a/Numa/contracts/Test/Printer.t.sol b/Numa/contracts/Test/Printer.t.sol index 54918c2..78b87bf 100644 --- a/Numa/contracts/Test/Printer.t.sol +++ b/Numa/contracts/Test/Printer.t.sol @@ -633,6 +633,7 @@ contract PrinterTest is Setup { uint globalCF = vaultManager.getGlobalCF(); vm.startPrank(deployer); + uint warningCF = vaultManager.cf_warning(); vaultManager.setScalingParameters( vaultManager.cf_critical(), globalCF + 1, @@ -710,6 +711,7 @@ contract PrinterTest is Setup { uint globalCF = vaultManager.getGlobalCF(); vm.startPrank(deployer); + uint warningCF = vaultManager.cf_warning(); vaultManager.setScalingParameters( vaultManager.cf_critical(), globalCF + 1, @@ -813,7 +815,7 @@ contract PrinterTest is Setup { vm.startPrank(deployer); vaultManager.setScalingParameters( vaultManager.cf_critical(), - globalCF - 1, + warningCF, vaultManager.cf_severe(), vaultManager.debaseValue(), vaultManager.rebaseValue(), diff --git a/Numa/contracts/Test/Printer.t.sol.rej b/Numa/contracts/Test/Printer.t.sol.rej new file mode 100644 index 0000000..36bddca --- /dev/null +++ b/Numa/contracts/Test/Printer.t.sol.rej @@ -0,0 +1,699 @@ +diff a/Numa/contracts/Test/Printer.t.sol b/Numa/contracts/Test/Printer.t.sol (rejected hunks) +@@ -11,6 +11,9 @@ import "./uniV3Interfaces/ISwapRouter.sol"; + // + import {Setup, FEE_LOW} from "./utils/SetupDeployNuma_Arbitrum.sol"; + ++import "../NumaProtocol/USDToEthConverter.sol"; ++import {NuAsset2} from "../nuAssets/nuAsset2.sol"; ++import "../utils/constants.sol"; + + contract PrinterTest is Setup { + uint numaPriceVault; +@@ -76,13 +79,17 @@ contract PrinterTest is Setup { + nuAssetMgr.addNuAsset( + address(nuUSD), + PRICEFEEDETHUSD_ARBI, +- HEART_BEAT_CUSTOM ++ HEART_BEAT_CUSTOM, ++ true, ++ address(0) + ); + nuAssets = nuAssetMgr.getNuAssetList(); + assertEq(nuAssets.length,2); + nuAssetMgr.updateNuAsset(address(nuUSD), + PRICEFEEDETHUSD_ARBI, +- HEART_BEAT_CUSTOM ++ HEART_BEAT_CUSTOM, ++ true, ++ address(0) + ); + } + +@@ -201,6 +208,20 @@ contract PrinterTest is Setup { + assertEq(numaNeeded2, numaNeeded, "fee ko"); + } + ++ ++ function test_maxSpotOffsetBps() external ++ { ++ assertEq(NumaOracle(address(numaOracle)).maxSpotOffsetPlus1SqrtBps(), 10072, "maxSpotOffsetPlus1SqrtBps ko"); ++ assertEq(NumaOracle(address(numaOracle)).maxSpotOffsetMinus1SqrtBps(), 9927, "maxSpotOffsetMinus1SqrtBps ko"); ++ ++ //NumaOracle(address(numaOracle)).setMaxSpotOffsetBps(145);//1.45% ++ NumaOracle(address(numaOracle)).setMaxSpotOffset(0.0145 ether);//1.45% ++ ++ assertEq(NumaOracle(address(numaOracle)).maxSpotOffsetPlus1SqrtBps(), 10072, "maxSpotOffsetPlus1SqrtBps ko"); ++ assertEq(NumaOracle(address(numaOracle)).maxSpotOffsetMinus1SqrtBps(), 9927, "maxSpotOffsetMinus1SqrtBps ko"); ++ } ++ ++ + function test_getNbOfNumaFromAssetWithFee() external { + vm.stopPrank(); + vm.startPrank(userA); +@@ -245,7 +266,7 @@ contract PrinterTest is Setup { + + // to compare, we scale amount with fees + uint totalOut = numaOut + fee; +- totalOut = (totalOut * 800) / 1000; ++ totalOut = (totalOut * (BASE_SCALE - 0.2 ether)) / BASE_SCALE; + uint feeEstim2 = (totalOut * (moneyPrinter.burnAssetFeeBps())) / 10000; + + assertEq(numaOut2, totalOut - feeEstim2, "amount ko"); +@@ -259,17 +280,17 @@ contract PrinterTest is Setup { + ); + + // compute critical debase factor +- uint criticalDebaseFactor = (vaultManager.getGlobalCF() * 1000) / ++ uint criticalDebaseFactor = (vaultManager.getGlobalCF() * (1 ether)) / + vaultManager.cf_critical(); + // we apply this multiplier on the factor for when it's used on synthetics burning price + criticalDebaseFactor = + (criticalDebaseFactor * 1000) / + vaultManager.criticalDebaseMult(); +- assertLt(criticalDebaseFactor, 1000); ++ assertLt(criticalDebaseFactor, 1 ether); + (uint scaleSynthBurn2, ) = vaultManager.getSynthScalingUpdate(); + assertEq(scaleSynthBurn2, criticalDebaseFactor); + totalOut = numaOut + fee; +- totalOut = (criticalDebaseFactor * totalOut) / 1000; ++ totalOut = (criticalDebaseFactor * totalOut) / 1 ether; + feeEstim2 = (totalOut * (moneyPrinter.burnAssetFeeBps())) / 10000; + + assertEq(numaOut2, totalOut - feeEstim2, "amount ko"); +@@ -408,6 +430,8 @@ contract PrinterTest is Setup { + vaultManager.minimumScale(), + vaultManager.criticalDebaseMult() + ); ++ ++ + vm.startPrank(userA); + vm.expectRevert("minting forbidden"); + moneyPrinter.mintAssetFromNumaInput( +@@ -429,6 +453,7 @@ contract PrinterTest is Setup { + vaultManager.minimumScale(), + vaultManager.criticalDebaseMult() + ); ++ + vm.startPrank(userA); + // slippage test + vm.expectRevert("min amount"); +@@ -444,6 +469,7 @@ contract PrinterTest is Setup { + vm.startPrank(deployer); + moneyPrinter.pause(); + vm.stopPrank(); ++ + vm.startPrank(userA); + vm.expectRevert(bytes4(0xd93c0665)); + moneyPrinter.mintAssetFromNumaInput( +@@ -462,6 +488,7 @@ contract PrinterTest is Setup { + uint balnuUSD = nuUSD.balanceOf(userA); + uint balnuma = numa.balanceOf(userA); + ++ + moneyPrinter.mintAssetFromNumaInput( + address(nuUSD), + numaAmount, +@@ -531,7 +559,7 @@ contract PrinterTest is Setup { + vm.startPrank(deployer); + vaultManager.setScalingParameters( + vaultManager.cf_critical(), +- globalCF - 1, ++ warningCF, + vaultManager.cf_severe(), + vaultManager.debaseValue(), + vaultManager.rebaseValue(), +@@ -1118,189 +1146,99 @@ contract PrinterTest is Setup { + assertGt(numaNeeded2, (numaOut2 * 15) / 10, "amount ko"); + } + +- // function test_Converter() external { +- // //uint usdcAmountIn = 1000000; +- // uint usdcAmountIn = 100000000; +- // uint ethAmount = usdcEthConverter.convertTokenToEth(usdcAmountIn); +- // console2.log(ethAmount); +- +- // uint usdcAmount = usdcEthConverter.convertEthToToken(ethAmount); +- // console2.log(usdcAmount); +- +- // // TODOTEST +- // //assertEq(usdcAmountIn, usdcAmount); // it will fail because the code logic is not correct +- // } +- +- // function test_Mint_Estimations() external { +- // uint numaAmount = 1000e18; +- +- // // with 1000 NUMA "nuUSDAmount" will be minted +- // (uint256 nuUSDAmount, uint fee) = moneyPrinter.getNbOfNuAssetFromNuma( +- // address(nuUSD), +- // numaAmount +- // ); +- +- // // plug in the above numa amount to see they are identical! +- // (uint numaNeeded, uint fee2) = moneyPrinter.getNbOfNumaNeededAndFee( +- // address(nuUSD), +- // nuUSDAmount +- // ); +- +- // console2.log("nuUSD would be minted given NUMA: ", nuUSDAmount); +- +- // console2.log( +- // "If the same nuUSD would be the input for reverse trade the NUMA amount: ", +- // numaNeeded +- // ); +- +- // // TODOTEST +- // // refacto test +- // // assertEq(numaAmount, numaNeeded); // it will fail because the code logic is not correct +- // // assertEq(fee, fee2); // same as above. +- // } +- +- // function test_Mint_EstimationsRefacto() external { +- // // removing fees for now to see if it matches better +- // vm.stopPrank(); +- // vm.startPrank(deployer); +- // moneyPrinter.setPrintAssetFeeBps(0); +- // vm.stopPrank(); +- // vm.startPrank(userA); +- +- // uint numaAmount = 1000e18; +- +- // (uint256 nuUSDAmount4, uint fee4) = moneyPrinter.getNbOfNuAssetFromNuma( +- // address(nuUSD), +- // numaAmount +- // ); +- // console2.log( +- // "nuUSD would be minted given NUMA new fct: ", +- // nuUSDAmount4 +- // ); +- +- // (uint numaNeeded3, uint fee3) = moneyPrinter.getNbOfNumaNeededAndFee( +- // address(nuUSD), +- // nuUSDAmount4 +- // ); +- +- // console2.log( +- // "If the same nuUSD would be the input for reverse trade the NUMA amount new fct: ", +- // numaNeeded3 +- // ); +- +- // // TODOTEST +- // // refacto test +- // // assertEq(numaAmount, numaNeeded3); // it will fail because the code logic is not correct +- // // assertEq(fee4, fee3); // same as above. +- // } +- +- // function test_Burn() external { +- // numa.approve(address(moneyPrinter), type(uint).max); +- +- // uint numaAmount = 10_000e18; +- +- // uint nuUSDMinted = moneyPrinter.mintAssetFromNumaInput( +- // address(nuUSD), +- // numaAmount, +- // 0, +- // deployer +- // ); +- +- // console2.log("nuUSD minted", nuUSDMinted); +- +- // nuUSD.approve(address(moneyPrinter), type(uint).max); +- +- // uint numaMinted = moneyPrinter.burnAssetInputToNuma( +- // address(nuUSD), +- // nuUSDMinted, +- // 0, +- // deployer +- // ); +- +- // console2.log("Numa minted", numaMinted); +- // } +- +- // // gets +- // function testFuzz_mintAssetEstimations(uint nuAssetAmount) public view { +- // // we have 10000000 numa so we can not get more than 5000000 nuUSD +- // // and there is a 5% print fee, so max is 4750000 +- // vm.assume(nuAssetAmount <= 4750000 ether); +- +- // //vm.assume(nuAssetAmount == 16334);// KO +- +- // // test only reasonnable amounts +- // vm.assume(nuAssetAmount > 0.000001 ether); +- +- // (uint cost, uint fee) = moneyPrinter.getNbOfNumaNeededAndFee( +- // address(nuUSD), +- // nuAssetAmount +- // ); +- +- // // check the numbers +- // uint256 amountToBurn = (cost * moneyPrinter.printAssetFeeBps()) / 10000; +- // uint costWithoutFee = cost - amountToBurn; +- // uint estimatedOutput = costWithoutFee; +- +- // console.log(cost); +- // console.log(amountToBurn); +- // assertEq(fee, amountToBurn, "fees estim ko"); +- // assertApproxEqAbs( +- // estimatedOutput, +- // (nuAssetAmount * 1e18) / numaPricePoolL, +- // 0.01 ether, +- // "output estim ko" +- // ); // epsilon is big... +- +- // // other direction +- // (uint outAmount, uint fee2) = moneyPrinter.getNbOfNuAssetFromNuma( +- // address(nuUSD), +- // cost +- // ); +- // assertApproxEqAbs( +- // outAmount, +- // nuAssetAmount, +- // 0.0000000001 ether, +- // "nuAsset amount ko" +- // ); +- // assertEq(fee2, fee, "fee matching ko"); +- // } +- +- // function testFuzz_mintAssetEstimationsVaultClipped(uint nuAssetAmount) public { +- +- // // we have 10000000 numa so we can not get more than 5000000 nuUSD +- // // and there is a 5% print fee, so max is 4750000 +- // vm.assume(nuAssetAmount <= 4750000 ether); +- +- // // as we use pool lowest price here, to be clipped by vault, we need to set vault price inferior +- // // to do that we can mint some numa +- // numa.mint(deployer,numaSupply/2); +- +- // uint numaPriceVault2 = vaultManager.numaToEth(1 ether,IVaultManager.PriceType.NoFeePrice); +- // assertLt(numaPriceVault2,numaPriceVault,"new price ko"); +- // assertLt(numaPriceVault2,numaPricePoolL,"new price ko"); +- +- // (uint cost,uint fee) = moneyPrinter.getNbOfNumaNeededAndFee(address(nuUSD),nuAssetAmount); +- +- // // check the numbers +- // uint256 amountToBurn = (cost * moneyPrinter.printAssetFeeBps()) / 10000; +- // uint costWithoutFee = cost - amountToBurn; +- // uint estimatedOutput = costWithoutFee; +- // assertEq(fee,amountToBurn,"fees estim ko"); +- // assertEq(estimatedOutput,(nuAssetAmount*1e18)/numaPriceVault2,"output estim ko"); +- +- // // other direction +- // (uint outAmount,uint fee2) = moneyPrinter.getNbOfNuAssetFromNuma(address(nuUSD),cost); +- // assertEq(outAmount,nuAssetAmount,"nuAsset amount ko"); +- // assertEq(fee2,fee,"fee matching ko"); +- // } + ++ function test_priceFeedNoEth() external { ++ ++ USDToEthConverter converter = new USDToEthConverter(PRICEFEEDETHUSD_ARBI,HEART_BEAT_CUSTOM,UPTIME_FEED_ARBI); ++ NuAsset2 nuBTC2 = new NuAsset2("nuBTC2", "nuBTC2", deployer, deployer); ++ nuAssetMgr.addNuAsset( ++ address(nuBTC2), ++ PRICEFEEDBTCUSD_ARBI, ++ HEART_BEAT_CUSTOM, ++ false, ++ address(converter) ++ ); ++ ++ // set printer as a NuUSD minter ++ nuBTC2.grantRole(MINTER_ROLE, address(moneyPrinter)); ++ ++ uint numaAmount = 100000e18; ++ numa.transfer(userA, numaAmount); ++ ++ vm.stopPrank(); ++ vm.startPrank(userA); ++ ++ ++ ++ // TEST ESTIMATION ++ (uint256 nuBTCAmount, uint fee) = moneyPrinter.getNbOfNuAssetFromNuma( ++ address(nuBTC), ++ numaAmount ++ ); ++ ++ (uint256 nuBTCAmount2, uint fee2) = moneyPrinter.getNbOfNuAssetFromNuma( ++ address(nuBTC2), ++ numaAmount ++ ); ++ assertApproxEqAbs(nuBTCAmount, nuBTCAmount2,0.001 ether,"price ko"); ++ assertApproxEqAbs(fee, fee2, 0.00001 ether,"fee ko"); ++ ++ ++ // TEST ESTIMATION 2 (needed) ++ uint nuBtcAmount = 10e18; ++ ++ // compare getNbOfNuAssetFromNuma ++ (uint numaNeeded, uint fee3) = moneyPrinter.getNbOfNumaNeededAndFee( ++ address(nuBTC2), ++ nuBtcAmount ++ ); ++ (uint numaNeeded2, uint fee4) = moneyPrinter.getNbOfNumaNeededAndFee( ++ address(nuBTC), ++ nuBtcAmount ++ ); ++ assertApproxEqAbs(numaNeeded, numaNeeded2,5000 ether,"price ko"); ++ assertApproxEqAbs(fee, fee2, 50 ether,"fee ko"); ++ ++ // TEST TRANSACTION ++ uint balnuBTC2 = nuBTC2.balanceOf(userA); ++ uint balnuma = numa.balanceOf(userA); ++ ++ numa.approve(address(moneyPrinter),numaAmount); ++ moneyPrinter.mintAssetFromNumaInput( ++ address(nuBTC2), ++ numaAmount, ++ nuBTCAmount2, ++ userA ++ ); ++ uint balnuBTC2After = nuBTC2.balanceOf(userA); ++ uint balnumaAfter = numa.balanceOf(userA); ++ assertEq(balnuBTC2After - balnuBTC2, nuBTCAmount2); ++ assertEq(balnuma - balnumaAfter, numaAmount); ++ ++ // TEST TOTAL SYNTH VALUE ++ uint synthValue = nuAssetMgr.getTotalSynthValueEth(); ++ ++ ++ console2.log("nuBTCAmount2",nuBTCAmount2); ++ console2.log("(nuBTCAmount2*uint(btcusd))/(uint(ethusd))",(nuBTCAmount2*uint(btcusd))/(uint(ethusd))); ++ assertEq(synthValue, (nuBTCAmount2*uint(btcusd))/(uint(ethusd))); ++ ++ ++ } ++ ++ ++ + function forceSynthDebasing() public { + uint globalCF = vaultManager.getGlobalCF(); +- ++ console2.log("globalCF", globalCF); + vm.startPrank(deployer); ++ // now we can not mint if after mint we become below warningCF. ++ // so I can not mint such that we become in cf_severe. ++ // simulate cf_severe can only be done by changing oracle prices ++ // or modifying cf_warning + vaultManager.setScalingParameters( + vaultManager.cf_critical(), +- vaultManager.cf_warning(), ++ vaultManager.cf_warning() -1000, + vaultManager.cf_severe(), + vaultManager.debaseValue(), + vaultManager.rebaseValue(), +@@ -1310,6 +1248,18 @@ contract PrinterTest is Setup { + vaultManager.criticalDebaseMult() + ); + ++ // vaultManager.setScalingParameters( ++ // vaultManager.cf_critical(), ++ // vaultManager.cf_warning(), ++ // vaultManager.cf_severe(), ++ // vaultManager.debaseValue(), ++ // vaultManager.rebaseValue(), ++ // 1 hours, ++ // 2 hours, ++ // vaultManager.minimumScale(), ++ // vaultManager.criticalDebaseMult() ++ // ); ++ + numa.approve(address(moneyPrinter), 10000000 ether); + + // checking numa price +@@ -1336,7 +1286,20 @@ contract PrinterTest is Setup { + uint scaleSynthBurn2, + uint criticalScaleForNumaPriceAndSellFee2 + ) = vaultManager.getSynthScalingUpdate(); +- assertEq(scaleSynthBurn2, 800); ++ ++ assertEq(scaleSynthBurn2, BASE_SCALE - 0.2 ether); ++ // put it back ++ vaultManager.setScalingParameters( ++ vaultManager.cf_critical(), ++ vaultManager.cf_warning() + 1000, ++ vaultManager.cf_severe(), ++ vaultManager.debaseValue(), ++ vaultManager.rebaseValue(), ++ 1 hours, ++ 2 hours, ++ vaultManager.minimumScale(), ++ vaultManager.criticalDebaseMult() ++ ); + } + + function forceSynthDebasingCritical() public { +@@ -1368,7 +1331,7 @@ contract PrinterTest is Setup { + // //assertEq(scaleSynthBurn2,800); + + uint globalCF2 = vaultManager.getGlobalCF(); +- console2.log(globalCF2); ++ console2.log("current CF",globalCF2); + + // critical_cf + vaultManager.setScalingParameters( +@@ -1383,247 +1346,4 @@ contract PrinterTest is Setup { + vaultManager.criticalDebaseMult() + ); + } +- +- // function test_SynthScaling() public { +- // uint globalCF = vaultManager.getGlobalCF(); +- // assertGt(globalCF, vaultManager.cf_critical()); +- // console2.log(globalCF); +- // vm.startPrank(deployer); +- // vaultManager.setScalingParameters( +- // vaultManager.cf_critical(), +- // vaultManager.cf_warning(), +- // vaultManager.cf_severe(), +- // vaultManager.debaseValue(), +- // vaultManager.rebaseValue(), +- // 1 hours, +- // 2 hours, +- // vaultManager.minimumScale(), +- // vaultManager.criticalDebaseMult() +- // ); +- +- // numa.approve(address(moneyPrinter), 10000000 ether); +- +- // moneyPrinter.mintAssetOutputFromNuma( +- // address(nuUSD), +- // 4500000 ether, +- // 10000000 ether, +- // deployer +- // ); +- +- // uint globalCF2 = vaultManager.getGlobalCF(); +- // console2.log(globalCF2); +- // assertLt(globalCF2, globalCF); +- +- // (uint scaleSynthBurn,uint criticalScaleForNumaPriceAndSellFee) = vaultManager.getSynthScalingUpdate(); +- // console2.log(scaleSynthBurn); +- // console2.log(criticalScaleForNumaPriceAndSellFee); +- // assertEq(scaleSynthBurn,1000); +- // assertEq(criticalScaleForNumaPriceAndSellFee,1000); +- // // test debase +- // vm.warp(block.timestamp + 10 hours); +- // (uint scaleSynthBurn2,uint criticalScaleForNumaPriceAndSellFee2) = vaultManager.getSynthScalingUpdate(); +- // console2.log(scaleSynthBurn2); +- // console2.log(criticalScaleForNumaPriceAndSellFee2); +- // assertEq(scaleSynthBurn2,800); +- // assertEq(criticalScaleForNumaPriceAndSellFee2,criticalScaleForNumaPriceAndSellFee); +- +- // vm.warp(block.timestamp + 10 hours); +- // (scaleSynthBurn2,criticalScaleForNumaPriceAndSellFee2) = vaultManager.getSynthScalingUpdate(); +- // console2.log(scaleSynthBurn2); +- // console2.log(criticalScaleForNumaPriceAndSellFee2); +- // assertEq(scaleSynthBurn2,600); +- // assertEq(criticalScaleForNumaPriceAndSellFee2,criticalScaleForNumaPriceAndSellFee); +- +- // // min reached +- // vm.warp(block.timestamp + 10 hours); +- // (scaleSynthBurn2,criticalScaleForNumaPriceAndSellFee2) = vaultManager.getSynthScalingUpdate(); +- // console2.log(scaleSynthBurn2); +- // console2.log(criticalScaleForNumaPriceAndSellFee2); +- // assertEq(scaleSynthBurn2,vaultManager.minimumScale()); +- // assertEq(criticalScaleForNumaPriceAndSellFee2,criticalScaleForNumaPriceAndSellFee); +- +- // globalCF2 = vaultManager.getGlobalCF(); +- // console2.log(globalCF2); +- +- // // critical_cf +- // vaultManager.setScalingParameters( +- // globalCF2 + 1, +- // vaultManager.cf_warning(), +- // vaultManager.cf_severe(), +- // vaultManager.debaseValue(), +- // vaultManager.rebaseValue(), +- // 1 hours, +- // 2 hours, +- // vaultManager.minimumScale(), +- // vaultManager.criticalDebaseMult() +- // ); +- // (uint scaleSynthBurn3,uint criticalScaleForNumaPriceAndSellFee3) = vaultManager.getSynthScalingUpdate(); +- // console2.log(scaleSynthBurn3); +- // console2.log(criticalScaleForNumaPriceAndSellFee3); +- +- // uint criticalDebaseFactor = (globalCF2 * 1000) / vaultManager.cf_critical(); +- // console2.log("criticalDebaseFactor",criticalDebaseFactor); +- +- // uint scalePriceFee = criticalDebaseFactor; +- +- // // we apply this multiplier on the factor for when it's used on synthetics burning price +- // criticalDebaseFactor = +- // (criticalDebaseFactor * 1000) / +- // vaultManager.criticalDebaseMult(); +- +- // console2.log("criticalDebaseFactorMult",criticalDebaseFactor); +- +- // console2.log("criticalScaleForNumaPriceAndSellFee",scalePriceFee); +- +- // // test that we use min for scaling +- // assertEq(scaleSynthBurn3,vaultManager.minimumScale()); +- // // for price and sell fee we use critical debase factor +- // assertEq(criticalScaleForNumaPriceAndSellFee3,scalePriceFee); +- +- // // check critical scale value +- // vaultManager.setScalingParameters( +- // globalCF2 *3, +- // vaultManager.cf_warning(), +- // vaultManager.cf_severe(), +- // vaultManager.debaseValue(), +- // vaultManager.rebaseValue(), +- // 1 hours, +- // 2 hours, +- // vaultManager.minimumScale(), +- // vaultManager.criticalDebaseMult() +- // ); +- // (scaleSynthBurn3,criticalScaleForNumaPriceAndSellFee3) = vaultManager.getSynthScalingUpdate(); +- // console2.log(scaleSynthBurn3); +- // console2.log(criticalScaleForNumaPriceAndSellFee3); +- +- // criticalDebaseFactor = (globalCF2 * 1000) / vaultManager.cf_critical(); +- // console2.log("criticalDebaseFactor",criticalDebaseFactor); +- +- // scalePriceFee = criticalDebaseFactor; +- +- // // we apply this multiplier on the factor for when it's used on synthetics burning price +- // criticalDebaseFactor = +- // (criticalDebaseFactor * 1000) / +- // vaultManager.criticalDebaseMult(); +- +- // console2.log("criticalDebaseFactorMult",criticalDebaseFactor); +- +- // console2.log("criticalScaleForNumaPriceAndSellFee",scalePriceFee); +- +- // // test rebase +- // vaultManager.setScalingParameters( +- // 1100, +- // vaultManager.cf_warning(), +- // vaultManager.cf_severe(), +- // vaultManager.debaseValue(), +- // vaultManager.rebaseValue(), +- // 1 hours, +- // 2 hours, +- // vaultManager.minimumScale(), +- // vaultManager.criticalDebaseMult() +- // ); +- +- // nuUSD.approve(address(moneyPrinter), 4500000 ether); +- +- // moneyPrinter.burnAssetInputToNuma( +- // address(nuUSD), +- // 4500000 ether, +- // 0, +- // userA +- // ); +- // (scaleSynthBurn2,criticalScaleForNumaPriceAndSellFee2) = vaultManager.getSynthScalingUpdate(); +- // assertEq(scaleSynthBurn2,vaultManager.minimumScale()); +- // assertEq(criticalScaleForNumaPriceAndSellFee2,criticalScaleForNumaPriceAndSellFee); +- +- // // rebase +- // vm.warp(block.timestamp + 10 hours); +- // (scaleSynthBurn2,criticalScaleForNumaPriceAndSellFee2) = vaultManager.getSynthScalingUpdate(); +- // assertEq(scaleSynthBurn2,vaultManager.minimumScale()+150); +- // assertEq(criticalScaleForNumaPriceAndSellFee2,criticalScaleForNumaPriceAndSellFee); +- // // rebase again +- // vm.warp(block.timestamp + 10 hours); +- // (scaleSynthBurn2,criticalScaleForNumaPriceAndSellFee2) = vaultManager.getSynthScalingUpdate(); +- // assertEq(scaleSynthBurn2,vaultManager.minimumScale()+300); +- // assertEq(criticalScaleForNumaPriceAndSellFee2,criticalScaleForNumaPriceAndSellFee); +- +- // // test max +- // vm.warp(block.timestamp + 30 hours); +- // (scaleSynthBurn2,criticalScaleForNumaPriceAndSellFee2) = vaultManager.getSynthScalingUpdate(); +- // assertEq(scaleSynthBurn2,1000); +- // assertEq(criticalScaleForNumaPriceAndSellFee2,criticalScaleForNumaPriceAndSellFee); +- +- // } +- // function testFuzz_mintAssetEstimationsSynthScaled( +- // uint nuAssetAmount +- // ) public { +- // // we have 10000000 numa so we can not get more than 5000000 nuUSD +- // // and there is a 5% print fee, so max is 4750000 +- // vm.assume(nuAssetAmount <= 4750000 ether); +- +- // // +- +- // uint numaPriceVault2 = vaultManager.numaToEth( +- // 1 ether, +- // IVaultManager.PriceType.NoFeePrice +- // ); +- // assertLt(numaPriceVault2, numaPriceVault, "new price ko"); +- // assertLt(numaPriceVault2, numaPricePoolL, "new price ko"); +- +- // (uint cost, uint fee) = moneyPrinter.getNbOfNumaNeededAndFee( +- // address(nuUSD), +- // nuAssetAmount +- // ); +- +- // // check the numbers +- // uint256 amountToBurn = (cost * moneyPrinter.printAssetFeeBps()) / 10000; +- // uint costWithoutFee = cost - amountToBurn; +- // uint estimatedOutput = costWithoutFee; +- // assertEq(fee, amountToBurn, "fees estim ko"); +- // assertEq( +- // estimatedOutput, +- // (nuAssetAmount * 1e18) / numaPriceVault2, +- // "output estim ko" +- // ); +- +- // // other direction +- // (uint outAmount, uint fee2) = moneyPrinter.getNbOfNuAssetFromNuma( +- // address(nuUSD), +- // cost +- // ); +- // assertEq(outAmount, nuAssetAmount, "nuAsset amount ko"); +- // assertEq(fee2, fee, "fee matching ko"); +- // } +- +- // numa.approve(address(moneyPrinter),type(uint).max); +- +- // // check slippage test +- // vm.expectRevert("min amount"); +- // uint maxAmountReached = cost - 1; +- // moneyPrinter.mintAssetOutputFromNuma(address(nuUSD),nuAssetAmount,maxAmountReached,deployer); +- +- // // check print +- // uint numaBalBefore = numa.balanceOf(deployer); +- // uint nuUSDBefore = nuUSD.balanceOf(deployer); +- +- // moneyPrinter.mintAssetOutputFromNuma(address(nuUSD),nuAssetAmount,cost,deployer); +- // uint numaBalAfter = numa.balanceOf(deployer); +- // uint nuUSDAfter = nuUSD.balanceOf(deployer); +- +- // assertEq(numaBalBefore - numaBalAfter,cost,"input amount ko"); +- // assertEq(nuUSDAfter - nuUSDBefore,nuAssetAmount,"output amount ko"); +- +- // function testFuzz_SwapEstimations(uint nuUSDAmountIn) public { +- +- // // +- // (uint nuBTCAmountOut,uint swapFee1) = moneyPrinter.getNbOfNuAssetFromNuAsset(address(nuUSD),address(nuBTC),nuUSDAmountIn); +- // // check fee +- // assertEq(swapFee1,(nuUSDAmountIn * moneyPrinter.swapAssetFeeBps()) / 10000); +- // // +- // (uint nuUSDCIn,uint swapFee2) = moneyPrinter.getNbOfNuAssetNeededForNuAsset(address(nuUSD),address(nuBTC),nuBTCAmountOut); +- +- // // check matching +- // assertEq(nuUSDCIn,nuUSDAmountIn); +- // // check fee +- // assertEq(swapFee1,swapFee2); +- // } + } diff --git a/Numa/contracts/Test/VaultBuySellFee.t.sol.rej b/Numa/contracts/Test/VaultBuySellFee.t.sol.rej new file mode 100644 index 0000000..8aff40a --- /dev/null +++ b/Numa/contracts/Test/VaultBuySellFee.t.sol.rej @@ -0,0 +1,77 @@ +diff a/Numa/contracts/Test/VaultBuySellFee.t.sol b/Numa/contracts/Test/VaultBuySellFee.t.sol (rejected hunks) +@@ -5,7 +5,7 @@ import "forge-std/console2.sol"; + import {Setup} from "./utils/SetupDeployNuma_Arbitrum.sol"; + import "../lending/ExponentialNoError.sol"; + import "../interfaces/IVaultManager.sol"; +- ++import "../utils/constants.sol"; + contract VaultBuySellFeeTest is Setup, ExponentialNoError { + uint buy_fee_PID; + function setUp() public virtual override { +@@ -682,10 +682,24 @@ contract VaultBuySellFeeTest is Setup, ExponentialNoError { + uint globalCF = vaultManager.getGlobalCF(); + assertGt(globalCF, vaultManager.cf_critical()); + +- vm.prank(deployer); ++ vm.startPrank(deployer); + numa.approve(address(moneyPrinter), 10000000 ether); +- // function mintAssetOutputFromNuma( +- vm.prank(deployer); ++ ++ // change warning cf so that we can mint enough ++ uint warningCF = vaultManager.cf_warning(); ++ vaultManager.setScalingParameters( ++ vaultManager.cf_critical(), ++ 0, ++ vaultManager.cf_severe(), ++ vaultManager.debaseValue(), ++ vaultManager.rebaseValue(), ++ 1 hours, ++ 2 hours, ++ vaultManager.minimumScale(), ++ vaultManager.criticalDebaseMult() ++ ); ++ ++ + moneyPrinter.mintAssetOutputFromNuma( + address(nuUSD), + 4500000 ether, +@@ -701,7 +715,7 @@ contract VaultBuySellFeeTest is Setup, ExponentialNoError { + vm.startPrank(deployer); + vaultManager.setScalingParameters( + 1200, +- vaultManager.cf_warning(), ++ warningCF, + vaultManager.cf_severe(), + vaultManager.debaseValue(), + vaultManager.rebaseValue(), +@@ -711,12 +725,14 @@ contract VaultBuySellFeeTest is Setup, ExponentialNoError { + vaultManager.criticalDebaseMult() + ); + +- uint criticalScaleForNumaPriceAndSellFee = (1000 * globalCF2) / ++ uint criticalScaleForNumaPriceAndSellFee = (BASE_SCALE * globalCF2) / + vaultManager.cf_critical(); + ++ console2.log("criticalScaleForNumaPriceAndSellFee TEST", criticalScaleForNumaPriceAndSellFee); ++ + +- uint sell_fee_increaseCriticalCF = ((1000 - +- criticalScaleForNumaPriceAndSellFee) * 1 ether) / 1000; ++ uint sell_fee_increaseCriticalCF = ((BASE_SCALE - ++ criticalScaleForNumaPriceAndSellFee) * 1 ether) / BASE_SCALE; + // add a multiplier on top + sell_fee_increaseCriticalCF = + (sell_fee_increaseCriticalCF * +@@ -733,8 +749,9 @@ contract VaultBuySellFeeTest is Setup, ExponentialNoError { + if (sell_fee_criticalCF < vaultManager.sell_fee_minimum_critical()) + sell_fee_criticalCF = vaultManager.sell_fee_minimum_critical(); + (sell_feePID, , ) = vaultManager.getSellFeeScaling(); +- ++ console2.log("sell_fee_criticalCF", sell_fee_criticalCF); ++ console2.log("sell_feePID", sell_feePID); + +- assertEq(sell_feePID, sell_fee_criticalCF); ++ assertEq(sell_feePID, sell_fee_criticalCF,"sell fee critical"); + } + } diff --git a/Numa/contracts/Test/VaultMigrations.t.sol.rej b/Numa/contracts/Test/VaultMigrations.t.sol.rej new file mode 100644 index 0000000..b587483 --- /dev/null +++ b/Numa/contracts/Test/VaultMigrations.t.sol.rej @@ -0,0 +1,19 @@ +diff a/Numa/contracts/Test/VaultMigrations.t.sol b/Numa/contracts/Test/VaultMigrations.t.sol (rejected hunks) +@@ -19,7 +19,7 @@ import "forge-std/console2.sol"; + import {Setup} from "./utils/Setup_ArbitrumFork.sol"; + import "../lending/ExponentialNoError.sol"; + import "../interfaces/IVaultManager.sol"; +-import {nuAssetManager} from "./../nuAssets/nuAssetManager.sol"; ++import {nuAssetManager2} from "./../nuAssets/nuAssetManager2.sol"; + import {NumaMinter} from "./../NumaProtocol/NumaMinter.sol"; + import {VaultOracleSingle} from "./../NumaProtocol/VaultOracleSingle.sol"; + import {VaultManager} from "./../NumaProtocol/VaultManager.sol"; +@@ -39,7 +39,7 @@ contract VaultMigrationTest is Setup, ExponentialNoError { + // uint sellResult; + + // +- nuAssetManager nuAssetMgr2; ++ nuAssetManager2 nuAssetMgr2; + NumaMinter numaMinter2; + VaultOracleSingle vaultOracle2; + VaultManager vaultManager2; diff --git a/Numa/contracts/Test/utils/ConstantsTest.sol.rej b/Numa/contracts/Test/utils/ConstantsTest.sol.rej new file mode 100644 index 0000000..3c37969 --- /dev/null +++ b/Numa/contracts/Test/utils/ConstantsTest.sol.rej @@ -0,0 +1,11 @@ +diff a/Numa/contracts/Test/utils/ConstantsTest.sol b/Numa/contracts/Test/utils/ConstantsTest.sol (rejected hunks) +@@ -62,6 +62,9 @@ contract ConstantsTest { + + // lending protocol + uint blocksPerYear = 2102400; // TODO here eth values for test ++ uint borrowRateMaxMantissa = 0.0005e16;// TODO here eth values for test ++ ++ + uint baseRatePerYear = 0.02 ether; // 2% + uint multiplierPerYear = 0.01 ether; // 1% + uint jumpMultiplierPerYear = 4 ether; //400% diff --git a/Numa/contracts/Test/utils/SetupBase.sol.rej b/Numa/contracts/Test/utils/SetupBase.sol.rej new file mode 100644 index 0000000..a349435 --- /dev/null +++ b/Numa/contracts/Test/utils/SetupBase.sol.rej @@ -0,0 +1,75 @@ +diff a/Numa/contracts/Test/utils/SetupBase.sol b/Numa/contracts/Test/utils/SetupBase.sol (rejected hunks) +@@ -25,7 +25,7 @@ import {FakeNuma} from "../mocks/FakeNuma.sol"; + import "../../interfaces/INuma.sol"; + + import {LstTokenMock} from "../mocks/LstTokenMock.sol"; +-import {nuAssetManager} from "../../nuAssets/nuAssetManager.sol"; ++import {nuAssetManager2} from "../../nuAssets/nuAssetManager2.sol"; + import {NumaMinter} from "../../NumaProtocol/NumaMinter.sol"; + import {VaultOracleSingle} from "../../NumaProtocol/VaultOracleSingle.sol"; + import {VaultManager} from "../../NumaProtocol/VaultManager.sol"; +@@ -61,7 +61,7 @@ contract SetupBase is + ERC20 rEth; + ERC20 usdc; + // Vault +- nuAssetManager nuAssetMgr; ++ nuAssetManager2 nuAssetMgr; + NumaMinter numaMinter; + VaultOracleSingle vaultOracle; + VaultManager vaultManager; +@@ -119,7 +119,7 @@ contract SetupBase is + ) + internal + returns ( +- nuAssetManager nuAM, ++ nuAssetManager2 nuAM, + NumaMinter minter, + VaultManager vaultm, + VaultOracleSingle vo, +@@ -141,7 +141,7 @@ contract SetupBase is + address(rEth) + ); + +- nuAM = new nuAssetManager(UPTIME_FEED_ARBI); ++ nuAM = new nuAssetManager2(UPTIME_FEED_ARBI); + (nuAM, minter, vaultm, vo, v) = setupVaultAndAssetManager(parameters); + + +@@ -217,19 +217,23 @@ contract SetupBase is + address _printerAddress + ) internal { + // register nuAsset +- nuAssetManager(_nuAssetMgrAddress).addNuAsset( ++ nuAssetManager2(_nuAssetMgrAddress).addNuAsset( + address(nuUSD), + PRICEFEEDETHUSD_ARBI, +- HEART_BEAT_CUSTOM ++ HEART_BEAT_CUSTOM, ++ true, ++ address(0) + ); + // set printer as a NuUSD minter + nuUSD.grantRole(MINTER_ROLE, _printerAddress); // owner is NuUSD deployer + + // register nuAsset +- nuAssetManager(_nuAssetMgrAddress).addNuAsset( ++ nuAssetManager2(_nuAssetMgrAddress).addNuAsset( + address(nuBTC), + PRICEFEEDBTCETH_ARBI, +- HEART_BEAT ++ HEART_BEAT, ++ false, ++ address(0) + ); + // set printer as a NuUSD minter + nuBTC.grantRole(MINTER_ROLE, _printerAddress); // owner is NuUSD deployer +@@ -327,6 +331,9 @@ contract SetupBase is + address(_vault) + ); + ++ cReth._setBorrowRateMaxMantissa(borrowRateMaxMantissa); ++ cNuma._setBorrowRateMaxMantissa(borrowRateMaxMantissa); ++ + _vault.setMaxBorrow(1000 ether); + _vault.setCTokens(address(cNuma), address(cReth)); + diff --git a/Numa/contracts/Test/utils/Setup_ArbitrumFork.sol.rej b/Numa/contracts/Test/utils/Setup_ArbitrumFork.sol.rej new file mode 100644 index 0000000..dcc2348 --- /dev/null +++ b/Numa/contracts/Test/utils/Setup_ArbitrumFork.sol.rej @@ -0,0 +1,10 @@ +diff a/Numa/contracts/Test/utils/Setup_ArbitrumFork.sol b/Numa/contracts/Test/utils/Setup_ArbitrumFork.sol (rejected hunks) +@@ -62,7 +62,7 @@ contract Setup is SetupBase { + function setUp() public virtual { + numa_admin = NUMA_ADMIN; + // setup fork +- string memory ARBI_RPC_URL = vm.envString("URLARBI"); ++ string memory ARBI_RPC_URL = vm.envString("URL6"); + uint256 arbitrumFork = vm.createFork(ARBI_RPC_URL); + + vm.selectFork(arbitrumFork); diff --git a/Numa/contracts/deployment/utils.sol.rej b/Numa/contracts/deployment/utils.sol.rej new file mode 100644 index 0000000..7b3be7f --- /dev/null +++ b/Numa/contracts/deployment/utils.sol.rej @@ -0,0 +1,31 @@ +diff a/Numa/contracts/deployment/utils.sol b/Numa/contracts/deployment/utils.sol (rejected hunks) +@@ -2,7 +2,7 @@ + pragma solidity 0.8.20; + + import "../interfaces/INuma.sol"; +-import {nuAssetManager} from "../nuAssets/nuAssetManager.sol"; ++import {nuAssetManager2} from "../nuAssets/nuAssetManager2.sol"; + import {NumaMinter} from "../NumaProtocol/NumaMinter.sol"; + import {VaultOracleSingle} from "../NumaProtocol/VaultOracleSingle.sol"; + import {VaultManager} from "../NumaProtocol/VaultManager.sol"; +@@ -30,7 +30,7 @@ contract deployUtils { + ) + public + returns ( +- nuAssetManager nuAM, ++ nuAssetManager2 nuAM, + NumaMinter minter, + VaultManager vaultm, + VaultOracleSingle vo, +@@ -39,9 +39,9 @@ contract deployUtils { + { + // nuAssetManager + if (_parameters._existingAssetManager != address(0)) { +- nuAM = nuAssetManager(_parameters._existingAssetManager); ++ nuAM = nuAssetManager2(_parameters._existingAssetManager); + } else { +- nuAM = new nuAssetManager(_parameters._uptimefeed); ++ nuAM = new nuAssetManager2(_parameters._uptimefeed); + } + + diff --git a/Numa/contracts/deployment/vaultV2Deployer.sol.rej b/Numa/contracts/deployment/vaultV2Deployer.sol.rej new file mode 100644 index 0000000..673419b --- /dev/null +++ b/Numa/contracts/deployment/vaultV2Deployer.sol.rej @@ -0,0 +1,19 @@ +diff a/Numa/contracts/deployment/vaultV2Deployer.sol b/Numa/contracts/deployment/vaultV2Deployer.sol (rejected hunks) +@@ -4,7 +4,7 @@ import "../interfaces/INuma.sol"; + + import "./utils.sol"; + +-import {nuAssetManager} from "../nuAssets/nuAssetManager.sol"; ++import {nuAssetManager2} from "../nuAssets/nuAssetManager2.sol"; + import {NumaMinter} from "../NumaProtocol/NumaMinter.sol"; + import {VaultOracleSingle} from "../NumaProtocol/VaultOracleSingle.sol"; + import {VaultManager} from "../NumaProtocol/VaultManager.sol"; +@@ -27,7 +27,7 @@ contract vaultV2Deployer is deployUtils { + address uptimefeed; + + // out +- nuAssetManager public nuAssetMgr; ++ nuAssetManager2 public nuAssetMgr; + NumaMinter public numaMinter; + VaultOracleSingle public vaultOracle; + VaultManager public vaultManager; diff --git a/Numa/contracts/interfaces/INumaVault.sol.rej b/Numa/contracts/interfaces/INumaVault.sol.rej new file mode 100644 index 0000000..8e1e1c4 --- /dev/null +++ b/Numa/contracts/interfaces/INumaVault.sol.rej @@ -0,0 +1,10 @@ +diff a/Numa/contracts/interfaces/INumaVault.sol b/Numa/contracts/interfaces/INumaVault.sol (rejected hunks) +@@ -18,4 +18,8 @@ interface INumaVault { + function updateVault() external; + function getcNumaAddress() external view returns (address); + function getcLstAddress() external view returns (address); ++ ++ function getMinBorrowAmountAllowPartialLiquidation(address) external view returns (uint); ++ function borrowAllowed(address _ctokenAddress) external returns (bool); ++ + } diff --git a/Numa/contracts/interfaces/IVaultManager.sol.rej b/Numa/contracts/interfaces/IVaultManager.sol.rej new file mode 100644 index 0000000..6b2bdd7 --- /dev/null +++ b/Numa/contracts/interfaces/IVaultManager.sol.rej @@ -0,0 +1,7 @@ +diff a/Numa/contracts/interfaces/IVaultManager.sol b/Numa/contracts/interfaces/IVaultManager.sol (rejected hunks) +@@ -50,4 +50,5 @@ interface IVaultManager { + + function getSynthScaling() external view returns (uint, uint, uint, uint); + function getWarningCF() external view returns (uint); ++ function numaBorrowAllowed() external view returns (bool allowed); + } diff --git a/Numa/contracts/lending/CNumaToken.sol.rej b/Numa/contracts/lending/CNumaToken.sol.rej new file mode 100644 index 0000000..955f7ce --- /dev/null +++ b/Numa/contracts/lending/CNumaToken.sol.rej @@ -0,0 +1,193 @@ +diff a/Numa/contracts/lending/CNumaToken.sol b/Numa/contracts/lending/CNumaToken.sol (rejected hunks) +@@ -141,9 +141,20 @@ contract CNumaToken is CErc20Immutable { + function leverageStrategy( + uint _suppliedAmount, + uint _borrowAmount, ++ uint _maxBorrowAmount, + CNumaToken _collateral, + uint _strategyIndex + ) external { ++ ++ // Sherlock-issue 120 ++ require( ++ ( ++ ((address(this) == vault.getcLstAddress()) && (address(_collateral) == vault.getcNumaAddress())) ++ || ++ ((address(this) == vault.getcNumaAddress()) && (address(_collateral) == vault.getcLstAddress())) ++ ) ++ , "invalid collateral"); ++ + // AUDITV2FIX if we don't do that, borrow balance might change when calling borrowinternal + accrueInterest(); + _collateral.accrueInterest(); +@@ -179,24 +190,30 @@ contract CNumaToken is CErc20Immutable { + ); + + // send collateral to sender +- uint receivedtokens = balCtokenAfter - balCtokenBefore; +- require(receivedtokens > 0, "no collateral"); ++ //uint receivedtokens = balCtokenAfter - balCtokenBefore; ++ require((balCtokenAfter - balCtokenBefore) > 0, "no collateral"); + + // transfer collateral to sender + SafeERC20.safeTransfer( + IERC20(address(_collateral)), + msg.sender, +- receivedtokens ++ (balCtokenAfter - balCtokenBefore) + ); + + // how much to we need to borrow to repay vault + uint borrowAmount = strat.getAmountIn(_borrowAmount, false); +- // ++ ++ // sherlock issue-182 ++ require(borrowAmount <= _maxBorrowAmount); ++ ++ // Sherlock-issue 120 ++ //uint accountBorrowBefore = accountBorrows[msg.sender].principal; ++ uint accountBorrowBefore = borrowBalanceStored(msg.sender); + +- uint accountBorrowBefore = accountBorrows[msg.sender].principal; + // borrow but do not transfer borrowed tokens + borrowInternalNoTransfer(borrowAmount, msg.sender); +- //uint accountBorrowAfter = accountBorrows[msg.sender].principal; ++ ++ // + require( + (accountBorrows[msg.sender].principal - accountBorrowBefore) == + borrowAmount, +@@ -263,8 +280,19 @@ contract CNumaToken is CErc20Immutable { + function closeLeverageStrategy( + CNumaToken _collateral, + uint _borrowtorepay, ++ uint _maxRedeemedAmount, + uint _strategyIndex + ) external { ++ ++ // Sherlock-issue 120 ++ require( ++ ( ++ ((address(this) == vault.getcLstAddress()) && (address(_collateral) == vault.getcNumaAddress())) ++ || ++ ((address(this) == vault.getcNumaAddress()) && (address(_collateral) == vault.getcLstAddress())) ++ ) ++ , "invalid collateral"); ++ + // AUDITV2FIX + accrueInterest(); + _collateral.accrueInterest(); +@@ -275,8 +303,7 @@ contract CNumaToken is CErc20Immutable { + address underlyingCollateral = _collateral.underlying(); + // get borrowed amount + uint borrowAmountFull = borrowBalanceStored(msg.sender); +- require(borrowAmountFull >= _borrowtorepay, "no borrow"); +- ++ + // clip to borrowed amount + if (_borrowtorepay > borrowAmountFull) + _borrowtorepay = borrowAmountFull; +@@ -293,6 +320,9 @@ contract CNumaToken is CErc20Immutable { + _borrowtorepay, + _strategyIndex + ); ++ // sherlock issue-182 ++ //require(swapAmountIn >= _minRedeemedAmount); ++ require(swapAmountIn <= _maxRedeemedAmount); + + SafeERC20.safeTransferFrom( + IERC20(address(_collateral)), +@@ -346,17 +376,18 @@ contract CNumaToken is CErc20Immutable { + * @param borrower The borrower of this cToken to be liquidated + * @param repayAmount The amount of the underlying borrowed asset to repay + * @param cTokenCollateral The market in which to seize collateral from the borrower +- * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) +- */ ++ * @return (uint error, uint badDebt) The error code and amount of bad debt */ + function liquidateBorrow( + address borrower, + uint repayAmount, + CTokenInterface cTokenCollateral +- ) external override returns (uint) { ++ ) external override returns (uint,uint) { + // only vault can liquidate + require(msg.sender == address(vault), "vault only"); +- liquidateBorrowInternal(borrower, repayAmount, cTokenCollateral); +- return NO_ERROR; ++ // sherlock 101 153 returning bad debt so that vault can decide whether to clip ++ // profit or not ++ uint badDebt = liquidateBorrowInternal(borrower, repayAmount, cTokenCollateral); ++ return (NO_ERROR,badDebt); + } + + function liquidateBadDebt( +@@ -375,4 +406,69 @@ contract CNumaToken is CErc20Immutable { + ); + return NO_ERROR; + } ++ ++ ++ /** ++ * @notice Users borrow assets from the protocol to their own address ++ * @param borrowAmount The amount of the underlying asset to borrow ++ */ ++ function borrowFreshNoTransfer( ++ address payable borrower, ++ uint borrowAmount ++ ) internal virtual override { ++ /* Fail if borrow not allowed */ ++ uint allowed = comptroller.borrowAllowed( ++ address(this), ++ borrower, ++ borrowAmount ++ ); ++ if (allowed != 0) { ++ revert BorrowComptrollerRejection(allowed); ++ } ++ ++ // check if vault allows borrows ++ // borrowing numa is not allowed when CF < CF_SEVERE ++ // only needed for numa borrows (lst borrows will go through CNumaLst::borrowFreshNoTransfer) ++ if (address(vault) != address(0)) { ++ if (!vault.borrowAllowed(address(this))) ++ { ++ revert BorrowNotAllowed(); ++ } ++ } ++ ++ ++ /* Verify market's block number equals current block number */ ++ if (accrualBlockNumber != getBlockNumber()) { ++ revert BorrowFreshnessCheck(); ++ } ++ ++ /* Fail gracefully if protocol has insufficient underlying cash */ ++ if (getCashPrior() < borrowAmount) { ++ revert BorrowCashNotAvailable(); ++ } ++ ++ /* ++ * We calculate the new borrower and total borrow balances, failing on overflow: ++ * accountBorrowNew = accountBorrow + borrowAmount ++ * totalBorrowsNew = totalBorrows + borrowAmount ++ */ ++ uint accountBorrowsPrev = borrowBalanceStoredInternal(borrower); ++ uint accountBorrowsNew = accountBorrowsPrev + borrowAmount; ++ uint totalBorrowsNew = totalBorrows + borrowAmount; ++ ++ ///////////////////////// ++ // EFFECTS & INTERACTIONS ++ // (No safe failures beyond this point) ++ ++ /* ++ * We write the previously calculated values into storage. ++ * Note: Avoid token reentrancy attacks by writing increased borrow before external transfer. ++ `*/ ++ accountBorrows[borrower].principal = accountBorrowsNew; ++ accountBorrows[borrower].interestIndex = borrowIndex; ++ totalBorrows = totalBorrowsNew; ++ ++ /* We emit a Borrow event */ ++ emit Borrow(borrower, borrowAmount, accountBorrowsNew, totalBorrowsNew); ++ } + } diff --git a/Numa/contracts/lending/CToken.sol.rej b/Numa/contracts/lending/CToken.sol.rej new file mode 100644 index 0000000..22248d1 --- /dev/null +++ b/Numa/contracts/lending/CToken.sol.rej @@ -0,0 +1,164 @@ +diff a/Numa/contracts/lending/CToken.sol b/Numa/contracts/lending/CToken.sol (rejected hunks) +@@ -7,7 +7,7 @@ import "./ErrorReporter.sol"; + import "./EIP20Interface.sol"; + import "./InterestRateModel.sol"; + import "./ExponentialNoError.sol"; +-import "forge-std/console2.sol"; ++ + /** + * @title Compound's CToken Contract + * @notice Abstract base for CTokens +@@ -86,6 +86,9 @@ abstract contract CToken is + address dst, + uint tokens + ) internal returns (uint) { ++ // sherlock issue 168 ++ // Before transferring CToken, the accrueInterest() function should be called first ++ accrueInterest(); + /* Fail if transfer not allowed */ + uint allowed = comptroller.transferAllowed( + address(this), +@@ -558,6 +561,17 @@ abstract contract CToken is + + uint mintTokens = div_(actualMintAmount, exchangeRate); + ++ ++ ++ // sherlock issue-253 ++ // first depositor bug ++ if (totalSupply == 0) ++ { ++ totalSupply = 1000; ++ accountTokens[address(0)] = 1000; ++ mintTokens -= 1000; ++ } ++ + /* + * We calculate the new total supply of cTokens and minter token balance, checking for overflow: + * totalSupplyNew = totalSupply + mintTokens +@@ -646,7 +660,7 @@ abstract contract CToken is + redeemer, + redeemTokens + ); +- console2.log("redeem?",allowed); ++ + if (allowed != 0) { + revert RedeemComptrollerRejection(allowed); + } +@@ -897,12 +911,13 @@ abstract contract CToken is + * @param borrower The borrower of this cToken to be liquidated + * @param cTokenCollateral The market in which to seize collateral from the borrower + * @param repayAmount The amount of the underlying borrowed asset to repay ++ * @return badDebt the amount of baddebt the position had + */ + function liquidateBorrowInternal( + address borrower, + uint repayAmount, + CTokenInterface cTokenCollateral +- ) internal nonReentrant { ++ ) internal nonReentrant returns (uint){ + accrueInterest(); + + uint error = cTokenCollateral.accrueInterest(); +@@ -912,7 +927,7 @@ abstract contract CToken is + } + + // liquidateBorrowFresh emits borrow-specific logs on errors, so we don't need to +- liquidateBorrowFresh( ++ return liquidateBorrowFresh( + msg.sender, + borrower, + repayAmount, +@@ -950,18 +965,18 @@ abstract contract CToken is + * @param liquidator The address repaying the borrow and seizing collateral + * @param cTokenCollateral The market in which to seize collateral from the borrower + * @param repayAmount The amount of the underlying borrowed asset to repay ++ * @return badDebt the amount of baddebt the position had + */ + function liquidateBorrowFresh( + address liquidator, + address borrower, + uint repayAmount, + CTokenInterface cTokenCollateral +- ) internal { ++ ) internal returns (uint) { + /* Fail if liquidate not allowed */ +- uint allowed = comptroller.liquidateBorrowAllowed( ++ (uint allowed,uint badDebt,uint restOfDebt) = comptroller.liquidateBorrowAllowed( + address(this), + address(cTokenCollateral), +- liquidator, + borrower, + repayAmount + ); +@@ -1017,11 +1032,24 @@ abstract contract CToken is + "LIQUIDATE_COMPTROLLER_CALCULATE_AMOUNT_SEIZE_FAILED" + ); + +- /* Revert if borrower collateral token balance < seizeTokens */ +- require( +- cTokenCollateral.balanceOf(borrower) >= seizeTokens, +- "LIQUIDATE_SEIZE_TOO_MUCH" +- ); ++ ++ ++ if (cTokenCollateral.balanceOf(borrower) < seizeTokens) { ++ // sherlock 101 153 ++ // not enough collateral to pay liquidator with incentives ++ // if we are not in bad debt territory, it will still be profitable to take all collateral ++ // but we do this only if it's a full liquidation, so that we don't become in bad debt after ++ if ((badDebt == 0) && (restOfDebt == 0)) { ++ // no bad debt ++ // full borrow balance liquidation ++ seizeTokens = cTokenCollateral.balanceOf(borrower); ++ } ++ else ++ { ++ revert("LIQUIDATE_SEIZE_TOO_MUCH") ; ++ } ++ ++ } + + // If this is also the collateral, run seizeInternal to avoid re-entrancy, otherwise make an external call + if (address(cTokenCollateral) == address(this)) { +@@ -1042,6 +1070,9 @@ abstract contract CToken is + address(cTokenCollateral), + seizeTokens + ); ++ // sherlock 101 153 returning bad debt so that vault can decide whether to clip ++ // profit or not ++ return badDebt; + } + + function liquidateBadDebtFresh( +@@ -1542,6 +1573,22 @@ abstract contract CToken is + } + + /** ++ * @notice change borrowRateMaxMantissa ++ * @dev needed because values should be adjustedbased on chain block time ++ * @param _borrowRateMaxMantissa the new value ++ */ ++ function _setBorrowRateMaxMantissa( ++ uint _borrowRateMaxMantissa ++ ) public override ++ { ++ if (msg.sender != admin) { ++ revert SetBorrowRateMaxMantissaOwnerCheck(); ++ } ++ borrowRateMaxMantissa = _borrowRateMaxMantissa; ++ } ++ ++ ++ /** + * @notice accrues interest and updates the interest rate model using _setInterestRateModelFresh + * @dev Admin function to accrue interest and update the interest rate model + * @param newInterestRateModel the new interest rate model to use +@@ -1555,6 +1602,7 @@ abstract contract CToken is + return _setInterestRateModelFresh(newInterestRateModel); + } + ++ + /** + * @notice updates the interest rate model (*requires fresh interest accrual) + * @dev Admin function to update the interest rate model diff --git a/Numa/contracts/lending/CTokenInterfaces.sol.rej b/Numa/contracts/lending/CTokenInterfaces.sol.rej new file mode 100644 index 0000000..4390fca --- /dev/null +++ b/Numa/contracts/lending/CTokenInterfaces.sol.rej @@ -0,0 +1,31 @@ +diff a/Numa/contracts/lending/CTokenInterfaces.sol b/Numa/contracts/lending/CTokenInterfaces.sol (rejected hunks) +@@ -28,7 +28,7 @@ contract CTokenStorage { + uint8 public decimals; + + // Maximum borrow rate that can ever be applied (.0005% / block) +- uint internal constant borrowRateMaxMantissa = 0.0005e16; ++ uint public borrowRateMaxMantissa = 0.0005e16; + + // Maximum fraction of interest that can be set aside for reserves + uint internal constant reserveFactorMaxMantissa = 1e18; +@@ -320,6 +320,11 @@ abstract contract CTokenInterface is CTokenStorage { + function _setInterestRateModel( + InterestRateModel newInterestRateModel + ) external virtual returns (uint); ++ ++ function _setBorrowRateMaxMantissa( ++ uint _borrowRateMaxMantissa ++ ) external virtual; ++ + } + + contract CErc20Storage { +@@ -347,7 +352,7 @@ abstract contract CErc20Interface is CErc20Storage { + address borrower, + uint repayAmount, + CTokenInterface cTokenCollateral +- ) external virtual returns (uint); ++ ) external virtual returns (uint,uint); + function liquidateBadDebt( + address borrower, + uint repayAmount, diff --git a/Numa/contracts/lending/ComptrollerInterface.sol.rej b/Numa/contracts/lending/ComptrollerInterface.sol.rej new file mode 100644 index 0000000..bffd482 --- /dev/null +++ b/Numa/contracts/lending/ComptrollerInterface.sol.rej @@ -0,0 +1,13 @@ +diff a/Numa/contracts/lending/ComptrollerInterface.sol b/Numa/contracts/lending/ComptrollerInterface.sol (rejected hunks) +@@ -74,10 +74,9 @@ abstract contract ComptrollerInterface { + function liquidateBorrowAllowed( + address cTokenBorrowed, + address cTokenCollateral, +- address liquidator, + address borrower, + uint repayAmount +- ) external virtual returns (uint); ++ ) external virtual returns (uint,uint,uint); + function liquidateBorrowVerify( + address cTokenBorrowed, + address cTokenCollateral, diff --git a/Numa/contracts/lending/ErrorReporter.sol.rej b/Numa/contracts/lending/ErrorReporter.sol.rej new file mode 100644 index 0000000..4e5fb76 --- /dev/null +++ b/Numa/contracts/lending/ErrorReporter.sol.rej @@ -0,0 +1,17 @@ +diff a/Numa/contracts/lending/ErrorReporter.sol b/Numa/contracts/lending/ErrorReporter.sol (rejected hunks) +@@ -96,6 +96,8 @@ contract TokenErrorReporter { + error BorrowFreshnessCheck(); + error BorrowCashNotAvailable(); + ++ error BorrowNotAllowed(); ++ + error RepayBorrowComptrollerRejection(uint256 errorCode); + error RepayBorrowFreshnessCheck(); + +@@ -129,5 +131,6 @@ contract TokenErrorReporter { + error ReduceReservesCashValidation(); + + error SetInterestRateModelOwnerCheck(); ++ error SetBorrowRateMaxMantissaOwnerCheck(); + error SetInterestRateModelFreshCheck(); + } diff --git a/Numa/contracts/lending/Lens/CompoundLens.sol.rej b/Numa/contracts/lending/Lens/CompoundLens.sol.rej new file mode 100644 index 0000000..750707f --- /dev/null +++ b/Numa/contracts/lending/Lens/CompoundLens.sol.rej @@ -0,0 +1,20 @@ +diff a/Numa/contracts/lending/Lens/CompoundLens.sol b/Numa/contracts/lending/Lens/CompoundLens.sol (rejected hunks) +@@ -14,7 +14,7 @@ interface ComptrollerLensInterface { + address, + CToken, + CToken +- ) external view returns (uint, uint, uint, uint); ++ ) external view returns (uint, uint, uint, uint,uint); + function getAssetsIn(address) external view returns (CToken[] memory); + function borrowCaps(address) external view returns (uint); + } +@@ -222,7 +222,8 @@ contract CompoundLens { + uint errorCode, + uint liquidity, + uint shortfall, +- uint badDebt ++ uint badDebt, ++ + ) = comptroller.getAccountLiquidityIsolate(account, collateral, borrow); + require(errorCode == 0); + diff --git a/Numa/contracts/lending/NumaComptroller.sol b/Numa/contracts/lending/NumaComptroller.sol index aa32888..23d191f 100644 --- a/Numa/contracts/lending/NumaComptroller.sol +++ b/Numa/contracts/lending/NumaComptroller.sol @@ -1409,13 +1409,25 @@ contract NumaComptroller is vars.sumBorrowPlusEffects ); + uint ltv; + if (vars.sumCollateralNoCollateralFactor > 0) + { + ltv = (vars.sumBorrowPlusEffects * 1 ether) / vars.sumCollateralNoCollateralFactor; + } + else if (vars.sumBorrowPlusEffects > 0) + { + // no collateral but some borrow, ltv is infinite + ltv = type(uint).max; + + } // These are safe, as the underflow condition is checked first if (vars.sumCollateral > vars.sumBorrowPlusEffects) { return ( Error.NO_ERROR, vars.sumCollateral - vars.sumBorrowPlusEffects, 0, - 0 + 0, + ltv ); } else { if ( diff --git a/Numa/contracts/lending/NumaComptroller.sol.rej b/Numa/contracts/lending/NumaComptroller.sol.rej new file mode 100644 index 0000000..0c10f8f --- /dev/null +++ b/Numa/contracts/lending/NumaComptroller.sol.rej @@ -0,0 +1,525 @@ +diff a/Numa/contracts/lending/NumaComptroller.sol b/Numa/contracts/lending/NumaComptroller.sol (rejected hunks) +@@ -110,6 +110,9 @@ contract NumaComptroller is + // No collateralFactorMantissa may exceed this value + uint internal constant collateralFactorMaxMantissa = 0.99e18; // 0.99 + ++ uint ltvMinBadDebtLiquidations = 0.98 ether; ++ uint ltvMinPartialLiquidations = 1.1 ether; ++ + constructor() { + admin = msg.sender; + } +@@ -550,25 +553,22 @@ contract NumaComptroller is + * @notice Checks if the liquidation should be allowed to occur + * @param cTokenBorrowed Asset which was borrowed by the borrower + * @param cTokenCollateral Asset which was used as collateral and will be seized +- * @param liquidator The address repaying the borrow and seizing the collateral + * @param borrower The address of the borrower + * @param repayAmount The amount of underlying being repaid ++ * @return (error,baddebt,restOfDebt) + */ + function liquidateBorrowAllowed( + address cTokenBorrowed, + address cTokenCollateral, +- address liquidator, + address borrower, + uint repayAmount +- ) external view override returns (uint) { +- // Shh - currently unused +- liquidator; ++ ) external view override returns (uint,uint,uint) { + require((cTokenBorrowed) != (cTokenCollateral), "not isolate"); + if ( + !markets[cTokenBorrowed].isListed || + !markets[cTokenCollateral].isListed + ) { +- return uint(Error.MARKET_NOT_LISTED); ++ return (uint(Error.MARKET_NOT_LISTED),0,0); + } + + uint borrowBalance = CToken(cTokenBorrowed).borrowBalanceStored( +@@ -576,29 +576,62 @@ contract NumaComptroller is + ); + + /* allow accounts to be liquidated if the market is deprecated */ ++ ++ ( ++ Error err, ++ , ++ uint shortfall, ++ uint badDebt, ++ uint ltv ++ ++ ) = getAccountLiquidityIsolateInternal( ++ borrower, ++ CNumaToken(cTokenCollateral), ++ CNumaToken(cTokenBorrowed) ++ ); ++ if (err != Error.NO_ERROR) { ++ return (uint(err),0,0); ++ } ++ ++ ++ // sherlock 101 153 ++ // min amount allowed for liquidations from vault ++ uint minAmount = 0; ++ bool badDebtLiquidationAllowed = false; ++ ++ // Min amount from vault. ++ // we should have a vault, if not, it will revert which is ok ++ minAmount = CNumaToken(cTokenBorrowed).vault().getMinBorrowAmountAllowPartialLiquidation(cTokenBorrowed); ++ ++ // if ltv > ltvMinPartialLiquidations, partial liquidations are enabled ++ if (ltv > ltvMinPartialLiquidations) ++ { ++ minAmount = 0; ++ // if ltv > ltvMinPartialLiquidations, it means we are in bad debt ++ // so we need to allow bad debt liquidation ++ badDebtLiquidationAllowed = true; ++ } ++ ++ if (borrowBalance < minAmount) minAmount = borrowBalance; ++ ++ require(repayAmount >= minAmount, "min liquidation"); ++ ++ + if (isDeprecated(CToken(cTokenBorrowed))) { + require( + borrowBalance >= repayAmount, + "Can not repay more than the total borrow" + ); +- } else { +- /* The borrower must have shortfall in order to be liquidatable */ +- ( +- Error err, +- , +- uint shortfall, +- uint badDebt +- ) = getAccountLiquidityIsolateInternal( +- borrower, +- CNumaToken(cTokenCollateral), +- CNumaToken(cTokenBorrowed) +- ); +- if (err != Error.NO_ERROR) { +- return uint(err); ++ // sherlock issue 67. Even if deprecated we don't want that liquidation type if in bad debt ++ if (badDebt > 0 && (!badDebtLiquidationAllowed)) { ++ return (uint(Error.BAD_DEBT),badDebt,0); + } ++ } else { + +- if (shortfall == 0) { +- return uint(Error.INSUFFICIENT_SHORTFALL); ++ /* The borrower must have shortfall in order to be liquidatable */ ++ if (shortfall == 0) ++ { ++ return (uint(Error.INSUFFICIENT_SHORTFALL),badDebt,0); + } + + /* The liquidator may not repay more than what is allowed by the closeFactor */ +@@ -607,14 +640,16 @@ contract NumaComptroller is + borrowBalance + ); + if (repayAmount > maxClose) { +- return uint(Error.TOO_MUCH_REPAY); ++ return (uint(Error.TOO_MUCH_REPAY),badDebt,0); + } + /* revert if there is bad debt, specific bad debt liquidations functions should be called */ +- if (badDebt > 0) { +- return uint(Error.BAD_DEBT); ++ if (badDebt > 0 && (!badDebtLiquidationAllowed)) { ++ return (uint(Error.BAD_DEBT),badDebt,0); + } + } +- return uint(Error.NO_ERROR); ++ // sherlock 101 153 returning badDebt and restOfDebt ++ // because we want to know if liquidation is partial and not in baddebt to allow taking all collateral ++ return (uint(Error.NO_ERROR),badDebt,borrowBalance - repayAmount); + } + + function liquidateBadDebtAllowed( +@@ -639,33 +674,38 @@ contract NumaComptroller is + borrower + ); + ++ ( ++ Error err, ++ , ++ uint shortfall, ++ , ++ uint ltv ++ ++ ) = getAccountLiquidityIsolateInternal( ++ borrower, ++ CNumaToken(cTokenCollateral), ++ CNumaToken(cTokenBorrowed) ++ ); ++ if (err != Error.NO_ERROR) { ++ return uint(err); ++ } + /* allow accounts to be liquidated if the market is deprecated */ + if (isDeprecated(CToken(cTokenBorrowed))) { + require( + borrowBalance >= repayAmount, + "Can not repay more than the total borrow" + ); ++ // sherlock issue 67. Even if deprecated some bad debt is needed ++ if (ltv < ltvMinBadDebtLiquidations) { ++ return uint(Error.INSUFFICIENT_BADDEBT); ++ } + } else { + /* The borrower must have shortfall in order to be liquidatable */ +- ( +- Error err, +- , +- uint shortfall, +- uint badDebt +- ) = getAccountLiquidityIsolateInternal( +- borrower, +- CNumaToken(cTokenCollateral), +- CNumaToken(cTokenBorrowed) +- ); +- if (err != Error.NO_ERROR) { +- return uint(err); +- } +- + if (shortfall == 0) { + return uint(Error.INSUFFICIENT_SHORTFALL); + } +- +- if (badDebt == 0) { ++ // bad debt liquidation allowed only above ltvMinBadDebtLiquidations ++ if (ltv < ltvMinBadDebtLiquidations) { + return uint(Error.INSUFFICIENT_BADDEBT); + } + } +@@ -860,28 +900,29 @@ contract NumaComptroller is + address account, + CNumaToken collateral, + CNumaToken borrow +- ) public view returns (uint, uint, uint, uint) { ++ ) public view returns (uint, uint, uint, uint,uint) { + ( + Error err, + uint liquidity, + uint shortfall, +- uint badDebt ++ uint badDebt, ++ uint ltv + ) = getAccountLiquidityIsolateInternal(account, collateral, borrow); +- return (uint(err), liquidity, shortfall, badDebt); ++ return (uint(err), liquidity, shortfall, badDebt,ltv); + } + +- function getAccountLTVIsolate( +- address account, +- CNumaToken collateral, +- CNumaToken borrow +- ) public view returns (uint, uint) { +- (Error err, uint ltv) = getAccountLTVIsolateInternal( +- account, +- collateral, +- borrow +- ); +- return (uint(err), ltv); +- } ++ // function getAccountLTVIsolate( ++ // address account, ++ // CNumaToken collateral, ++ // CNumaToken borrow ++ // ) public view returns (uint, uint) { ++ // (Error err, uint ltv) = getAccountLTVIsolateInternal( ++ // account, ++ // collateral, ++ // borrow ++ // ); ++ // return (uint(err), ltv); ++ // } + + /** + * @notice Determine the current account liquidity wrt collateral requirements +@@ -893,7 +934,7 @@ contract NumaComptroller is + address account, + CNumaToken collateral, + CNumaToken borrow +- ) internal view returns (Error, uint, uint, uint) { ++ ) internal view returns (Error, uint, uint, uint,uint) { + AccountLiquidityLocalVars memory vars; // Holds all our calculation results + uint oErr; + +@@ -908,7 +949,7 @@ contract NumaComptroller is + ) = collateral.getAccountSnapshot(account); + if (oErr != 0) { + // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades +- return (Error.SNAPSHOT_ERROR, 0, 0, 0); ++ return (Error.SNAPSHOT_ERROR, 0, 0, 0,0); + } + vars.collateralFactor = Exp({ + mantissa: markets[address(collateral)].collateralFactorMantissa +@@ -919,7 +960,7 @@ contract NumaComptroller is + vars.oraclePriceMantissaCollateral = oracle + .getUnderlyingPriceAsCollateral(collateral); + if (vars.oraclePriceMantissaCollateral == 0) { +- return (Error.PRICE_ERROR, 0, 0, 0); ++ return (Error.PRICE_ERROR, 0, 0, 0,0); + } + vars.oraclePriceCollateral = Exp({ + mantissa: vars.oraclePriceMantissaCollateral +@@ -958,7 +999,7 @@ contract NumaComptroller is + ) = borrow.getAccountSnapshot(account); + if (oErr != 0) { + // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades +- return (Error.SNAPSHOT_ERROR, 0, 0, 0); ++ return (Error.SNAPSHOT_ERROR, 0, 0, 0,0); + } + // Get the normalized price of the asset + vars.oraclePriceMantissaBorrowed = oracle.getUnderlyingPriceAsBorrowed( +@@ -966,7 +1007,7 @@ contract NumaComptroller is + ); + + if (vars.oraclePriceMantissaBorrowed == 0) { +- return (Error.PRICE_ERROR, 0, 0, 0); ++ return (Error.PRICE_ERROR, 0, 0, 0,0); + } + //vars.oraclePriceCollateral = Exp({mantissa: vars.oraclePriceMantissaCollateral}); + vars.oraclePriceBorrowed = Exp({ +@@ -997,7 +1050,8 @@ contract NumaComptroller is + Error.NO_ERROR, + 0, + vars.sumBorrowPlusEffects - vars.sumCollateral, +- 0 ++ 0, ++ ltv + ); + // returning bad debt + else +@@ -1006,108 +1060,109 @@ contract NumaComptroller is + 0, + vars.sumBorrowPlusEffects - vars.sumCollateral, + vars.sumBorrowPlusEffects - +- vars.sumCollateralNoCollateralFactor ++ vars.sumCollateralNoCollateralFactor, ++ ltv + ); + } + } + +- function getAccountLTVIsolateInternal( +- address account, +- CNumaToken collateral, +- CNumaToken borrow +- ) internal view returns (Error, uint) { +- AccountLiquidityLocalVars memory vars; // Holds all our calculation results +- uint oErr; +- +- // Here we only consider only 2 tokens: collateral token and borrow token which should be different +- // collateral +- // Read the balances and exchange rate from the cToken +- ( +- oErr, +- vars.cTokenBalance, +- vars.borrowBalance, +- vars.exchangeRateMantissa +- ) = collateral.getAccountSnapshot(account); +- if (oErr != 0) { +- // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades +- return (Error.SNAPSHOT_ERROR, 0); +- } +- vars.collateralFactor = Exp({ +- mantissa: markets[address(collateral)].collateralFactorMantissa +- }); +- vars.exchangeRate = Exp({mantissa: vars.exchangeRateMantissa}); +- +- // Get the normalized price of the asset +- vars.oraclePriceMantissaCollateral = oracle +- .getUnderlyingPriceAsCollateral(collateral); +- if (vars.oraclePriceMantissaCollateral == 0) { +- return (Error.PRICE_ERROR, 0); +- } +- vars.oraclePriceCollateral = Exp({ +- mantissa: vars.oraclePriceMantissaCollateral +- }); +- +- // Pre-compute a conversion factor from tokens -> ether (normalized price value) +- vars.tokensToDenomCollateral = mul_( +- mul_(vars.collateralFactor, vars.exchangeRate), +- vars.oraclePriceCollateral +- ); +- vars.tokensToDenomCollateralNoCollateralFactor = mul_( +- vars.exchangeRate, +- vars.oraclePriceCollateral +- ); +- // sumCollateral += tokensToDenom * cTokenBalance ++ // function getAccountLTVIsolateInternal( ++ // address account, ++ // CNumaToken collateral, ++ // CNumaToken borrow ++ // ) internal view returns (Error, uint) { ++ // AccountLiquidityLocalVars memory vars; // Holds all our calculation results ++ // uint oErr; + +- // NUMALENDING: use collateral price +- vars.sumCollateral = mul_ScalarTruncateAddUInt( +- vars.tokensToDenomCollateral, +- vars.cTokenBalance, +- vars.sumCollateral +- ); +- vars.sumCollateralNoCollateralFactor = mul_ScalarTruncateAddUInt( +- vars.tokensToDenomCollateralNoCollateralFactor, +- vars.cTokenBalance, +- vars.sumCollateralNoCollateralFactor +- ); ++ // // Here we only consider only 2 tokens: collateral token and borrow token which should be different ++ // // collateral ++ // // Read the balances and exchange rate from the cToken ++ // ( ++ // oErr, ++ // vars.cTokenBalance, ++ // vars.borrowBalance, ++ // vars.exchangeRateMantissa ++ // ) = collateral.getAccountSnapshot(account); ++ // if (oErr != 0) { ++ // // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades ++ // return (Error.SNAPSHOT_ERROR, 0); ++ // } ++ // vars.collateralFactor = Exp({ ++ // mantissa: markets[address(collateral)].collateralFactorMantissa ++ // }); ++ // vars.exchangeRate = Exp({mantissa: vars.exchangeRateMantissa}); ++ ++ // // Get the normalized price of the asset ++ // vars.oraclePriceMantissaCollateral = oracle ++ // .getUnderlyingPriceAsCollateral(collateral); ++ // if (vars.oraclePriceMantissaCollateral == 0) { ++ // return (Error.PRICE_ERROR, 0); ++ // } ++ // vars.oraclePriceCollateral = Exp({ ++ // mantissa: vars.oraclePriceMantissaCollateral ++ // }); ++ ++ // // Pre-compute a conversion factor from tokens -> ether (normalized price value) ++ // vars.tokensToDenomCollateral = mul_( ++ // mul_(vars.collateralFactor, vars.exchangeRate), ++ // vars.oraclePriceCollateral ++ // ); ++ // vars.tokensToDenomCollateralNoCollateralFactor = mul_( ++ // vars.exchangeRate, ++ // vars.oraclePriceCollateral ++ // ); ++ // // sumCollateral += tokensToDenom * cTokenBalance + +- // borrow +- // Read the balances and exchange rate from the cToken +- ( +- oErr, +- vars.cTokenBalance, +- vars.borrowBalance, +- vars.exchangeRateMantissa +- ) = borrow.getAccountSnapshot(account); +- if (oErr != 0) { +- // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades +- return (Error.SNAPSHOT_ERROR, 0); +- } +- // Get the normalized price of the asset +- vars.oraclePriceMantissaBorrowed = oracle.getUnderlyingPriceAsBorrowed( +- borrow +- ); ++ // // NUMALENDING: use collateral price ++ // vars.sumCollateral = mul_ScalarTruncateAddUInt( ++ // vars.tokensToDenomCollateral, ++ // vars.cTokenBalance, ++ // vars.sumCollateral ++ // ); ++ // vars.sumCollateralNoCollateralFactor = mul_ScalarTruncateAddUInt( ++ // vars.tokensToDenomCollateralNoCollateralFactor, ++ // vars.cTokenBalance, ++ // vars.sumCollateralNoCollateralFactor ++ // ); + +- if (vars.oraclePriceMantissaBorrowed == 0) { +- return (Error.PRICE_ERROR, 0); +- } +- //vars.oraclePriceCollateral = Exp({mantissa: vars.oraclePriceMantissaCollateral}); +- vars.oraclePriceBorrowed = Exp({ +- mantissa: vars.oraclePriceMantissaBorrowed +- }); ++ // // borrow ++ // // Read the balances and exchange rate from the cToken ++ // ( ++ // oErr, ++ // vars.cTokenBalance, ++ // vars.borrowBalance, ++ // vars.exchangeRateMantissa ++ // ) = borrow.getAccountSnapshot(account); ++ // if (oErr != 0) { ++ // // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades ++ // return (Error.SNAPSHOT_ERROR, 0); ++ // } ++ // // Get the normalized price of the asset ++ // vars.oraclePriceMantissaBorrowed = oracle.getUnderlyingPriceAsBorrowed( ++ // borrow ++ // ); + +- // sumBorrowPlusEffects += oraclePrice * borrowBalance +- // NUMALENDING: use borrow price +- vars.sumBorrowPlusEffects = mul_ScalarTruncateAddUInt( +- vars.oraclePriceBorrowed, +- vars.borrowBalance, +- vars.sumBorrowPlusEffects +- ); ++ // if (vars.oraclePriceMantissaBorrowed == 0) { ++ // return (Error.PRICE_ERROR, 0); ++ // } ++ // //vars.oraclePriceCollateral = Exp({mantissa: vars.oraclePriceMantissaCollateral}); ++ // vars.oraclePriceBorrowed = Exp({ ++ // mantissa: vars.oraclePriceMantissaBorrowed ++ // }); ++ ++ // // sumBorrowPlusEffects += oraclePrice * borrowBalance ++ // // NUMALENDING: use borrow price ++ // vars.sumBorrowPlusEffects = mul_ScalarTruncateAddUInt( ++ // vars.oraclePriceBorrowed, ++ // vars.borrowBalance, ++ // vars.sumBorrowPlusEffects ++ // ); + +- return ( +- Error.NO_ERROR, +- (vars.sumBorrowPlusEffects * 1000) / vars.sumCollateral +- ); +- } ++ // return ( ++ // Error.NO_ERROR, ++ // (vars.sumBorrowPlusEffects * 1 ether) / vars.sumCollateral ++ // ); ++ // } + + /** + * @notice Determine what the account liquidity would be if the given amounts were redeemed/borrowed +@@ -1513,6 +1568,26 @@ contract NumaComptroller is + + /*** Admin Functions ***/ + ++ /** ++ * @notice Sets the ltv thresholds used when liquidating borrows ++ * @dev Admin function to set ltv thresholds ++ * @param _ltvMinBadDebt min ltv to allow bad debt liquidation ++ * @param _ltvMinPartialLiquidation min to allow partial liquidation ++ * @return uint 0=success, otherwise a failure ++ */ ++ function _setLtvThresholds( ++ uint _ltvMinBadDebt, ++ uint _ltvMinPartialLiquidation ++ ) external returns (uint) { ++ // Check caller is admin ++ require(msg.sender == admin, "only admin"); ++ ltvMinBadDebtLiquidations = _ltvMinBadDebt; ++ ltvMinPartialLiquidations = _ltvMinPartialLiquidation; ++ ++ return uint(Error.NO_ERROR); ++ } ++ ++ + /** + * @notice Sets a new price oracle for the comptroller + * @dev Admin function to set a new price oracle diff --git a/Numa/contracts/utils/constants.sol.rej b/Numa/contracts/utils/constants.sol.rej new file mode 100644 index 0000000..0875a64 --- /dev/null +++ b/Numa/contracts/utils/constants.sol.rej @@ -0,0 +1,9 @@ +diff a/Numa/contracts/utils/constants.sol b/Numa/contracts/utils/constants.sol (rejected hunks) +@@ -1,5 +1,7 @@ + // SPDX-License-Identifier: MIT + pragma solidity 0.8.20; + ++ ++uint constant BASE_SCALE = 1 ether; + uint constant BASE_1000 = 1000; + uint constant MAX_CF = 100000; diff --git a/Numa/scripts/DeployLending.sol.rej b/Numa/scripts/DeployLending.sol.rej new file mode 100644 index 0000000..c902a9c --- /dev/null +++ b/Numa/scripts/DeployLending.sol.rej @@ -0,0 +1,31 @@ +diff a/Numa/scripts/DeployLending.sol b/Numa/scripts/DeployLending.sol (rejected hunks) +@@ -71,6 +71,10 @@ contract DeployLending is Script { + // uint liquidationIncentive = 1.02 ether; + // uint maxLiquidationProfit; + ++ // 0.0005e16 for ethereum chain, should be 10 x less for arbitrum as blocktime is /10 ++ //uint borrowRateMaxMantissaARBI = 0.0005e16; ++ uint borrowRateMaxMantissaARBI = 0.00005e16; ++ + // SEPOLIA + address constant VAULT_ADMIN = 0xe8153Afbe4739D4477C1fF86a26Ab9085C4eDC69; + address constant NUMA_ADMIN = 0xe8153Afbe4739D4477C1fF86a26Ab9085C4eDC69; +@@ -214,7 +218,9 @@ contract DeployLending is Script { + address(vault) + ); + +- ++ // arbitrum values ++ cReth._setBorrowRateMaxMantissa(borrowRateMaxMantissaARBI); ++ cNuma._setBorrowRateMaxMantissa(borrowRateMaxMantissaARBI); + + vault.setMaxBorrow(1000 ether); + vault.setCTokens(address(cNuma), address(cReth)); +@@ -232,6 +238,7 @@ contract DeployLending is Script { + // 100% liquidation close factor + comptroller._setCloseFactor(closeFactor); + comptroller._setLiquidationIncentive(liquidationIncentive); ++ comptroller._setLtvThresholds(0.98 ether,1.1 ether); + vault.setMaxLiquidationsProfit(maxLiquidationProfit); + + // strategies