diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 03269e8..35baea7 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -5,7 +5,7 @@ name: Node.js CI on: push: - branches: [ main, tax-token-support, bsc ] + branches: [ main, tax-token-support, bsc, dev, limit-order ] pull_request: branches: [ main ] diff --git a/contracts/OpenLevInterface.sol b/contracts/OpenLevInterface.sol index 6159a41..13a68cb 100644 --- a/contracts/OpenLevInterface.sol +++ b/contracts/OpenLevInterface.sol @@ -46,7 +46,7 @@ abstract contract OpenLevStorage { mapping(address => mapping(uint16 => mapping(bool => Types.Trade))) public activeTrades; //useless - mapping(address => bool) public allowedDepositTokens; + mapping(address => bool) internal allowedDepositTokens; CalculateConfig public calculateConfig; @@ -59,6 +59,8 @@ abstract contract OpenLevStorage { // map(marketId, tokenAddress, index) => taxRate) mapping(uint16 => mapping(address => mapping(uint => uint24))) public taxes; + address public opLimitOrder; + event MarginTrade( address trader, uint16 marketId, @@ -135,9 +137,15 @@ interface OpenLevInterface { ) external returns (uint16); - function marginTrade(uint16 marketId, bool longToken, bool depositToken, uint deposit, uint borrow, uint minBuyAmount, bytes memory dexData) external payable; + function marginTrade(uint16 marketId, bool longToken, bool depositToken, uint deposit, uint borrow, uint minBuyAmount, bytes memory dexData) external payable returns (uint256); + + function marginTradeFor(address trader, uint16 marketId, bool longToken, bool depositToken, uint deposit, uint borrow, uint minBuyAmount, bytes memory dexData) external payable returns (uint256); + + function closeTrade(uint16 marketId, bool longToken, uint closeAmount, uint minOrMaxAmount, bytes memory dexData) external returns (uint256); + + function closeTradeFor(address trader, uint16 marketId, bool longToken, uint closeHeld, uint minOrMaxAmount, bytes memory dexData) external returns (uint256); - function closeTrade(uint16 marketId, bool longToken, uint closeAmount, uint minOrMaxAmount, bytes memory dexData) external; + function payoffTrade(uint16 marketId, bool longToken) external payable; function liquidate(address owner, uint16 marketId, bool longToken, uint minBuy, uint maxAmount, bytes memory dexData) external; @@ -145,11 +153,6 @@ interface OpenLevInterface { function updatePrice(uint16 marketId, bytes memory dexData) external; - function shouldUpdatePrice(uint16 marketId, bytes memory dexData) external view returns (bool); - - function getMarketSupportDexs(uint16 marketId) external view returns (uint32[] memory); - - // function getCalculateConfig() external view returns (OpenLevStorage.CalculateConfig memory); /*** Admin Functions ***/ function setCalculateConfig(uint16 defaultFeesRate, uint8 insuranceRatio, uint16 defaultMarginLimit, uint16 priceDiffientRatio, @@ -165,4 +168,6 @@ interface OpenLevInterface { function setTaxRate(uint16 marketId, address token, uint index, uint24 tax) external; + function setOpLimitOrder(address _opLimitOrder) external; + } diff --git a/contracts/OpenLevV1.sol b/contracts/OpenLevV1.sol index 4d6cd7b..f8a033f 100644 --- a/contracts/OpenLevV1.sol +++ b/contracts/OpenLevV1.sol @@ -70,19 +70,11 @@ contract OpenLevV1 is DelegateInterface, Adminable, ReentrancyGuard, OpenLevInte ) external override returns (uint16) { uint16 marketId = numPairs; OpenLevV1Lib.addMarket(pool0, pool1, marginLimit, dexData, marketId, markets, calculateConfig, addressConfig, supportDexs, taxes); - require(numPairs < 65535, "TMP"); numPairs ++; return marketId; } - /// @notice Margin trade or just add more deposit tokens. - /// @dev To support token with tax and reward. Stores share of all token balances of this contract. - /// @param longToken Token to long. False for token0, true for token1. - /// @param depositToken Token to deposit. False for token0, true for token1. - /// @param deposit Amount of ERC20 tokens to deposit. WETH deposit is not supported. - /// @param borrow Amount of ERC20 to borrow from the short token pool. - /// @param minBuyAmount Slippage for Dex trading. - /// @param dexData Index and fee rate for the trading Dex. + function marginTrade( uint16 marketId, bool longToken, @@ -91,43 +83,59 @@ contract OpenLevV1 is DelegateInterface, Adminable, ReentrancyGuard, OpenLevInte uint borrow, uint minBuyAmount, bytes memory dexData - ) external payable override nonReentrant onlySupportDex(dexData) { + ) external payable override nonReentrant onlySupportDex(dexData) returns (uint256) { + return _marginTradeFor(msg.sender, marketId, longToken, depositToken, deposit, borrow, minBuyAmount, dexData); + } + + function marginTradeFor(address trader, uint16 marketId, bool longToken, bool depositToken, uint deposit, uint borrow, uint minBuyAmount, bytes memory dexData) external payable override nonReentrant onlySupportDex(dexData) returns (uint256){ + require(msg.sender == opLimitOrder, 'OLO'); + return _marginTradeFor(trader, marketId, longToken, depositToken, deposit, borrow, minBuyAmount, dexData); + } + /// @notice Margin trade or just add more deposit tokens. + /// @dev To support token with tax and reward. Stores share of all token balances of this contract. + /// @param longToken Token to long. False for token0, true for token1. + /// @param depositToken Token to deposit. False for token0, true for token1. + /// @param deposit Amount of ERC20 tokens to deposit. WETH deposit is not supported. + /// @param borrow Amount of ERC20 to borrow from the short token pool. + /// @param minBuyAmount Slippage for Dex trading. + /// @param dexData Index and fee rate for the trading Dex. + function _marginTradeFor(address trader, uint16 marketId, bool longToken, bool depositToken, uint deposit, uint borrow, uint minBuyAmount, bytes memory dexData) internal returns (uint256 newHeld){ Types.TradeVars memory tv; Types.MarketVars memory vars = toMarketVar(longToken, true, markets[marketId]); { - Types.Trade memory t = activeTrades[msg.sender][marketId][longToken]; - OpenLevV1Lib.verifyTrade(vars, longToken, depositToken, deposit, borrow, dexData, addressConfig, t); + Types.Trade memory t = activeTrades[trader][marketId][longToken]; + OpenLevV1Lib.verifyTrade(vars, longToken, depositToken, deposit, borrow, dexData, addressConfig, t, msg.sender == opLimitOrder ? false : true); (ControllerInterface(addressConfig.controller)).marginTradeAllowed(marketId); if (dexData.isUniV2Class()) { - OpenLevV1Lib.updatePriceInternal(address(vars.buyToken), address(vars.sellToken), dexData); + OpenLevV1Lib.updatePrice(address(vars.buyToken), address(vars.sellToken), dexData); } } tv.totalHeld = totalHelds[address(vars.buyToken)]; tv.depositErc20 = depositToken == longToken ? vars.buyToken : vars.sellToken; - deposit = transferIn(msg.sender, tv.depositErc20, deposit); + deposit = transferIn(msg.sender, tv.depositErc20, deposit, msg.sender == opLimitOrder ? false : true); // Borrow uint borrowed; if (borrow > 0) { { uint balance = vars.sellToken.balanceOf(address(this)); - vars.sellPool.borrowBehalf(msg.sender, borrow); + vars.sellPool.borrowBehalf(trader, borrow); borrowed = vars.sellToken.balanceOf(address(this)).sub(balance); } - if (depositToken == longToken){ + if (depositToken == longToken) { (uint currentPrice, uint8 priceDecimals) = addressConfig.dexAggregator.getPrice(address(vars.sellToken), address(vars.buyToken), dexData); tv.borrowValue = borrow.mul(currentPrice).div(10 ** uint(priceDecimals)); - }else{ + } else { tv.borrowValue = borrow; } } require(borrow == 0 || deposit.mul(10000).div(tv.borrowValue) > vars.marginLimit, "MAM"); tv.fees = feesAndInsurance( - msg.sender, + trader, deposit.add(tv.borrowValue), address(tv.depositErc20), marketId, @@ -137,19 +145,19 @@ contract OpenLevV1 is DelegateInterface, Adminable, ReentrancyGuard, OpenLevInte tv.depositAfterFees = deposit.sub(tv.fees); tv.dexDetail = dexData.toDexDetail(); - if (depositToken == longToken ){ - if (borrowed > 0){ + if (depositToken == longToken) { + if (borrowed > 0) { tv.newHeld = flashSell(address(vars.buyToken), address(vars.sellToken), borrowed, minBuyAmount, dexData); tv.token0Price = longToken ? tv.newHeld.mul(1e18).div(borrowed) : borrowed.mul(1e18).div(tv.newHeld); } tv.newHeld = tv.newHeld.add(tv.depositAfterFees); - }else{ + } else { tv.tradeSize = tv.depositAfterFees.add(borrowed); tv.newHeld = flashSell(address(vars.buyToken), address(vars.sellToken), tv.tradeSize, minBuyAmount, dexData); tv.token0Price = longToken ? tv.newHeld.mul(1e18).div(tv.tradeSize) : tv.tradeSize.mul(1e18).div(tv.newHeld); } - - Types.Trade storage trade = activeTrades[msg.sender][marketId][longToken]; + newHeld = tv.newHeld; + Types.Trade storage trade = activeTrades[trader][marketId][longToken]; tv.newHeld = OpenLevV1Lib.amountToShare(tv.newHeld, tv.totalHeld, vars.reserveBuyToken); trade.held = trade.held.add(tv.newHeld); trade.depositToken = depositToken; @@ -159,14 +167,25 @@ contract OpenLevV1 is DelegateInterface, Adminable, ReentrancyGuard, OpenLevInte totalHelds[address(vars.buyToken)] = totalHelds[address(vars.buyToken)].add(tv.newHeld); require(OpenLevV1Lib.isPositionHealthy( - msg.sender, + trader, true, OpenLevV1Lib.shareToAmount(trade.held, totalHelds[address(vars.buyToken)], vars.buyToken.balanceOf(address(this))), vars, dexData ), "PNH"); - emit MarginTrade(msg.sender, marketId, longToken, depositToken, deposit, borrow, tv.newHeld, tv.fees, tv.token0Price, tv.dexDetail); + emit MarginTrade(trader, marketId, longToken, depositToken, deposit, borrow, tv.newHeld, tv.fees, tv.token0Price, tv.dexDetail); + + } + + + function closeTrade(uint16 marketId, bool longToken, uint closeHeld, uint minOrMaxAmount, bytes memory dexData) external override nonReentrant onlySupportDex(dexData) returns (uint256){ + return _closeTradeFor(msg.sender, marketId, longToken, closeHeld, minOrMaxAmount, dexData); + } + + function closeTradeFor(address trader, uint16 marketId, bool longToken, uint closeHeld, uint minOrMaxAmount, bytes memory dexData) external override nonReentrant onlySupportDex(dexData) returns (uint256){ + require(msg.sender == opLimitOrder, 'OLO'); + return _closeTradeFor(trader, marketId, longToken, closeHeld, minOrMaxAmount, dexData); } /// @notice Close trade by shares. @@ -175,9 +194,10 @@ contract OpenLevV1 is DelegateInterface, Adminable, ReentrancyGuard, OpenLevInte /// @param closeHeld Amount of shares to close. /// @param minOrMaxAmount Slippage for Dex trading. /// @param dexData Index and fee rate for the trading Dex. - function closeTrade(uint16 marketId, bool longToken, uint closeHeld, uint minOrMaxAmount, bytes memory dexData) external override nonReentrant onlySupportDex(dexData) { - Types.Trade storage trade = activeTrades[msg.sender][marketId][longToken]; + function _closeTradeFor(address trader, uint16 marketId, bool longToken, uint closeHeld, uint minOrMaxAmount, bytes memory dexData) internal returns (uint256){ + Types.Trade storage trade = activeTrades[trader][marketId][longToken]; Types.MarketVars memory marketVars = toMarketVar(longToken, false, markets[marketId]); + bool depositToken = trade.depositToken; //verify require(closeHeld <= trade.held, "CBH"); @@ -187,11 +207,11 @@ contract OpenLevV1 is DelegateInterface, Adminable, ReentrancyGuard, OpenLevInte uint closeAmount = OpenLevV1Lib.shareToAmount(closeHeld, totalHelds[address(marketVars.sellToken)], marketVars.reserveSellToken); Types.CloseTradeVars memory closeTradeVars; - closeTradeVars.fees = feesAndInsurance(msg.sender, closeAmount, address(marketVars.sellToken), marketId, totalHelds[address(marketVars.sellToken)], marketVars.reserveSellToken); + closeTradeVars.fees = feesAndInsurance(trader, closeAmount, address(marketVars.sellToken), marketId, totalHelds[address(marketVars.sellToken)], marketVars.reserveSellToken); closeTradeVars.closeAmountAfterFees = closeAmount.sub(closeTradeVars.fees); closeTradeVars.closeRatio = closeHeld.mul(1e18).div(trade.held); closeTradeVars.isPartialClose = closeHeld != trade.held; - closeTradeVars.borrowed = marketVars.buyPool.borrowBalanceCurrent(msg.sender); + closeTradeVars.borrowed = marketVars.buyPool.borrowBalanceCurrent(trader); closeTradeVars.repayAmount = Utils.toAmountBeforeTax(closeTradeVars.borrowed, taxes[marketId][address(marketVars.buyToken)][0]); closeTradeVars.dexDetail = dexData.toDexDetail(); @@ -204,16 +224,16 @@ contract OpenLevV1 is DelegateInterface, Adminable, ReentrancyGuard, OpenLevInte closeTradeVars.depositDecrease = trade.deposited; } - if (trade.depositToken != longToken) { + if (depositToken != longToken) { minOrMaxAmount = Utils.maxOf(closeTradeVars.repayAmount, minOrMaxAmount); closeTradeVars.receiveAmount = flashSell(address(marketVars.buyToken), address(marketVars.sellToken), closeTradeVars.closeAmountAfterFees, minOrMaxAmount, dexData); require(closeTradeVars.receiveAmount >= closeTradeVars.repayAmount, "ISR"); closeTradeVars.sellAmount = closeTradeVars.closeAmountAfterFees; - marketVars.buyPool.repayBorrowBehalf(msg.sender, closeTradeVars.repayAmount); + marketVars.buyPool.repayBorrowBehalf(trader, closeTradeVars.repayAmount); closeTradeVars.depositReturn = closeTradeVars.receiveAmount.sub(closeTradeVars.repayAmount); - doTransferOut(msg.sender, marketVars.buyToken, closeTradeVars.depositReturn); + doTransferOut(trader, marketVars.buyToken, closeTradeVars.depositReturn); } else { uint balance = marketVars.buyToken.balanceOf(address(this)); minOrMaxAmount = Utils.minOf(closeTradeVars.closeAmountAfterFees, minOrMaxAmount); @@ -221,18 +241,18 @@ contract OpenLevV1 is DelegateInterface, Adminable, ReentrancyGuard, OpenLevInte closeTradeVars.receiveAmount = marketVars.buyToken.balanceOf(address(this)).sub(balance); require(closeTradeVars.receiveAmount >= closeTradeVars.repayAmount, "ISR"); - marketVars.buyPool.repayBorrowBehalf(msg.sender, closeTradeVars.repayAmount); + marketVars.buyPool.repayBorrowBehalf(trader, closeTradeVars.repayAmount); closeTradeVars.depositReturn = closeTradeVars.closeAmountAfterFees.sub(closeTradeVars.sellAmount); require(marketVars.sellToken.balanceOf(address(this)) >= closeTradeVars.depositReturn, "ISB"); - doTransferOut(msg.sender, marketVars.sellToken, closeTradeVars.depositReturn); + doTransferOut(trader, marketVars.sellToken, closeTradeVars.depositReturn); } - uint repayed = closeTradeVars.borrowed.sub(marketVars.buyPool.borrowBalanceCurrent(msg.sender)); + uint repayed = closeTradeVars.borrowed.sub(marketVars.buyPool.borrowBalanceCurrent(trader)); require(repayed >= closeTradeVars.borrowed.mul(closeTradeVars.closeRatio).div(1e18), "IRP"); if (!closeTradeVars.isPartialClose) { - delete activeTrades[msg.sender][marketId][longToken]; - }else{ + delete activeTrades[trader][marketId][longToken]; + } else { trade.held = trade.held.sub(closeHeld); trade.lastBlockNum = uint128(block.number); } @@ -241,11 +261,41 @@ contract OpenLevV1 is DelegateInterface, Adminable, ReentrancyGuard, OpenLevInte closeTradeVars.token0Price = longToken ? closeTradeVars.sellAmount.mul(1e18).div(closeTradeVars.receiveAmount) : closeTradeVars.receiveAmount.mul(1e18).div(closeTradeVars.sellAmount); if (dexData.isUniV2Class()) { - OpenLevV1Lib.updatePriceInternal(address(marketVars.buyToken), address(marketVars.sellToken), dexData); + OpenLevV1Lib.updatePrice(address(marketVars.buyToken), address(marketVars.sellToken), dexData); } - emit TradeClosed(msg.sender, marketId, longToken, trade.depositToken, closeHeld, closeTradeVars.depositDecrease, closeTradeVars.depositReturn, closeTradeVars.fees, + emit TradeClosed(trader, marketId, longToken, depositToken, closeHeld, closeTradeVars.depositDecrease, closeTradeVars.depositReturn, closeTradeVars.fees, closeTradeVars.token0Price, closeTradeVars.dexDetail); + return closeTradeVars.depositReturn; + } + + /// @notice payoff trade by shares. + /// @dev To support token with tax, function expect to fail if share of borrowed funds not repayed. + /// @param longToken Token to long. False for token0, true for token1. + function payoffTrade(uint16 marketId, bool longToken) external payable override nonReentrant { + Types.Trade storage trade = activeTrades[msg.sender][marketId][longToken]; + bool depositToken = trade.depositToken; + uint deposited = trade.deposited; + Types.MarketVars memory marketVars = toMarketVar(longToken, false, markets[marketId]); + + //verify + require(trade.held != 0 && trade.lastBlockNum != block.number, "HI0"); + (ControllerInterface(addressConfig.controller)).closeTradeAllowed(marketId); + uint heldAmount = trade.held; + uint closeAmount = OpenLevV1Lib.shareToAmount(heldAmount, totalHelds[address(marketVars.sellToken)], marketVars.reserveSellToken); + uint borrowed = marketVars.buyPool.borrowBalanceCurrent(msg.sender); + + //first transfer token to OpenLeve, then repay to pool, two transactions with two tax deductions + uint24 taxRate = taxes[marketId][address(marketVars.buyToken)][0]; + uint firstAmount = Utils.toAmountBeforeTax(borrowed, taxRate); + uint transferAmount = transferIn(msg.sender, marketVars.buyToken, Utils.toAmountBeforeTax(firstAmount, taxRate), true); + marketVars.buyPool.repayBorrowBehalf(msg.sender, transferAmount); + require(marketVars.buyPool.borrowBalanceCurrent(msg.sender) == 0, "IRP"); + delete activeTrades[msg.sender][marketId][longToken]; + totalHelds[address(marketVars.sellToken)] = totalHelds[address(marketVars.sellToken)].sub(heldAmount); + doTransferOut(msg.sender, marketVars.sellToken, closeAmount); + + emit TradeClosed(msg.sender, marketId, longToken, depositToken, heldAmount, deposited, heldAmount, 0, 0, 0); } /// @notice Liquidate if trade below margin limit. @@ -259,7 +309,7 @@ contract OpenLevV1 is DelegateInterface, Adminable, ReentrancyGuard, OpenLevInte Types.Trade memory trade = activeTrades[owner][marketId][longToken]; Types.MarketVars memory marketVars = toMarketVar(longToken, false, markets[marketId]); if (dexData.isUniV2Class()) { - OpenLevV1Lib.updatePriceInternal(address(marketVars.buyToken), address(marketVars.sellToken), dexData); + OpenLevV1Lib.updatePrice(address(marketVars.buyToken), address(marketVars.sellToken), dexData); } require(trade.held != 0 && trade.lastBlockNum != block.number && OpenLevV1Lib.isInSupportDex(marketVars.dexs, dexData.toDexDetail()), "HI0"); @@ -310,7 +360,7 @@ contract OpenLevV1 is DelegateInterface, Adminable, ReentrancyGuard, OpenLevInte liquidateVars.receiveAmount = flashSell(address(marketVars.buyToken), address(marketVars.sellToken), liquidateVars.sellAmount, minBuy, dexData); if (liquidateVars.receiveAmount >= liquidateVars.borrowed) { // fail if buy failed but sell succeeded - require (longToken != trade.depositToken, "PH"); + require(longToken != trade.depositToken, "PH"); marketVars.buyPool.repayBorrowBehalf(owner, liquidateVars.borrowed); liquidateVars.depositReturn = liquidateVars.receiveAmount.sub(liquidateVars.borrowed); doTransferOut(owner, marketVars.buyToken, liquidateVars.depositReturn); @@ -331,31 +381,7 @@ contract OpenLevV1 is DelegateInterface, Adminable, ReentrancyGuard, OpenLevInte } function toMarketVar(bool longToken, bool open, Types.Market storage market) internal view returns (Types.MarketVars memory) { - return open == longToken ? - Types.MarketVars( - market.pool1, - market.pool0, - IERC20(market.token1), - IERC20(market.token0), - IERC20(market.token1).balanceOf(address(this)), - IERC20(market.token0).balanceOf(address(this)), - market.pool1Insurance, - market.pool0Insurance, - market.marginLimit, - market.priceDiffientRatio, - market.dexs) : - Types.MarketVars( - market.pool0, - market.pool1, - IERC20(market.token0), - IERC20(market.token1), - IERC20(market.token0).balanceOf(address(this)), - IERC20(market.token1).balanceOf(address(this)), - market.pool0Insurance, - market.pool1Insurance, - market.marginLimit, - market.priceDiffientRatio, - market.dexs); + return OpenLevV1Lib.toMarketVar(longToken, open, market); } /// @notice Get ratios of deposited token value to borrowed token value. @@ -368,46 +394,16 @@ contract OpenLevV1 is DelegateInterface, Adminable, ReentrancyGuard, OpenLevInte /// @return hAvg Margin ratio calculated using last recorded twap price. /// @return limit The liquidation trigger ratio of deposited token value to borrowed token value. function marginRatio(address owner, uint16 marketId, bool longToken, bytes memory dexData) external override onlySupportDex(dexData) view returns (uint current, uint cAvg, uint hAvg, uint32 limit) { - address tokenToLong; - Types.Market memory market = markets[marketId]; - tokenToLong = longToken ? market.token1 : market.token0; - limit = market.marginLimit; - - uint amount = activeTrades[owner][marketId][longToken].held; - amount = OpenLevV1Lib.shareToAmount( - amount, - totalHelds[tokenToLong], - IERC20(tokenToLong).balanceOf(address(this)) - ); - - (current, cAvg, hAvg,,) = - OpenLevV1Lib.marginRatio( - owner, - amount, - tokenToLong, - longToken ? market.token0 : market.token1, - longToken ? market.pool0 : market.pool1, - dexData - ); - } - - /// @notice Check if a price update is required on Dex. - /// @param dexData Index and fee rate for the trading Dex. - function shouldUpdatePrice(uint16 marketId, bytes memory dexData) external override view returns (bool){ - Types.Market memory market = markets[marketId]; - return OpenLevV1Lib.shouldUpdatePriceInternal(addressConfig.dexAggregator, calculateConfig.twapDuration, market.priceDiffientRatio, market.token0, market.token1, dexData); + (current, cAvg, hAvg, limit) = OpenLevV1Lib.marginRatio(marketId, owner, longToken, dexData); } /// @notice Update price on Dex. /// @param dexData Index and fee rate for the trading Dex. function updatePrice(uint16 marketId, bytes memory dexData) external override { - OpenLevV1Lib.updatePrice(marketId, markets[marketId], addressConfig, calculateConfig, dexData); + OpenLevV1Lib.updatePrice(markets[marketId], dexData); } - /// @notice List of all supporting Dexes. - function getMarketSupportDexs(uint16 marketId) external override view returns (uint32[] memory){ - return markets[marketId].dexs; - } + function reduceInsurance(uint totalRepayment, uint remaining, uint16 marketId, bool longToken, address token, uint reserve) internal returns (uint maxCanRepayAmount) { Types.Market storage market = markets[marketId]; @@ -419,25 +415,19 @@ contract OpenLevV1 is DelegateInterface, Adminable, ReentrancyGuard, OpenLevInte return OpenLevV1Lib.feeAndInsurance(trader, tradeSize, token, addressConfig.xOLE, totalHeld, reserve, market, totalHelds, calculateConfig); } - function flashSell(address buyToken, address sellToken, uint sellAmount, uint minBuyAmount, bytes memory data) internal returns (uint buyAmount){ - if (sellAmount > 0){ - DexAggregatorInterface dexAggregator = addressConfig.dexAggregator; - IERC20(sellToken).safeApprove(address(dexAggregator), sellAmount); - buyAmount = dexAggregator.sell(buyToken, sellToken, sellAmount, minBuyAmount, data); - } + function flashSell(address buyToken, address sellToken, uint sellAmount, uint minBuyAmount, bytes memory data) internal returns (uint){ + return OpenLevV1Lib.flashSell(buyToken, sellToken, sellAmount, minBuyAmount, data, addressConfig.dexAggregator); } - function flashBuy(uint16 marketId, address buyToken, address sellToken, uint buyAmount, uint maxSellAmount, bytes memory data) internal returns (uint sellAmount){ - if (buyAmount > 0){ - DexAggregatorInterface dexAggregator = addressConfig.dexAggregator; - IERC20(sellToken).safeApprove(address(dexAggregator), maxSellAmount); - sellAmount = dexAggregator.buy(buyToken, sellToken, taxes[marketId][buyToken][2], taxes[marketId][sellToken][1], buyAmount, maxSellAmount, data); - } + function flashBuy(uint16 marketId, address buyToken, address sellToken, uint buyAmount, uint maxSellAmount, bytes memory data) internal returns (uint){ + uint24 buyTax = taxes[marketId][buyToken][2]; + uint24 sellTax = taxes[marketId][sellToken][1]; + return OpenLevV1Lib.flashBuy(buyToken, sellToken, buyAmount, maxSellAmount, data, addressConfig.dexAggregator, buyTax, sellTax); } /// @dev All credited on this contract and share with all token holder if any rewards for the transfer. - function transferIn(address from, IERC20 token, uint amount) internal returns (uint) { - return OpenLevV1Lib.transferIn(from, token, addressConfig.wETH, amount); + function transferIn(address from, IERC20 token, uint amount, bool convertWeth) internal returns (uint) { + return OpenLevV1Lib.transferIn(from, token, convertWeth ? addressConfig.wETH : address(0), amount); } /// @dev All credited on "to" if any taxes for the transfer. @@ -482,10 +472,14 @@ contract OpenLevV1 is DelegateInterface, Adminable, ReentrancyGuard, OpenLevInte supportDexs[dex] = support; } - function setTaxRate(uint16 marketId, address token, uint index, uint24 tax) external override onlyAdmin(){ + function setTaxRate(uint16 marketId, address token, uint index, uint24 tax) external override onlyAdmin() { taxes[marketId][token][index] = tax; } + function setOpLimitOrder(address _opLimitOrder) external override onlyAdmin() { + opLimitOrder = _opLimitOrder; + } + modifier onlySupportDex(bytes memory dexData) { require(OpenLevV1Lib.isSupportDex(supportDexs, dexData.toDex()), "UDX"); _; diff --git a/contracts/OpenLevV1Lib.sol b/contracts/OpenLevV1Lib.sol index f331935..a2d9ef7 100644 --- a/contracts/OpenLevV1Lib.sol +++ b/contracts/OpenLevV1Lib.sol @@ -34,6 +34,7 @@ library OpenLevV1Lib { mapping(uint8 => bool) storage _supportDexs, mapping(uint16 => mapping(address => mapping(uint => uint24))) storage taxes ) external { + require(marketId < 65535, "TMP"); address token0 = pool0.underlying(); address token1 = pool1.underlying(); uint8 dex = dexData.toDex(); @@ -59,7 +60,7 @@ library OpenLevV1Lib { markets[marketId] = Types.Market(pool0, pool1, token0, token1, marginLimit, config.defaultFeesRate, config.priceDiffientRatio, address(0), 0, 0, dexs); // Init price oracle if (dexData.isUniV2Class()) { - updatePriceInternal(token0, token1, dexData); + updatePrice(token0, token1, dexData); } else if (dex == DexData.DEX_UNIV3) { addressConfig.dexAggregator.updateV3Observation(token0, token1, dexData); } @@ -116,15 +117,43 @@ library OpenLevV1Lib { market.priceDiffientRatio = priceDiffientRatio; } + + struct MarketWithoutDexs {// Market info + LPoolInterface pool0; + LPoolInterface pool1; + address token0; + address token1; + uint16 marginLimit; + } + function marginRatio( + uint16 marketId, address owner, - uint held, - address heldToken, - address sellToken, - LPoolInterface borrowPool, + bool longToken, bytes memory dexData - ) external view returns (uint, uint, uint, uint, uint){ - return marginRatioPrivate(owner, held, heldToken, sellToken, borrowPool, false, dexData); + ) external view returns (uint current, uint cAvg, uint hAvg, uint32 limit){ + address tokenToLong; + MarketWithoutDexs memory market; + (market.pool0, market.pool1, market.token0, market.token1, market.marginLimit,,,,,) = (OpenLevStorage(address(this))).markets(marketId); + tokenToLong = longToken ? market.token1 : market.token0; + limit = market.marginLimit; + (,uint amount,,) = OpenLevStorage(address(this)).activeTrades(owner, marketId, longToken); + amount = shareToAmount( + amount, + OpenLevStorage(address(this)).totalHelds(tokenToLong), + IERC20(tokenToLong).balanceOf(address(this)) + ); + + (current, cAvg, hAvg,,) = + marginRatioPrivate( + owner, + amount, + tokenToLong, + longToken ? market.token0 : market.token1, + longToken ? market.pool0 : market.pool1, + true, + dexData + ); } function marginRatioPrivate( @@ -196,44 +225,35 @@ library OpenLevV1Lib { } } - function updatePriceInternal(address token0, address token1, bytes memory dexData) internal returns (bool){ + function updatePrice(address token0, address token1, bytes memory dexData) public returns (bool){ (DexAggregatorInterface dexAggregator,,,) = OpenLevStorage(address(this)).addressConfig(); (,,,,,,,,,uint16 twapDuration) = OpenLevStorage(address(this)).calculateConfig(); return dexAggregator.updatePriceOracle(token0, token1, twapDuration, dexData); } - function shouldUpdatePriceInternal(DexAggregatorInterface dexAggregator, uint16 twapDuration, uint16 priceDiffientRatio, address token0, address token1, bytes memory dexData) public view returns (bool){ - if (!dexData.isUniV2Class()) { - return false; - } - (, uint cAvgPrice, uint hAvgPrice,, uint lastUpdateTime) = dexAggregator.getPriceCAvgPriceHAvgPrice(token0, token1, twapDuration, dexData); - if (block.timestamp < lastUpdateTime.add(twapDuration)) { - return false; - } - //Not initialized yet - if (cAvgPrice == 0 || hAvgPrice == 0) { - return true; - } - //price difference - uint one = 100; - uint differencePriceRatio = cAvgPrice.mul(one).div(hAvgPrice); - if (differencePriceRatio >= (one.add(priceDiffientRatio)) || differencePriceRatio <= (one.sub(priceDiffientRatio))) { - return true; - } - return false; - } - function updatePrice(uint16 marketId, Types.Market storage market, OpenLevStorage.AddressConfig storage addressConfig, - OpenLevStorage.CalculateConfig storage calculateConfig, bytes memory dexData) external { - bool shouldUpdate = shouldUpdatePriceInternal(addressConfig.dexAggregator, calculateConfig.twapDuration, market.priceDiffientRatio, market.token1, market.token0, dexData); - bool updateResult = updatePriceInternal(market.token0, market.token1, dexData); + function updatePrice(Types.Market storage market, bytes memory dexData) external { + bool updateResult = updatePrice(market.token0, market.token1, dexData); if (updateResult) { //Discount market.priceUpdater = msg.sender; - //Reward OLE - if (shouldUpdate) { - (ControllerInterface(addressConfig.controller)).updatePriceAllowed(marketId, msg.sender); - } + } + } + + function flashSell(address buyToken, address sellToken, uint sellAmount, uint minBuyAmount, bytes memory data, DexAggregatorInterface dexAggregator) external returns (uint buyAmount){ + if (sellAmount > 0) { + IERC20(sellToken).safeApprove(address(dexAggregator), sellAmount); + buyAmount = dexAggregator.sell(buyToken, sellToken, sellAmount, minBuyAmount, data); + } + } + + function flashBuy(address buyToken, address sellToken, uint buyAmount, uint maxSellAmount, bytes memory data, + DexAggregatorInterface dexAggregator, + uint24 buyTax, + uint24 sellTax) external returns (uint sellAmount){ + if (buyAmount > 0) { + IERC20(sellToken).safeApprove(address(dexAggregator), maxSellAmount); + sellAmount = dexAggregator.buy(buyToken, sellToken, buyTax, sellTax, buyAmount, maxSellAmount, data); } } @@ -281,10 +301,6 @@ library OpenLevV1Lib { ) external returns (uint newFees) { uint defaultFees = tradeSize.mul(market.feesRate).div(10000); newFees = defaultFees; - // if trader holds more xOLE, then should enjoy trading discount. - if (XOLEInterface(xOLE).balanceOf(trader) > calculateConfig.feesDiscountThreshold) { - newFees = defaultFees.sub(defaultFees.mul(calculateConfig.feesDiscount).div(100)); - } // if trader update price, then should enjoy trading discount. if (market.priceUpdater == trader) { newFees = newFees.sub(defaultFees.mul(calculateConfig.updatePriceDiscount).div(100)); @@ -366,14 +382,15 @@ library OpenLevV1Lib { } } - function verifyTrade(Types.MarketVars memory vars, bool longToken, bool depositToken, uint deposit, uint borrow, bytes memory dexData, OpenLevStorage.AddressConfig memory addressConfig, Types.Trade memory trade) external view { + function verifyTrade(Types.MarketVars memory vars, bool longToken, bool depositToken, uint deposit, uint borrow, + bytes memory dexData, OpenLevStorage.AddressConfig memory addressConfig, Types.Trade memory trade, bool convertWeth) external view { //verify if deposit token allowed address depositTokenAddr = depositToken == longToken ? address(vars.buyToken) : address(vars.sellToken); //verify minimal deposit > absolute value 0.0001 uint decimals = ERC20(depositTokenAddr).decimals(); uint minimalDeposit = decimals > 4 ? 10 ** (decimals - 4) : 1; - uint actualDeposit = depositTokenAddr == addressConfig.wETH ? msg.value : deposit; + uint actualDeposit = depositTokenAddr == addressConfig.wETH && convertWeth ? msg.value : deposit; require(actualDeposit > minimalDeposit, "DTS"); require(isInSupportDex(vars.dexs, dexData.toDexDetail()), "DNS"); @@ -386,4 +403,32 @@ library OpenLevV1Lib { require(depositToken == trade.depositToken && trade.lastBlockNum != uint128(block.number), " DTS"); } } + + function toMarketVar(bool longToken, bool open, Types.Market storage market) external view returns (Types.MarketVars memory) { + return open == longToken ? + Types.MarketVars( + market.pool1, + market.pool0, + IERC20(market.token1), + IERC20(market.token0), + IERC20(market.token1).balanceOf(address(this)), + IERC20(market.token0).balanceOf(address(this)), + market.pool1Insurance, + market.pool0Insurance, + market.marginLimit, + market.priceDiffientRatio, + market.dexs) : + Types.MarketVars( + market.pool0, + market.pool1, + IERC20(market.token0), + IERC20(market.token1), + IERC20(market.token0).balanceOf(address(this)), + IERC20(market.token1).balanceOf(address(this)), + market.pool0Insurance, + market.pool1Insurance, + market.marginLimit, + market.priceDiffientRatio, + market.dexs); + } } \ No newline at end of file diff --git a/contracts/dex/bsc/BscDexAggregatorV1.sol b/contracts/dex/bsc/BscDexAggregatorV1.sol index 6764da1..80b6059 100644 --- a/contracts/dex/bsc/BscDexAggregatorV1.sol +++ b/contracts/dex/bsc/BscDexAggregatorV1.sol @@ -21,7 +21,7 @@ contract BscDexAggregatorV1 is DelegateInterface, Adminable, DexAggregatorInterf mapping(IUniswapV2Pair => V2PriceOracle) public uniV2PriceOracle; IUniswapV2Factory public pancakeFactory; address public openLev; - uint8 private constant priceDecimals = 18; + uint8 private constant priceDecimals = 24; mapping(uint8 => DexInfo) public dexInfo; diff --git a/contracts/dex/bsc/UniV2ClassDex.sol b/contracts/dex/bsc/UniV2ClassDex.sol index fa69b5a..a95c5d8 100644 --- a/contracts/dex/bsc/UniV2ClassDex.sol +++ b/contracts/dex/bsc/UniV2ClassDex.sol @@ -186,7 +186,12 @@ contract UniV2ClassDex { function calTPrice(uint currentPriceCumulativeLast, uint historyPriceCumulativeLast, uint32 timeElapsed, uint8 decimals) internal pure returns (uint){ - return ((currentPriceCumulativeLast.sub(historyPriceCumulativeLast).mul(10 ** decimals)) >> 112).div(timeElapsed); + uint256 diff = currentPriceCumulativeLast.sub(historyPriceCumulativeLast); + if (diff < (2 ** 170)) { + return ((diff.mul(10 ** decimals)) >> 112).div(timeElapsed); + } else { + return ((diff) >> 112).mul(10 ** decimals).div(timeElapsed); + } } function toUint32(uint256 y) internal pure returns (uint32 z) { diff --git a/contracts/dex/cronos/CronosDexAggregatorV1.sol b/contracts/dex/cronos/CronosDexAggregatorV1.sol new file mode 100755 index 0000000..6bca2dd --- /dev/null +++ b/contracts/dex/cronos/CronosDexAggregatorV1.sol @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.7.6; +pragma experimental ABIEncoderV2; + +import "./CronosUniV2Dex.sol"; +import "../DexAggregatorInterface.sol"; +import "../../lib/DexData.sol"; +import "../../lib/Utils.sol"; +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "../../DelegateInterface.sol"; +import "../../Adminable.sol"; + +/// @title Swap logic on cronos +/// @author OpenLeverage +/// @notice Use this contract to swap tokens. +/// @dev Routers for different swap requests. +contract CronosDexAggregatorV1 is DelegateInterface, Adminable, DexAggregatorInterface, CronosUniV2Dex { + using DexData for bytes; + using SafeMath for uint; + + mapping(IUniswapV2Pair => V2PriceOracle) public uniV2PriceOracle; + IUniswapV2Factory public vvsFactory; + address public openLev; + uint8 private constant priceDecimals = 24; + + mapping(uint8 => DexInfo) public dexInfo; + + function initialize( + IUniswapV2Factory _vvsFactory, + address _unusedFactory + ) public { + require(msg.sender == admin, "Not admin"); + _unusedFactory; + vvsFactory = _vvsFactory; + dexInfo[DexData.DEX_VVS] = DexInfo(_vvsFactory, 30); + } + + /// @notice Save factories of the dex. + /// @param dexName Index of Dex. find list of dex in contracts/lib/DexData.sol. + /// @param factoryAddr Factory address of Different dex forked from uniswap. + /// @param fees Swap fee collects by. + function setDexInfo(uint8[] memory dexName, IUniswapV2Factory[] memory factoryAddr, uint16[] memory fees) external override onlyAdmin { + require(dexName.length == factoryAddr.length && dexName.length == fees.length, 'EOR'); + for (uint i = 0; i < dexName.length; i++) { + dexInfo[dexName[i]] = DexInfo(factoryAddr[i], fees[i]); + } + } + + /// @dev SetOpenlev address to update dex price + function setOpenLev(address _openLev) external onlyAdmin { + require(address(0) != _openLev, '0x'); + openLev = _openLev; + } + + + /// @notice Sell tokens + /// @dev Sell exact amount of token with tax applied + /// @param buyToken Address of token transfer from Dex pair + /// @param sellToken Address of token transfer into Dex pair + /// @param sellAmount Exact amount to sell + /// @param minBuyAmount minmum amount of token to receive. + /// @param data Dex to use for swap + /// @return buyAmount Exact Amount bought + function sell(address buyToken, address sellToken, uint sellAmount, uint minBuyAmount, bytes memory data) external override returns (uint buyAmount){ + address payer = msg.sender; + buyAmount = uniClassSell(dexInfo[data.toDex()], buyToken, sellToken, sellAmount, minBuyAmount, payer, payer); + } + + /// @notice Sell tokens + /// @dev Sell exact amount of token through path + /// @param sellAmount Exact amount to sell + /// @param minBuyAmount minmum amount of token to receive. + /// @param data Dex to use for swap and path of the swap + /// @return buyAmount Exact amount bought + function sellMul(uint sellAmount, uint minBuyAmount, bytes memory data) external override returns (uint buyAmount){ + buyAmount = uniClassSellMul(dexInfo[data.toDex()], sellAmount, minBuyAmount, data.toUniV2Path()); + } + + /// @notice Buy tokens + /// @dev Buy exact amount of token with tax applied + /// @param buyToken Address of token transfer from Dex pair + /// @param sellToken Address of token transfer into Dex pair + /// @param buyTax Tax applyed by buyToken while transfer from Dex pair + /// @param sellTax Tax applyed by sellToken while transfer into Dex pair + /// @param buyAmount Exact amount to buy + /// @param maxSellAmount maximum amount of token to receive. + /// @param data Dex to use for swap + /// @return sellAmount Exact amount sold + function buy(address buyToken, address sellToken, uint24 buyTax, uint24 sellTax, uint buyAmount, uint maxSellAmount, bytes memory data) external override returns (uint sellAmount){ + sellAmount = uniClassBuy(dexInfo[data.toDex()], buyToken, sellToken, buyAmount, maxSellAmount, buyTax, sellTax); + } + + /// @notice Calculate amount of token to buy + /// @dev Calculate exact amount of token to buy with tax applied + /// @param buyToken Address of token transfer from Dex pair + /// @param sellToken Address of token transfer into Dex pair + /// @param buyTax Tax applyed by buyToken while transfer from Dex pair + /// @param sellTax Tax applyed by sellToken while transfer into Dex pair + /// @param sellAmount Exact amount to sell + /// @param data Dex to use for swap + /// @return buyAmount Amount of buyToken would bought + function calBuyAmount(address buyToken, address sellToken, uint24 buyTax, uint24 sellTax, uint sellAmount, bytes memory data) external view override returns (uint buyAmount) { + sellAmount = Utils.toAmountAfterTax(sellAmount, sellTax); + buyAmount = uniClassCalBuyAmount(dexInfo[data.toDex()], buyToken, sellToken, sellAmount); + buyAmount = Utils.toAmountAfterTax(buyAmount, buyTax); + } + + /// @notice Calculate amount of token to sell + /// @dev Calculate exact amount of token to sell with tax applied + /// @param buyToken Address of token transfer from Dex pair + /// @param sellToken Address of token transfer into Dex pair + /// @param buyTax Tax applyed by buyToken while transfer from Dex pair + /// @param sellTax Tax applyed by SellToken while transfer into Dex pair + /// @param buyAmount Exact amount to buy + /// @param data Dex to use for swap + /// @return sellAmount Amount of sellToken would sold + function calSellAmount(address buyToken, address sellToken, uint24 buyTax, uint24 sellTax, uint buyAmount, bytes memory data) external view override returns (uint sellAmount){ + sellAmount = uniClassCalSellAmount(dexInfo[data.toDex()], buyToken, sellToken, buyAmount, buyTax, sellTax); + } + + /// @notice Get price + /// @dev Get current price of desToken / quoteToken + /// @param desToken Token to be priced + /// @param quoteToken Token used for pricing + /// @param data Dex to use for swap + function getPrice(address desToken, address quoteToken, bytes memory data) external view override returns (uint256 price, uint8 decimals){ + decimals = priceDecimals; + price = uniClassGetPrice(dexInfo[data.toDex()].factory, desToken, quoteToken, decimals); + } + + /// @dev Get average price of desToken / quoteToken in the last period of time + /// @param desToken Token to be priced + /// @param quoteToken Token used for pricing + /// @param secondsAgo Time period of the average + /// @param data Dex to use for swap + function getAvgPrice(address desToken, address quoteToken, uint32 secondsAgo, bytes memory data) external view override returns (uint256 price, uint8 decimals, uint256 timestamp){ + require(data.isUniV2Class(), "unsupported dex"); + // Shh - currently unused + secondsAgo; + decimals = priceDecimals; + address pair = getUniClassPair(desToken, quoteToken, dexInfo[data.toDex()].factory); + V2PriceOracle memory priceOracle = uniV2PriceOracle[IUniswapV2Pair(pair)]; + (price, timestamp) = uniClassGetAvgPrice(desToken, quoteToken, priceOracle); + } + + /// @notice Fet current and history price + /// @param desToken Token to be priced + /// @param quoteToken Token used for pricing + /// @param secondsAgo not used on BSC + /// @param dexData dex parameters + /// @return price Real-time price + /// @return cAvgPrice Current TWAP price + /// @return hAvgPrice Historical TWAP price + /// @return decimals Token price decimal + /// @return timestamp Last TWAP price update timestamp + function getPriceCAvgPriceHAvgPrice( + address desToken, + address quoteToken, + uint32 secondsAgo, + bytes memory dexData + ) external view override returns (uint price, uint cAvgPrice, uint256 hAvgPrice, uint8 decimals, uint256 timestamp){ + require(dexData.isUniV2Class(), "unsupported dex"); + secondsAgo; + decimals = priceDecimals; + address pair = getUniClassPair(desToken, quoteToken, dexInfo[dexData.toDex()].factory); + V2PriceOracle memory priceOracle = uniV2PriceOracle[IUniswapV2Pair(pair)]; + (price, cAvgPrice, hAvgPrice, timestamp) = uniClassGetPriceCAvgPriceHAvgPrice(pair, priceOracle, desToken, quoteToken, decimals); + } + + /// @dev Update Dex price if not updated over time window + /// @param desToken Token to be priced + /// @param quoteToken Token used for pricing + /// @param timeWindow minmum time gap between two updates + /// @param data dex parameters + /// @return If updated + function updatePriceOracle(address desToken, address quoteToken, uint32 timeWindow, bytes memory data) external override returns (bool){ + require(msg.sender == openLev, "Only openLev can update price"); + require(data.isUniV2Class(), "unsupported dex"); + address pair = getUniClassPair(desToken, quoteToken, dexInfo[data.toDex()].factory); + V2PriceOracle memory priceOracle = uniV2PriceOracle[IUniswapV2Pair(pair)]; + (V2PriceOracle memory updatedPriceOracle, bool updated) = uniClassUpdatePriceOracle(pair, priceOracle, timeWindow, priceDecimals); + if (updated) { + uniV2PriceOracle[IUniswapV2Pair(pair)] = updatedPriceOracle; + } + return updated; + } + + /// @dev Update UniV3 observations + /// @param desToken Token to be priced + /// @param quoteToken Token used for pricing + /// @param data Dex parameters + function updateV3Observation(address desToken, address quoteToken, bytes memory data) external pure override { + // Shh - currently unused + (desToken,quoteToken, data); + revert("Not implemented"); + } +} diff --git a/contracts/dex/cronos/CronosUniV2Dex.sol b/contracts/dex/cronos/CronosUniV2Dex.sol new file mode 100755 index 0000000..8f63905 --- /dev/null +++ b/contracts/dex/cronos/CronosUniV2Dex.sol @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.7.6; +pragma experimental ABIEncoderV2; + +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol"; +import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; +import "../../lib/TransferHelper.sol"; +import "../../lib/DexData.sol"; +import "../../lib/Utils.sol"; + +contract CronosUniV2Dex { + using SafeMath for uint; + using Utils for uint; + using TransferHelper for IERC20; + + struct V2PriceOracle { + uint32 blockTimestampLast; // Last block timestamp when price updated + uint price0; // recorded price for token0 + uint price1; // recorded price for token1 + uint price0CumulativeLast; // Cumulative TWAP for token0 + uint price1CumulativeLast; // Cumulative TWAP for token1 + } + + struct DexInfo { + IUniswapV2Factory factory; + uint16 fees; + } + + function uniClassSell(DexInfo memory dexInfo, + address buyToken, + address sellToken, + uint sellAmount, + uint minBuyAmount, + address payer, + address payee + ) internal returns (uint buyAmount){ + address pair = getUniClassPair(buyToken, sellToken, dexInfo.factory); + IUniswapV2Pair(pair).sync(); + sellAmount = transferOut(IERC20(sellToken), payer, pair, sellAmount); + (uint256 token0Reserves, uint256 token1Reserves,) = IUniswapV2Pair(pair).getReserves(); + sellAmount = buyToken < sellToken ? IERC20(sellToken).balanceOf(pair).sub(token1Reserves) : IERC20(sellToken).balanceOf(pair).sub(token0Reserves); + + uint balanceBefore = IERC20(buyToken).balanceOf(payee); + dexInfo.fees = getPairFees(dexInfo, pair); + + if (buyToken < sellToken) { + buyAmount = getAmountOut(sellAmount, token1Reserves, token0Reserves, dexInfo.fees); + IUniswapV2Pair(pair).swap(buyAmount, 0, payee, ""); + } else { + buyAmount = getAmountOut(sellAmount, token0Reserves, token1Reserves, dexInfo.fees); + IUniswapV2Pair(pair).swap(0, buyAmount, payee, ""); + } + buyAmount = IERC20(buyToken).balanceOf(payee).sub(balanceBefore); + require(buyAmount >= minBuyAmount, 'buy amount less than min'); + } + + function uniClassSellMul(DexInfo memory dexInfo, uint sellAmount, uint minBuyAmount, address[] memory tokens) + internal returns (uint buyAmount){ + for (uint i = 1; i < tokens.length; i++) { + address sellToken = tokens[i - 1]; + address buyToken = tokens[i]; + bool isLast = i == tokens.length - 1; + address payer = i == 1 ? msg.sender : address(this); + address payee = isLast ? msg.sender : address(this); + buyAmount = uniClassSell(dexInfo, buyToken, sellToken, sellAmount, 0, payer, payee); + if (!isLast) { + sellAmount = buyAmount; + } + } + require(buyAmount >= minBuyAmount, 'buy amount less than min'); + } + + function uniClassBuy( + DexInfo memory dexInfo, + address buyToken, + address sellToken, + uint buyAmount, + uint maxSellAmount, + uint24 buyTokenFeeRate, + uint24 sellTokenFeeRate) + internal returns (uint sellAmount){ + address pair = getUniClassPair(buyToken, sellToken, dexInfo.factory); + IUniswapV2Pair(pair).sync(); + (uint256 token0Reserves, uint256 token1Reserves,) = IUniswapV2Pair(pair).getReserves(); + uint balanceBefore = IERC20(buyToken).balanceOf(msg.sender); + dexInfo.fees = getPairFees(dexInfo, pair); + if (buyToken < sellToken) { + sellAmount = getAmountIn(buyAmount.toAmountBeforeTax(buyTokenFeeRate), token1Reserves, token0Reserves, dexInfo.fees); + sellAmount = sellAmount.toAmountBeforeTax(sellTokenFeeRate); + require(sellAmount <= maxSellAmount, 'sell amount not enough'); + transferOut(IERC20(sellToken), msg.sender, pair, sellAmount); + IUniswapV2Pair(pair).swap(buyAmount.toAmountBeforeTax(buyTokenFeeRate), 0, msg.sender, ""); + } else { + sellAmount = getAmountIn(buyAmount.toAmountBeforeTax(buyTokenFeeRate), token0Reserves, token1Reserves, dexInfo.fees); + sellAmount = sellAmount.toAmountBeforeTax(sellTokenFeeRate); + require(sellAmount <= maxSellAmount, 'sell amount not enough'); + transferOut(IERC20(sellToken), msg.sender, pair, sellAmount); + IUniswapV2Pair(pair).swap(0, buyAmount.toAmountBeforeTax(buyTokenFeeRate), msg.sender, ""); + } + + uint balanceAfter = IERC20(buyToken).balanceOf(msg.sender); + require(buyAmount <= balanceAfter.sub(balanceBefore), "wrong amount bought"); + } + + function uniClassCalBuyAmount(DexInfo memory dexInfo, address buyToken, address sellToken, uint sellAmount) internal view returns (uint) { + address pair = getUniClassPair(buyToken, sellToken, dexInfo.factory); + (uint256 token0Reserves, uint256 token1Reserves,) = IUniswapV2Pair(pair).getReserves(); + if (buyToken < sellToken) { + return getAmountOut(sellAmount, token1Reserves, token0Reserves, getPairFees(dexInfo, pair)); + } else { + return getAmountOut(sellAmount, token0Reserves, token1Reserves, getPairFees(dexInfo, pair)); + } + } + + function uniClassCalSellAmount( + DexInfo memory dexInfo, + address buyToken, + address sellToken, + uint buyAmount, + uint24 buyTokenFeeRate, + uint24 sellTokenFeeRate) internal view returns (uint sellAmount) { + address pair = getUniClassPair(buyToken, sellToken, dexInfo.factory); + (uint256 token0Reserves, uint256 token1Reserves,) = IUniswapV2Pair(pair).getReserves(); + sellAmount = buyToken < sellToken ? + getAmountIn(buyAmount.toAmountBeforeTax(buyTokenFeeRate), token1Reserves, token0Reserves, getPairFees(dexInfo, pair)) : + getAmountIn(buyAmount.toAmountBeforeTax(buyTokenFeeRate), token0Reserves, token1Reserves, getPairFees(dexInfo, pair)); + + return sellAmount.toAmountBeforeTax(sellTokenFeeRate); + } + + function uniClassGetPrice(IUniswapV2Factory factory, address desToken, address quoteToken, uint8 decimals) internal view returns (uint256){ + address pair = getUniClassPair(desToken, quoteToken, factory); + (uint256 token0Reserves, uint256 token1Reserves,) = IUniswapV2Pair(pair).getReserves(); + return desToken == IUniswapV2Pair(pair).token0() ? + token1Reserves.mul(10 ** decimals).div(token0Reserves) : + token0Reserves.mul(10 ** decimals).div(token1Reserves); + } + + function uniClassGetAvgPrice(address desToken, address quoteToken, V2PriceOracle memory priceOracle) internal pure returns (uint256 price, uint256 timestamp){ + timestamp = priceOracle.blockTimestampLast; + price = desToken < quoteToken ? uint(priceOracle.price0) : uint(priceOracle.price1); + } + + + function uniClassGetPriceCAvgPriceHAvgPrice(address pair, V2PriceOracle memory priceOracle, address desToken, address quoteToken, uint8 decimals) + internal view returns (uint price, uint cAvgPrice, uint256 hAvgPrice, uint256 timestamp){ + bool isToken0 = desToken < quoteToken; + (uint256 token0Reserves, uint256 token1Reserves, uint32 uniBlockTimeLast) = IUniswapV2Pair(pair).getReserves(); + price = isToken0 ? + token1Reserves.mul(10 ** decimals).div(token0Reserves) : + token0Reserves.mul(10 ** decimals).div(token1Reserves); + + hAvgPrice = isToken0 ? uint(priceOracle.price0) : uint(priceOracle.price1); + timestamp = priceOracle.blockTimestampLast; + + if (uniBlockTimeLast <= priceOracle.blockTimestampLast) { + cAvgPrice = hAvgPrice; + } else { + uint32 timeElapsed = uniBlockTimeLast - priceOracle.blockTimestampLast; + cAvgPrice = uint256(isToken0 ? + calTPrice(IUniswapV2Pair(pair).price0CumulativeLast(), priceOracle.price0CumulativeLast, timeElapsed, decimals) : + calTPrice(IUniswapV2Pair(pair).price1CumulativeLast(), priceOracle.price1CumulativeLast, timeElapsed, decimals)); + } + } + + function uniClassUpdatePriceOracle(address pair, V2PriceOracle memory priceOracle, uint32 timeWindow, uint8 decimals) internal returns (V2PriceOracle memory, bool updated) { + uint32 currentBlockTime = toUint32(block.timestamp); + if (currentBlockTime < (priceOracle.blockTimestampLast + timeWindow)) { + return (priceOracle, false); + } + IUniswapV2Pair(pair).sync(); + uint32 timeElapsed = currentBlockTime - priceOracle.blockTimestampLast; + uint currentPrice0CumulativeLast = IUniswapV2Pair(pair).price0CumulativeLast(); + uint currentPrice1CumulativeLast = IUniswapV2Pair(pair).price1CumulativeLast(); + if (priceOracle.blockTimestampLast != 0) { + priceOracle.price0 = calTPrice(currentPrice0CumulativeLast, priceOracle.price0CumulativeLast, timeElapsed, decimals); + priceOracle.price1 = calTPrice(currentPrice1CumulativeLast, priceOracle.price1CumulativeLast, timeElapsed, decimals); + } + priceOracle.price0CumulativeLast = currentPrice0CumulativeLast; + priceOracle.price1CumulativeLast = currentPrice1CumulativeLast; + priceOracle.blockTimestampLast = currentBlockTime; + return (priceOracle, true); + } + + function calTPrice(uint currentPriceCumulativeLast, uint historyPriceCumulativeLast, uint32 timeElapsed, uint8 decimals) + internal pure returns (uint){ + uint256 diff = currentPriceCumulativeLast.sub(historyPriceCumulativeLast); + if (diff < (2 ** 170)) { + return ((diff.mul(10 ** decimals)) >> 112).div(timeElapsed); + } else { + return ((diff) >> 112).mul(10 ** decimals).div(timeElapsed); + } + } + + function toUint32(uint256 y) internal pure returns (uint32 z) { + require((z = uint32(y)) == y); + } + + function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut, uint16 fees) private pure returns (uint amountOut) + { + require(amountIn > 0, 'INSUFFICIENT_INPUT_AMOUNT'); + require(reserveIn > 0 && reserveOut > 0, 'INSUFFICIENT_LIQUIDITY'); + uint amountInWithFee = amountIn.mul(uint(10000).sub(fees)); + uint numerator = amountInWithFee.mul(reserveOut); + uint denominator = reserveIn.mul(10000).add(amountInWithFee); + amountOut = numerator / denominator; + } + + function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut, uint16 fees) private pure returns (uint amountIn) { + require(amountOut > 0, 'INSUFFICIENT_OUTPUT_AMOUNT'); + require(reserveIn > 0 && reserveOut > 0, 'INSUFFICIENT_LIQUIDITY'); + uint numerator = reserveIn.mul(amountOut).mul(10000); + uint denominator = reserveOut.sub(amountOut).mul(uint(10000).sub(fees)); + amountIn = (numerator / denominator).add(1); + } + + function transferOut(IERC20 token, address payer, address to, uint amount) private returns (uint256 amountReceived) { + if (payer == address(this)) { + amountReceived = token.safeTransfer(to, amount); + } else { + amountReceived = token.safeTransferFrom(payer, to, amount); + } + } + + function getUniClassPair(address tokenA, address tokenB, IUniswapV2Factory factory) internal view returns (address pair){ + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + if (address(factory) == 0x3B44B2a187a7b3824131F8db5a74194D0a42Fc15) { + // VVS + return address(uint(keccak256(abi.encodePacked( + hex'ff', + address(factory), + keccak256(abi.encodePacked(token0, token1)), + hex'a77ee1cc0f39570ddde947459e293d7ebc2c30ff4e8fc45860afdcb2c2d3dc17' + )))); + } else { + return factory.getPair(tokenA, tokenB); + } + } + + function getPairFees(DexInfo memory dexInfo, address pair) private view returns (uint16){ + return dexInfo.fees; + } + + function toUint16(uint256 y) internal pure returns (uint16 z) { + require((z = uint16(y)) == y); + } +} diff --git a/contracts/dex/eth/EthDexAggregatorV1.sol b/contracts/dex/eth/EthDexAggregatorV1.sol index 64457a2..c835c96 100644 --- a/contracts/dex/eth/EthDexAggregatorV1.sol +++ b/contracts/dex/eth/EthDexAggregatorV1.sol @@ -23,7 +23,7 @@ contract EthDexAggregatorV1 is DelegateInterface, Adminable, DexAggregatorInterf IUniswapV2Factory public uniV2Factory; address public openLev; - uint8 private constant priceDecimals = 18; + uint8 private constant priceDecimals = 24; mapping(uint8 => DexInfo) public dexInfo; diff --git a/contracts/dex/eth/UniV2Dex.sol b/contracts/dex/eth/UniV2Dex.sol index 92c91e3..516cb53 100644 --- a/contracts/dex/eth/UniV2Dex.sol +++ b/contracts/dex/eth/UniV2Dex.sol @@ -187,7 +187,12 @@ contract UniV2Dex { function calTPrice(uint currentPriceCumulativeLast, uint historyPriceCumulativeLast, uint32 timeElapsed, uint8 decimals) internal pure returns (uint){ - return ((currentPriceCumulativeLast.sub(historyPriceCumulativeLast).mul(10 ** decimals)) >> 112).div(timeElapsed); + uint256 diff = currentPriceCumulativeLast.sub(historyPriceCumulativeLast); + if (diff < (2 ** 170)) { + return ((diff.mul(10 ** decimals)) >> 112).div(timeElapsed); + } else { + return ((diff) >> 112).mul(10 ** decimals).div(timeElapsed); + } } function toUint32(uint256 y) internal pure returns (uint32 z) { diff --git a/contracts/dex/eth/UniV3Dex.sol b/contracts/dex/eth/UniV3Dex.sol index bce05d8..9588f57 100644 --- a/contracts/dex/eth/UniV3Dex.sol +++ b/contracts/dex/eth/UniV3Dex.sol @@ -81,7 +81,7 @@ contract UniV3Dex is IUniswapV3SwapCallback { require(buyAmount >= minBuyAmount, 'buy amount less than min'); } - function uniV3Buy(address buyToken, address sellToken, uint buyAmount, uint maxSellAmount, uint24 fee, bool checkPool) internal returns (uint amountIn){ + function uniV3Buy(address buyToken, address sellToken, uint buyAmount, uint maxSellAmount, uint24 fee, bool checkPool) internal returns (uint amountIn){ SwapCallbackData memory data = SwapCallbackData({tokenIn : sellToken, tokenOut : buyToken, fee : fee, payer : msg.sender}); bool zeroForOne = data.tokenIn < data.tokenOut; IUniswapV3Pool pool = getPool(data.tokenIn, data.tokenOut, fee); @@ -134,11 +134,16 @@ contract UniV3Dex is IUniswapV3SwapCallback { uint priceScale = 10 ** decimals; // maximum~2** uint token0Price; - // when sqrtPrice>1 retain 4 decimals + // when sqrtPrice>1 retain 6 decimals if (sqrtPriceX96 > (2 ** 96)) { - token0Price = (uint(sqrtPriceX96) >> (86)).mul((uint(sqrtPriceX96) >> (86))).mul(priceScale) >> (10 * 2); + token0Price = (uint(sqrtPriceX96) >> (78)).mul((uint(sqrtPriceX96) >> (78))).mul(priceScale) >> (18 * 2); } else { - token0Price = uint(sqrtPriceX96).mul(uint(sqrtPriceX96)).mul(priceScale) >> (96 * 2); + uint priceX192 = uint(sqrtPriceX96).mul(uint(sqrtPriceX96)); + if (priceX192 >= (2 ** 170)) { + token0Price = (priceX192 >> (96)).mul(priceScale) >> (96); + } else { + token0Price = priceX192.mul(priceScale) >> (96 * 2); + } } if (desToken < quoteToken) { return token0Price; @@ -197,7 +202,7 @@ contract UniV3Dex is IUniswapV3SwapCallback { uint24 fee ) internal view returns (IUniswapV3Pool) { if (address(uniV3Factory) == 0x1F98431c8aD98523631AE4a59f267346ea31F984) { - return IUniswapV3Pool(PoolAddress.computeAddress(address(uniV3Factory) , PoolAddress.getPoolKey(tokenA, tokenB, fee))); + return IUniswapV3Pool(PoolAddress.computeAddress(address(uniV3Factory), PoolAddress.getPoolKey(tokenA, tokenB, fee))); } else { return IUniswapV3Pool(uniV3Factory.getPool(tokenA, tokenB, fee)); } diff --git a/contracts/dex/kcc/KccDexAggregatorV1.sol b/contracts/dex/kcc/KccDexAggregatorV1.sol index cc4c58a..a867060 100755 --- a/contracts/dex/kcc/KccDexAggregatorV1.sol +++ b/contracts/dex/kcc/KccDexAggregatorV1.sol @@ -21,7 +21,7 @@ contract KccDexAggregatorV1 is DelegateInterface, Adminable, DexAggregatorInterf mapping(IUniswapV2Pair => V2PriceOracle) public uniV2PriceOracle; IUniswapV2Factory public mojitoFactory; address public openLev; - uint8 private constant priceDecimals = 18; + uint8 private constant priceDecimals = 24; mapping(uint8 => DexInfo) public dexInfo; diff --git a/contracts/dex/kcc/KccUniV2Dex.sol b/contracts/dex/kcc/KccUniV2Dex.sol index ecc8629..36eb9de 100755 --- a/contracts/dex/kcc/KccUniV2Dex.sol +++ b/contracts/dex/kcc/KccUniV2Dex.sol @@ -186,7 +186,12 @@ contract KccUniV2Dex { function calTPrice(uint currentPriceCumulativeLast, uint historyPriceCumulativeLast, uint32 timeElapsed, uint8 decimals) internal pure returns (uint){ - return ((currentPriceCumulativeLast.sub(historyPriceCumulativeLast).mul(10 ** decimals)) >> 112).div(timeElapsed); + uint256 diff = currentPriceCumulativeLast.sub(historyPriceCumulativeLast); + if (diff < (2 ** 170)) { + return ((diff.mul(10 ** decimals)) >> 112).div(timeElapsed); + } else { + return ((diff) >> 112).mul(10 ** decimals).div(timeElapsed); + } } function toUint32(uint256 y) internal pure returns (uint32 z) { diff --git a/contracts/lib/DexData.sol b/contracts/lib/DexData.sol index a16c45f..46bedec 100644 --- a/contracts/lib/DexData.sol +++ b/contracts/lib/DexData.sol @@ -34,6 +34,7 @@ library DexData { uint8 constant DEX_MOJITO = 13; uint8 constant DEX_KU = 14; uint8 constant DEX_BISWAP=15; + uint8 constant DEX_VVS=20; struct V3PoolData { address tokenA; diff --git a/contracts/liquidity/LPoolInterface.sol b/contracts/liquidity/LPoolInterface.sol index 47a87c0..1fc3c61 100644 --- a/contracts/liquidity/LPoolInterface.sol +++ b/contracts/liquidity/LPoolInterface.sol @@ -100,6 +100,11 @@ abstract contract LPoolStorage { mapping(address => BorrowSnapshot) internal accountBorrows; + /** + * Block timestamp that interest was last accrued at + */ + uint public accrualBlockTimestamp; + /*** Token Events ***/ diff --git a/contracts/liquidity/LTimePool.sol b/contracts/liquidity/LTimePool.sol new file mode 100644 index 0000000..68f1792 --- /dev/null +++ b/contracts/liquidity/LTimePool.sol @@ -0,0 +1,989 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.7.6; + + +import "./LPoolInterface.sol"; +import "./LPoolDepositor.sol"; +import "../lib/Exponential.sol"; +import "../Adminable.sol"; +import "../lib/CarefulMath.sol"; +import "../lib/TransferHelper.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; + +import "../DelegateInterface.sol"; +import "../ControllerInterface.sol"; +import "../IWETH.sol"; + +/// @title OpenLeverage's LToken Contract +/// @dev Abstract base for LTokens +/// @author OpenLeverage +contract LTimePool is DelegateInterface, Adminable, LPoolInterface, Exponential, ReentrancyGuard { + using TransferHelper for IERC20; + using SafeMath for uint; + + constructor() { + + } + + /// @notice Initialize the money market + /// @param controller_ The address of the Controller + /// @param baseRatePerBlock_ The base interest rate which is the y-intercept when utilization rate is 0 + /// @param multiplierPerBlock_ The multiplier of utilization rate that gives the slope of the interest rate + /// @param jumpMultiplierPerBlock_ The multiplierPerBlock after hitting a specified utilization point + /// @param kink_ The utilization point at which the jump multiplier is applied + /// @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18 + /// @param name_ EIP-20 name of this token + /// @param symbol_ EIP-20 symbol of this token + /// @param decimals_ EIP-20 decimal precision of this token + function initialize( + address underlying_, + bool isWethPool_, + address controller_, + uint256 baseRatePerBlock_, + uint256 multiplierPerBlock_, + uint256 jumpMultiplierPerBlock_, + uint256 kink_, + uint initialExchangeRateMantissa_, + string memory name_, + string memory symbol_, + uint8 decimals_) public { + require(underlying_ != address(0), "underlying_ address cannot be 0"); + require(controller_ != address(0), "controller_ address cannot be 0"); + require(msg.sender == admin, "Only allow to be called by admin"); + require(accrualBlockTimestamp == 0 && borrowIndex == 0, "inited once"); + + // Set initial exchange rate + initialExchangeRateMantissa = initialExchangeRateMantissa_; + require(initialExchangeRateMantissa > 0, "Initial Exchange Rate Mantissa should be greater zero"); + //set controller + controller = controller_; + isWethPool = isWethPool_; + //set interestRateModel + baseRatePerBlock = baseRatePerBlock_; + multiplierPerBlock = multiplierPerBlock_; + jumpMultiplierPerBlock = jumpMultiplierPerBlock_; + kink = kink_; + + // Initialize block timestamp and borrow index (block timestamp mocks depend on controller being set) + accrualBlockTimestamp = getBlockTimestamp(); + borrowIndex = 1e25; + //80% + borrowCapFactorMantissa = 0.8e18; + //10% + reserveFactorMantissa = 0.1e18; + + + name = name_; + symbol = symbol_; + decimals = decimals_; + + _notEntered = true; + + // Set underlying and sanity check it + underlying = underlying_; + IERC20(underlying).totalSupply(); + emit Transfer(address(0), msg.sender, 0); + } + + /// @notice Transfer `tokens` tokens from `src` to `dst` by `spender` + /// @dev Called by both `transfer` and `transferFrom` internally + /// @param spender The address of the account performing the transfer + /// @param src The address of the source account + /// @param dst The address of the destination account + /// @param tokens The number of tokens to transfer + /// @return Whether or not the transfer succeeded + function transferTokens(address spender, address src, address dst, uint tokens) internal returns (bool) { + require(dst != address(0), "dst address cannot be 0"); + /* Do not allow self-transfers */ + require(src != dst, "src = dst"); + + (ControllerInterface(controller)).transferAllowed(src, dst, tokens); + + /* Get the allowance, infinite for the account owner */ + uint startingAllowance = 0; + if (spender == src) { + startingAllowance = uint(- 1); + } else { + startingAllowance = transferAllowances[src][spender]; + } + + /* Do the calculations, checking for {under,over}flow */ + MathError mathErr; + uint allowanceNew; + uint srcTokensNew; + uint dstTokensNew; + + (mathErr, allowanceNew) = subUInt(startingAllowance, tokens); + require(mathErr == MathError.NO_ERROR, 'not allowed'); + + (mathErr, srcTokensNew) = subUInt(accountTokens[src], tokens); + require(mathErr == MathError.NO_ERROR, 'not enough'); + + (mathErr, dstTokensNew) = addUInt(accountTokens[dst], tokens); + require(mathErr == MathError.NO_ERROR, 'too much'); + + accountTokens[src] = srcTokensNew; + accountTokens[dst] = dstTokensNew; + + /* Eat some of the allowance (if necessary) */ + if (startingAllowance != uint(- 1)) { + transferAllowances[src][spender] = allowanceNew; + } + /* We emit a Transfer event */ + emit Transfer(src, dst, tokens); + return true; + } + + /// @notice Transfer `amount` tokens from `msg.sender` to `dst` + /// @param dst The address of the destination account + /// @param amount The number of tokens to transfer + /// @return Whether or not the transfer succeeded + function transfer(address dst, uint256 amount) external override nonReentrant returns (bool) { + return transferTokens(msg.sender, msg.sender, dst, amount); + } + + /// @notice Transfer `amount` tokens from `src` to `dst` + /// @param src The address of the source account + /// @param dst The address of the destination account + /// @param amount The number of tokens to transfer + /// @return Whether or not the transfer succeeded + function transferFrom(address src, address dst, uint256 amount) external override nonReentrant returns (bool) { + return transferTokens(msg.sender, src, dst, amount); + } + + /// @notice Approve `spender` to transfer up to `amount` from `src` + /// @dev This will overwrite the approval amount for `spender` + /// and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) + /// @param spender The address of the account which may transfer tokens + /// @param amount The number of tokens that are approved (-1 means infinite) + /// @return Whether or not the approval succeeded + function approve(address spender, uint256 amount) external override returns (bool) { + address src = msg.sender; + transferAllowances[src][spender] = amount; + emit Approval(src, spender, amount); + return true; + } + + /// @notice Get the current allowance from `owner` for `spender` + /// @param owner The address of the account which owns the tokens to be spent + /// @param spender The address of the account which may transfer tokens + /// @return The number of tokens allowed to be spent (-1 means infinite) + function allowance(address owner, address spender) external override view returns (uint256) { + return transferAllowances[owner][spender]; + } + + /// @notice Get the token balance of the `owner` + /// @param owner The address of the account to query + /// @return The number of tokens owned by `owner` + function balanceOf(address owner) external override view returns (uint256) { + return accountTokens[owner]; + } + + /// @notice Get the underlying balance of the `owner` + /// @dev This also accrues interest in a transaction + /// @param owner The address of the account to query + /// @return The amount of underlying owned by `owner` + function balanceOfUnderlying(address owner) external override returns (uint) { + Exp memory exchangeRate = Exp({mantissa : exchangeRateCurrent()}); + (MathError mErr, uint balance) = mulScalarTruncate(exchangeRate, accountTokens[owner]); + require(mErr == MathError.NO_ERROR, "calc failed"); + return balance; + } + + /*** User Interface ***/ + + /// @notice Sender supplies assets into the market and receives lTokens in exchange + /// @dev Accrues interest whether or not the operation succeeds, unless reverted + /// @param mintAmount The amount of the underlying asset to supply + function mint(uint mintAmount) external override nonReentrant { + accrueInterest(); + mintFresh(msg.sender, mintAmount, false); + } + + function mintTo(address to, uint amount) external payable override nonReentrant { + accrueInterest(); + if (isWethPool) { + mintFresh(to, msg.value, false); + } else { + mintFresh(to, amount, true); + } + } + + function mintEth() external payable override nonReentrant { + require(isWethPool, "not eth pool"); + accrueInterest(); + mintFresh(msg.sender, msg.value, false); + } + + /// @notice Sender redeems lTokens in exchange for the underlying asset + /// @dev Accrues interest whether or not the operation succeeds, unless reverted + /// @param redeemTokens The number of lTokens to redeem into underlying + function redeem(uint redeemTokens) external override nonReentrant { + accrueInterest(); + // redeemFresh emits redeem-specific logs on errors, so we don't need to + redeemFresh(msg.sender, redeemTokens, 0); + } + + /// @notice Sender redeems lTokens in exchange for a specified amount of underlying asset + /// @dev Accrues interest whether or not the operation succeeds, unless reverted + /// @param redeemAmount The amount of underlying to redeem + function redeemUnderlying(uint redeemAmount) external override nonReentrant { + accrueInterest(); + // redeemFresh emits redeem-specific logs on errors, so we don't need to + redeemFresh(msg.sender, 0, redeemAmount); + } + + function borrowBehalf(address borrower, uint borrowAmount) external override nonReentrant { + accrueInterest(); + // borrowFresh emits borrow-specific logs on errors, so we don't need to + borrowFresh(payable(borrower), msg.sender, borrowAmount); + } + + /// @notice Sender repays a borrow belonging to borrower + /// @param borrower the account with the debt being payed off + /// @param repayAmount The amount to repay + function repayBorrowBehalf(address borrower, uint repayAmount) external override nonReentrant { + accrueInterest(); + // repayBorrowFresh emits repay-borrow-specific logs on errors, so we don't need to + repayBorrowFresh(msg.sender, borrower, repayAmount, false); + } + + function repayBorrowEndByOpenLev(address borrower, uint repayAmount) external override nonReentrant { + accrueInterest(); + repayBorrowFresh(msg.sender, borrower, repayAmount, true); + } + + + /*** Safe Token ***/ + + /// Gets balance of this contract in terms of the underlying + /// @dev This excludes the value of the current message, if any + /// @return The quantity of underlying tokens owned by this contract + function getCashPrior() internal view returns (uint) { + return IERC20(underlying).balanceOf(address(this)); + } + + + /** + * @dev Similar to EIP20 transfer, except it handles a False result from `transferFrom` and reverts in that case. + * This will revert due to insufficient balance or insufficient allowance. + * This function returns the actual amount received, + * which may be less than `amount` if there is a fee attached to the transfer. + * + * Note: This wrapper safely handles non-standard ERC-20 tokens that do not return a value. + * See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca + */ + function doTransferIn(address from, uint amount, bool convertWeth) internal returns (uint actualAmount) { + if (isWethPool && convertWeth) { + actualAmount = msg.value; + IWETH(underlying).deposit{value : actualAmount}(); + } else { + actualAmount = IERC20(underlying).safeTransferFrom(from, address(this), amount); + } + } + + /** + * @dev Similar to EIP20 transfer, except it handles a False success from `transfer` and returns an explanatory + * error code rather than reverting. If caller has not called checked protocol's balance, this may revert due to + * insufficient cash held in this contract. If caller has checked protocol's balance prior to this call, and verified + * it is >= amount, this should not revert in normal conditions. + * + * Note: This wrapper safely handles non-standard ERC-20 tokens that do not return a value. + * See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca + */ + function doTransferOut(address payable to, uint amount, bool convertWeth) internal { + if (isWethPool && convertWeth) { + IWETH(underlying).withdraw(amount); + (bool success, ) = to.call{value: amount}(""); + require(success); + } else { + IERC20(underlying).safeTransfer(to, amount); + } + } + + function availableForBorrow() external view override returns (uint){ + uint cash = getCashPrior(); + (MathError err0, uint sum) = addThenSubUInt(cash, totalBorrows, totalReserves); + if (err0 != MathError.NO_ERROR) { + return 0; + } + (MathError err1, uint maxAvailable) = mulScalarTruncate(Exp({mantissa : sum}), borrowCapFactorMantissa); + if (err1 != MathError.NO_ERROR) { + return 0; + } + if (totalBorrows > maxAvailable) { + return 0; + } + return maxAvailable - totalBorrows; + } + + + /// @notice Get a snapshot of the account's balances, and the cached exchange rate + /// @dev This is used by controller to more efficiently perform liquidity checks. + /// @param account Address of the account to snapshot + /// @return ( token balance, borrow balance, exchange rate mantissa) + function getAccountSnapshot(address account) external override view returns (uint, uint, uint) { + uint cTokenBalance = accountTokens[account]; + uint borrowBalance; + uint exchangeRateMantissa; + + MathError mErr; + + (mErr, borrowBalance) = borrowBalanceStoredInternal(account); + if (mErr != MathError.NO_ERROR) { + return (0, 0, 0); + } + + (mErr, exchangeRateMantissa) = exchangeRateStoredInternal(); + if (mErr != MathError.NO_ERROR) { + return (0, 0, 0); + } + + return (cTokenBalance, borrowBalance, exchangeRateMantissa); + } + + /// @dev Function to simply retrieve block timestamp + /// This exists mainly for inheriting test contracts to stub this result. + function getBlockTimestamp() internal view returns (uint) { + return block.timestamp; + } + + /// @notice Returns the current per-block borrow interest rate for this cToken + /// @return The borrow interest rate per block, scaled by 1e18 + function borrowRatePerBlock() external override view returns (uint) { + return getBorrowRateInternal(getCashPrior(), totalBorrows, totalReserves); + } + + + /// @notice Returns the current per-block supply interest rate for this cToken + /// @return The supply interest rate per block, scaled by 1e18 + function supplyRatePerBlock() external override view returns (uint) { + return getSupplyRateInternal(getCashPrior(), totalBorrows, totalReserves, reserveFactorMantissa); + } + + function utilizationRate(uint cash, uint borrows, uint reserves) internal pure returns (uint) { + // Utilization rate is 0 when there are no borrows + if (borrows == 0) { + return 0; + } + return borrows.mul(1e18).div(cash.add(borrows).sub(reserves)); + } + + /// @notice Calculates the current borrow rate per block, with the error code expected by the market + /// @param cash The amount of cash in the market + /// @param borrows The amount of borrows in the market + /// @return The borrow rate percentage per block as a mantissa (scaled by 1e18) + function getBorrowRateInternal(uint cash, uint borrows, uint reserves) internal view returns (uint) { + uint util = utilizationRate(cash, borrows, reserves); + if (util <= kink) { + return util.mul(multiplierPerBlock).div(1e18).add(baseRatePerBlock); + } else { + uint normalRate = kink.mul(multiplierPerBlock).div(1e18).add(baseRatePerBlock); + uint excessUtil = util.sub(kink); + return excessUtil.mul(jumpMultiplierPerBlock).div(1e18).add(normalRate); + } + } + + /// @notice Calculates the current supply rate per block + /// @param cash The amount of cash in the market + /// @param borrows The amount of borrows in the market + /// @return The supply rate percentage per block as a mantissa (scaled by 1e18) + function getSupplyRateInternal(uint cash, uint borrows, uint reserves, uint reserveFactor) internal view returns (uint) { + uint oneMinusReserveFactor = uint(1e18).sub(reserveFactor); + uint borrowRate = getBorrowRateInternal(cash, borrows, reserves); + uint rateToPool = borrowRate.mul(oneMinusReserveFactor).div(1e18); + return utilizationRate(cash, borrows, reserves).mul(rateToPool).div(1e18); + } + + /// @notice Returns the current total borrows plus accrued interest + /// @return The total borrows with interest + function totalBorrowsCurrent() external override view returns (uint) { + /* Remember the initial block timestamp */ + uint currentBlockTimestamp = getBlockTimestamp(); + uint accrualBlockTimestampPrior = accrualBlockTimestamp; + + /* Short-circuit accumulating 0 interest */ + if (accrualBlockTimestampPrior == currentBlockTimestamp) { + return totalBorrows; + } + + /* Read the previous values out of storage */ + uint cashPrior = getCashPrior(); + uint borrowsPrior = totalBorrows; + uint reservesPrior = totalReserves; + + /* Calculate the current borrow interest rate */ + uint borrowRateMantissa = getBorrowRateInternal(cashPrior, borrowsPrior, reservesPrior); + require(borrowRateMantissa <= borrowRateMaxMantissa, "borrower rate higher"); + + /* Calculate the number of timestamp elapsed since the last accrual */ + (MathError mathErr, uint blockDelta) = subUInt(currentBlockTimestamp, accrualBlockTimestampPrior); + require(mathErr == MathError.NO_ERROR, "calc block delta erro"); + + Exp memory simpleInterestFactor; + uint interestAccumulated; + uint totalBorrowsNew; + + (mathErr, simpleInterestFactor) = mulScalar(Exp({mantissa : borrowRateMantissa}), blockDelta); + require(mathErr == MathError.NO_ERROR, 'calc interest factor error'); + + (mathErr, interestAccumulated) = mulScalarTruncate(simpleInterestFactor, borrowsPrior); + require(mathErr == MathError.NO_ERROR, 'calc interest acc error'); + + (mathErr, totalBorrowsNew) = addUInt(interestAccumulated, borrowsPrior); + require(mathErr == MathError.NO_ERROR, 'calc total borrows error'); + + return totalBorrowsNew; + } + + /// @notice Accrue interest to updated borrowIndex and then calculate account's borrow balance using the updated borrowIndex + /// @param account The address whose balance should be calculated after updating borrowIndex + /// @return The calculated balance + function borrowBalanceCurrent(address account) external view override returns (uint) { + (MathError err0, uint borrowIndex) = calCurrentBorrowIndex(); + require(err0 == MathError.NO_ERROR, "calc borrow index fail"); + (MathError err1, uint result) = borrowBalanceStoredInternalWithBorrowerIndex(account, borrowIndex); + require(err1 == MathError.NO_ERROR, "calc fail"); + return result; + } + + function borrowBalanceStored(address account) external override view returns (uint){ + return accountBorrows[account].principal; + } + + + /// @notice Return the borrow balance of account based on stored data + /// @param account The address whose balance should be calculated + /// @return (error code, the calculated balance or 0 if error code is non-zero) + function borrowBalanceStoredInternal(address account) internal view returns (MathError, uint) { + return borrowBalanceStoredInternalWithBorrowerIndex(account, borrowIndex); + } + + /// @notice Return the borrow balance of account based on stored data + /// @param account The address whose balance should be calculated + /// @return (error code, the calculated balance or 0 if error code is non-zero) + function borrowBalanceStoredInternalWithBorrowerIndex(address account, uint borrowIndex) internal view returns (MathError, uint) { + /* Note: we do not assert that the market is up to date */ + MathError mathErr; + uint principalTimesIndex; + uint result; + + /* Get borrowBalance and borrowIndex */ + BorrowSnapshot storage borrowSnapshot = accountBorrows[account]; + + /* If borrowBalance = 0 then borrowIndex is likely also 0. + * Rather than failing the calculation with a division by 0, we immediately return 0 in this case. + */ + if (borrowSnapshot.principal == 0) { + return (MathError.NO_ERROR, 0); + } + + /* Calculate new borrow balance using the interest index: + * recentBorrowBalance = borrower.borrowBalance * market.borrowIndex / borrower.borrowIndex + */ + (mathErr, principalTimesIndex) = mulUInt(borrowSnapshot.principal, borrowIndex); + if (mathErr != MathError.NO_ERROR) { + return (mathErr, 0); + } + + (mathErr, result) = divUInt(principalTimesIndex, borrowSnapshot.interestIndex); + if (mathErr != MathError.NO_ERROR) { + return (mathErr, 0); + } + + return (MathError.NO_ERROR, result); + } + + /// @notice Accrue interest then return the up-to-date exchange rate + /// @return Calculated exchange rate scaled by 1e18 + function exchangeRateCurrent() public override nonReentrant returns (uint) { + accrueInterest(); + return exchangeRateStored(); + } + + /// Calculates the exchange rate from the underlying to the LToken + /// @dev This function does not accrue interest before calculating the exchange rate + /// @return Calculated exchange rate scaled by 1e18 + function exchangeRateStored() public override view returns (uint) { + (MathError err, uint result) = exchangeRateStoredInternal(); + require(err == MathError.NO_ERROR, "calc fail"); + return result; + } + + /// @notice Calculates the exchange rate from the underlying to the LToken + /// @dev This function does not accrue interest before calculating the exchange rate + /// @return (error code, calculated exchange rate scaled by 1e18) + function exchangeRateStoredInternal() internal view returns (MathError, uint) { + uint _totalSupply = totalSupply; + if (_totalSupply == 0) { + /* + * If there are no tokens minted: + * exchangeRate = initialExchangeRate + */ + return (MathError.NO_ERROR, initialExchangeRateMantissa); + } else { + /* + * Otherwise: + * exchangeRate = (totalCash + totalBorrows - totalReserves) / totalSupply + */ + uint _totalCash = getCashPrior(); + uint cashPlusBorrowsMinusReserves; + Exp memory exchangeRate; + MathError mathErr; + + (mathErr, cashPlusBorrowsMinusReserves) = addThenSubUInt(_totalCash, totalBorrows, totalReserves); + if (mathErr != MathError.NO_ERROR) { + return (mathErr, 0); + } + + (mathErr, exchangeRate) = getExp(cashPlusBorrowsMinusReserves, _totalSupply); + if (mathErr != MathError.NO_ERROR) { + return (mathErr, 0); + } + + return (MathError.NO_ERROR, exchangeRate.mantissa); + } + } + + /// @notice Get cash balance of this cToken in the underlying asset + /// @return The quantity of underlying asset owned by this contract + function getCash() external override view returns (uint) { + return IERC20(underlying).balanceOf(address(this)); + } + + function calCurrentBorrowIndex() internal view returns (MathError, uint) { + /* Remember the initial timestamp number */ + uint currentBlockTimestamp = getBlockTimestamp(); + uint accrualBlockTimestampPrior = accrualBlockTimestamp; + uint borrowIndexNew; + /* Short-circuit accumulating 0 interest */ + if (accrualBlockTimestampPrior == currentBlockTimestamp) { + return (MathError.NO_ERROR, borrowIndex); + } + uint borrowRateMantissa = getBorrowRateInternal(getCashPrior(), totalBorrows, totalReserves); + (MathError mathErr, uint blockDelta) = subUInt(currentBlockTimestamp, accrualBlockTimestampPrior); + + Exp memory simpleInterestFactor; + (mathErr, simpleInterestFactor) = mulScalar(Exp({mantissa : borrowRateMantissa}), blockDelta); + if (mathErr != MathError.NO_ERROR) { + return (mathErr, 0); + } + (mathErr, borrowIndexNew) = mulScalarTruncateAddUInt(simpleInterestFactor, borrowIndex, borrowIndex); + return (mathErr, borrowIndexNew); + } + + /// @notice Applies accrued interest to total borrows and reserves + /// @dev This calculates interest accrued from the last checkpointed block + /// up to the current block and writes new checkpoint to storage. + function accrueInterest() public override { + /* Remember the initial timestamp number */ + uint currentBlockTimestamp = getBlockTimestamp(); + uint accrualBlockTimestampPrior = accrualBlockTimestamp; + + /* Short-circuit accumulating 0 interest */ + if (accrualBlockTimestampPrior == currentBlockTimestamp) { + return; + } + + /* Read the previous values out of storage */ + uint cashPrior = getCashPrior(); + uint borrowsPrior = totalBorrows; + uint borrowIndexPrior = borrowIndex; + uint reservesPrior = totalReserves; + + /* Calculate the current borrow interest rate */ + uint borrowRateMantissa = getBorrowRateInternal(cashPrior, borrowsPrior, reservesPrior); + require(borrowRateMantissa <= borrowRateMaxMantissa, "borrower rate higher"); + + /* Calculate the number of timestamp elapsed since the last accrual */ + (MathError mathErr, uint blockDelta) = subUInt(currentBlockTimestamp, accrualBlockTimestampPrior); + require(mathErr == MathError.NO_ERROR, "calc block delta erro"); + + + /* + * Calculate the interest accumulated into borrows and reserves and the new index: + * simpleInterestFactor = borrowRate * blockDelta + * interestAccumulated = simpleInterestFactor * totalBorrows + * totalBorrowsNew = interestAccumulated + totalBorrows + * borrowIndexNew = simpleInterestFactor * borrowIndex + borrowIndex + */ + + Exp memory simpleInterestFactor; + uint interestAccumulated; + uint totalBorrowsNew; + uint borrowIndexNew; + uint totalReservesNew; + + (mathErr, simpleInterestFactor) = mulScalar(Exp({mantissa : borrowRateMantissa}), blockDelta); + require(mathErr == MathError.NO_ERROR, 'calc interest factor error'); + + (mathErr, interestAccumulated) = mulScalarTruncate(simpleInterestFactor, borrowsPrior); + require(mathErr == MathError.NO_ERROR, 'calc interest acc error'); + + (mathErr, totalBorrowsNew) = addUInt(interestAccumulated, borrowsPrior); + require(mathErr == MathError.NO_ERROR, 'calc total borrows error'); + + (mathErr, totalReservesNew) = mulScalarTruncateAddUInt(Exp({mantissa : reserveFactorMantissa}), interestAccumulated, reservesPrior); + require(mathErr == MathError.NO_ERROR, 'calc total reserves error'); + + (mathErr, borrowIndexNew) = mulScalarTruncateAddUInt(simpleInterestFactor, borrowIndexPrior, borrowIndexPrior); + require(mathErr == MathError.NO_ERROR, 'calc borrows index error'); + + + /* We write the previously calculated values into storage */ + accrualBlockTimestamp = currentBlockTimestamp; + borrowIndex = borrowIndexNew; + totalBorrows = totalBorrowsNew; + totalReserves = totalReservesNew; + + /* We emit an AccrueInterest event */ + emit AccrueInterest(cashPrior, interestAccumulated, borrowIndexNew, totalBorrowsNew); + + } + + struct MintLocalVars { + MathError mathErr; + uint exchangeRateMantissa; + uint mintTokens; + uint totalSupplyNew; + uint accountTokensNew; + uint actualMintAmount; + } + + /// @notice User supplies assets into the market and receives lTokens in exchange + /// @dev Assumes interest has already been accrued up to the current block + /// @param minter The address of the account which is supplying the assets + /// @param mintAmount The amount of the underlying asset to supply + /// @return uint the actual mint amount. + function mintFresh(address minter, uint mintAmount, bool isDelegete) internal sameTimestamp returns (uint) { + MintLocalVars memory vars; + (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal(); + require(vars.mathErr == MathError.NO_ERROR, 'calc exchangerate error'); + + /* + * We call `doTransferIn` for the minter and the mintAmount. + * Note: The cToken must handle variations between ERC-20 and ETH underlying. + * `doTransferIn` reverts if anything goes wrong, since we can't be sure if + * side-effects occurred. The function returns the amount actually transferred, + * in case of a fee. On success, the cToken holds an additional `actualMintAmount` + * of cash. + */ + if (isDelegete) { + uint balanceBefore = getCashPrior(); + LPoolDepositor(msg.sender).transferToPool(minter, mintAmount); + uint balanceAfter = getCashPrior(); + require(balanceAfter > balanceBefore, 'mint 0'); + vars.actualMintAmount = balanceAfter - balanceBefore; + } else { + vars.actualMintAmount = doTransferIn(minter, mintAmount, true); + } + /* + * We get the current exchange rate and calculate the number of lTokens to be minted: + * mintTokens = actualMintAmount / exchangeRate + */ + + (vars.mathErr, vars.mintTokens) = divScalarByExpTruncate(vars.actualMintAmount, Exp({mantissa : vars.exchangeRateMantissa})); + require(vars.mathErr == MathError.NO_ERROR, "calc mint token error"); + + (ControllerInterface(controller)).mintAllowed(minter, vars.mintTokens); + /* + * We calculate the new total supply of lTokens and minter token balance, checking for overflow: + * totalSupplyNew = totalSupply + mintTokens + * accountTokensNew = accountTokens[minter] + mintTokens + */ + (vars.mathErr, vars.totalSupplyNew) = addUInt(totalSupply, vars.mintTokens); + require(vars.mathErr == MathError.NO_ERROR, "calc supply new failed"); + + (vars.mathErr, vars.accountTokensNew) = addUInt(accountTokens[minter], vars.mintTokens); + require(vars.mathErr == MathError.NO_ERROR, "calc tokens new ailed"); + + /* We write previously calculated values into storage */ + totalSupply = vars.totalSupplyNew; + accountTokens[minter] = vars.accountTokensNew; + + /* We emit a Mint event, and a Transfer event */ + emit Mint(minter, vars.actualMintAmount, vars.mintTokens); + emit Transfer(address(this), minter, vars.mintTokens); + + /* We call the defense hook */ + + return vars.actualMintAmount; + } + + + struct RedeemLocalVars { + MathError mathErr; + uint exchangeRateMantissa; + uint redeemTokens; + uint redeemAmount; + uint totalSupplyNew; + uint accountTokensNew; + } + + /// @notice User redeems lTokens in exchange for the underlying asset + /// @dev Assumes interest has already been accrued up to the current block + /// @param redeemer The address of the account which is redeeming the tokens + /// @param redeemTokensIn The number of lTokens to redeem into underlying (only one of redeemTokensIn or redeemAmountIn may be non-zero) + /// @param redeemAmountIn The number of underlying tokens to receive from redeeming lTokens (only one of redeemTokensIn or redeemAmountIn may be non-zero) + function redeemFresh(address payable redeemer, uint redeemTokensIn, uint redeemAmountIn) internal sameTimestamp { + require(redeemTokensIn == 0 || redeemAmountIn == 0, "one be zero"); + + RedeemLocalVars memory vars; + + /* exchangeRate = invoke Exchange Rate Stored() */ + (vars.mathErr, vars.exchangeRateMantissa) = exchangeRateStoredInternal(); + require(vars.mathErr == MathError.NO_ERROR, 'calc exchangerate error'); + + /* If redeemTokensIn > 0: */ + if (redeemTokensIn > 0) { + /* + * We calculate the exchange rate and the amount of underlying to be redeemed: + * redeemTokens = redeemTokensIn + * redeemAmount = redeemTokensIn x exchangeRateCurrent + */ + vars.redeemTokens = redeemTokensIn; + + (vars.mathErr, vars.redeemAmount) = mulScalarTruncate(Exp({mantissa : vars.exchangeRateMantissa}), redeemTokensIn); + require(vars.mathErr == MathError.NO_ERROR, 'calc redeem amount error'); + } else { + /* + * We get the current exchange rate and calculate the amount to be redeemed: + * redeemTokens = redeemAmountIn / exchangeRate + * redeemAmount = redeemAmountIn + */ + + (vars.mathErr, vars.redeemTokens) = divScalarByExpTruncate(redeemAmountIn, Exp({mantissa : vars.exchangeRateMantissa})); + require(vars.mathErr == MathError.NO_ERROR, 'calc redeem tokens error'); + vars.redeemAmount = redeemAmountIn; + } + + (ControllerInterface(controller)).redeemAllowed(redeemer, vars.redeemTokens); + + /* + * We calculate the new total supply and redeemer balance, checking for underflow: + * totalSupplyNew = totalSupply - redeemTokens + * accountTokensNew = accountTokens[redeemer] - redeemTokens + */ + (vars.mathErr, vars.totalSupplyNew) = subUInt(totalSupply, vars.redeemTokens); + require(vars.mathErr == MathError.NO_ERROR, 'calc supply new error'); + + (vars.mathErr, vars.accountTokensNew) = subUInt(accountTokens[redeemer], vars.redeemTokens); + require(vars.mathErr == MathError.NO_ERROR, 'calc token new error'); + require(getCashPrior() >= vars.redeemAmount, 'cash < redeem'); + + /* We write previously calculated values into storage */ + totalSupply = vars.totalSupplyNew; + accountTokens[redeemer] = vars.accountTokensNew; + /* + * We invoke doTransferOut for the redeemer and the redeemAmount. + * Note: The cToken must handle variations between ERC-20 and ETH underlying. + * On success, the cToken has redeemAmount less of cash. + * doTransferOut reverts if anything goes wrong, since we can't be sure if side effects occurred. + */ + doTransferOut(redeemer, vars.redeemAmount, true); + + + /* We emit a Transfer event, and a Redeem event */ + emit Transfer(redeemer, address(this), vars.redeemTokens); + emit Redeem(redeemer, vars.redeemAmount, vars.redeemTokens); + + /* We call the defense hook */ + } + + struct BorrowLocalVars { + MathError mathErr; + uint accountBorrows; + uint accountBorrowsNew; + uint totalBorrowsNew; + } + + /// @notice Users borrow assets from the protocol to their own address + /// @param borrowAmount The amount of the underlying asset to borrow + function borrowFresh(address payable borrower, address payable payee, uint borrowAmount) internal sameTimestamp { + (ControllerInterface(controller)).borrowAllowed(borrower, payee, borrowAmount); + + /* Fail gracefully if protocol has insufficient underlying cash */ + require(getCashPrior() >= borrowAmount, 'cash vars.actualRepayAmount) { + vars.badDebtsAmount = vars.accountBorrows - vars.actualRepayAmount; + } + + /* + * We calculate the new borrower and total borrow balances, failing on underflow: + * accountBorrowsNew = accountBorrows - repayAmount + * totalBorrowsNew = totalBorrows - repayAmount + */ + if (vars.accountBorrows < vars.actualRepayAmount) { + require(vars.actualRepayAmount.mul(1e18).div(vars.accountBorrows) <= 105e16, 'repay more than 5%'); + vars.accountBorrowsNew = 0; + } else { + if (isEnd) { + vars.accountBorrowsNew = 0; + } else { + vars.accountBorrowsNew = vars.accountBorrows - vars.actualRepayAmount; + } + } + //Avoid mantissa errors + if (vars.actualRepayAmount > totalBorrows) { + vars.totalBorrowsNew = 0; + } else { + if (isEnd) { + vars.totalBorrowsNew = totalBorrows.sub(vars.accountBorrows); + } else { + vars.totalBorrowsNew = totalBorrows - vars.actualRepayAmount; + } + } + + /* We write the previously calculated values into storage */ + accountBorrows[borrower].principal = vars.accountBorrowsNew; + accountBorrows[borrower].interestIndex = borrowIndex; + totalBorrows = vars.totalBorrowsNew; + + /* We emit a RepayBorrow event */ + emit RepayBorrow(payer, borrower, vars.actualRepayAmount, vars.badDebtsAmount, vars.accountBorrowsNew, vars.totalBorrowsNew); + + /* We call the defense hook */ + + return vars.actualRepayAmount; + } + + /*** Admin Functions ***/ + + /// @notice Sets a new CONTROLLER for the market + /// @dev Admin function to set a new controller + function setController(address newController) external override onlyAdmin { + require(address(0) != newController, "0x"); + address oldController = controller; + controller = newController; + // Emit NewController(oldController, newController) + emit NewController(oldController, newController); + } + + function setBorrowCapFactorMantissa(uint newBorrowCapFactorMantissa) external override onlyAdmin { + require(newBorrowCapFactorMantissa <= 1e18, 'Factor too large'); + uint oldBorrowCapFactorMantissa = borrowCapFactorMantissa; + borrowCapFactorMantissa = newBorrowCapFactorMantissa; + emit NewBorrowCapFactorMantissa(oldBorrowCapFactorMantissa, borrowCapFactorMantissa); + } + + function setInterestParams(uint baseRatePerBlock_, uint multiplierPerBlock_, uint jumpMultiplierPerBlock_, uint kink_) external override onlyAdmin { + //accrueInterest except first + if (baseRatePerBlock != 0) { + accrueInterest(); + } + // total rate perYear < 2000% + require(baseRatePerBlock_ < 1e13, 'Base rate too large'); + baseRatePerBlock = baseRatePerBlock_; + require(multiplierPerBlock_ < 1e13, 'Mul rate too large'); + multiplierPerBlock = multiplierPerBlock_; + require(jumpMultiplierPerBlock_ < 1e13, 'Jump rate too large'); + jumpMultiplierPerBlock = jumpMultiplierPerBlock_; + require(kink_ <= 1e18, 'Kline too large'); + kink = kink_; + emit NewInterestParam(baseRatePerBlock_, multiplierPerBlock_, jumpMultiplierPerBlock_, kink_); + } + + function setReserveFactor(uint newReserveFactorMantissa) external override onlyAdmin { + require(newReserveFactorMantissa <= 1e18, 'Factor too large'); + accrueInterest(); + uint oldReserveFactorMantissa = reserveFactorMantissa; + reserveFactorMantissa = newReserveFactorMantissa; + emit NewReserveFactor(oldReserveFactorMantissa, newReserveFactorMantissa); + } + + function addReserves(uint addAmount) external payable override nonReentrant { + accrueInterest(); + uint totalReservesNew; + uint actualAddAmount = doTransferIn(msg.sender, addAmount, true); + totalReservesNew = totalReserves.add(actualAddAmount); + totalReserves = totalReservesNew; + emit ReservesAdded(msg.sender, actualAddAmount, totalReservesNew); + } + + function reduceReserves(address payable to, uint reduceAmount) external override nonReentrant onlyAdmin { + accrueInterest(); + uint totalReservesNew; + totalReservesNew = totalReserves.sub(reduceAmount); + totalReserves = totalReservesNew; + doTransferOut(to, reduceAmount, true); + emit ReservesReduced(to, reduceAmount, totalReservesNew); + } + + modifier sameTimestamp() { + require(accrualBlockTimestamp == getBlockTimestamp(), 'not same timestamp'); + _; + } +} + diff --git a/migrations/10_initialize.js b/migrations/10_initialize.js index f912b7e..546b0cb 100755 --- a/migrations/10_initialize.js +++ b/migrations/10_initialize.js @@ -97,6 +97,11 @@ async function initializeLenderPool(accounts, network) { m.log("waiting controller create KSC - KUS market ......"); await intializeMarket(accounts, network, '0x4446fc4eb47f2f6586f9faab68b3498f86c07521', '0x4a81704d8c16d9fb0d7f61b747d0b5a272badf14', 3000, '0x0e00000002'); break; + case utils.cronosTest: + case utils.cronosMainnet: + m.log("waiting controller create wcro - usdc market ......"); + await intializeMarket(accounts, network, '0x5C7F8A570d578ED84E63fdFA7b1eE72dEae1AE23', '0xc21223249CA28397B4B6541dfFaEcC539BfF0c59', 3000, '0x1400000002'); + break; } } diff --git a/migrations/2_deploy_basic.js b/migrations/2_deploy_basic.js index ae7de94..acb3a32 100755 --- a/migrations/2_deploy_basic.js +++ b/migrations/2_deploy_basic.js @@ -4,12 +4,15 @@ const xOLEDelegator = artifacts.require("XOLEDelegator"); const EthDexAggregatorV1 = artifacts.require("EthDexAggregatorV1"); const BscDexAggregatorV1 = artifacts.require("BscDexAggregatorV1"); const KccDexAggregatorV1 = artifacts.require("KccDexAggregatorV1"); +const CronosDexAggregatorV1 = artifacts.require("CronosDexAggregatorV1"); const DexAggregatorDelegator = artifacts.require("DexAggregatorDelegator"); const Gov = artifacts.require("GovernorAlpha"); +const QueryHelper = artifacts.require("QueryHelper"); const Timelock = artifacts.require("Timelock"); const ControllerV1 = artifacts.require("ControllerV1"); const ControllerDelegator = artifacts.require("ControllerDelegator"); const LPool = artifacts.require("LPool"); +const LTimePool = artifacts.require("LTimePool"); const OpenLevV1 = artifacts.require("OpenLevV1"); const OpenLevV1Lib = artifacts.require("OpenLevV1Lib"); const OpenLevDelegator = artifacts.require("OpenLevDelegator"); @@ -40,11 +43,16 @@ module.exports = async function (deployer, network, accounts) { case utils.kccMainnet: oleAddr = '0x1ccca1ce62c62f7be95d4a67722a8fdbed6eecb4'; break; + case utils.cronosMainnet: + oleAddr = '0x97a21A4f05b152a5D3cDf6273EE8b1d3D8fa8E40'; + break; default: await deployer.deploy(OLEToken, adminAccount, adminCtr, utils.tokenName(network), utils.tokenSymbol(network), utils.deployOption(accounts)); oleAddr = OLEToken.address; } + //queryHelper + await deployer.deploy(QueryHelper, utils.deployOption(accounts)); //airdrop await deployer.deploy(Airdrop, oleAddr, utils.deployOption(accounts)); //dexAgg @@ -58,6 +66,11 @@ module.exports = async function (deployer, network, accounts) { await deployer.deploy(KccDexAggregatorV1, utils.deployOption(accounts)); await deployer.deploy(DexAggregatorDelegator, utils.uniswapV2Address(network), utils.uniswapV3Address(network), adminCtr, KccDexAggregatorV1.address, utils.deployOption(accounts)); break; + case utils.cronosTest: + case utils.cronosMainnet: + await deployer.deploy(CronosDexAggregatorV1, utils.deployOption(accounts)); + await deployer.deploy(DexAggregatorDelegator, utils.uniswapV2Address(network), utils.uniswapV3Address(network), adminCtr, CronosDexAggregatorV1.address, utils.deployOption(accounts)); + break; default: await deployer.deploy(EthDexAggregatorV1, utils.deployOption(accounts)); await deployer.deploy(DexAggregatorDelegator, utils.uniswapV2Address(network), utils.uniswapV3Address(network), adminCtr, EthDexAggregatorV1.address, utils.deployOption(accounts)); @@ -71,7 +84,8 @@ module.exports = async function (deployer, network, accounts) { //reserve await deployer.deploy(Reserve, adminCtr, oleAddr, utils.deployOption(accounts)); //controller - await deployer.deploy(LPool, utils.deployOption(accounts)); + //await deployer.deploy(LPool, utils.deployOption(accounts)); + await deployer.deploy(LTimePool, utils.deployOption(accounts)); await deployer.deploy(ControllerV1, utils.deployOption(accounts)); switch (network) { case utils.bscIntegrationTest: @@ -81,6 +95,10 @@ module.exports = async function (deployer, network, accounts) { case utils.kccMainnet: await deployer.deploy(ControllerDelegator, oleAddr, xOLEDelegator.address, weth9, LPool.address, utils.zeroAddress, DexAggregatorDelegator.address, '0x0d', adminCtr, ControllerV1.address, utils.deployOption(accounts)); break; + case utils.cronosTest: + case utils.cronosMainnet: + await deployer.deploy(ControllerDelegator, oleAddr, xOLEDelegator.address, weth9, LTimePool.address, utils.zeroAddress, DexAggregatorDelegator.address, '0x14', adminCtr, ControllerV1.address, utils.deployOption(accounts)); + break; default: await deployer.deploy(ControllerDelegator, oleAddr, xOLEDelegator.address, weth9, LPool.address, utils.zeroAddress, DexAggregatorDelegator.address, '0x02000bb8', adminCtr, ControllerV1.address, utils.deployOption(accounts)); } @@ -96,6 +114,10 @@ module.exports = async function (deployer, network, accounts) { case utils.kccMainnet: await deployer.deploy(OpenLevDelegator, ControllerDelegator.address, DexAggregatorDelegator.address, utils.getDepositTokens(network), weth9, xOLEDelegator.address, [13, 14], adminCtr, OpenLevV1.address, utils.deployOption(accounts)); break; + case utils.cronosTest: + case utils.cronosMainnet: + await deployer.deploy(OpenLevDelegator, ControllerDelegator.address, DexAggregatorDelegator.address, utils.getDepositTokens(network), weth9, xOLEDelegator.address, [20], adminCtr, OpenLevV1.address, utils.deployOption(accounts)); + break; default: await deployer.deploy(OpenLevDelegator, ControllerDelegator.address, DexAggregatorDelegator.address, utils.getDepositTokens(network), weth9, xOLEDelegator.address, [1, 2], adminCtr, OpenLevV1.address, utils.deployOption(accounts)); } @@ -112,11 +134,16 @@ module.exports = async function (deployer, network, accounts) { await (await Timelock.at(Timelock.address)).executeTransaction(DexAggregatorDelegator.address, 0, 'setDexInfo(uint8[],address[],uint16[])', encodeParameters(['uint8[]', 'address[]', 'uint16[]'], [[11, 12], ['0xbcfccbde45ce874adcb698cc183debcf17952812', '0x86407bea2078ea5f5eb5a52b2caa963bc1f889da'], [20, 20]]), 0); - }else if (network == utils.kccMainnet) { + } else if (network == utils.kccMainnet) { m.log("Waiting dexAgg set factory ......"); await (await Timelock.at(Timelock.address)).executeTransaction(DexAggregatorDelegator.address, 0, 'setDexInfo(uint8[],address[],uint16[])', encodeParameters(['uint8[]', 'address[]', 'uint16[]'], [[14], ['0xAE46cBBCDFBa3bE0F02F463Ec5486eBB4e2e65Ae'], [10]]), 0); + } else if (network == utils.cronosTest || network == utils.cronosMainnet) { + m.log("Waiting dexAgg set factory ......"); + await (await Timelock.at(Timelock.address)).executeTransaction(DexAggregatorDelegator.address, 0, 'setDexInfo(uint8[],address[],uint16[])', + encodeParameters(['uint8[]', 'address[]', 'uint16[]'], + [[20], ['0x3B44B2a187a7b3824131F8db5a74194D0a42Fc15'], [30]]), 0); } }; diff --git a/migrations/util.js b/migrations/util.js index 92785b4..8fef514 100644 --- a/migrations/util.js +++ b/migrations/util.js @@ -4,6 +4,10 @@ let bscTestnet = exports.bscTestnet = 'bscTestnet'; let bscIntegrationTest = exports.bscIntegrationTest = 'bscIntegrationTest'; let mainnet = exports.mainnet = 'mainnet'; let kccMainnet = exports.kccMainnet = 'kccMainnet'; +let cronosTest = exports.cronosTest = 'cronosTest'; +let cronosMainnet = exports.cronosMainnet = 'cronosMainnet'; + + exports.isSkip = function (network) { return network == ('development') || @@ -28,6 +32,9 @@ exports.uniswapV2Address = function (network) { return '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f'; case kccMainnet: return "0x79855A03426e15Ad120df77eFA623aF87bd54eF3"; + case cronosTest: + case cronosMainnet: + return "0x3B44B2a187a7b3824131F8db5a74194D0a42Fc15"; default: return zeroAddress; } @@ -90,6 +97,9 @@ exports.blocksPerYear = function (network) { return 10512000; case kccMainnet: return 10512000; + case cronosTest: + case cronosMainnet: + return 31536000; } } @@ -97,6 +107,7 @@ exports.tokenName = function (network) { switch (network){ case bscIntegrationTest: case bscTestnet: + case cronosTest: return "ELO"; default: return "OpenLeverage"; @@ -107,6 +118,7 @@ exports.tokenSymbol = function (network) { switch (network){ case bscIntegrationTest: case bscTestnet: + case cronosTest: return "ELO" default: return "OLE"; @@ -127,6 +139,9 @@ exports.getWChainToken = function (network) { return "0x094616f0bdfb0b526bd735bf66eca0ad254ca81f"; case kccMainnet: return "0x4446fc4eb47f2f6586f9faab68b3498f86c07521"; + case cronosTest: + case cronosMainnet: + return "0x5C7F8A570d578ED84E63fdFA7b1eE72dEae1AE23"; default: return zeroAddress; } @@ -155,6 +170,9 @@ exports.getUniV2DexData = function (network){ return "0x03"; case kccMainnet: return "0x0d"; + case cronosTest: + case cronosMainnet: + return "0x14" default: return zeroAddress; } diff --git a/test/BscDexAggergator.js b/test/BscDexAggergator.js index 9547ba1..abc2753 100644 --- a/test/BscDexAggergator.js +++ b/test/BscDexAggergator.js @@ -55,8 +55,8 @@ contract("DexAggregator BSC", async accounts => { it("get Price", async () => { r = await dexAgg.getPrice(token0.address, token1.address, utils.PancakeDexData); m.log(r.price, r.decimals); - assert.equal(r.price, "1000000000000000000", "wrong token1/token0 price"); - assert.equal(r.decimals, "18", "wrong decimals"); + assert.equal(r.price, "1000000000000000000000000", "wrong token1/token0 price"); + assert.equal(r.decimals, "24", "wrong decimals"); }) it("sell exact amount", async () => { @@ -137,11 +137,11 @@ contract("DexAggregator BSC", async accounts => { let originalPriceData = await dexAgg.getPriceCAvgPriceHAvgPrice(token0.address, token1.address, 60, utils.PancakeDexData); m.log("originalPriceData: \t", JSON.stringify(originalPriceData)); - assert.equal(originalPriceData.price, "1000000000000000000", "wrong token1/token0 price"); + assert.equal(originalPriceData.price, "1000000000000000000000000", "wrong token1/token0 price"); assert.equal(originalPriceData.hAvgPrice, "0", "wrong hAvgPrice token1/token0 price"); - assert.equal(originalPriceData.decimals, "18", "wrong decimals"); + assert.equal(originalPriceData.decimals, "24", "wrong decimals"); assert.equal(originalPriceData.timestamp, "0" , "wrong timestamp"); - assert.equal(originalPriceData.cAvgPrice, "19259299", "wrong cAvgPrice token1/token0 price"); + assert.equal(originalPriceData.cAvgPrice.toString(), "19259299443872", "wrong cAvgPrice token1/token0 price"); await pair.setPriceUpdateAfter(token0.address, token1.address, "120"); reserveData = await pair.getReserves(); @@ -161,10 +161,10 @@ contract("DexAggregator BSC", async accounts => { let updatedPriceData0 = await dexAgg.getPriceCAvgPriceHAvgPrice(token0.address, token1.address, 60, utils.PancakeDexData); m.log("updatedPriceData0: \t", JSON.stringify(updatedPriceData0)); - assert.equal(updatedPriceData0.price, "1200000000000000000", "wrong token1/token0 price"); - assert.equal(updatedPriceData0.cAvgPrice, "1199999999999999999", "wrong cAvgPrice token1/token0 price"); - assert.equal(updatedPriceData0.hAvgPrice, "1199999999999999999", "wrong hAvgPrice token1/token0 price"); - assert.equal(updatedPriceData0.decimals, "18", "wrong decimals"); + assert.equal(updatedPriceData0.price, "1200000000000000000000000", "wrong token1/token0 price"); + assert.equal(updatedPriceData0.cAvgPrice, "1199999999999999999999999", "wrong cAvgPrice token1/token0 price"); + assert.equal(updatedPriceData0.hAvgPrice, "1199999999999999999999999", "wrong hAvgPrice token1/token0 price"); + assert.equal(updatedPriceData0.decimals, "24", "wrong decimals"); assert.equal(updatedPriceData0.timestamp.toString(), reserveData[2].toString(), "wrong timestamp"); }) @@ -180,8 +180,8 @@ contract("DexAggregator BSC", async accounts => { await dexAgg.updatePriceOracle(token0.address, token1.address, timeWindow, utils.PancakeDexData); r = await dexAgg.getAvgPrice(token0.address, token1.address, 0, utils.PancakeDexData); - assert.equal(r.price, "1199999999999999999", "wrong token1/token0 avg price"); - assert.equal(r.decimals, "18", "wrong decimals"); + assert.equal(r.price.toString(), "1199999999999999999999999", "wrong token1/token0 avg price"); + assert.equal(r.decimals, "24", "wrong decimals"); assert.equal(r.timestamp, await utils.lastBlockTime() , "wrong timestamp"); }) }); diff --git a/test/LPoolTest.js b/test/LPoolTest.js index 2998ca9..466bddc 100644 --- a/test/LPoolTest.js +++ b/test/LPoolTest.js @@ -365,6 +365,8 @@ contract("LPoolDelegator", async accounts => { assert.equal(await erc20Pool.getCash(), utils.toWei(25000).toString()); assert.equal(await erc20Pool.totalSupply(), '29999998858447553797103'); + m.log("logging-totalBorrows--cashs", await erc20Pool.totalBorrows(), await erc20Pool.totalReserves(), await erc20Pool.getCash()) + assert.equal(await erc20Pool.supplyRatePerBlock(), '4227972917'); assert.equal((await erc20Pool.borrowRatePerBlock()).mul(blocksPerYear).toString(), '66666668725411200'); diff --git a/test/LTimePoolTest.js b/test/LTimePoolTest.js new file mode 100644 index 0000000..286a01d --- /dev/null +++ b/test/LTimePoolTest.js @@ -0,0 +1,726 @@ +const utils = require("./utils/OpenLevUtil"); +const {toWei, toETH, assertPrint, approxAssertPrint, approxPrecisionAssertPrint, assertThrows, createUniswapV2Factory, createEthDexAgg, createXOLE} = require("./utils/OpenLevUtil"); + +const {toBN, blockNumber, maxUint, advanceMultipleBlocksAndAssignTime, advanceBlockAndSetTime, advanceMultipleBlocks} = require("./utils/EtheUtil"); +const m = require('mocha-logger'); +const {advanceTime} = require("ganache-time-traveler"); +const timeMachine = require("ganache-time-traveler"); +const {number} = require("truffle/build/735.bundled"); +const LPoolDelegator = artifacts.require('LPoolDelegator'); +const LPoolDepositor = artifacts.require('LPoolDepositor'); +const LPoolDepositorDelegator = artifacts.require("LPoolDepositorDelegator") + +contract("LPoolDelegator", async accounts => { + + // roles + let admin = accounts[0]; + let borrower0 = accounts[1]; + let borrower1 = accounts[2]; + let lender0 = accounts[3]; + let lender1 = accounts[4]; + + let snapshotId; + beforeEach(async () => { + let snapshot = await timeMachine.takeSnapshot(); + snapshotId = snapshot['result']; + }) + + afterEach(async () => { + await timeMachine.revertToSnapshot(snapshotId); + }); + + // --- new block timestamp test --- + + it("badDebtsAmount test with one seconds to produce a block", async () => { + let controller = await utils.createController(accounts[0]); + let createPoolResult = await utils.createTimePool(accounts[0], controller, admin); + let erc20Pool = createPoolResult.pool; + let testToken = createPoolResult.token; + await utils.mint(testToken, admin, 10000); + await erc20Pool.setReserveFactor(toBN(2e17)); + // deposit 10000 + await testToken.approve(erc20Pool.address, maxUint()); + await erc20Pool.mint(10000 * 1e10); + await testToken.approve(erc20Pool.address, maxUint(), {from: accounts[1]}); + await controller.setOpenLev(accounts[1]); + //Borrow money 5000 + await erc20Pool.borrowBehalf(accounts[2], 5000 * 1e10, {from: accounts[1]}); + // advance 1000 seconds... + await advanceMultipleBlocksAndAssignTime( 1, 1000); + let tx = await erc20Pool.repayBorrowEndByOpenLev(accounts[2], 1000 * 1e10, {from: accounts[1]}); + m.log("tx", JSON.stringify(tx)); + approxPrecisionAssertPrint("badDebtsAmount", '40000158707508', toBN(tx.logs[3].args.badDebtsAmount), 8); + }) + + it("badDebtsAmount test with five seconds to produce a block", async () => { + let controller = await utils.createController(accounts[0]); + let createPoolResult = await utils.createTimePool(accounts[0], controller, admin); + let erc20Pool = createPoolResult.pool; + let testToken = createPoolResult.token; + await utils.mint(testToken, admin, 10000); + await erc20Pool.setReserveFactor(toBN(2e17)); + // deposit 10000 + await testToken.approve(erc20Pool.address, maxUint()); + await erc20Pool.mint(10000 * 1e10); + await testToken.approve(erc20Pool.address, maxUint(), {from: accounts[1]}); + await controller.setOpenLev(accounts[1]); + //Borrow money 5000 + await erc20Pool.borrowBehalf(accounts[2], 5000 * 1e10, {from: accounts[1]}); + // advance 5000 seconds... + await advanceMultipleBlocksAndAssignTime( 1, 5000); + let tx = await erc20Pool.repayBorrowEndByOpenLev(accounts[2], 1000 * 1e10, {from: accounts[1]}); + m.log("tx", JSON.stringify(tx)); + approxPrecisionAssertPrint("badDebtsAmount", '40000792586250', toBN(tx.logs[3].args.badDebtsAmount), 8); + }) + + it("badDebtsAmount test with three seconds to produce a block", async () => { + let controller = await utils.createController(accounts[0]); + let createPoolResult = await utils.createTimePool(accounts[0], controller, admin); + let erc20Pool = createPoolResult.pool; + let testToken = createPoolResult.token; + await utils.mint(testToken, admin, 10000); + await erc20Pool.setReserveFactor(toBN(2e17)); + // deposit 10000 + await testToken.approve(erc20Pool.address, maxUint()); + await erc20Pool.mint(10000 * 1e10); + await testToken.approve(erc20Pool.address, maxUint(), {from: accounts[1]}); + await controller.setOpenLev(accounts[1]); + //Borrow money 5000 + await erc20Pool.borrowBehalf(accounts[2], 5000 * 1e10, {from: accounts[1]}); + // advance 3000 seconds... + await advanceMultipleBlocksAndAssignTime( 1, 3000); + let tx = await erc20Pool.repayBorrowEndByOpenLev(accounts[2], 1000 * 1e10, {from: accounts[1]}); + m.log("tx", JSON.stringify(tx)); + approxPrecisionAssertPrint("badDebtsAmount", '40000475646879', toBN(tx.logs[3].args.badDebtsAmount), 8); + }) + + // --- old block number test --- + it("Supply,borrow,repay,redeem test", async () => { + let controller = await utils.createController(accounts[0]); + let createPoolResult = await utils.createTimePool(accounts[0], controller, admin); + let erc20Pool = createPoolResult.pool; + let blocksPerYear = toBN(31536000); + let testToken = createPoolResult.token; + await utils.mint(testToken, admin, 10000); + let cash = await erc20Pool.getCash(); + assert.equal(cash, 0); + /** + * deposit + */ + //deposit10000 + await testToken.approve(erc20Pool.address, maxUint()); + await erc20Pool.mint(10000 * 1e10); + await erc20Pool.setReserveFactor(toBN(2e17)); + //Checking deposits + assert.equal(await erc20Pool.getCash(), 10000 * 1e10); + assert.equal(await erc20Pool.totalSupply(), 10000 * 1e10); + //Check deposit rate + assert.equal(await erc20Pool.supplyRatePerBlock(), 0); + //Check loan interest rate + //(=5e16/31536000) + assert.equal((await erc20Pool.borrowRatePerBlock()).toString(), 1585489599); + + /** + * borrow money + */ + //borrow money5000 + await erc20Pool.borrowBehalf(accounts[0], 5000 * 1e10); + //=(0.05+0.05)/31536000*10e18*31536000 + assert.equal((await erc20Pool.borrowRatePerBlock()).mul(blocksPerYear).toString(), 99999999988128000); + //=(0.5*1e17*(1e18-2e17)/1e18) + assert.equal((await erc20Pool.supplyRatePerBlock()).mul(blocksPerYear).toString(), 39999999988944000); + + //inspect snapshot + let accountSnapshot = await erc20Pool.getAccountSnapshot(accounts[0]); + assert.equal(accountSnapshot[0], 10000 * 1e10); + assert.equal(accountSnapshot[1], 5000 * 1e10); + assert.equal(accountSnapshot[2], 1e18); + //Borrow 2000 more + await advanceTime(864000); + let lastbkBefore = await web3.eth.getBlock('latest'); + await erc20Pool.borrowBehalf(accounts[0], 2000 * 1e10); + let lastbkAfter = await web3.eth.getBlock('latest'); + m.log("Block distance with borrowBehalf()", lastbkAfter.timestamp - lastbkBefore.timestamp, lastbkAfter.number - lastbkBefore.number); + + //(current cash 30000000000000 borrows 70136986301353 reserves 27397260270) + approxPrecisionAssertPrint("borrowRatePerBlock", '4443189242', (await erc20Pool.borrowRatePerBlock()).toString(), 8); + approxPrecisionAssertPrint("supplyRatePerBlock", '2490326099', (await erc20Pool.supplyRatePerBlock()).toString(), 8); + + accountSnapshot = await erc20Pool.getAccountSnapshot(accounts[0]); + approxPrecisionAssertPrint("accountSnapshot1", '70136986301353', accountSnapshot[1].toString(), 8); + approxPrecisionAssertPrint("accountSnapshot2", '1001095890410830000',accountSnapshot[2].toString(), 8); + //Total borrowings + approxPrecisionAssertPrint("totalBorrows", '70136986301353', (await erc20Pool.totalBorrows()).toString(), 8); + + //Update total borrowings and interest + m.log("Advancing 10 days"); + await advanceTime(864000); // + let lastbkBefore2 = await web3.eth.getBlock('latest'); + await erc20Pool.accrueInterest(); + let lastbkAfter2 = await web3.eth.getBlock('latest'); + m.log("Block distance with borrowBehalf()", lastbkAfter2.timestamp - lastbkBefore2.timestamp, lastbkAfter2.number - lastbkBefore2.number); + + approxPrecisionAssertPrint("borrowRatePerBlock", '4450670023', (await erc20Pool.borrowRatePerBlock()).toString(), 7); + approxPrecisionAssertPrint("supplyRatePerBlock", '2498718839', (await erc20Pool.supplyRatePerBlock()).toString(), 7); + //rate of exchange + //(token-Ltoken= (totalCash + totalBorrows - totalReserves) / totalSupply) + approxPrecisionAssertPrint("exchangeRateStored", '1003249890124370000', (await erc20Pool.exchangeRateStored()).toString(), 7); + + /** + * repayment + */ + m.log("Advancing 10 days"); + await advanceTime(864000); + await erc20Pool.repayBorrowBehalf(accounts[0], maxUint()); + accountSnapshot = await erc20Pool.getAccountSnapshot(accounts[0]); + assert.equal(accountSnapshot[0], 10000 * 1e10); + assert.equal(accountSnapshot[1], 0); + //Total borrowings + assert.equal(await erc20Pool.totalBorrows(), 0); + //Total deposit + assert.equal(await erc20Pool.totalSupply(), 10000 * 1e10); + //Loan interest rate and deposit interest rate + assert.equal((await erc20Pool.borrowRatePerBlock()).toString(), 1585489599); + assert.equal((await erc20Pool.supplyRatePerBlock()).toString(), 0); + m.log("10 day elapsed by the time the repayment was executed and the exchange rate rate increased"); + approxPrecisionAssertPrint("exchangeRateStored", '1005415801874060000', (await erc20Pool.exchangeRateStored()).toString(), 7); + + /** + * Withdrawal + */ + m.log("Advancing 10 days"); + await advanceTime(864000); + await erc20Pool.redeem((await erc20Pool.getAccountSnapshot(accounts[0]))[0]); + accountSnapshot = await erc20Pool.getAccountSnapshot(accounts[0]); + assert.equal(accountSnapshot[0], 0); + // Not enough money to pay back + assert.equal((await testToken.balanceOf(accounts[0])).toString(), 9999999999864604953146); + assert.equal((await erc20Pool.borrowRatePerBlock()).toString(), 1585489599); + assert.equal((await erc20Pool.supplyRatePerBlock()).mul(blocksPerYear).toString(), '0'); + assert.equal((await erc20Pool.exchangeRateStored()).toString(), 1e18); + assert.equal((await erc20Pool.availableForBorrow()).toString(), '0'); + + }) + + it("borrowTo test", async () => { + let controller = await utils.createController(accounts[0]); + let createPoolResult = await utils.createTimePool(accounts[0], controller, admin); + let erc20Pool = createPoolResult.pool; + let testToken = createPoolResult.token; + await utils.mint(testToken, admin, 10000); + + // deposit 10000 + await testToken.approve(erc20Pool.address, maxUint()); + await erc20Pool.mint(10000 * 1e10); + + //Borrow money 5000 + await advanceTime(864000); + await erc20Pool.borrowBehalf(accounts[0], 5000 * 1e10, {from: accounts[1]}); + + assert.equal((await testToken.balanceOf(accounts[1])).toString(), toBN(5000).mul(toBN(1e10)).toString()); + // inspect snapshot + let accountSnapshot = await erc20Pool.getAccountSnapshot(accounts[0]); + assert.equal(accountSnapshot[0], 10000 * 1e10); + assert.equal(accountSnapshot[1], 5000 * 1e10); + assert.equal(accountSnapshot[2], 1e18); + + }) + + it("repayBorrowEndByOpenLev test", async () => { + let controller = await utils.createController(accounts[0]); + let createPoolResult = await utils.createTimePool(accounts[0], controller, admin); + let erc20Pool = createPoolResult.pool; + let testToken = createPoolResult.token; + await utils.mint(testToken, admin, 10000); + await erc20Pool.setReserveFactor(toBN(2e17)); + + // deposit 10000 + await testToken.approve(erc20Pool.address, maxUint()); + + await erc20Pool.mint(10000 * 1e10); + + //Borrow money 5000 + await erc20Pool.borrowBehalf(accounts[2], 5000 * 1e10, {from: accounts[1]}); + // advance 15000 seconds + await advanceTime(15000); + m.log("advance 15000 seconds..."); + let exchangeRateStored1 = await erc20Pool.exchangeRateStored(); + m.log("exchangeRateStored1", exchangeRateStored1); + assert.equal('1000000000000000000', exchangeRateStored1); + + await testToken.approve(erc20Pool.address, maxUint(), {from: accounts[1]}); + + await controller.setOpenLev(accounts[1]); + + // Test return not enough money to penetration warehouse + let tx = await erc20Pool.repayBorrowEndByOpenLev(accounts[2], 1000 * 1e10, {from: accounts[1]}); + m.log("tx", JSON.stringify(tx)); + assertPrint("repayAmount", '10000000000000', toBN(tx.logs[3].args.repayAmount)); + approxPrecisionAssertPrint("badDebtsAmount", '40002378392947', toBN(tx.logs[3].args.badDebtsAmount), 8); + assertPrint("accountBorrowsNew", '0', toBN(tx.logs[3].args.accountBorrows)); + assertPrint("totalBorrows", '0', toBN(tx.logs[3].args.totalBorrows)); + + let borrowsCurrent = await erc20Pool.borrowBalanceCurrent(accounts[1]); + assert.equal(0, borrowsCurrent); + let totalBorrowCurrent = await erc20Pool.totalBorrowsCurrent(); + assert.equal(0, totalBorrowCurrent); + let exchangeRateStored2 = await erc20Pool.exchangeRateStored(); + let getCash2 = await erc20Pool.getCash(); + m.log("exchangeRateStored2", exchangeRateStored2); + m.log("getCash2", getCash2); + m.log("----getPoolInfo----", (await erc20Pool.getCash()).toString(), (await erc20Pool.totalBorrows()).toString(), (await erc20Pool.totalReserves()).toString(), (await erc20Pool.totalSupply()).toString()); + approxPrecisionAssertPrint("exchangeRateStored", '599995243531210000', exchangeRateStored2.toString(), 8); + assert.equal('60000000000000', getCash2); + + await erc20Pool.mint(1000 * 1e10); + m.log("----getAccountSnapshot---- ", (await erc20Pool.getAccountSnapshot(accounts[0]))[0]); + // LTOKEN cannot be exchanged for a token of equal value after the penetration warehouse + await erc20Pool.redeem((await erc20Pool.getAccountSnapshot(accounts[0]))[0]); + let getCash3 = await erc20Pool.getCash(); + let totalReserves = await erc20Pool.totalReserves(); + m.log("cacsh3--", getCash3, totalReserves); + approxPrecisionAssertPrint("getCash3", '475646880', getCash3.toString(), 4); + approxPrecisionAssertPrint("totalReserves", '475646879', totalReserves.toString(), 4); + let exchangeRateStored3 = await erc20Pool.exchangeRateStored(); + m.log("exchangeRateStored3", exchangeRateStored3); + m.log("----getPoolInfo----", (await erc20Pool.getCash()).toString(), (await erc20Pool.totalBorrows()).toString(), (await erc20Pool.totalReserves()).toString(), (await erc20Pool.totalSupply()).toString()); + assert.equal('1000000000000000000', exchangeRateStored3); + }) + + it("borrow out of range test", async () => { + let controller = await utils.createController(accounts[0]); + let createPoolResult = await utils.createTimePool(accounts[0], controller, admin); + let testToken = createPoolResult.token; + let erc20Pool = createPoolResult.pool; + await utils.mint(testToken, admin, 10000); + + //deposit 10000 + await testToken.approve(erc20Pool.address, maxUint()); + await erc20Pool.mint(10000 * 1e10); + await advanceTime(100000); + let maxBorrow = await erc20Pool.availableForBorrow(); + m.log('maxBorrow', maxBorrow.toString()); + //Maximum borrowing amount + 1 + await assertThrows(erc20Pool.borrowBehalf(accounts[0], maxBorrow.add(toBN('1'))), 'Borrow out of range'); + + }) + + it("mint redeem eth test", async () => { + let weth = await utils.createWETH(); + let controller = await utils.createController(accounts[0]); + let createPoolResult = await utils.createTimePool(accounts[0], controller, admin, weth); + let erc20Pool = createPoolResult.pool; + let mintAmount = toWei(1); + //deposit 1 + let ethBegin = await web3.eth.getBalance(admin); + m.log("ethBegin=", ethBegin); + let tx = await erc20Pool.mintEth({value: mintAmount}); + + m.log("MintEth Gas Used: ", tx.receipt.gasUsed); + assert.equal((await erc20Pool.getCash()).toString(), mintAmount.toString()); + assert.equal((await erc20Pool.totalSupply()).toString(), mintAmount.toString()); + //redeem + let ethBefore = await web3.eth.getBalance(admin); + await erc20Pool.redeemUnderlying(mintAmount); + + assert.equal(await erc20Pool.getCash(), 0); + assert.equal(await erc20Pool.totalSupply(), 0); + let ethAfter = await web3.eth.getBalance(admin); + m.log("ethBefore=", ethBefore); + m.log("ethAfter=", ethAfter); + assert.equal(toBN(ethAfter).gt(toBN(ethBefore)), true); + }) + + it("Depositor deposit eth redeem test", async () => { + let weth = await utils.createWETH(); + let controller = await utils.createController(accounts[0]); + let createPoolResult = await utils.createTimePool(accounts[0], controller, admin, weth); + let erc20Pool = createPoolResult.pool; + let poolDepositor = await LPoolDepositorDelegator.new((await LPoolDepositor.new()).address, accounts[0]); + poolDepositor = await LPoolDepositor.at(poolDepositor.address); + let mintAmount = toWei(1); + //deposit 1 + let ethBegin = await web3.eth.getBalance(admin); + m.log("ethBegin=", ethBegin); + let tx = await poolDepositor.depositNative(erc20Pool.address, {value: mintAmount}); + + m.log("DepositEth Gas Used: ", tx.receipt.gasUsed); + assert.equal((await erc20Pool.getCash()).toString(), mintAmount.toString()); + assert.equal((await erc20Pool.totalSupply()).toString(), mintAmount.toString()); + //redeem + let ethBefore = await web3.eth.getBalance(admin); + await erc20Pool.redeemUnderlying(mintAmount); + + assert.equal(await erc20Pool.getCash(), 0); + assert.equal(await erc20Pool.totalSupply(), 0); + let ethAfter = await web3.eth.getBalance(admin); + m.log("ethBefore=", ethBefore); + m.log("ethAfter=", ethAfter); + assert.equal(toBN(ethAfter).gt(toBN(ethBefore)), true); + }) + + it("Depositor deposit erc20 redeem test", async () => { + let controller = await utils.createController(accounts[0]); + let createPoolResult = await utils.createTimePool(accounts[0], controller, admin); + let erc20Pool = createPoolResult.pool; + let testToken = createPoolResult.token; + await utils.mint(testToken, admin, 1); + + let poolDepositor = await LPoolDepositorDelegator.new((await LPoolDepositor.new()).address, accounts[0]); + poolDepositor = await LPoolDepositor.at(poolDepositor.address); + let mintAmount = toWei(1); + //deposit 1 + await testToken.approve(poolDepositor.address, maxUint()); + let tx = await poolDepositor.deposit(erc20Pool.address, mintAmount); + + m.log("DepositErc20 Gas Used: ", tx.receipt.gasUsed); + assert.equal((await erc20Pool.getCash()).toString(), mintAmount.toString()); + assert.equal((await erc20Pool.totalSupply()).toString(), mintAmount.toString()); + //redeem + await erc20Pool.redeemUnderlying(mintAmount); + + assert.equal(await erc20Pool.getCash(), 0); + assert.equal(await erc20Pool.totalSupply(), 0); + assert.equal(toWei(1).toString(), (await testToken.balanceOf(admin)).toString()); + }) + it("Supply -> raise Balance -> supply -> redeem", async () => { + let controller = await utils.createController(accounts[0]); + let createPoolResult = await utils.createTimePool(accounts[0], controller, admin); + let erc20Pool = createPoolResult.pool; + let blocksPerYear = toBN(31536000); + let testToken = createPoolResult.token; + await utils.mint(testToken, admin, 10000); + let cash = await erc20Pool.getCash(); + assert.equal(cash, 0); + /** + * deposit + */ + //deposit10000 + await testToken.approve(erc20Pool.address, maxUint()); + await erc20Pool.mint(utils.toWei(10000)); + await erc20Pool.setReserveFactor(toBN(2e17)); + + //Checking deposits + assert.equal(await erc20Pool.getCash(), utils.toWei(10000).toString()); + assert.equal(await erc20Pool.totalSupply(), utils.toWei(10000).toString()); + //Check deposit rate + assert.equal(await erc20Pool.supplyRatePerBlock(), 0); + //Check loan interest rate + assert.equal((await erc20Pool.borrowRatePerBlock()).mul(blocksPerYear).toString(), '49999999994064000'); + + /* raise balance */ + await utils.mint(testToken, erc20Pool.address, 10000); + + assert.equal(await erc20Pool.getCash(), utils.toWei(20000).toString()); + assert.equal(await erc20Pool.totalSupply(), utils.toWei(10000).toString()); + assert.equal(await erc20Pool.supplyRatePerBlock(), 0); + assert.equal((await erc20Pool.borrowRatePerBlock()).mul(blocksPerYear).toString(), '49999999994064000'); + + /* deposit again by another account*/ + await utils.mint(testToken, accounts[1], 20000); + await testToken.approve(erc20Pool.address, maxUint(), {from: accounts[1]}); + await erc20Pool.mint(utils.toWei(20000), {from: accounts[1]}); + + assert.equal(await erc20Pool.getCash(), utils.toWei(40000).toString()); + // token mint will not add the totalSupply, so the exchangeRateMantissa from 1:1 change to 2:1, when mint 20000 again, totalSupply only add 20000/2 = 10000 + assert.equal(await erc20Pool.totalSupply(), utils.toWei(20000).toString()); + assert.equal(await erc20Pool.supplyRatePerBlock(), 0); + assert.equal((await erc20Pool.borrowRatePerBlock()).mul(blocksPerYear).toString(), '49999999994064000'); + + /* Withdrawal + */ + await erc20Pool.redeem((await erc20Pool.getAccountSnapshot(accounts[0]))[0]); + accountSnapshot = await erc20Pool.getAccountSnapshot(accounts[0]); + assert.equal(accountSnapshot[0], 0); + assert.equal((await testToken.balanceOf(accounts[0])).toString(), utils.toWei(20000).toString()); + + await erc20Pool.redeem((await erc20Pool.getAccountSnapshot(accounts[1]))[0], {from: accounts[1]}); + accountSnapshot = await erc20Pool.getAccountSnapshot(accounts[1]); + assert.equal(accountSnapshot[0], 0); + assert.equal((await testToken.balanceOf(accounts[1])).toString(), utils.toWei(20000).toString()); + + assert.equal((await erc20Pool.borrowRatePerBlock()).mul(blocksPerYear).toString(), '49999999994064000'); + assert.equal((await erc20Pool.supplyRatePerBlock()).mul(blocksPerYear).toString(), '0'); + assert.equal((await erc20Pool.exchangeRateStored()).toString(), 1e18); + assert.equal((await erc20Pool.availableForBorrow()).toString(), '0'); + }) + + it("Supply -> borrow -> supply more -> raise -> borrow more -> redeem partial -> repay -> redeem all", async () => { + let controller = await utils.createController(accounts[0]); + let createPoolResult = await utils.createTimePool(accounts[0], controller, admin); + let erc20Pool = createPoolResult.pool; + let blocksPerYear = toBN(31536000); + let testToken = createPoolResult.token; + await utils.mint(testToken, admin, 10000); + + let cash = await erc20Pool.getCash(); + assert.equal(cash, 0); + /** + * deposit + */ + //deposit10000 + await testToken.approve(erc20Pool.address, maxUint()); + await erc20Pool.mint(utils.toWei(10000)); + await erc20Pool.setReserveFactor(toBN(2e17)); + + //Checking deposits + assert.equal(await erc20Pool.getCash(), utils.toWei(10000).toString()); + assert.equal(await erc20Pool.totalSupply(), utils.toWei(10000).toString()); + //Check deposit rate + assert.equal(await erc20Pool.supplyRatePerBlock(), 0); + //Check loan interest rate + assert.equal((await erc20Pool.borrowRatePerBlock()).mul(blocksPerYear).toString(), '49999999994064000'); + + /** + * borrow money + */ + //borrow money5000 + await erc20Pool.borrowBehalf(borrower0, utils.toWei(5000), {from: borrower0}); + assert.equal((await erc20Pool.borrowRatePerBlock()).toString(), '3170979198'); + assert.equal((await erc20Pool.supplyRatePerBlock()).toString(), '1268391679'); + //inspect snapshot + let accountSnapshot = await erc20Pool.getAccountSnapshot(borrower0); + assert.equal(accountSnapshot[0], 0); + assert.equal(accountSnapshot[1], utils.toWei(5000).toString()); + assert.equal(accountSnapshot[2], 1e18); + + /* deposit again by another account*/ + await advanceMultipleBlocksAndAssignTime(1,864000); + await utils.mint(testToken, lender0, 20000, {from: lender0}); + await testToken.approve(erc20Pool.address, maxUint(), {from: lender0}); + await erc20Pool.mint(utils.toWei(20000), {from: lender0}); + + assert.equal(await erc20Pool.getCash(), utils.toWei(25000).toString()); + approxPrecisionAssertPrint("totalSupply", "29978106185005333115234", (await erc20Pool.totalSupply()).toString(), 8) + approxPrecisionAssertPrint("borrowRate", '2115240551', (await erc20Pool.borrowRatePerBlock()).toString(), 8); + approxPrecisionAssertPrint("supplyRate", '282701493', (await erc20Pool.supplyRatePerBlock()).toString(), 8); + + /* raise balance */ + await utils.mint(testToken, erc20Pool.address, 60000); + + assert.equal(await erc20Pool.getCash(), utils.toWei(85000).toString()); + approxPrecisionAssertPrint("totalSupply", "29978106185005333115234", (await erc20Pool.totalSupply()).toString(), 8) + approxPrecisionAssertPrint("borrowRate", '1762116248', (await erc20Pool.borrowRatePerBlock()).toString(), 8); + approxPrecisionAssertPrint("supplyRate", '78521281', (await erc20Pool.supplyRatePerBlock()).toString(), 8); + + //Borrow 5000 more + await erc20Pool.borrowBehalf(borrower0, utils.toWei(5000), {from: borrower0}); + + approxPrecisionAssertPrint("borrowRate", '1938260311', (await erc20Pool.borrowRatePerBlock()).toString(), 8); + approxPrecisionAssertPrint("supplyRate", '172504813', (await erc20Pool.supplyRatePerBlock()).toString(), 8); + accountSnapshot = await erc20Pool.getAccountSnapshot(borrower0); + + approxPrecisionAssertPrint("accountSnapshot 1", "10013698638970079818736", accountSnapshot[1].toString(), 8) + approxPrecisionAssertPrint("accountSnapshot 2", "3002556544288925063", accountSnapshot[2].toString(), 8) + //Total borrowings + approxPrecisionAssertPrint("accountSnapshot 2", "10013698638970079818736", await erc20Pool.totalBorrows(), 8) + + //Update total borrowings and interest + await advanceMultipleBlocksAndAssignTime(1,864000); + await erc20Pool.accrueInterest(); + approxPrecisionAssertPrint("borrowRate", '1938798422', (await erc20Pool.borrowRatePerBlock()).toString(), 8); + approxPrecisionAssertPrint("supplyRate", '172815915', (await erc20Pool.supplyRatePerBlock()).toString(), 8); + //rate of exchange + approxPrecisionAssertPrint("exchangeRateStored", '3003004060527666431', (await erc20Pool.exchangeRateStored()).toString(), 8); + + /** + * Withdrawal partial + */ + await erc20Pool.redeem((await erc20Pool.getAccountSnapshot(lender0))[0], {from: lender0}); + + accountSnapshot = await erc20Pool.getAccountSnapshot(lender0); + assert.equal(accountSnapshot[0], 0); + assert.equal(await erc20Pool.totalSupply(), utils.toWei(10000).toString()); + approxPrecisionAssertPrint("balanceOf", "59994333946443210028545", (await testToken.balanceOf(lender0)).toString(), 8); + approxPrecisionAssertPrint("borrowRatePerBlock", '2644642542', (await erc20Pool.borrowRatePerBlock()).toString(), 8); + approxPrecisionAssertPrint("supplyRatePerBlock", '706679106', (await erc20Pool.supplyRatePerBlock()).toString(), 8); + approxPrecisionAssertPrint("exchangeRateStored", "3003004058085958896", (await erc20Pool.exchangeRateStored()).toString(), 8); + approxPrecisionAssertPrint("availableForBorrow", '13993564305559172431876', (await erc20Pool.availableForBorrow()).toString(), 8); + + /** + * repayment + */ + await utils.mint(testToken, borrower0, 10000); + await testToken.approve(erc20Pool.address, maxUint(), {from: borrower0}); + accountSnapshot = await erc20Pool.getAccountSnapshot(borrower0); + m.log("account borrow", accountSnapshot[1]) + await erc20Pool.repayBorrowBehalf(borrower0, maxUint(), {from: borrower0}); + + accountSnapshot = await erc20Pool.getAccountSnapshot(borrower0); + assert.equal(accountSnapshot[0], 0); + assert.equal(accountSnapshot[1], 0); + //Total deposit + //Loan interest rate and deposit interest rate + m.log("----getPoolInfo2----", (await erc20Pool.getCash()).toString(), (await erc20Pool.totalBorrows()).toString(), (await erc20Pool.totalReserves()).toString(), (await erc20Pool.totalSupply()).toString()); + approxPrecisionAssertPrint("borrowRatePerBlock", '1585489599', (await erc20Pool.borrowRatePerBlock()).toString(), 8); + assert.equal((await erc20Pool.supplyRatePerBlock()).mul(blocksPerYear).toString(), '0'); + //rate of exchange + approxPrecisionAssertPrint("exchangeRateStored", "3003004062649826654", (await erc20Pool.exchangeRateStored()).toString(), 8); + + /** + * Withdrawal all + */ + let redeemAmount = (await erc20Pool.getAccountSnapshot(accounts[0]))[0]; + await erc20Pool.redeem(redeemAmount); + accountSnapshot = await erc20Pool.getAccountSnapshot(accounts[0]); + assert.equal(accountSnapshot[0], 0); + approxPrecisionAssertPrint("balanceOf", "30030040580859588960000", (await testToken.balanceOf(accounts[0])).toString(), 8); + m.log("----getPoolInfo3----", (await erc20Pool.getCash()).toString(), (await erc20Pool.totalBorrows()).toString(), (await erc20Pool.totalReserves()).toString(), (await erc20Pool.totalSupply()).toString(), accountSnapshot[1]); + // sometimes total borrows = 1 + let totalBorrows = (await erc20Pool.totalBorrows()).toString(); + if (totalBorrows == 1){ + m.log("totalBorrows is 1----", totalBorrows) + assert.equal((await erc20Pool.borrowRatePerBlock()).toString(), '1588225560'); + } else { + m.log("totalBorrows is 0----", totalBorrows) + assert.equal((await erc20Pool.borrowRatePerBlock()).toString(), '1585489599'); + } + assert.equal(toETH(await erc20Pool.supplyRatePerBlock()).toString(), '0'); + assert.equal((await erc20Pool.exchangeRateStored()).toString(), 1e18); + }) + + it("pool not allowed test", async () => { + let controller = await utils.createController(accounts[0]); + let createPoolResult = await utils.createTimePool(accounts[0], controller, admin); + let testToken = createPoolResult.token; + let erc20Pool = createPoolResult.pool; + await utils.mint(testToken, admin, 10000); + //deposit 10000 + await testToken.approve(erc20Pool.address, maxUint()); + controller.setLPoolUnAllowed(await erc20Pool.address, true); + await assertThrows(erc20Pool.mint(10000 * 1e10), 'LPool paused'); + }) + + it("pool change admin test", async () => { + let controller = await utils.createController(accounts[0]); + let createPoolResult = await utils.createTimePool(accounts[0], controller, admin); + let erc20Pool = createPoolResult.pool; + let newAdmin = accounts[1]; + await erc20Pool.setPendingAdmin(newAdmin); + assert.equal(newAdmin, await erc20Pool.pendingAdmin()); + await erc20Pool.acceptAdmin({from: accounts[1]}); + assert.equal(newAdmin, await erc20Pool.admin()); + assert.equal("0x0000000000000000000000000000000000000000", await erc20Pool.pendingAdmin()); + }) + + it("reverses test ", async () => { + let controller = await utils.createController(accounts[0]); + let createPoolResult = await utils.createTimePool(accounts[0], controller, admin); + let erc20Pool = createPoolResult.pool; + let blocksPerYear = toBN(31536000); + let testToken = createPoolResult.token; + await utils.mint(testToken, admin, 10000); + await erc20Pool.setReserveFactor(toBN(2e17)); + //deposit 9000 + await testToken.approve(erc20Pool.address, maxUint()); + await erc20Pool.mint(toWei(9000)); + //borrow 1000 + await erc20Pool.borrowBehalf(accounts[0], toWei(1000)); + await advanceTime(864000); + //repay + await erc20Pool.repayBorrowBehalf(accounts[0], maxUint()); + accountSnapshot = await erc20Pool.getAccountSnapshot(accounts[0]); + assert.equal(accountSnapshot[0].toString(), toWei(9000).toString()); + assert.equal(accountSnapshot[1], 0); + + //withdrawal + await erc20Pool.redeem((await erc20Pool.getAccountSnapshot(accounts[0]))[0]); + let totalBorrows = await erc20Pool.totalBorrows(); + let totalCash = await erc20Pool.getCash(); + let reserves = await erc20Pool.totalReserves(); + m.log("totalBorrows", totalBorrows); + m.log("totalCash ", totalCash); + m.log("reserves", reserves); + + accountSnapshot = await erc20Pool.getAccountSnapshot(accounts[0]); + assert.equal(accountSnapshot[0], 0); + approxPrecisionAssertPrint("balanceOf", '9999665144596864000000', (await testToken.balanceOf(accounts[0])).toString(), 8); + approxPrecisionAssertPrint("Check borrow rate", '1585489599', await erc20Pool.borrowRatePerBlock(), 8); + assert.equal(toETH(await erc20Pool.supplyRatePerBlock()), 0); + assert.equal((await erc20Pool.exchangeRateStored()).toString(), 1e18); + approxPrecisionAssertPrint("reserves", '334855403136000000', reserves.toString(), 8); + + //reduce reserves + await erc20Pool.reduceReserves(accounts[1], '34855403136000000'); + let reservesAfterReduce = await erc20Pool.totalReserves(); + approxPrecisionAssertPrint("reservesAfterReduce", '300000000000000000', reservesAfterReduce.toString(), 8); + approxPrecisionAssertPrint("Check borrow rate", '1585489599', (await erc20Pool.borrowRatePerBlock()).toString(), 8); + assert.equal(toETH(await erc20Pool.supplyRatePerBlock()), 0); + assert.equal((await erc20Pool.exchangeRateStored()).toString(), 1e18); + approxPrecisionAssertPrint("balanceOf", '34855403136000000', (await testToken.balanceOf(accounts[1])).toString(), 8); + approxPrecisionAssertPrint("cash", '300000000000000000', (await erc20Pool.getCash()).toString(), 8); + // add reserves + await erc20Pool.addReserves('100000000000000000'); + approxPrecisionAssertPrint("totalReserves", '400000000000000000', (await erc20Pool.totalReserves()).toString(), 8); + approxPrecisionAssertPrint("cash", '400000000000000000', (await erc20Pool.getCash()).toString(), 8); + + }) + + it("update interestParams test", async () => { + let controller = await utils.createController(accounts[0]); + let createPoolResult = await utils.createTimePool(accounts[0], controller, admin); + let erc20Pool = createPoolResult.pool; + let testToken = createPoolResult.token; + //5% base 100000 blocks + await erc20Pool.setInterestParams(toBN(5e16).div(toBN(31536000)), toBN(10e16).div(toBN(31536000)), toBN(20e16).div(toBN(31536000)), 50e16 + ''); + await erc20Pool.setReserveFactor(toBN(2e17)); + + await utils.mint(testToken, admin, toWei(100000)); + // deposit 10000 + await testToken.approve(erc20Pool.address, maxUint()); + await erc20Pool.mint(toWei(10000)); + //borrow 5000 + await erc20Pool.borrowBehalf(accounts[0], toWei(4000), {from: accounts[1]}); + await advanceMultipleBlocksAndAssignTime(1, 86400); + + // check borrows=4000+(5%+10%*40%)*4000*864000/31536000 + let borrowsBefore = await erc20Pool.borrowBalanceCurrent(accounts[0]); + m.log("borrowsBefore =", borrowsBefore.toString()); + approxPrecisionAssertPrint("borrowsBefore", '4000986301369676799999', borrowsBefore.toString(), 8); + + //base interest change to 10% 31536000 blocks + await erc20Pool.setInterestParams(toBN(10e16).div(toBN(31536000)), toBN(10e16).div(toBN(31536000)), toBN(20e16).div(toBN(31536000)), 50e16 + ''); + + let borrowsAfterUpdate = await erc20Pool.borrowBalanceCurrent(accounts[0]); + let totalBorrowsAfterUpdate = await erc20Pool.totalBorrowsCurrent(); + let totalBorrowsStoredAfterUpdate = await erc20Pool.totalBorrows(); + let baseRatePerBlockAfterUpdate = await erc20Pool.baseRatePerBlock(); + // check borrows=4000+(5%+10%*40%)*4000*1001/100000 + m.log("borrowsAfterUpdate =", borrowsAfterUpdate.toString()); + m.log("totalBorrowsAfterUpdate =", totalBorrowsAfterUpdate.toString()); + m.log("totalBorrowsStoredAfterUpdate =", totalBorrowsStoredAfterUpdate.toString()); + m.log("baseRatePerBlockAfterUpdate =", baseRatePerBlockAfterUpdate.toString()); + approxPrecisionAssertPrint("borrowsAfterUpdate", '4000986301369676799999', borrowsAfterUpdate.toString(), 8); + approxPrecisionAssertPrint("totalBorrowsAfterUpdate", '4000986301369676800000', totalBorrowsAfterUpdate.toString(), 8); + assert.equal("3170979198", baseRatePerBlockAfterUpdate.toString()); + await advanceTime(864000); + + // check borrows=4000.36+(10%+10%*40%)*4000.36*864000/31536000 + borrowsAfterUpdate = await erc20Pool.borrowBalanceCurrent(accounts[0]); + totalBorrowsAfterUpdate = await erc20Pool.totalBorrowsCurrent(); + m.log("borrowsAfterUpdate =", borrowsAfterUpdate.toString()); + m.log("totalBorrowsAfterUpdate =", totalBorrowsAfterUpdate.toString()); + approxPrecisionAssertPrint("borrowsAfterUpdate", '4000986301369676800000', borrowsAfterUpdate.toString(), 8); + approxPrecisionAssertPrint("totalBorrowsAfterUpdate", '4000986301369676800000', totalBorrowsAfterUpdate.toString(), 8); + + // repay + await erc20Pool.repayBorrowBehalf(accounts[0], maxUint()); + m.log("after repay..."); + borrowsAfterUpdate = await erc20Pool.borrowBalanceCurrent(accounts[0]); + totalBorrowsAfterUpdate = await erc20Pool.totalBorrowsCurrent(); + assert.equal("0", borrowsAfterUpdate.toString()); + assert.equal("0", totalBorrowsAfterUpdate.toString()); + // redeem + await erc20Pool.redeem((await erc20Pool.getAccountSnapshot(accounts[0]))[0]); + let cashInPool = await erc20Pool.getCash(); + let reserves = await erc20Pool.totalReserves(); + m.log("cashInPool =", cashInPool.toString()); + m.log("reserves =", reserves.toString()); + let avaiableCash = cashInPool.sub(reserves); + m.log("avaiableCash =", avaiableCash.toString()); + approxPrecisionAssertPrint("cashInPool", '3266657062937755091', cashInPool.toString(), 5); + approxPrecisionAssertPrint("reserves", '3266657062937755091', reserves.toString(), 5); + }) + + +}) diff --git a/test/OpenLevV1UniV2Test1.js b/test/OpenLevV1UniV2Test1.js index 70899dc..80c8f7b 100644 --- a/test/OpenLevV1UniV2Test1.js +++ b/test/OpenLevV1UniV2Test1.js @@ -28,6 +28,8 @@ contract("OpenLev UniV2", async accounts => { let saver = accounts[1]; let trader = accounts[2]; + let trader2 = accounts[5]; + let dev = accounts[3]; let liquidator1 = accounts[8]; let liquidator2 = accounts[9]; @@ -58,7 +60,7 @@ contract("OpenLev UniV2", async accounts => { openLevV1Lib = await OpenLevV1Lib.new(); await OpenLevV1.link("OpenLevV1Lib", openLevV1Lib.address); delegatee = await OpenLevV1.new(); - + openLev = await OpenLevDelegator.new(controller.address, dexAgg.address, [token0.address, token1.address], weth.address, xole.address, [1, 2], accounts[0], delegatee.address); openLev = await OpenLevV1.at(openLev.address); await openLev.setCalculateConfig(30, 33, 3000, 5, 25, 25, (30e18) + '', 300, 10, 60); @@ -75,7 +77,6 @@ contract("OpenLev UniV2", async accounts => { assert.equal(await openLev.numPairs(), 1, "Should have one active pair"); m.log("Reset OpenLev instance: ", last8(openLev.address)); }); - it("Deposit Eth,return eth ", async () => { gotPair = await utils.createUniswapV2Pool(uniswapFactory, weth, token1); await controller.createLPoolPair(weth.address, token1.address, 3000, Uni2DexData); // 30% margin ratio by default @@ -119,7 +120,6 @@ contract("OpenLev UniV2", async accounts => { m.log("ethAfter=", ethAfter); assert.equal(toBN(ethAfter).gt(toBN(ethBefore)), true); }) - it("LONG Token0, Not Init Price ,Not Succeed ", async () => { let pairId = 0; await printBlockNum(); @@ -212,7 +212,7 @@ contract("OpenLev UniV2", async accounts => { assert.equal(marginRatio.hAvg.toString(), 7733); let tradeBefore = await openLev.activeTrades(trader, pairId, 0); assert.equal(tradeBefore.held, "886675826237735294796"); - + let closeTradeTx = await openLev.closeTrade(0, false, tradeBefore.held, 0, Uni2DexData, {from: trader}); m.log("V2 Close Trade Gas Used: ", closeTradeTx.receipt.gasUsed); @@ -289,7 +289,6 @@ contract("OpenLev UniV2", async accounts => { m.log("Margin Ratio havg:", marginRatio.hAvg / 100, "%"); assert.equal(marginRatio.current.toString(), 13599); }) - it("LONG Token0, Price Diffience>10%, 60s Later, Liquidation", async () => { let pairId = 0; await printBlockNum(); @@ -325,8 +324,6 @@ contract("OpenLev UniV2", async accounts => { let priceData0 = await dexAgg.getPriceCAvgPriceHAvgPrice(token0.address, token1.address, 60, Uni2DexData); m.log("PriceData0: \t", JSON.stringify(priceData0)); - let shouldUpatePrice = await openLev.shouldUpdatePrice(pairId, Uni2DexData); - assert.equal(shouldUpatePrice, true); // should update price first await assertThrows(openLev.liquidate(trader, pairId, 0, 0, utils.maxUint(), Uni2DexData, {from: liquidator2}), 'MPT'); @@ -344,7 +341,6 @@ contract("OpenLev UniV2", async accounts => { assertPrint("Deposit Decrease", '397300000000000000000', liquidationTx.logs[0].args.depositDecrease); assertPrint("Deposit Return", '0', liquidationTx.logs[0].args.depositReturn); }) - it("LONG Token0, Price Diffience>10%,Update Price, Liquidation", async () => { let pairId = 0; await printBlockNum(); @@ -380,8 +376,6 @@ contract("OpenLev UniV2", async accounts => { let priceData0 = await dexAgg.getPriceCAvgPriceHAvgPrice(token0.address, token1.address, 60, Uni2DexData); m.log("PriceData0: \t", JSON.stringify(priceData0)); - let shouldUpatePrice = await openLev.shouldUpdatePrice(pairId, Uni2DexData); - assert.equal(shouldUpatePrice, true); // should update price first await assertThrows(openLev.liquidate(trader, pairId, 0, 0, utils.maxUint(), Uni2DexData, {from: liquidator2}), 'MPT'); @@ -405,7 +399,6 @@ contract("OpenLev UniV2", async accounts => { assertPrint("Deposit Return", '0', liquidationTx.logs[0].args.depositReturn); }) - it("LONG Token0, Update Price, Discount", async () => { let pairId = 0; await printBlockNum(); @@ -430,4 +423,90 @@ contract("OpenLev UniV2", async accounts => { assert.equal(tradeBefore.held, "887336915523826444724"); assertPrint("Insurance of Pool1:", '668250000000000000', (await openLev.markets(pairId)).pool1Insurance); }) + + it("MarginTrade For trader2", async () => { + let pairId = 0; + await printBlockNum(); + // provide some funds for trader and saver + await utils.mint(token1, trader, 10000); + await utils.mint(token1, saver, 10000); + // Trader to approve openLev to spend + let deposit = utils.toWei(400); + await token1.approve(openLev.address, deposit, {from: trader}); + // Saver deposit to pool1 + let saverSupply = utils.toWei(1000); + let pool1 = await LPool.at((await openLev.markets(pairId)).pool1); + await token1.approve(await pool1.address, utils.toWei(1000), {from: saver}); + await pool1.mint(saverSupply, {from: saver}); + let borrow = utils.toWei(500); + m.log("toBorrow from Pool 1: \t", borrow); + await advanceMultipleBlocksAndTime(200); + await openLev.updatePrice(pairId, Uni2DexData, {from: trader2}); + await assertThrows(openLev.marginTradeFor(trader2, pairId, false, true, deposit, borrow, 0, Uni2DexData, {from: trader}), 'OLO'); + await openLev.setOpLimitOrder(trader); + await openLev.marginTradeFor(trader2, pairId, false, true, deposit, borrow, 0, Uni2DexData, {from: trader}); + let tradeBefore = await openLev.activeTrades(trader2, pairId, 0); + m.log("Trade.held:", tradeBefore.held); + assert.equal(tradeBefore.held, "887336915523826444724"); + await openLev.closeTrade(pairId, false, tradeBefore.held, 0, Uni2DexData, {from: trader2}); + let tradeAfter = await openLev.activeTrades(trader2, pairId, 0); + assert.equal(tradeAfter.held, "0"); + m.log("Trader2.balanceOf:", await token1.balanceOf(trader2)); + assert.equal(await token1.balanceOf(trader2), "390651883276774977117"); + // weth + gotPair = await utils.createUniswapV2Pool(uniswapFactory, weth, token1); + await controller.createLPoolPair(weth.address, token1.address, 3000, Uni2DexData); + pairId = 1; + await utils.mint(token1, saver, 1000); + await utils.mint(weth, trader, 1000); + deposit = utils.toWei(1); + saverSupply = utils.toWei(1000); + pool1 = await LPool.at((await openLev.markets(pairId)).pool1); + await token1.approve(await pool1.address, saverSupply, {from: saver}); + await pool1.mint(saverSupply, {from: saver}); + borrow = utils.toWei(1); + m.log("toBorrow from Pool 1: \t", borrow); + m.log("totalBorrow from Pool 1: \t", await pool1.availableForBorrow()); + + await advanceMultipleBlocksAndTime(200); + await openLev.updatePrice(pairId, Uni2DexData, {from: trader2}); + await weth.approve(openLev.address, deposit, {from: trader}); + + await openLev.marginTradeFor(trader2, pairId, false, false, deposit, borrow, 0, Uni2DexData, {from: trader}); + tradeBefore = await openLev.activeTrades(trader2, pairId, 0); + m.log("Trade.held:", tradeBefore.held); + assert.equal(tradeBefore.held.toString(), "1992490060009101709"); + }) + it("CloseTrade For trader2", async () => { + let pairId = 0; + await printBlockNum(); + // provide some funds for trader and saver + await utils.mint(token1, trader, 400); + await utils.mint(token1, saver, 10000); + // Trader to approve openLev to spend + let deposit = utils.toWei(400); + await token1.approve(openLev.address, deposit, {from: trader}); + // Saver deposit to pool1 + let saverSupply = utils.toWei(1000); + let pool1 = await LPool.at((await openLev.markets(pairId)).pool1); + await token1.approve(await pool1.address, utils.toWei(1000), {from: saver}); + await pool1.mint(saverSupply, {from: saver}); + let borrow = utils.toWei(500); + m.log("toBorrow from Pool 1: \t", borrow); + await advanceMultipleBlocksAndTime(200); + await openLev.updatePrice(pairId, Uni2DexData, {from: trader}); + await openLev.marginTrade(pairId, false, true, deposit, borrow, 0, Uni2DexData, {from: trader}); + let tradeBefore = await openLev.activeTrades(trader, pairId, 0); + m.log("Trade.held:", tradeBefore.held); + assert.equal(tradeBefore.held, "887336915523826444724"); + await assertThrows(openLev.closeTradeFor(trader, pairId, false, tradeBefore.held, 0, Uni2DexData, {from: trader2}), 'OLO'); + await openLev.setOpLimitOrder(trader2); + + await openLev.closeTradeFor(trader, pairId, false, tradeBefore.held, 0, Uni2DexData, {from: trader2}) + let tradeAfter = await openLev.activeTrades(trader, pairId, 0); + assert.equal(tradeAfter.held, "0"); + m.log("Trader1.balanceOf:", await token1.balanceOf(trader)); + + assert.equal((await token1.balanceOf(trader)).toString(), "390651431412239210117"); + }) }) diff --git a/test/OpenLevV1UniV3Test1.js b/test/OpenLevV1UniV3Test1.js index bcf727c..f62013c 100644 --- a/test/OpenLevV1UniV3Test1.js +++ b/test/OpenLevV1UniV3Test1.js @@ -157,7 +157,7 @@ contract("OpenLev UniV3", async accounts => { m.log("Trade.deposited:", trade.deposited); m.log("Margin Ratio after deposit:", marginRatio_3.current, marginRatio_3.limit); - assert.equal(marginRatio_3.current.toString(), 12098); // TODO check + assert.equal(marginRatio_3.current.toString(), 12107); // TODO check // Close trade await openLev.closeTrade(0, 0, "821147572990716389330", 0, Uni3DexData, {from: trader}); @@ -491,8 +491,8 @@ contract("OpenLev UniV3", async accounts => { assert.equal(2, market.feesRate); assert.equal(3, market.marginLimit); assert.equal(4, market.priceDiffientRatio); - let dexes = await openLev.getMarketSupportDexs(1); - assert.equal(1, dexes[0]); + // let dexes = await openLev.getMarketSupportDexs(1); + // assert.equal(1, dexes[0]); await assertThrows(openLev.setMarketConfig(1, 2, 3, 4, [1]), 'caller must be admin'); }) @@ -562,6 +562,15 @@ contract("OpenLev UniV3", async accounts => { }) + it("Admin setOpLimitOrder test", async () => { + let {timeLock, openLev} = await instanceSimpleOpenLev(); + let opLimitOrder = timeLock.address; + await timeLock.executeTransaction(openLev.address, 0, 'setOpLimitOrder(address)', + web3.eth.abi.encodeParameters(['address'], [opLimitOrder]), 0); + assert.equal(opLimitOrder, await openLev.opLimitOrder()); + await assertThrows(openLev.setOpLimitOrder(opLimitOrder), 'caller must be admin'); + }) + it("Admin setImplementation test", async () => { openLevV1Lib = await OpenLevV1Lib.new(); await OpenLevV1.link("OpenLevV1Lib", openLevV1Lib.address); diff --git a/test/OpenLevV1UniV3Test2.js b/test/OpenLevV1UniV3Test2.js index b635c13..d919776 100644 --- a/test/OpenLevV1UniV3Test2.js +++ b/test/OpenLevV1UniV3Test2.js @@ -234,7 +234,7 @@ contract("OpenLev UniV3", async accounts => { let ratio = await openLev.marginRatio(trader, 0, 0, Uni3DexData, {from: saver}); m.log("Ratio, current:", ratio.current, "limit", ratio.limit); - assert.equal(7907, ratio.current.toString()); + assert.equal(7908, ratio.current.toString()); // // Partial Close trade let tx_full_close = await openLev.closeTrade(0, 0, "486675826237735294796", 0, Uni3DexData, {from: trader}); @@ -304,7 +304,7 @@ contract("OpenLev UniV3", async accounts => { // Market price change, then check margin ratio let marginRatio_1 = await openLev.marginRatio(trader, 0, 0, Uni3DexData, {from: saver}); m.log("Margin Ratio:", marginRatio_1.current / 100, "%"); - assert.equal(marginRatio_1.current.toString(), 8046); + assert.equal(marginRatio_1.current.toString(), 8052); m.log("Margin Trade Again:", "Deposit=", deposit, "Borrow=", borrow); await token1.approve(openLev.address, deposit, {from: trader}); diff --git a/test/OpenLevV1UniV3Test3.js b/test/OpenLevV1UniV3Test3.js index 257855b..ff2b892 100644 --- a/test/OpenLevV1UniV3Test3.js +++ b/test/OpenLevV1UniV3Test3.js @@ -119,7 +119,7 @@ contract("OpenLev UniV3", async accounts => { let marginRatio_2 = await openLev.marginRatio(trader, 0, 0, Uni3DexData, {from: saver}); m.log("Margin Ratio:", marginRatio_2.current / 100, "%"); - assert.equal(8041, marginRatio_2.current.toString()); + assert.equal(8045, marginRatio_2.current.toString()); // Partial Close trade m.log("Partial Close Trade", 400); @@ -140,7 +140,7 @@ contract("OpenLev UniV3", async accounts => { let ratio = await openLev.marginRatio(trader, 0, 0, Uni3DexData, {from: saver}); m.log("Ratio, current:", ratio.current, "limit", ratio.marketLimit); - assert.equal(7936, ratio.current.toString()); + assert.equal(7964, ratio.current.toString()); // Partial Close trade let tx_full_close = await openLev.closeTrade(0, 0, "493327303890107812554", maxUint(), Uni3DexData, {from: trader}); @@ -199,7 +199,7 @@ contract("OpenLev UniV3", async accounts => { let marginRatio_2 = await openLev.marginRatio(trader, 0, 0, Uni3DexData, {from: saver}); m.log("Margin Ratio:", marginRatio_2.current / 100, "%"); - assert.equal(8041, marginRatio_2.current.toString()); + assert.equal(8045, marginRatio_2.current.toString()); // Partial Close trade m.log("Partial Close Trade", 400); @@ -220,7 +220,7 @@ contract("OpenLev UniV3", async accounts => { let ratio = await openLev.marginRatio(trader, 0, 0, Uni3DexData, {from: saver}); m.log("Ratio, current:", ratio.current, "limit", ratio.marketLimit); - assert.equal(7936, ratio.current.toString()); + assert.equal(7964, ratio.current.toString()); // Partial Close trade let tx_full_close = await openLev.closeTrade(0, 0, "493327303890107812554", maxUint(), Uni3DexData, {from: trader}); diff --git a/test/PayOffTradeTaxTokenTest.js b/test/PayOffTradeTaxTokenTest.js new file mode 100644 index 0000000..47acaf4 --- /dev/null +++ b/test/PayOffTradeTaxTokenTest.js @@ -0,0 +1,270 @@ +const utils = require("./utils/OpenLevUtil"); +const {Uni2DexData, assertThrows} = require("./utils/OpenLevUtil"); +const {advanceMultipleBlocksAndTime, toBN, advanceMultipleBlocks} = require("./utils/EtheUtil"); +const Controller = artifacts.require("ControllerV1"); +const ControllerDelegator = artifacts.require("ControllerDelegator"); +const OpenLevV1 = artifacts.require("OpenLevV1"); +const OpenLevDelegator = artifacts.require("OpenLevDelegator"); +const m = require('mocha-logger'); +const {from} = require("truffle/build/987.bundled"); +const LPool = artifacts.require("LPool"); +const TestToken = artifacts.require("MockERC20"); +const MockTaxToken = artifacts.require("MockTaxToken"); +const UniswapV2Factory = artifacts.require("UniswapV2Factory"); +const UniswapV2Router = artifacts.require("UniswapV2Router02"); +const OpenLevV1Lib = artifacts.require("OpenLevV1Lib") + +// list all cases for tax token since there is no smaller unit to divide. +contract("OpenLev payoff trade tax token", async accounts => { + // components + let openLev; + let ole; + let treasury; + let factory; + let router; + let gotPair; + let dexAgg; + let pool0; + let poolEth; + + // roles + let admin = accounts[0]; + let saver = accounts[1]; + let trader = accounts[2]; + + let dev = accounts[3]; + let liquidator2 = accounts[8]; + let token0; + let delegatee; + let weth; + + let pairId = 0; + + beforeEach(async () => { + weth = await utils.createWETH(); + ole = await TestToken.new('OpenLevERC20', 'OLE'); + factory = await UniswapV2Factory.new("0x0000000000000000000000000000000000000000"); + router = await UniswapV2Router.new(factory.address, weth.address); + token0 = await MockTaxToken.new('TokenA', 'TKA', 5, 2, router.address); + + await web3.eth.sendTransaction({from: accounts[9], to: admin, value: utils.toWei(1)}); + await token0.approve(router.address, utils.toWei(1)); + let block = await web3.eth.getBlock("latest"); + await router.addLiquidityETH(token0.address, utils.toWei(1), utils.toWei(1), utils.toWei(1), admin, block.timestamp + 60, {from: admin, value: utils.toWei(1)}); + + dexAgg = await utils.createEthDexAgg(factory.address, "0x0000000000000000000000000000000000000000", accounts[0]); + xole = await utils.createXOLE(ole.address, admin, dev, dexAgg.address); + + let instance = await Controller.new(); + let controller = await ControllerDelegator.new( + ole.address, + xole.address, + weth.address, + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + dexAgg.address, + "0x01", + admin, + instance.address); + controller = await Controller.at(controller.address); + + openLevV1Lib = await OpenLevV1Lib.new(); + await OpenLevV1.link("OpenLevV1Lib", openLevV1Lib.address); + delegatee = await OpenLevV1.new(); + + openLev = await OpenLevDelegator.new(controller.address, dexAgg.address, [token0.address, weth.address], weth.address, xole.address, [1, 2], accounts[0], delegatee.address); + openLev = await OpenLevV1.at(openLev.address); + await openLev.setCalculateConfig(30, 33, 3000, 5, 25, 25, (30e18) + '', 300, 10, 60); + await controller.setOpenLev(openLev.address); + await controller.setLPoolImplementation((await utils.createLPoolImpl()).address); + await controller.setInterestParam(toBN(90e16).div(toBN(2102400)), toBN(10e16).div(toBN(2102400)), toBN(20e16).div(toBN(2102400)), 50e16 + ''); + await dexAgg.setOpenLev(openLev.address); + let dexData = Uni2DexData + "011170000000011170000000011170000000"; + await controller.createLPoolPair(token0.address, weth.address, 3000, dexData); // 30% margin ratio by default + + market = await openLev.markets(0); + let pool0Address = market.pool0; + let poolEthAddress = market.pool1; + pool0 = await LPool.at(pool0Address); + poolEth = await LPool.at(poolEthAddress); + + await token0.approve(pool0.address, utils.toWei(1)); + await pool0.mint(utils.toWei(1)); + await poolEth.mintEth({from: saver, value: utils.toWei(1)}); + await token0.transfer(trader, utils.toWei(1)); + await token0.approve(openLev.address, utils.toWei(1), {from: trader}); + // set weth to trader + await weth.mint(trader, utils.toWei(1)); + // set tax rate = 7% + await openLev.setTaxRate(pairId, token0.address, 0, 70000); + await advanceMultipleBlocksAndTime(30); + }); + + it("if repay token is a tax Token need pay tax deductions with twice ", async () => { + let deposit = toBN(1e15); + let borrow = toBN(1e15); + m.log("-- marginTrade..."); + await openLev.marginTrade(pairId, true, false, deposit, borrow, 0, Uni2DexData, {from: trader}); + let tradeBefore = await openLev.activeTrades(trader, pairId, 1); + let borrowedBefore = await pool0.borrowBalanceCurrent(trader); + let token0BalanceBefore = await token0.balanceOf(trader); + m.log("current held =", tradeBefore.held); + m.log("current borrowed =", borrowedBefore); + m.log("current tax token balance = ", token0BalanceBefore); + assert.equal(tradeBefore.held.toString(), "1717213647663711"); + assert.equal(borrowedBefore, 1000000000000000); + assert.equal(token0BalanceBefore, 999001927118839757); + + m.log("-- payoffTrade..."); + let payoffTradeTx = await openLev.payoffTrade(pairId, true, {from: trader}); + let tradeAfter = await openLev.activeTrades(trader, pairId, 1); + let borrowedAfter = await pool0.borrowBalanceCurrent(trader); + let token0BalanceAfter = await token0.balanceOf(trader); + m.log("current held =", tradeAfter.held); + m.log("current borrowed =", borrowedAfter); + m.log("current tax token balance = ", token0BalanceAfter); + assert.equal(tradeAfter.held, 0); + assert.equal(borrowedAfter.toString(), '0'); + assert.equal(token0BalanceAfter.toString(), "997846836928621941"); + + m.log("-- check event..."); + let depositToken = payoffTradeTx.logs[0].args.depositToken; + let depositDecrease = payoffTradeTx.logs[0].args.depositDecrease; + let closeAmount = payoffTradeTx.logs[0].args.closeAmount; + assert.equal(depositToken, false); + assert.equal(depositDecrease.toString(), "924210463605232"); + assert.equal(closeAmount.toString(), "1717213647663711"); + }) + + it("if transfer in with tax token amount can't pay it all off, will fail", async () => { + let deposit = toBN(1e15); + let borrow = toBN(1e15); + m.log("-- marginTrade..."); + await openLev.marginTrade(pairId, true, false, deposit, borrow, 0, Uni2DexData, {from: trader}); + let tradeBefore = await openLev.activeTrades(trader, pairId, 1); + let borrowedBefore = await pool0.borrowBalanceCurrent(trader); + let token0BalanceBefore = await token0.balanceOf(trader); + m.log("current held =", tradeBefore.held); + m.log("current borrowed =", borrowedBefore); + m.log("current tax token balance = ", token0BalanceBefore); + assert.equal(tradeBefore.held.toString(), "1717213647663711"); + assert.equal(borrowedBefore, 1000000000000000); + assert.equal(token0BalanceBefore, 999001927118839757); + + let transferOutAmount = toBN(997900000000000000); + m.log("transfer out tak token,amount = ", transferOutAmount); + await token0.transfer(saver, transferOutAmount, {from: trader}); + let token0AfterTransferOutBalance = await token0.balanceOf(trader); + m.log("current tax token balance =", token0AfterTransferOutBalance) + assert.equal(token0AfterTransferOutBalance, 1102477199838617); + + m.log("-- payoffTrade..."); + await assertThrows(openLev.payoffTrade(pairId, true, {from: trader}), 'TFF'); + m.log("payoffTrade fail --- TFF, test pass.") + }) + + it("if repay token is eth, repay weth will fail", async () => { + let deposit = toBN(1e16); + let borrow = toBN(1e16); + + m.log("-- marginTrade..."); + await openLev.marginTrade(pairId, false, true, deposit, borrow, 0, Uni2DexData, {from: trader, value: deposit}); + let tradeBefore = await openLev.activeTrades(trader, pairId, 0); + let borrowedBefore = await poolEth.borrowBalanceCurrent(trader); + let token0BalanceBefore = await token0.balanceOf(trader); + let wethBalance = await weth.balanceOf(trader); + + m.log("current held =", tradeBefore.held); + m.log("current borrowed =", borrowedBefore); + m.log("current token0 balance = ", token0BalanceBefore); + m.log("current weth balance =", wethBalance); + assert.equal(tradeBefore.held.toString(), "18128352683015392"); + assert.equal(borrowedBefore, 10000000000000000); + assert.equal(token0BalanceBefore, 1000009746426173664); + + await assertThrows(openLev.payoffTrade(pairId, false, {from: trader}), 'IRP'); + }) + + it("if repay token is eth, repay current borrow Amount will fail", async () => { + let deposit = toBN(1e16); + let borrow = toBN(1e16); + + m.log("-- marginTrade..."); + await openLev.marginTrade(pairId, false, true, deposit, borrow, 0, Uni2DexData, {from: trader, value: deposit}); + let tradeBefore = await openLev.activeTrades(trader, pairId, 0); + let borrowed = await poolEth.borrowBalanceCurrent(trader); + let token0Balance = await token0.balanceOf(trader); + let ethBalance = await web3.eth.getBalance(trader); + + m.log("current held =", tradeBefore.held); + m.log("current borrowed =", borrowed); + m.log("current token0 balance = ", token0Balance); + m.log("current eth balance =", ethBalance); + assert.equal(tradeBefore.held.toString(), "18128352683015392"); + assert.equal(borrowed, 10000000000000000); + assert.equal(token0Balance, 1000009746426173664); + + await advanceMultipleBlocks(1); + m.log("advance 1 blocks"); + await assertThrows(openLev.payoffTrade(pairId, false, {from: trader, value: borrowed}), 'IRP'); + }) + + it("if repay token is eth, need to repay 1/100000 more of the borrow amount, and received tax token is less than held", async () => { + let deposit = toBN(1e16); + let borrow = toBN(1e16); + let token0Balance = await token0.balanceOf(trader); + m.log("current token0 balance = ", token0Balance); + + m.log("-- marginTrade..."); + await openLev.marginTrade(pairId, false, true, deposit, borrow, 0, Uni2DexData, {from: trader, value: deposit}); + let tradeBefore = await openLev.activeTrades(trader, pairId, 0); + let borrowedBefore = await poolEth.borrowBalanceCurrent(trader); + let token0BalanceBefore = await token0.balanceOf(trader); + let wethBalanceBefore = await weth.balanceOf(trader); + let ethBalanceBefore = await web3.eth.getBalance(trader); + + m.log("current held =", tradeBefore.held); + m.log("current borrowed =", borrowedBefore); + m.log("current token0 balance = ", token0BalanceBefore); + m.log("current weth balance = ", wethBalanceBefore); + m.log("trader eth balance = ", ethBalanceBefore); + assert.equal(tradeBefore.held.toString(), "18128352683015392"); + assert.equal(borrowedBefore, 10000000000000000); + assert.equal(token0BalanceBefore, 1000009746426173664); + + m.log("-- payoffTrade..."); + let borrowReturn = toBN(borrowedBefore * (1 + 1e-5)) + m.log("transfer eth amount = ", borrowReturn); + let gas_price = 10000000000; + let payoffTradeTx = await openLev.payoffTrade(pairId, false, {from: trader, value: borrowReturn, gasPrice: gas_price}); + let tradeAfter = await openLev.activeTrades(trader, pairId, 0); + let borrowedAfter = await poolEth.borrowBalanceCurrent(trader); + let token0BalanceAfter = await token0.balanceOf(trader); + let wethBalanceAfter = await weth.balanceOf(trader); + let ethBalanceAfter = await web3.eth.getBalance(trader); + + m.log("current held =", tradeAfter.held); + m.log("current borrowed =", borrowedAfter); + m.log("current token0 balance = ", token0BalanceAfter); + m.log("current weth balance = ", wethBalanceAfter); + m.log("trader eth balance = ", ethBalanceAfter); + assert.equal(tradeAfter.held, 0); + assert.equal(borrowedAfter.toString(), '0'); + assert.equal(token0BalanceAfter.toString(), "1016878331585893332"); + assert.equal(wethBalanceAfter.toString(), wethBalanceBefore.toString()); + m.log("weth amount of before payoffTrade equals to after payoffTrade. ") + let gasUsed = payoffTradeTx.receipt.gasUsed; + m.log("payoffTrade gas used = ", gasUsed * gas_price); + assert.equal((toBN(ethBalanceBefore).sub(toBN(ethBalanceAfter))).toString(), ((toBN(gasUsed).mul(toBN(gas_price))).add(toBN(borrowReturn))).toString()); + m.log("eth balance of after payoffTrade = balance of before payoffTrade + gas used + repay amount") + + m.log("-- check event..."); + let depositToken = payoffTradeTx.logs[0].args.depositToken; + let depositDecrease = payoffTradeTx.logs[0].args.depositDecrease; + let closeAmount = payoffTradeTx.logs[0].args.closeAmount; + assert.equal(depositToken, true); + assert.equal(depositDecrease.toString(), "9940000000000000"); + assert.equal(closeAmount.toString(), "18128352683015392"); + }) + +}) \ No newline at end of file diff --git a/test/PayOffTradeTest.js b/test/PayOffTradeTest.js new file mode 100644 index 0000000..ea10321 --- /dev/null +++ b/test/PayOffTradeTest.js @@ -0,0 +1,174 @@ +const utils = require("./utils/OpenLevUtil"); +const { + last8, + Uni2DexData, + assertThrows, +} = require("./utils/OpenLevUtil"); +const {advanceMultipleBlocksAndTime, toBN} = require("./utils/EtheUtil"); +const OpenLevV1 = artifacts.require("OpenLevV1"); +const OpenLevDelegator = artifacts.require("OpenLevDelegator"); +const TestToken = artifacts.require("MockERC20"); +const m = require('mocha-logger'); +const LPool = artifacts.require("LPool"); +const OpenLevV1Lib = artifacts.require("OpenLevV1Lib") + +contract("OpenLev payoff trade", async accounts => { + + // components + let openLev; + let ole; + let treasury; + let uniswapFactory; + let gotPair; + let dexAgg; + // roles + let admin = accounts[0]; + let saver = accounts[1]; + let trader = accounts[2]; + let dev = accounts[3]; + let token0; + let token1; + let controller; + let delegatee; + let weth; + + beforeEach(async () => { + + // runs once before the first test in this block + controller = await utils.createController(admin); + m.log("Created Controller", last8(controller.address)); + + ole = await TestToken.new('OpenLevERC20', 'OLE'); + token0 = await TestToken.new('TokenA', 'TKA'); + token1 = await TestToken.new('TokenB', 'TKB'); + weth = await utils.createWETH(); + + uniswapFactory = await utils.createUniswapV2Factory(); + gotPair = await utils.createUniswapV2Pool(uniswapFactory, token0, token1); + dexAgg = await utils.createEthDexAgg(uniswapFactory.address, "0x0000000000000000000000000000000000000000", accounts[0]); + xole = await utils.createXOLE(ole.address, admin, dev, dexAgg.address); + openLevV1Lib = await OpenLevV1Lib.new(); + await OpenLevV1.link("OpenLevV1Lib", openLevV1Lib.address); + delegatee = await OpenLevV1.new(); + + openLev = await OpenLevDelegator.new(controller.address, dexAgg.address, [token0.address, token1.address], weth.address, xole.address, [1, 2], accounts[0], delegatee.address); + openLev = await OpenLevV1.at(openLev.address); + await openLev.setCalculateConfig(30, 33, 3000, 5, 25, 25, (30e18) + '', 300, 10, 60); + await controller.setOpenLev(openLev.address); + await controller.setLPoolImplementation((await utils.createLPoolImpl()).address); + await controller.setInterestParam(toBN(90e16).div(toBN(2102400)), toBN(10e16).div(toBN(2102400)), toBN(20e16).div(toBN(2102400)), 50e16 + ''); + await dexAgg.setOpenLev(openLev.address); + + let createPoolTx = await controller.createLPoolPair(token0.address, token1.address, 3000, Uni2DexData); // 30% margin ratio by default + m.log("Create Market Gas Used: ", createPoolTx.receipt.gasUsed); + }); + + it("current held is zero, transaction fail ", async () => { + let pairId = 0; + await utils.mint(token1, trader, 10000); + let saverSupply = utils.toWei(1000); + let pool1 = await LPool.at((await openLev.markets(0)).pool1); + await token1.approve(pool1.address, utils.toWei(10000), {from: trader}); + await token1.approve(openLev.address, utils.toWei(10000), {from: trader}); + await pool1.mint(saverSupply, {from: trader}); + m.log("mint token1 to pool1, amount = ", saverSupply) + await advanceMultipleBlocksAndTime(1000); + await openLev.updatePrice(pairId, Uni2DexData); + m.log("updatePrice ---"); + + let deposit = utils.toWei(1); + let borrow = utils.toWei(1); + await openLev.marginTrade(pairId, false, true, deposit, borrow, 0, Uni2DexData, {from: trader}); + let tradeBefore = await openLev.activeTrades(trader, pairId, 0); + m.log("finish marginTrade, current held = ", tradeBefore.held) + assert.equal(tradeBefore.held.toString(), "1987978478630008709"); + + await openLev.closeTrade(pairId, false, tradeBefore.held, 0, Uni2DexData, {from: trader}); + let tradeAfter = await openLev.activeTrades(trader, 0, 0); + m.log("finish closeTrade, current held = ", tradeAfter.held) + assert.equal(tradeAfter.held, 0); + m.log("start payoffTrade, current held is zero ---") + await assertThrows(openLev.payoffTrade(pairId, false, {from: trader}), 'HI0'); + m.log("payoffTrade fail --- HI0, test pass.") + }) + + it("not enough to repay current borrow, transaction fail ", async () => { + let pairId = 0; + await utils.mint(token1, trader, 1001); + m.log("mint 1001 amount token1 to trader") + let saverSupply = utils.toWei(1000); + let pool1 = await LPool.at((await openLev.markets(0)).pool1); + await token1.approve(pool1.address, utils.toWei(10000), {from: trader}); + await token1.approve(openLev.address, utils.toWei(10000), {from: trader}); + await pool1.mint(saverSupply, {from: trader}); + m.log("trader mint 1000 token1 to pool1") + m.log("trader token1 balance = ", utils.toETH(await token1.balanceOf(trader))); + await advanceMultipleBlocksAndTime(1000); + await openLev.updatePrice(pairId, Uni2DexData); + m.log("updatePrice ---"); + + let deposit = utils.toWei(1); + let borrow = utils.toWei(1); + m.log("start marginTrade, deposit token1 amount = ", utils.toETH(deposit)) + await openLev.marginTrade(pairId, false, true, deposit, borrow, 0, Uni2DexData, {from: trader}); + m.log("finish marginTrade, trader current token1 balance is ---", utils.toETH(await token1.balanceOf(trader))) + await assertThrows(openLev.payoffTrade(pairId, false, {from: trader}), 'TFF'); + m.log("payoffTrade fail --- TFF, test pass.") + }) + + it("after payoff trade finished, account current borrow and held is zero, receive held token ", async () => { + let pairId = 0; + await utils.mint(token1, trader, 10000); + let saverSupply = utils.toWei(1000); + let pool1 = await LPool.at((await openLev.markets(0)).pool1); + await token1.approve(pool1.address, utils.toWei(10000), {from: trader}); + await token1.approve(openLev.address, utils.toWei(10000), {from: trader}); + await pool1.mint(saverSupply, {from: trader}); + await advanceMultipleBlocksAndTime(1000); + await openLev.updatePrice(pairId, Uni2DexData); + m.log("updatePrice ---"); + + let deposit = utils.toWei(1); + let borrow = utils.toWei(1); + await openLev.marginTrade(pairId, false, true, deposit, borrow, 0, Uni2DexData, {from: trader}); + + let tradeBefore = await openLev.activeTrades(trader, pairId, 0); + let borrowedBefore = utils.toETH(await pool1.borrowBalanceCurrent(trader)); + let token0BalanceBefore = utils.toETH(await token0.balanceOf(trader)); + let token1BalanceBefore = utils.toETH(await token1.balanceOf(trader)); + m.log("before payoffTrade ---"); + m.log("current held =", tradeBefore.held); + m.log("current borrowed =", borrowedBefore); + m.log("current token0 balance = ", token0BalanceBefore); + m.log("current token1 balance = ", token1BalanceBefore); + assert.equal(tradeBefore.held.toString(), "1987978478630008709"); + assert.equal(borrowedBefore, 1); + assert.equal(token0BalanceBefore, 0); + assert.equal(token1BalanceBefore, 8999); + + let payoffTradeTx = await openLev.payoffTrade(pairId, false, {from: trader}); + + let tradeAfter = await openLev.activeTrades(trader, 0, 0); + let borrowedAfter = await pool1.borrowBalanceCurrent(trader); + let token0BalanceAfter = await token0.balanceOf(trader); + let token1BalanceAfter = await token1.balanceOf(trader); + m.log("after payoffTrade ---"); + m.log("current held =", tradeAfter.held); + m.log("current borrowed =", borrowedAfter); + m.log("current token0 balance = ", token0BalanceAfter); + m.log("current token1 balance = ", token1BalanceAfter); + assert.equal(tradeAfter.held, 0); + assert.equal(borrowedAfter, 0); + assert.equal(token0BalanceAfter, 1987978478630008709); + assert.equal(token1BalanceAfter, 8997999999571870243534); + + m.log("-- check event..."); + let depositToken = payoffTradeTx.logs[0].args.depositToken; + let depositDecrease = payoffTradeTx.logs[0].args.depositDecrease; + let closeAmount = payoffTradeTx.logs[0].args.closeAmount; + assert.equal(depositToken, true); + assert.equal(depositDecrease.toString(), "994000000000000000"); + assert.equal(closeAmount.toString(), "1987978478630008709"); + }) + +}) \ No newline at end of file diff --git a/test/integration/wholeProcessTest.js b/test/integration/wholeProcessTest.js index d8bf5e1..13c9a70 100644 --- a/test/integration/wholeProcessTest.js +++ b/test/integration/wholeProcessTest.js @@ -12,110 +12,109 @@ const XOLE = artifacts.require("XOLEDelegator"); let network; contract("OpenLev integration test ", async accounts => { - before(async () => { - network = process.env.NETWORK - }); - it("trade test", async () => { - if (network != 'integrationTest') { - console.log("Ignore swap test") - return; - } + before(async () => { + network = process.env.NETWORK + }); + it("trade test", async () => { + if (network != 'integrationTest') { + console.log("Ignore swap test") + return; + } - console.log("starting...."); - let marketId = 7; - let developer = accounts[0]; - let openLev = await OpenLevV1.at(OpenLevV1.address); - let controller = await Controller.at(Controller.address); - let treasury = await XOLE.at(XOLE.address); - let markets = await openLev.markets(marketId); - let pool0 = await LPool.at(markets.pool0); - let pool1 = await LPool.at(markets.pool1); - // - let token0 = await MockERC20.at(await pool0.underlying()); - let token1 = await MockERC20.at(await pool1.underlying()); - // - m.log("openLev=", openLev.address); - m.log("controller=", controller.address); - m.log("treasury=", treasury.address); - m.log("pool0=", pool0.address); - m.log("pool1=", pool1.address); - m.log("token0=", token0.address); - m.log("token1=", token1.address); - token0.mint(accounts[0], await utils.toWei(1000)); - token1.mint(accounts[0], await utils.toWei(1000)); - let uniV2 = "0x01"; - /** - * lpool supply - */ - // let rewardStartByBorrow = await controller.earned(pool1.address, developer, true); - // let rewardStartBySupply = await controller.earned(pool1.address, developer, false); + console.log("starting...."); + let marketId = 7; + let developer = accounts[0]; + let openLev = await OpenLevV1.at(OpenLevV1.address); + let controller = await Controller.at(Controller.address); + let treasury = await XOLE.at(XOLE.address); + let markets = await openLev.markets(marketId); + let pool0 = await LPool.at(markets.pool0); + let pool1 = await LPool.at(markets.pool1); + // + let token0 = await MockERC20.at(await pool0.underlying()); + let token1 = await MockERC20.at(await pool1.underlying()); + // + m.log("openLev=", openLev.address); + m.log("controller=", controller.address); + m.log("treasury=", treasury.address); + m.log("pool0=", pool0.address); + m.log("pool1=", pool1.address); + m.log("token0=", token0.address); + m.log("token1=", token1.address); + token0.mint(accounts[0], await utils.toWei(1000)); + token1.mint(accounts[0], await utils.toWei(1000)); + let uniV2 = "0x01"; + /** + * lpool supply + */ + // let rewardStartByBorrow = await controller.earned(pool1.address, developer, true); + // let rewardStartBySupply = await controller.earned(pool1.address, developer, false); - utils.resetStep(); - utils.step("lpool supply"); - await token1.approve(pool1.address, maxUint()); - let pool1BalanceBeforeSupply = await token1.balanceOf(pool1.address); - m.log("pool1BalanceBeforeSupply=", pool1BalanceBeforeSupply.toString()); - let supplyAmount = await utils.toWei(10); - await pool1.mint(supplyAmount); - let pool1BalanceAfterSupply = await token1.balanceOf(pool1.address); - m.log("pool1BalanceAfterSupply=", pool1BalanceAfterSupply.toString()); - assert.equal(pool1BalanceAfterSupply.sub(pool1BalanceBeforeSupply).toString(), supplyAmount.toString()); - while (await openLev.shouldUpdatePrice(marketId, uniV2) == true) { - m.log("update price..."); - await openLev.updatePrice(marketId, uniV2); - } - utils.step("openLev open margin trade 1"); - let deposit = await utils.toWei(10); - let borrow = await utils.toWei(2); - await token0.approve(openLev.address, maxUint()); - await token1.approve(openLev.address, maxUint()); + utils.resetStep(); + utils.step("lpool supply"); + await token1.approve(pool1.address, maxUint()); + let pool1BalanceBeforeSupply = await token1.balanceOf(pool1.address); + m.log("pool1BalanceBeforeSupply=", pool1BalanceBeforeSupply.toString()); + let supplyAmount = await utils.toWei(10); + await pool1.mint(supplyAmount); + let pool1BalanceAfterSupply = await token1.balanceOf(pool1.address); + m.log("pool1BalanceAfterSupply=", pool1BalanceAfterSupply.toString()); + assert.equal(pool1BalanceAfterSupply.sub(pool1BalanceBeforeSupply).toString(), supplyAmount.toString()); + m.log("update price..."); + await openLev.updatePrice(marketId, uniV2); - await openLev.marginTrade(marketId, false, false, deposit, borrow, 0, uniV2); - let activeTrade1 = await openLev.activeTrades(developer, marketId, false); - m.log("open trades1=", JSON.stringify(activeTrade1)); - /** - * openLev open margin trade 2 - */ - utils.step("openLev open margin trade 2"); - await openLev.marginTrade(marketId, false, false, deposit, borrow, 0, uniV2); - let activeTrade2 = await openLev.activeTrades(developer, marketId, false); - m.log("open trades1=", JSON.stringify(activeTrade2)); - // let rewardAfterByBorrow = await controller.earned(pool1.address, developer, true); - /** - * openLev close margin trade half - */ - utils.step("openLev close margin trade half"); - let borrowsBeforeClose = await pool1.borrowBalanceStored(developer); - let treasuryBeforeClose = await token0.balanceOf(treasury.address); - await openLev.closeTrade(marketId, false, toBN(activeTrade2[1]).div(toBN(2)), 0, uniV2); - let closeTrade = await openLev.activeTrades(developer, marketId, false); - m.log("close trades=", JSON.stringify(closeTrade)); - let borrowsAfterClose = await pool1.borrowBalanceStored(developer); - let treasuryAfterClose = await token0.balanceOf(treasury.address); - m.log("borrowsBeforeClose=", borrowsBeforeClose.toString()); - m.log("borrowsAfterClose=", borrowsAfterClose.toString()); - m.log("treasuryBeforeClose=", treasuryBeforeClose.toString()); - m.log("treasuryAfterClose=", treasuryAfterClose.toString()); + utils.step("openLev open margin trade 1"); + let deposit = await utils.toWei(10); + let borrow = await utils.toWei(2); + await token0.approve(openLev.address, maxUint()); + await token1.approve(openLev.address, maxUint()); - utils.step("checking borrows and treasury after closed..."); - assert.equal(toBN(borrowsBeforeClose).cmp(toBN(borrowsAfterClose)) > 0, true); - assert.equal(toBN(treasuryAfterClose).cmp(toBN(treasuryBeforeClose)) > 0, true); + await openLev.marginTrade(marketId, false, false, deposit, borrow, 0, uniV2); + let activeTrade1 = await openLev.activeTrades(developer, marketId, false); + m.log("open trades1=", JSON.stringify(activeTrade1)); + /** + * openLev open margin trade 2 + */ + utils.step("openLev open margin trade 2"); + await openLev.marginTrade(marketId, false, false, deposit, borrow, 0, uniV2); + let activeTrade2 = await openLev.activeTrades(developer, marketId, false); + m.log("open trades1=", JSON.stringify(activeTrade2)); + // let rewardAfterByBorrow = await controller.earned(pool1.address, developer, true); + /** + * openLev close margin trade half + */ + utils.step("openLev close margin trade half"); + let borrowsBeforeClose = await pool1.borrowBalanceStored(developer); + let treasuryBeforeClose = await token0.balanceOf(treasury.address); + await openLev.closeTrade(marketId, false, toBN(activeTrade2[1]).div(toBN(2)), 0, uniV2); + let closeTrade = await openLev.activeTrades(developer, marketId, false); + m.log("close trades=", JSON.stringify(closeTrade)); + let borrowsAfterClose = await pool1.borrowBalanceStored(developer); + let treasuryAfterClose = await token0.balanceOf(treasury.address); + m.log("borrowsBeforeClose=", borrowsBeforeClose.toString()); + m.log("borrowsAfterClose=", borrowsAfterClose.toString()); + m.log("treasuryBeforeClose=", treasuryBeforeClose.toString()); + m.log("treasuryAfterClose=", treasuryAfterClose.toString()); - /** - * supply & lender OLE reward - */ - // let rewardAfterBySupply = await controller.earned(pool1.address, developer, false); - // - // m.log("rewardStartByBorrow=", rewardStartByBorrow.toString()); - // m.log("rewardAfterByBorrow=", rewardAfterByBorrow.toString()); - // m.log("rewardStartBySupply=", rewardStartBySupply.toString()); - // m.log("rewardAfterBySupply=", rewardAfterBySupply.toString()); + utils.step("checking borrows and treasury after closed..."); + assert.equal(toBN(borrowsBeforeClose).cmp(toBN(borrowsAfterClose)) > 0, true); + assert.equal(toBN(treasuryAfterClose).cmp(toBN(treasuryBeforeClose)) > 0, true); - // utils.step("checking borrow & supply OLE rewards..."); - // assert.equal(toBN(rewardAfterByBorrow).cmp(toBN(rewardStartByBorrow)) > 0, true); - // assert.equal(toBN(rewardAfterBySupply).cmp(toBN(rewardStartBySupply)) > 0, true); + /** + * supply & lender OLE reward + */ + // let rewardAfterBySupply = await controller.earned(pool1.address, developer, false); + // + // m.log("rewardStartByBorrow=", rewardStartByBorrow.toString()); + // m.log("rewardAfterByBorrow=", rewardAfterByBorrow.toString()); + // m.log("rewardStartBySupply=", rewardStartBySupply.toString()); + // m.log("rewardAfterBySupply=", rewardAfterBySupply.toString()); - utils.step("ending..."); + // utils.step("checking borrow & supply OLE rewards..."); + // assert.equal(toBN(rewardAfterByBorrow).cmp(toBN(rewardStartByBorrow)) > 0, true); + // assert.equal(toBN(rewardAfterBySupply).cmp(toBN(rewardStartBySupply)) > 0, true); - }) + utils.step("ending..."); + + }) }) diff --git a/test/utils/EtheUtil.js b/test/utils/EtheUtil.js index 524dbe3..0cbf0ab 100644 --- a/test/utils/EtheUtil.js +++ b/test/utils/EtheUtil.js @@ -163,6 +163,17 @@ async function advanceMultipleBlocksAndTime(total) { } } +async function advanceMultipleBlocksAndAssignTime(total,time) { + let remain = total; + while (remain > 0) { + if (remain % 1000 == 0) { + m.log("Advancing", total - remain, "/", total, "blocks ..."); + } + await timeMachine.advanceTimeAndBlock(time); + remain--; + } +} + function sleep(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); @@ -195,6 +206,7 @@ module.exports = { advanceMultipleBlocks, advanceBlockAndSetTime, advanceMultipleBlocksAndTime, + advanceMultipleBlocksAndAssignTime, advanceBlocks, blockNumber, freezeTime, diff --git a/test/utils/OpenLevUtil.js b/test/utils/OpenLevUtil.js index 735b7a6..731fb13 100644 --- a/test/utils/OpenLevUtil.js +++ b/test/utils/OpenLevUtil.js @@ -2,6 +2,8 @@ const {toBN, maxUint} = require("./EtheUtil"); const LPoolDelegator = artifacts.require("LPoolDelegator"); const LPool = artifacts.require('LPool'); +const LTimePool = artifacts.require('LTimePool'); + const Controller = artifacts.require('ControllerV1'); const ControllerDelegator = artifacts.require('ControllerDelegator'); const TestToken = artifacts.require("MockERC20"); @@ -147,6 +149,26 @@ exports.createPool = async (tokenSymbol, controller, admin, wethToken) => { }; } +exports.createTimePool = async (tokenSymbol, controller, admin, wethToken) => { + let testToken = wethToken ? wethToken : await TestToken.new('Test Token: ' + tokenSymbol, tokenSymbol); + let erc20Delegate = await LTimePool.new(); + let pool = await LPoolDelegator.new(); + await pool.initialize(testToken.address, wethToken ? true : false, + controller.address, + toBN(5e16).div(toBN(31536000)), toBN(10e16).div(toBN(31536000)), toBN(20e16).div(toBN(31536000)), 50e16 + '', + 1e18 + '', + 'TestPool', + 'TestPool', + 18, + admin, + erc20Delegate.address); + return { + 'token': testToken, + 'controller': controller, + 'pool': await LTimePool.at(pool.address) + }; +} + exports.mint = async (token, to, amount) => { await token.mint(to, toBN(amount).mul(toBN(1e18)).toString()); } @@ -215,6 +237,17 @@ exports.approxAssertPrint = (desc, expected, value) => { "diff=" + diff + " diff/expectedNum=" + diff / expectedNum); } +exports.approxPrecisionAssertPrint = (desc, expected, value, precision) => { + m.log(desc, "approx equals to:", value); + let expectedNum = Number(expected); + let valueNum = Number(value); + let diff = expectedNum > valueNum ? expectedNum - valueNum : valueNum - expectedNum; + let diffLimit = Math.pow(0.1, precision); + m.log("approxPrecisionAssertPrint expectedNum, valueNum, diff/expectedNum, diffLimit", expectedNum, valueNum, (diff / expectedNum), diffLimit); + assert((diff / expectedNum) < diffLimit, "Diff is too big. expectedNum=" + expectedNum + " valueNum=" + valueNum + " " + + "diff=" + diff + " diff/expectedNum=" + diff / expectedNum+ " diffLimit=" + diffLimit); +} + let currentStep; exports.resetStep = () => { diff --git a/truffle-config.js b/truffle-config.js index e8a4d8f..d146478 100644 --- a/truffle-config.js +++ b/truffle-config.js @@ -17,7 +17,6 @@ * */ -const { bscTestnet } = require("./migrations/util"); // const HDWalletProvider = require('@truffle/hdwallet-provider'); // const infuraKey = "fj4jll3k....."; @@ -46,12 +45,7 @@ module.exports = { host: "127.0.0.1", // Localhost (default: none) port: 8545, // Standard Ethereum port (default: none) network_id: "*", - disableConfirmationListener: true - }, - local: { - host: "127.0.0.1", // Localhost (default: none) - port: 7545, // Standard Ethereum port (default: none) - network_id: "*", + gas : 8000000, disableConfirmationListener: true }, diff --git a/verify-script.sh b/verify-script.sh index c1167c1..08ab1b4 100644 --- a/verify-script.sh +++ b/verify-script.sh @@ -3,20 +3,21 @@ echo starting verify contact network $1. truffle run verify ControllerV1 --network $1 -truffle run verify EthDexAggregatorV1 --network $1 -truffle run verify LPool --network $1 +truffle run verify CronosDexAggregatorV1 --network $1 +truffle run verify LTimePool --network $1 truffle run verify LPoolDepositor --network $1 +truffle run verify LPoolDepositorDelegator --network $1 truffle run verify OpenLevV1 --network $1 +truffle run verify OpenLevV1Lib --network $1 + truffle run verify QueryHelper --network $1 truffle run verify Timelock --network $1 truffle run verify XOLE --network $1 truffle run verify ControllerDelegator --network $1 truffle run verify DexAggregatorDelegator --network $1 -truffle run verify LPoolDelegator --network $1 -truffle run verify LPoolDepositor --network $1 truffle run verify OpenLevDelegator --network $1 truffle run verify XOLEDelegator --network $1 - +truffle run verify Airdrop --network $1 truffle run verify OLEToken --network $1 @@ -28,3 +29,5 @@ truffle run verify OLETokenLock --network $1 truffle run verify FarmingPool --network $1 echo finished verify contact network $1. + +