diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index ac1efd1..2f52619 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, retroactive_airdrop_linear_releases ] pull_request: branches: [ main ] @@ -29,7 +29,7 @@ jobs: node-version: ${{ matrix.node-version }} cache: 'npm' - run: npm init -y - - run: npm install truffle -g + - run: npm install truffle@5.5.2 -g - run: npm install ganache-cli -g - run: npm install - run: truffle compile diff --git a/CONTRACTS.md b/CONTRACTS.md index 1e9592b..9080955 100644 --- a/CONTRACTS.md +++ b/CONTRACTS.md @@ -36,4 +36,19 @@ OpenLeverage source code is maintained by the Openleverage development team at [ | **TimeLock** | [`0x7B041bD2f676EEd1ab22A17c9eD5b6b3cb794673`](https://bscscan.com/address/0x7B041bD2f676EEd1ab22A17c9eD5b6b3cb794673) | https://github.com/OpenLeverageDev/openleverage-contracts/blob/main/contracts/gov/Timelock.sol | | **ControllerDelegator** | [`0x912C0462474c933499a6923a6Afc6AC1E58b8Ce4`](https://bscscan.com/address/0x912C0462474c933499a6923a6Afc6AC1E58b8Ce4) | https://github.com/OpenLeverageDev/openleverage-contracts/blob/v1.0.5-bsc/contracts/ControllerDelegator.sol | | **DexAggregatorDelegator** | [`0xE9E321D1cb6b540E922a5e4d8720FEed0749e93F`](https://bscscan.com/address/0xE9E321D1cb6b540E922a5e4d8720FEed0749e93F) | https://github.com/OpenLeverageDev/openleverage-contracts/blob/v1.0.5-bsc/contracts/dex/DexAggregatorDelegator.sol | -| **OpenLevDelegator** | [`0x6A75aC4b8d8E76d15502E69Be4cb6325422833B4`](https://bscscan.com/address/0x6A75aC4b8d8E76d15502E69Be4cb6325422833B4) | https://github.com/OpenLeverageDev/openleverage-contracts/blob/v1.0.5-bsc/contracts/OpenLevDelegator.sol | \ No newline at end of file +| **OpenLevDelegator** | [`0x6A75aC4b8d8E76d15502E69Be4cb6325422833B4`](https://bscscan.com/address/0x6A75aC4b8d8E76d15502E69Be4cb6325422833B4) | https://github.com/OpenLeverageDev/openleverage-contracts/blob/v1.0.5-bsc/contracts/OpenLevDelegator.sol | + +## KuCoin Community Chain +| Contract | Address | Source Code | +|-----------------------------|------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------| +| **ControllerV1** | [`0x01Af643dD8dD41ed1F1066FAc465cc8bfc52D6EB`](https://scan.kcc.io/address/0x01Af643dD8dD41ed1F1066FAc465cc8bfc52D6EB) | https://github.com/OpenLeverageDev/openleverage-contracts/blob/v1.0.7-kcc/contracts/ControllerV1.sol | +| **kccDexAggregatorV1** | [`0x2Ff5BDEE4EeFd7a3a052dAE9469dDA22f364d686`](https://scan.kcc.io/address/0x2Ff5BDEE4EeFd7a3a052dAE9469dDA22f364d686) | https://github.com/OpenLeverageDev/openleverage-contracts/blob/v1.0.7-kcc/contracts/dex/kcc/KccDexAggregatorV1.sol | +| **LPool** | [`0x15d530E620Da0BACdDBDea7fb37eaaaBB4ae1d07`](https://scan.kcc.io/address/0x15d530E620Da0BACdDBDea7fb37eaaaBB4ae1d07) | https://github.com/OpenLeverageDev/openleverage-contracts/blob/v1.0.7-kcc/contracts/liquidity/LPool.sol | +| **LPoolDepositor** | [`0xC7C3333f713eB2b100Dc6bdC9F79FA43a2A5dDc8`](https://scan.kcc.io/address/0xC7C3333f713eB2b100Dc6bdC9F79FA43a2A5dDc8) | https://github.com/OpenLeverageDev/openleverage-contracts/blob/v1.0.7-kcc/contracts/liquidity/LPoolDepositor.sol | +| **LPoolDepositorDelegator** | [`0xCC25Bcd56E425B13AbB6C8C653C50A1F6Adb7b18`](https://scan.kcc.io/address/0xCC25Bcd56E425B13AbB6C8C653C50A1F6Adb7b18) | https://github.com/OpenLeverageDev/openleverage-contracts/blob/v1.0.7-kcc/contracts/liquidity/LPoolDepositor.sol | +| **OpenLevV1** | [`0x7C0d5BF86427C02F6A841E28Ff7cF2934AA83B34`](https://scan.kcc.io/address/0x7C0d5BF86427C02F6A841E28Ff7cF2934AA83B34) | https://github.com/OpenLeverageDev/openleverage-contracts/blob/v1.0.7-kcc/contracts/OpenLevV1.sol | +| **QueryHelper** | [`0xbd51d161fb4109f2cde5b430ace615fc83062273`](https://scan.kcc.io/address/0xbd51d161fb4109f2cde5b430ace615fc83062273) | https://github.com/OpenLeverageDev/openleverage-contracts/blob/v1.0.7-kcc/contracts/periphery/QueryHelper.sol | +| **TimeLock** | [`0xAd0BFA476FB71215BA026EBE60FfA9b31D98Efe7`](https://scan.kcc.io/address/0xAd0BFA476FB71215BA026EBE60FfA9b31D98Efe7) | https://github.com/OpenLeverageDev/openleverage-contracts/blob/main/contracts/gov/Timelock.sol | +| **ControllerDelegator** | [`0x8A2F01b39319de1dFE219090018B6F460e968e00`](https://scan.kcc.io/address/0x8A2F01b39319de1dFE219090018B6F460e968e00) | https://github.com/OpenLeverageDev/openleverage-contracts/blob/v1.0.7-kcc/contracts/ControllerDelegator.sol | +| **DexAggregatorDelegator** | [`0xc2E51Ab824cC088CB5253E74963B00a15Be7d234`](https://scan.kcc.io/address/0xc2E51Ab824cC088CB5253E74963B00a15Be7d234) | https://github.com/OpenLeverageDev/openleverage-contracts/blob/v1.0.7-kcc/contracts/dex/DexAggregatorDelegator.sol | +| **OpenLevDelegator** | [`0xEF6890d740E1244fEa42E3D1B9Ff515C24c004Ce`](https://scan.kcc.io/address/0xEF6890d740E1244fEa42E3D1B9Ff515C24c004Ce) | https://github.com/OpenLeverageDev/openleverage-contracts/blob/v1.0.7-kcc/contracts/OpenLevDelegator.sol | \ No newline at end of file diff --git a/contracts/OleLpStakeAutomator.sol b/contracts/OleLpStakeAutomator.sol new file mode 100644 index 0000000..db7f490 --- /dev/null +++ b/contracts/OleLpStakeAutomator.sol @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.7.6; + +import "./DelegateInterface.sol"; +import "./Adminable.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "./OleLpStakeAutomatorInterface.sol"; +import "./lib/TransferHelper.sol"; + +contract OleLpStakeAutomator is DelegateInterface, Adminable, ReentrancyGuard, OleLpStakeAutomatorInterface, OleLpStakeAutomatorStorage { + using TransferHelper for IERC20; + using SafeMath for uint; + + function initialize( + XOLEInterface _xole, + IERC20 _ole, + IERC20 _otherToken, + IERC20 _lpToken, + IWETH _nativeToken, + IUniswapV2Router01 _router + ) public { + require(msg.sender == admin, "NAD"); + xole = _xole; + ole = _ole; + otherToken = _otherToken; + lpToken = _lpToken; + nativeToken = _nativeToken; + router = _router; + } + + function createLockBoth(uint oleAmount, uint otherAmount, uint unlockTime, uint oleMin, uint otherMin) external payable override nonReentrant { + transferInBothAndLock(oleAmount, otherAmount, unlockTime, oleMin, otherMin); + } + + function createLockOLE(uint oleAmount, uint unlockTime, uint oleMin, uint otherMin) external override nonReentrant { + transferInOleAndLock(oleAmount, unlockTime, oleMin, otherMin); + } + + function createLockOther(uint otherAmount, uint unlockTime, uint oleMin, uint otherMin) external payable override nonReentrant { + transferInOtherAndLock(otherAmount, unlockTime, oleMin, otherMin); + } + + + function increaseAmountBoth(uint oleAmount, uint otherAmount, uint oleMin, uint otherMin) external payable override nonReentrant { + transferInBothAndLock(oleAmount, otherAmount, 0, oleMin, otherMin); + } + + function increaseAmountOLE(uint oleAmount, uint oleMin, uint otherMin) external override nonReentrant { + transferInOleAndLock(oleAmount, 0, oleMin, otherMin); + } + + function increaseAmountOther(uint otherAmount, uint oleMin, uint otherMin) external payable override nonReentrant { + transferInOtherAndLock(otherAmount, 0, oleMin, otherMin); + } + + + function withdrawBoth(uint oleMin, uint otherMin) external override nonReentrant { + (uint oleOut, uint otherOut) = removeLiquidity(oleMin, otherMin); + doTransferOut(msg.sender, ole, oleOut); + doTransferOut(msg.sender, otherToken, otherOut); + } + + function withdrawOle(uint oleMin, uint otherMin) external override nonReentrant { + (uint oleOut, uint otherOut) = removeLiquidity(oleMin, otherMin); + //swap + otherToken.safeApprove(address(router), otherOut); + uint[] memory amounts = router.swapExactTokensForTokens(otherOut, 0, getPath(ole), address(this), timestamp()); + uint oleSwapIn = amounts[1]; + doTransferOut(msg.sender, ole, oleOut.add(oleSwapIn)); + } + + function withdrawOther(uint oleMin, uint otherMin) external override nonReentrant { + (uint oleOut, uint otherOut) = removeLiquidity(oleMin, otherMin); + //swap + ole.safeApprove(address(router), oleOut); + uint[] memory amounts = router.swapExactTokensForTokens(oleOut, 0, getPath(otherToken), address(this), timestamp()); + uint otherSwapIn = amounts[1]; + doTransferOut(msg.sender, otherToken, otherOut.add(otherSwapIn)); + } + + function transferInBothAndLock(uint oleAmount, uint otherAmount, uint unlockTime, uint oleMin, uint otherMin) internal { + // transferIn + uint oleIn = transferIn(msg.sender, ole, oleAmount); + uint otherIn = transferIn(msg.sender, otherToken, otherAmount); + // add liquidity and increase amount + addLiquidityAndLock(oleIn, otherIn, unlockTime, oleMin, otherMin); + } + + function transferInOleAndLock(uint oleAmount, uint unlockTime, uint oleMin, uint otherMin) internal { + // transferIn + uint oleIn = transferIn(msg.sender, ole, oleAmount); + // swap + uint oleSwapOut = oleIn.div(2); + ole.safeApprove(address(router), oleSwapOut); + uint[] memory amounts = router.swapExactTokensForTokens(oleSwapOut, 0, getPath(otherToken), address(this), timestamp()); + uint otherIn = amounts[1]; + // add liquidity and create lock + addLiquidityAndLock(oleIn.sub(oleSwapOut), otherIn, unlockTime, oleMin, otherMin); + } + + function transferInOtherAndLock(uint otherAmount, uint unlockTime, uint oleMin, uint otherMin) internal { + // transferIn + uint otherIn = transferIn(msg.sender, otherToken, otherAmount); + // swap + uint otherSwapOut = otherIn.div(2); + otherToken.safeApprove(address(router), otherSwapOut); + uint[] memory amounts = router.swapExactTokensForTokens(otherSwapOut, 0, getPath(ole), address(this), timestamp()); + uint oleIn = amounts[1]; + // add liquidity and create lock + addLiquidityAndLock(oleIn, otherIn.sub(otherSwapOut), unlockTime, oleMin, otherMin); + } + + function addLiquidityAndLock(uint oleIn, uint otherIn, uint unlockTime, uint oleMin, uint otherMin) internal { + // add liquidity + ole.safeApprove(address(router), oleIn); + otherToken.safeApprove(address(router), otherIn); + (uint oleOut, uint otherOut, uint liquidity) = router.addLiquidity(address(ole), address(otherToken), oleIn, otherIn, oleMin, otherMin, address(this), timestamp()); + // create lock + lpToken.safeApprove(address(xole), liquidity); + if (unlockTime > 0) { + xole.create_lock_for(msg.sender, liquidity, unlockTime); + } else { + xole.increase_amount_for(msg.sender, liquidity); + } + // back remainder + if (oleIn > oleOut) { + doTransferOut(msg.sender, ole, oleIn - oleOut); + } + if (otherIn > otherOut) { + doTransferOut(msg.sender, otherToken, otherIn - otherOut); + } + } + + function removeLiquidity(uint oleMin, uint otherMin) internal returns (uint oleOut, uint otherOut){ + //withdraw + xole.withdraw_automator(msg.sender); + uint liquidity = lpToken.balanceOf(address(this)); + lpToken.safeApprove(address(router), liquidity); + //remove liquidity + (oleOut, otherOut) = router.removeLiquidity(address(ole), address(otherToken), liquidity, oleMin, otherMin, address(this), timestamp()); + } + + function transferIn(address from, IERC20 token, uint amount) internal returns (uint) { + if (isNativeToken(token)) { + nativeToken.deposit{value : msg.value}(); + return msg.value; + } else { + return token.safeTransferFrom(from, address(this), amount); + } + } + + function doTransferOut(address to, IERC20 token, uint amount) internal { + if (isNativeToken(token)) { + nativeToken.withdraw(amount); + (bool success,) = to.call{value : amount}(""); + require(success); + } else { + token.safeTransfer(to, amount); + } + } + + function isNativeToken(IERC20 token) internal view returns (bool) { + return address(token) == address(nativeToken); + } + + function getPath(IERC20 destToken) internal view returns (address[] memory path) { + path = new address[](2); + path[0] = address(destToken) == address(ole) ? address(otherToken) : address(ole); + path[1] = address(destToken) == address(ole) ? address(ole) : address(otherToken); + } + + function timestamp() internal view returns (uint){ + return block.timestamp; + } +} + diff --git a/contracts/OleLpStakeAutomatorDelegator.sol b/contracts/OleLpStakeAutomatorDelegator.sol new file mode 100644 index 0000000..33e8551 --- /dev/null +++ b/contracts/OleLpStakeAutomatorDelegator.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.7.6; + +import "./Adminable.sol"; +import "./DelegatorInterface.sol"; + + +contract OleLpStakeAutomatorDelegator is DelegatorInterface, Adminable { + + constructor(address _xole, + address _ole, + address _otherToken, + address _lpToken, + address _nativeToken, + address _router, + address payable admin_, + address implementation_) { + admin = msg.sender; + // Creator of the contract is admin during initialization + // First delegate gets to initialize the delegator (i.e. storage contract) + delegateTo(implementation_, abi.encodeWithSignature("initialize(address,address,address,address,address,address)", + _xole, + _ole, + _otherToken, + _lpToken, + _nativeToken, + _router)); + implementation = implementation_; + // Set the proper admin now that initialization is done + admin = admin_; + } + + /** + * Called by the admin to update the implementation of the delegator + * @param implementation_ The address of the new implementation for delegation + */ + function setImplementation(address implementation_) public override onlyAdmin { + address oldImplementation = implementation; + implementation = implementation_; + emit NewImplementation(oldImplementation, implementation); + } +} \ No newline at end of file diff --git a/contracts/OleLpStakeAutomatorInterface.sol b/contracts/OleLpStakeAutomatorInterface.sol new file mode 100644 index 0000000..91c150a --- /dev/null +++ b/contracts/OleLpStakeAutomatorInterface.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.7.6; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import '@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol'; +import "./XOLEInterface.sol"; +import "./IWETH.sol"; + +contract OleLpStakeAutomatorStorage { + XOLEInterface public xole; + IERC20 public ole; + IERC20 public otherToken; + IERC20 public lpToken; + IWETH public nativeToken; + IUniswapV2Router01 router; +} + +interface OleLpStakeAutomatorInterface { + + function createLockBoth(uint oleAmount, uint otherAmount, uint unlockTime, uint oleMin, uint otherMin) external payable; + + function createLockOLE(uint oleAmount, uint unlockTime, uint oleMin, uint otherMin) external ; + + function createLockOther(uint otherAmount, uint unlockTime, uint oleMin, uint otherMin) external payable; + + + function increaseAmountBoth(uint oleAmount, uint otherAmount, uint oleMin, uint otherMin) external payable; + + function increaseAmountOLE(uint oleAmount, uint oleMin, uint otherMin) external ; + + function increaseAmountOther(uint otherAmount, uint oleMin, uint otherMin) external payable; + + + function withdrawBoth(uint oleMin, uint otherMin) external; + + function withdrawOle(uint oleMin, uint otherMin) external; + + function withdrawOther(uint oleMin, uint otherMin) external; + + +} \ No newline at end of file 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 e301701..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,65 +83,81 @@ 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, - deposit.add(tv.borrowValue), - address(tv.depositErc20), - marketId, - totalHelds[address(tv.depositErc20)], + trader, + deposit.add(tv.borrowValue), + address(tv.depositErc20), + marketId, + totalHelds[address(tv.depositErc20)], depositToken == longToken ? vars.reserveBuyToken : vars.reserveSellToken ); 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,23 +194,24 @@ 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"); require(trade.held != 0 && trade.lastBlockNum != block.number && OpenLevV1Lib.isInSupportDex(marketVars.dexs, dexData.toDexDetail()), "HI0"); (ControllerInterface(addressConfig.controller)).closeTradeAllowed(marketId); - + 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,13 +472,12 @@ 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; } - // should remove in later version call on update contract should be atomic - function updateLegacy(address[] calldata tokens) external onlyAdmin() { - OpenLevV1Lib.updateLegacy(tokens, totalHelds); + function setOpLimitOrder(address _opLimitOrder) external override onlyAdmin() { + opLimitOrder = _opLimitOrder; } modifier onlySupportDex(bytes memory dexData) { diff --git a/contracts/OpenLevV1Lib.sol b/contracts/OpenLevV1Lib.sol index f4fb1c7..a2d9ef7 100644 --- a/contracts/OpenLevV1Lib.sol +++ b/contracts/OpenLevV1Lib.sol @@ -34,20 +34,21 @@ 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(); + address token1 = pool1.underlying(); uint8 dex = dexData.toDex(); require(isSupportDex(_supportDexs, dex) && msg.sender == address(addressConfig.controller) && marginLimit >= config.defaultMarginLimit && marginLimit < 100000, "UDX"); { uint24[] memory taxRates = dexData.toTransferFeeRates(); - require(taxRates[0] < 200000 && taxRates[1] < 200000 && taxRates[2] < 200000 && taxRates[3] < 200000 &&taxRates[4] < 200000 && taxRates[5] < 200000, "WTR" ); - taxes[marketId][token0][0]= taxRates[0]; - taxes[marketId][token1][0]= taxRates[1]; - taxes[marketId][token0][1]= taxRates[2]; - taxes[marketId][token1][1]= taxRates[3]; - taxes[marketId][token0][2]= taxRates[4]; - taxes[marketId][token1][2]= taxRates[5]; + require(taxRates[0] < 200000 && taxRates[1] < 200000 && taxRates[2] < 200000 && taxRates[3] < 200000 && taxRates[4] < 200000 && taxRates[5] < 200000, "WTR"); + taxes[marketId][token0][0] = taxRates[0]; + taxes[marketId][token1][0] = taxRates[1]; + taxes[marketId][token0][1] = taxRates[2]; + taxes[marketId][token1][1] = taxRates[3]; + taxes[marketId][token0][2] = taxRates[4]; + taxes[marketId][token1][2] = taxRates[5]; } // Approve the max number for pools @@ -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); } } @@ -249,7 +269,7 @@ library OpenLevV1Lib { function doTransferOut(address to, IERC20 token, address weth, uint amount) external { if (address(token) == weth) { IWETH(weth).withdraw(amount); - (bool success, ) = to.call{value: amount}(""); + (bool success,) = to.call{value : amount}(""); require(success); } else { token.safeTransfer(to, amount); @@ -269,22 +289,18 @@ library OpenLevV1Lib { } function feeAndInsurance( - address trader, - uint tradeSize, - address token, + address trader, + uint tradeSize, + address token, address xOLE, - uint totalHeld, + uint totalHeld, uint reserve, - Types.Market storage market, + Types.Market storage market, mapping(address => uint) storage totalHelds, OpenLevStorage.CalculateConfig memory calculateConfig ) 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)); @@ -304,14 +320,14 @@ library OpenLevV1Lib { } function reduceInsurance( - uint totalRepayment, - uint remaining, - bool longToken, - address token, - uint reserve, - Types.Market storage market, + uint totalRepayment, + uint remaining, + bool longToken, + address token, + uint reserve, + Types.Market storage market, mapping(address => uint - ) storage totalHelds) external returns (uint maxCanRepayAmount) { + ) storage totalHelds) external returns (uint maxCanRepayAmount) { uint needed = totalRepayment.sub(remaining); needed = amountToShare(needed, totalHelds[token], reserve); maxCanRepayAmount = totalRepayment; @@ -328,6 +344,7 @@ library OpenLevV1Lib { } else { if (market.pool1Insurance >= needed) { market.pool1Insurance = market.pool1Insurance - needed; + totalHelds[token] = totalHelds[token].sub(needed); } else { maxCanRepayAmount = shareToAmount(market.pool1Insurance, totalHelds[token], reserve); maxCanRepayAmount = maxCanRepayAmount.add(remaining); @@ -335,25 +352,19 @@ library OpenLevV1Lib { market.pool1Insurance = 0; } } - } + } - function moveInsurance(Types.Market storage market, uint8 poolIndex, address to, uint amount, mapping(address => uint) storage totalHelds) external{ + function moveInsurance(Types.Market storage market, uint8 poolIndex, address to, uint amount, mapping(address => uint) storage totalHelds) external { if (poolIndex == 0) { market.pool0Insurance = market.pool0Insurance.sub(amount); - (IERC20(market.token0)).safeTransfer(to, shareToAmount(amount, totalHelds[market.token0], IERC20(market.token0).balanceOf(address(this)))); - }else{ + uint256 totalHeld = totalHelds[market.token0]; + totalHelds[market.token0] = totalHelds[market.token0].sub(amount); + (IERC20(market.token0)).safeTransfer(to, shareToAmount(amount, totalHeld, IERC20(market.token0).balanceOf(address(this)))); + } else { market.pool1Insurance = market.pool1Insurance.sub(amount); - (IERC20(market.token1)).safeTransfer(to, shareToAmount(amount, totalHelds[market.token1], IERC20(market.token1).balanceOf(address(this)))); - } - } - - function updateLegacy(address[] calldata tokens, mapping(address => uint) storage totalHelds) external{ - for(uint i; i < tokens.length; i++){ - address token = tokens[i]; - uint balance = IERC20(token).balanceOf(address(this)); - if (totalHelds[token] == 0 && balance > 0){ - totalHelds[token] = balance; - } + uint256 totalHeld = totalHelds[market.token1]; + totalHelds[market.token1] = totalHelds[market.token1].sub(amount); + (IERC20(market.token1)).safeTransfer(to, shareToAmount(amount, totalHeld, IERC20(market.token1).balanceOf(address(this)))); } } @@ -366,19 +377,20 @@ library OpenLevV1Lib { } function shareToAmount(uint share, uint totalShare, uint reserve) internal pure returns (uint amount){ - if (totalShare > 0 && reserve > 0){ + if (totalShare > 0 && reserve > 0) { amount = reserve.mul(share) / totalShare; } } - 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"); @@ -391,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/RetroactiveAirdropLock.sol b/contracts/RetroactiveAirdropLock.sol new file mode 100644 index 0000000..6b89493 --- /dev/null +++ b/contracts/RetroactiveAirdropLock.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.7.6; + + +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "./gov/OLEToken.sol"; +import "./Adminable.sol"; + +/// @title OLE token Locked +/// @author OpenLeverage +/// @notice Release retroactive airdrop OLE to beneficiaries linearly. +contract RetroactiveAirdropLock is Adminable{ + using SafeMath for uint256; + uint128 public startTime; + uint128 public endTime; + uint128 public expireTime; + OLEToken public token; + mapping(address => ReleaseVar) public releaseVars; + + event Release(address beneficiary, uint amount); + + struct ReleaseVar { + uint256 amount; + uint128 lastUpdateTime; + } + + constructor(OLEToken token_, address payable _admin, uint128 startTime_, uint128 endTime_, uint128 expireTime_) { + require(endTime_ > startTime_, "StartTime must be earlier than endTime"); + require(expireTime_ > endTime_, "EndTime must be earlier than expireTime"); + startTime = startTime_; + endTime = endTime_; + expireTime = expireTime_; + admin = _admin; + token = token_; + } + + function setReleaseBatch(address[] memory beneficiaries, uint256[] memory amounts) external onlyAdmin{ + require(beneficiaries.length == amounts.length, "Length must be same"); + for (uint i = 0; i < beneficiaries.length; i++) { + address beneficiary = beneficiaries[i]; + require(releaseVars[beneficiary].amount == 0, 'Beneficiary is exist'); + releaseVars[beneficiary] = ReleaseVar(amounts[i], startTime); + } + } + + function release() external { + require(expireTime >= block.timestamp, "time expired"); + releaseInternal(msg.sender); + } + + function withdraw(address to) external onlyAdmin{ + uint256 amount = token.balanceOf(address(this)); + require(amount > 0, "no amount available"); + token.transfer(to, amount); + } + + function releaseInternal(address beneficiary) internal { + uint256 amount = token.balanceOf(address(this)); + uint256 releaseAmount = releaseAbleAmount(beneficiary); + // The transfer out limit exceeds the available limit of the account + require(releaseAmount > 0, "no releasable amount"); + require(amount >= releaseAmount, "transfer out limit exceeds"); + releaseVars[beneficiary].lastUpdateTime = uint128(block.timestamp > endTime ? endTime : block.timestamp); + token.transfer(beneficiary, releaseAmount); + emit Release(beneficiary, releaseAmount); + } + + function releaseAbleAmount(address beneficiary) public view returns (uint256){ + ReleaseVar memory releaseVar = releaseVars[beneficiary]; + require(block.timestamp >= startTime, "not time to unlock"); + require(releaseVar.amount > 0, "beneficiary does not exist"); + uint256 calTime = block.timestamp > endTime ? endTime : block.timestamp; + return calTime.sub(releaseVar.lastUpdateTime).mul(releaseVar.amount) + .div(endTime - startTime); + } + + function lockedAmount(address beneficiary) public view returns (uint256){ + ReleaseVar memory releaseVar = releaseVars[beneficiary]; + return releaseVar.amount.mul(endTime - releaseVar.lastUpdateTime) + .div(endTime - startTime); + } + +} \ No newline at end of file diff --git a/contracts/XOLE.sol b/contracts/XOLE.sol index 0819f43..d7bcb6a 100644 --- a/contracts/XOLE.sol +++ b/contracts/XOLE.sol @@ -24,6 +24,12 @@ contract XOLE is DelegateInterface, Adminable, XOLEInterface, XOLEStorage, Reent using SignedSafeMath128 for int128; using DexData for bytes; + IERC20 public shareToken; + + IERC20 public oleLpStakeToken; + + address public oleLpStakeAutomator; + /* We cannot really do block numbers per se b/c slope is per time, not per block and per block could be fairly bad b/c Ethereum changes blocktimes. What we can do is to extrapolate ***At functions @@ -52,30 +58,90 @@ contract XOLE is DelegateInterface, Adminable, XOLEInterface, XOLEStorage, Reent dexAgg = _dexAgg; } + /*** Admin Functions ***/ + function setDevFundRatio(uint newRatio) external override onlyAdmin { + require(newRatio <= 10000); + devFundRatio = newRatio; + } + + function setDev(address newDev) external override onlyAdmin { + require(newDev != address(0), "0x"); + dev = newDev; + } + function setDexAgg(DexAggregatorInterface newDexAgg) external override onlyAdmin { dexAgg = newDexAgg; } - // Fees sharing functions ===== + function setShareToken(address _shareToken) external override onlyAdmin { + require(_shareToken != address(0), "0x"); + require(devFund == 0 && claimableTokenAmountInternal() == 0, 'Withdraw fund firstly'); + shareToken = IERC20(_shareToken); + } + + function setOleLpStakeToken(address _oleLpStakeToken) external override onlyAdmin { + require(_oleLpStakeToken != address(0), "0x"); + require(address(oleLpStakeToken) == address(0), "Initialized"); + oleLpStakeToken = IERC20(_oleLpStakeToken); + } + + function setOleLpStakeAutomator(address _oleLpStakeAutomator) external override onlyAdmin { + require(_oleLpStakeAutomator != address(0), "0x"); + oleLpStakeAutomator = _oleLpStakeAutomator; + } + // Fees sharing functions ===== function withdrawDevFund() external override { require(msg.sender == dev, "Dev only"); require(devFund != 0, "No fund to withdraw"); uint toSend = devFund; devFund = 0; - oleToken.transfer(dev, toSend); + shareToken.safeTransfer(dev, toSend); + } + + function withdrawCommunityFund(address to) external override onlyAdmin { + require(to != address(0), "0x"); + uint claimable = claimableTokenAmountInternal(); + require(claimable != 0, "No fund to withdraw"); + withdrewReward = withdrewReward.add(claimable); + shareToken.safeTransfer(to, claimable); + emit RewardPaid(to, claimable); + } + + function withdrawOle(address to) external override onlyAdmin { + oleToken.safeTransfer(to, oleToken.balanceOf(address(this))); + } + + function shareableTokenAmount() external override view returns (uint256){ + return shareableTokenAmountInternal(); + } + + function shareableTokenAmountInternal() internal view returns (uint256 shareableAmount){ + uint claimable = claimableTokenAmountInternal(); + if (address(shareToken) == address(oleLpStakeToken)) { + shareableAmount = shareToken.balanceOf(address(this)).sub(totalLocked).sub(claimable).sub(devFund); + } else { + shareableAmount = shareToken.balanceOf(address(this)).sub(claimable).sub(devFund); + } + } + + function claimableTokenAmount() external override view returns (uint256){ + return claimableTokenAmountInternal(); + } + + function claimableTokenAmountInternal() internal view returns (uint256 claimableAmount){ + claimableAmount = totalRewarded.sub(withdrewReward); } /// @dev swap feeCollected to reward token function convertToSharingToken(uint amount, uint minBuyAmount, bytes memory dexData) external override onlyAdminOrDeveloper() { - require(totalSupply > 0, "Can't share without locked OLE"); address fromToken; address toToken; - // If no swapping, then assuming OLE reward distribution + // If no swapping, then assuming shareToken reward distribution if (dexData.length == 0) { - fromToken = address(oleToken); + fromToken = address(shareToken); } - // Not OLE + // Not shareToken else { if (dexData.isUniV2Class()) { address[] memory path = dexData.toUniV2Path(); @@ -87,26 +153,29 @@ contract XOLE is DelegateInterface, Adminable, XOLEInterface, XOLEStorage, Reent toToken = path[path.length - 1].tokenB; } } + // If fromToken is ole, check amount + if (fromToken == address(oleLpStakeToken)) { + require(oleLpStakeToken.balanceOf(address(this)).sub(totalLocked) >= amount, 'Exceed OLE balance'); + } uint newReward; - if (fromToken == address(oleToken)) { - uint claimable = totalRewarded.sub(withdrewReward); - uint toShare = oleToken.balanceOf(address(this)).sub(claimable).sub(totalLocked).sub(devFund); - require(toShare >= amount, 'Exceed OLE balance'); - newReward = toShare; - } else { + if (fromToken == address(shareToken)) { + uint toShare = shareableTokenAmountInternal(); + require(toShare >= amount, 'Exceed share token balance'); + newReward = amount; + } + else { require(IERC20(fromToken).balanceOf(address(this)) >= amount, "Exceed available balance"); (IERC20(fromToken)).safeApprove(address(dexAgg), 0); (IERC20(fromToken)).safeApprove(address(dexAgg), amount); newReward = dexAgg.sellMul(amount, minBuyAmount, dexData); + require(newReward > 0, 'New reward is 0'); } - //fromToken or toToken equal OLE ,update reward - if (fromToken == address(oleToken) || toToken == address(oleToken)) { + //fromToken or toToken equal shareToken ,update reward + if (fromToken == address(shareToken) || toToken == address(shareToken)) { uint newDevFund = newReward.mul(devFundRatio).div(10000); newReward = newReward.sub(newDevFund); devFund = devFund.add(newDevFund); totalRewarded = totalRewarded.add(newReward); - lastUpdateTime = block.timestamp; - rewardPerTokenStored = rewardPerToken(newReward); emit RewardAdded(fromToken, amount, newReward); } else { emit RewardConvert(fromToken, toToken, amount, newReward); @@ -114,97 +183,6 @@ contract XOLE is DelegateInterface, Adminable, XOLEInterface, XOLEStorage, Reent } - /// @notice calculate the amount of token reward - function earned(address account) external override view returns (uint) { - return earnedInternal(account); - } - - function earnedInternal(address account) internal view returns (uint) { - return (balances[account]) - .mul(rewardPerToken(0).sub(userRewardPerTokenPaid[account])) - .div(1e18) - .add(rewards[account]); - } - - function rewardPerToken(uint newReward) internal view returns (uint) { - if (totalSupply == 0) { - return rewardPerTokenStored; - } - - if (block.timestamp == lastUpdateTime) { - return rewardPerTokenStored.add(newReward - .mul(1e18) - .div(totalSupply)); - } else { - return rewardPerTokenStored; - } - } - - /// @notice transfer rewarded ole to msg.sender - function withdrawReward() external override { - uint reward = getReward(); - oleToken.safeTransfer(msg.sender, reward); - emit RewardPaid(msg.sender, reward); - } - - function getReward() internal updateReward(msg.sender) returns (uint) { - uint reward = rewards[msg.sender]; - if (reward > 0) { - rewards[msg.sender] = 0; - withdrewReward = withdrewReward.add(reward); - } - return reward; - } - - modifier updateReward(address account) { - rewardPerTokenStored = rewardPerToken(0); - rewards[account] = earnedInternal(account); - userRewardPerTokenPaid[account] = rewardPerTokenStored; - _; - } - - /*** Admin Functions ***/ - function setDevFundRatio(uint newRatio) external override onlyAdmin { - require(newRatio <= 10000); - devFundRatio = newRatio; - } - - function setDev(address newDev) external override onlyAdmin { - dev = newDev; - } - - - function _mint(address account, uint amount) internal { - totalSupply = totalSupply.add(amount); - balances[account] = balances[account].add(amount); - emit Transfer(address(0), account, amount); - if (delegates[account] == address(0)) { - delegates[account] = account; - } - _moveDelegates(address(0), delegates[account], amount); - _updateTotalSupplyCheckPoints(); - } - - function _burn(address account) internal { - uint burnAmount = balances[account]; - totalSupply = totalSupply.sub(burnAmount); - balances[account] = 0; - emit Transfer(account, address(0), burnAmount); - _moveDelegates(delegates[account], address(0), burnAmount); - _updateTotalSupplyCheckPoints(); - } - - function _updateTotalSupplyCheckPoints() internal { - uint32 blockNumber = safe32(block.number, "block number exceeds 32 bits"); - if (totalSupplyNumCheckpoints > 0 && totalSupplyCheckpoints[totalSupplyNumCheckpoints - 1].fromBlock == blockNumber) { - totalSupplyCheckpoints[totalSupplyNumCheckpoints - 1].votes = totalSupply; - } - else { - totalSupplyCheckpoints[totalSupplyNumCheckpoints] = Checkpoint(blockNumber, totalSupply); - totalSupplyNumCheckpoints = totalSupplyNumCheckpoints + 1; - } - - } function balanceOf(address addr) external view override returns (uint256){ return balances[addr]; @@ -245,28 +223,44 @@ contract XOLE is DelegateInterface, Adminable, XOLEInterface, XOLEStorage, Reent /// @param _value Amount to deposit /// @param _unlock_time Epoch time when tokens unlock, rounded down to whole weeks function create_lock(uint256 _value, uint256 _unlock_time) external override nonReentrant() { - // Locktime is rounded down to weeks - uint256 unlock_time = _unlock_time.div(WEEK).mul(WEEK); - LockedBalance memory _locked = locked[msg.sender]; + uint256 unlock_time = create_lock_check(msg.sender, _value, _unlock_time); + _deposit_for(msg.sender, _value, unlock_time, locked[msg.sender], CREATE_LOCK_TYPE); + } + + function create_lock_for(address to, uint256 _value, uint256 _unlock_time) external override nonReentrant() { + uint256 unlock_time = create_lock_check(to, _value, _unlock_time); + _deposit_for(to, _value, unlock_time, locked[to], CREATE_LOCK_TYPE); + } + function create_lock_check(address to, uint256 _value, uint256 _unlock_time) internal view returns (uint unlock_time) { + // Locktime is rounded down to weeks + unlock_time = _unlock_time.div(WEEK).mul(WEEK); + LockedBalance memory _locked = locked[to]; require(_value > 0, "Non zero value"); require(_locked.amount == 0, "Withdraw old tokens first"); - require(unlock_time > block.timestamp, "Can only lock until time in the future"); + require(unlock_time >= block.timestamp + (2 * WEEK), "Can only lock until time in the future"); require(unlock_time <= block.timestamp + MAXTIME, "Voting lock can be 4 years max"); - - _deposit_for(msg.sender, _value, unlock_time, _locked, CREATE_LOCK_TYPE); } - + /// @notice Deposit `_value` additional tokens for `msg.sender` /// without modifying the unlock time /// @param _value Amount of tokens to deposit and add to the lock function increase_amount(uint256 _value) external override nonReentrant() { - LockedBalance memory _locked = locked[msg.sender]; + LockedBalance memory _locked = increase_amount_check(msg.sender, _value); + _deposit_for(msg.sender, _value, 0, _locked, INCREASE_LOCK_AMOUNT); + } + + function increase_amount_for(address to, uint256 _value) external override nonReentrant() { + LockedBalance memory _locked = increase_amount_check(to, _value); + _deposit_for(to, _value, 0, _locked, INCREASE_LOCK_AMOUNT); + } + + function increase_amount_check(address to, uint256 _value) internal view returns (LockedBalance memory _locked) { + _locked = locked[to]; require(_value > 0, "need non - zero value"); require(_locked.amount > 0, "No existing lock found"); require(_locked.end > block.timestamp, "Cannot add to expired lock. Withdraw"); - _deposit_for(msg.sender, _value, 0, _locked, INCREASE_LOCK_AMOUNT); } /// @notice Extend the unlock time for `msg.sender` to `_unlock_time` @@ -275,9 +269,9 @@ contract XOLE is DelegateInterface, Adminable, XOLEInterface, XOLEStorage, Reent LockedBalance memory _locked = locked[msg.sender]; // Locktime is rounded down to weeks uint256 unlock_time = _unlock_time.div(WEEK).mul(WEEK); - require(_locked.end > block.timestamp, "Lock expired"); require(_locked.amount > 0, "Nothing is locked"); require(unlock_time > _locked.end, "Can only increase lock duration"); + require(unlock_time >= block.timestamp + (2 * WEEK), "Can only lock until time in the future"); require(unlock_time <= block.timestamp + MAXTIME, "Voting lock can be 4 years max"); _deposit_for(msg.sender, 0, unlock_time, _locked, INCREASE_UNLOCK_TIME); @@ -289,9 +283,9 @@ contract XOLE is DelegateInterface, Adminable, XOLEInterface, XOLEStorage, Reent /// @param unlock_time New time when to unlock the tokens, or 0 if unchanged /// @param _locked Previous locked amount / timestamp /// @param _type For event only. - function _deposit_for(address _addr, uint256 _value, uint256 unlock_time, LockedBalance memory _locked, int128 _type) internal updateReward(_addr) { - uint256 locked_before = totalLocked; - totalLocked = locked_before.add(_value); + function _deposit_for(address _addr, uint256 _value, uint256 unlock_time, LockedBalance memory _locked, int128 _type) internal { + totalLocked = totalLocked.add(_value); + uint256 prevBalance = balances[_addr]; // Adding to existing lock, or if a lock is expired - creating a new one _locked.amount = _locked.amount.add(_value); @@ -301,7 +295,7 @@ contract XOLE is DelegateInterface, Adminable, XOLEInterface, XOLEStorage, Reent locked[_addr] = _locked; if (_value != 0) { - require(IERC20(oleToken).transferFrom(msg.sender, address(this), _value)); + oleLpStakeToken.safeTransferFrom(msg.sender, address(this), _value); } uint calExtraValue = _value; @@ -317,28 +311,67 @@ contract XOLE is DelegateInterface, Adminable, XOLEInterface, XOLEStorage, Reent } else { _mint(_addr, calExtraValue); } - emit Deposit(_addr, _value, _locked.end, _type, block.timestamp); + emit Deposit(_addr, _value, _locked.end, _type, prevBalance, balances[_addr]); } + function _mint(address account, uint amount) internal { + totalSupply = totalSupply.add(amount); + balances[account] = balances[account].add(amount); + emit Transfer(address(0), account, amount); + if (delegates[account] == address(0)) { + delegates[account] = account; + } + _moveDelegates(address(0), delegates[account], amount); + _updateTotalSupplyCheckPoints(); + } + + function _burn(address account) internal { + uint burnAmount = balances[account]; + totalSupply = totalSupply.sub(burnAmount); + balances[account] = 0; + emit Transfer(account, address(0), burnAmount); + _moveDelegates(delegates[account], address(0), burnAmount); + _updateTotalSupplyCheckPoints(); + } + + function _updateTotalSupplyCheckPoints() internal { + uint32 blockNumber = safe32(block.number, "block number exceeds 32 bits"); + if (totalSupplyNumCheckpoints > 0 && totalSupplyCheckpoints[totalSupplyNumCheckpoints - 1].fromBlock == blockNumber) { + totalSupplyCheckpoints[totalSupplyNumCheckpoints - 1].votes = totalSupply; + } + else { + totalSupplyCheckpoints[totalSupplyNumCheckpoints] = Checkpoint(blockNumber, totalSupply); + totalSupplyNumCheckpoints = totalSupplyNumCheckpoints + 1; + } + } + + /// @notice Withdraw all tokens for `msg.sender` /// @dev Only possible if the lock has expired - function withdraw() external override nonReentrant() updateReward(msg.sender) { - LockedBalance memory _locked = locked[msg.sender]; + function withdraw() external override nonReentrant() { + _withdraw_for(msg.sender, msg.sender); + } + + function withdraw_automator(address owner) external override nonReentrant() { + require(oleLpStakeAutomator == msg.sender, "Not automator"); + _withdraw_for(owner, oleLpStakeAutomator); + } + + function _withdraw_for(address owner, address to) internal { + LockedBalance memory _locked = locked[owner]; require(_locked.amount > 0, "Nothing to withdraw"); require(block.timestamp >= _locked.end, "The lock didn't expire"); + uint256 prevBalance = balances[owner]; uint256 value = _locked.amount; totalLocked = totalLocked.sub(value); _locked.end = 0; _locked.amount = 0; - locked[msg.sender] = _locked; - uint reward = getReward(); - require(IERC20(oleToken).transfer(msg.sender, value.add(reward))); - _burn(msg.sender); - emit Withdraw(msg.sender, value, block.timestamp); - emit RewardPaid(msg.sender, reward); + locked[to] = _locked; + oleLpStakeToken.safeTransfer(to, value); + _burn(owner); + emit Withdraw(owner, value, prevBalance, balances[owner]); } - /// Delegate votes from `msg.sender` to `delegatee` /// @param delegatee The address to delegate votes to function delegate(address delegatee) public { diff --git a/contracts/XOLEInterface.sol b/contracts/XOLEInterface.sol index fdafa7d..f1654d1 100644 --- a/contracts/XOLEInterface.sol +++ b/contracts/XOLEInterface.sol @@ -56,6 +56,7 @@ contract XOLEStorage { uint public devFundRatio; // ex. 5000 => 50% // user => reward + // useless mapping(address => uint256) public rewards; // useless @@ -66,10 +67,13 @@ contract XOLEStorage { uint public withdrewReward; + // useless uint public lastUpdateTime; + // useless uint public rewardPerTokenStored; + // useless mapping(address => uint256) public userRewardPerTokenPaid; @@ -109,22 +113,30 @@ contract XOLEStorage { event RewardAdded(address fromToken, uint convertAmount, uint reward); + event RewardConvert(address fromToken, address toToken, uint convertAmount, uint returnAmount); + event RewardPaid ( + address paidTo, + uint256 amount + ); + event Transfer(address indexed from, address indexed to, uint256 value); event Deposit ( address indexed provider, uint256 value, - uint256 indexed locktime, + uint256 unlocktime, int128 type_, - uint256 ts + uint256 prevBalance, + uint256 balance ); event Withdraw ( address indexed provider, uint256 value, - uint256 ts + uint256 prevBalance, + uint256 balance ); event Supply ( @@ -132,17 +144,12 @@ contract XOLEStorage { uint256 supply ); - event RewardPaid ( - address paidTo, - uint256 amount - ); - event FailedDelegateBySig( address indexed delegatee, - uint indexed nonce, + uint indexed nonce, uint expiry, - uint8 v, - bytes32 r, + uint8 v, + bytes32 r, bytes32 s ); } @@ -150,15 +157,19 @@ contract XOLEStorage { interface XOLEInterface { + function shareableTokenAmount() external view returns (uint256); + + function claimableTokenAmount() external view returns (uint256); + function convertToSharingToken(uint amount, uint minBuyAmount, bytes memory data) external; function withdrawDevFund() external; - function earned(address account) external view returns (uint); + /*** Admin Functions ***/ - function withdrawReward() external; + function withdrawCommunityFund(address to) external; - /*** Admin Functions ***/ + function withdrawOle(address to) external; function setDevFundRatio(uint newRatio) external; @@ -166,16 +177,28 @@ interface XOLEInterface { function setDexAgg(DexAggregatorInterface newDexAgg) external; + function setShareToken(address _shareToken) external; + + function setOleLpStakeToken(address _oleLpStakeToken) external; + + function setOleLpStakeAutomator(address _oleLpStakeAutomator) external; + // xOLE functions function create_lock(uint256 _value, uint256 _unlock_time) external; + function create_lock_for(address to, uint256 _value, uint256 _unlock_time) external; + function increase_amount(uint256 _value) external; + function increase_amount_for(address to, uint256 _value) external; + function increase_unlock_time(uint256 _unlock_time) external; function withdraw() external; + function withdraw_automator(address owner) external; + function balanceOf(address addr) external view returns (uint256); } 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 a4670c5..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) { @@ -237,7 +242,12 @@ contract UniV2ClassDex { //mdex if (address(dexInfo.factory) == 0x3CD1C46068dAEa5Ebb0d3f55F6915B10648062B8) { return toUint16((IMdexFactory)(address(dexInfo.factory)).getPairFees(pair)); - } else { + } + //biswap + else if (address(dexInfo.factory) == 0x858E3312ed3A876947EA49d572A7C42DE08af7EE) { + return toUint16((uint(10)).mul(IBiSwapPair(pair).swapFee())); + } + else { return dexInfo.fees; } } @@ -249,4 +259,8 @@ contract UniV2ClassDex { interface IMdexFactory { function getPairFees(address) external view returns (uint256); +} + +interface IBiSwapPair { + function swapFee() external view returns (uint32); } \ No newline at end of file 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 2795351..46bedec 100644 --- a/contracts/lib/DexData.sol +++ b/contracts/lib/DexData.sol @@ -33,6 +33,8 @@ library DexData { uint8 constant DEX_BABY = 12; 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/FeesShareTest.js b/test/FeesShareTest.js index 33aa499..69b854a 100644 --- a/test/FeesShareTest.js +++ b/test/FeesShareTest.js @@ -40,6 +40,8 @@ contract("XOLE", async accounts => { let john = accounts[1]; let tom = accounts[2]; let dev = accounts[7]; + let communityAcc = accounts[8]; + let daiOLEDexData; let usdtOLEDexData; let daiUsdtDexData; @@ -61,7 +63,7 @@ contract("XOLE", async accounts => { let pair = await MockUniswapV2Pair.new(usdt.address, dai.address, toWei(10000), toWei(10000)); let oleUsdtPair = await MockUniswapV2Pair.new(usdt.address, ole.address, toWei(100000), toWei(100000)); let oleDaiPair = await MockUniswapV2Pair.new(dai.address, ole.address, toWei(100000), toWei(100000)); - daiOLEDexData = Uni2DexData + addressToBytes(dai.address) + addressToBytes(ole.address); + daiOLEDexData = Uni2DexData + addressToBytes(dai.address) + addressToBytes(ole.address); usdtOLEDexData = Uni2DexData + addressToBytes(usdt.address) + addressToBytes(ole.address); daiUsdtDexData = Uni2DexData + addressToBytes(dai.address) + addressToBytes(usdt.address); @@ -84,11 +86,16 @@ contract("XOLE", async accounts => { assert.equal(await pair.token0(), await gotPair.token0()); assert.equal(await pair.token1(), await gotPair.token1()); xole = await utils.createXOLE(ole.address, admin, dev, dexAgg.address); - + await xole.setShareToken(ole.address); + await xole.setOleLpStakeToken(ole.address, {from: admin}); m.log("Created xOLE", last8(xole.address)); await utils.mint(usdt, xole.address, 10000); resetStep(); + let lastbk = await web3.eth.getBlock('latest'); + let timeToMove = lastbk.timestamp + (WEEK - lastbk.timestamp % WEEK); + m.log("Move time to start of the week", new Date(timeToMove)); + await advanceBlockAndSetTime(timeToMove); let snapshot = await timeMachine.takeSnapshot(); snapshotId = snapshot['result']; }); @@ -102,7 +109,8 @@ contract("XOLE", async accounts => { await ole.mint(admin, toWei(10000)); await ole.approve(xole.address, toWei(10000)); let lastbk = await web3.eth.getBlock('latest'); - await xole.create_lock(toWei(10000), lastbk.timestamp + WEEK); + await advanceBlockAndSetTime(lastbk.timestamp - 10); + await xole.create_lock(toWei(10000), lastbk.timestamp + 2 * WEEK + 10); await xole.convertToSharingToken(toWei(1), 0, usdtOLEDexData); m.log("devFund:", (await xole.devFund()).toString()); @@ -127,7 +135,8 @@ contract("XOLE", async accounts => { await ole.mint(admin, toWei(10000)); await ole.approve(xole.address, toWei(10000)); let lastbk = await web3.eth.getBlock('latest'); - await xole.create_lock(toWei(10000), lastbk.timestamp + WEEK); + await advanceBlockAndSetTime(lastbk.timestamp - 10); + await xole.create_lock(toWei(10000), lastbk.timestamp + 2 * WEEK + 10); await xole.convertToSharingToken(toWei(10000), 0, '0x'); @@ -139,7 +148,7 @@ contract("XOLE", async accounts => { m.log("totalRewarded:", await xole.totalRewarded()); m.log("withdrewReward:", await xole.withdrewReward()); m.log("devFund:", await xole.devFund()); - await assertThrows(xole.convertToSharingToken(toWei(1), 0, '0x'), 'Exceed OLE balance'); + await assertThrows(xole.convertToSharingToken(toWei(1), 0, '0x'), 'Exceed share token balance'); }) @@ -148,7 +157,8 @@ contract("XOLE", async accounts => { await ole.mint(admin, toWei(10000)); await ole.approve(xole.address, toWei(10000)); let lastbk = await web3.eth.getBlock('latest'); - await xole.create_lock(toWei(10000), lastbk.timestamp + WEEK); + await advanceBlockAndSetTime(lastbk.timestamp - 10); + await xole.create_lock(toWei(10000), lastbk.timestamp + 2 * WEEK + 10); await xole.convertToSharingToken(toWei(1000), 0, daiOLEDexData); m.log("xOLE OLE balance:", await ole.balanceOf(xole.address)); @@ -159,24 +169,24 @@ contract("XOLE", async accounts => { m.log("xOLE devFund:", await xole.devFund()); assert.equal('493579017198530649425', (await xole.devFund()).toString()); + m.log("xOLE withdrewReward:", await xole.withdrewReward()); assert.equal('0', (await xole.withdrewReward()).toString()); m.log("xole.totalSupply", (await xole.totalSupply()).toString()); m.log("xole.balanceOf", (await xole.balanceOf(admin)).toString()); - m.log("earned:", (await xole.earned(admin)).toString()); - m.log("rewardPerTokenStored before:", (await xole.rewardPerTokenStored()).toString()); - // withdraw ole reward - await xole.withdrawReward(); - m.log("rewardPerTokenStored after:", (await xole.rewardPerTokenStored()).toString()); - assert.equal('493579017198530640000', (await xole.withdrewReward()).toString()); - assert.equal('493579017198530640000', (await ole.balanceOf(admin)).toString()); + assert.equal('0', (await xole.rewardPerTokenStored()).toString()); + // withdraw devFund + await xole.withdrawDevFund({from: dev}); + assert.equal('493579017198530649425', (await ole.balanceOf(dev)).toString()); + // withdraw communityFund + await xole.withdrawCommunityFund(communityAcc); + assert.equal('493579017198530649425', (await ole.balanceOf(communityAcc)).toString()); + assert.equal('493579017198530649425', (await xole.withdrewReward()).toString()); //add sharingToken Reward 2000 await usdt.mint(xole.address, toWei(2000)); //sharing 1000 await xole.convertToSharingToken(toWei(1000), 0, usdtOLEDexData); - assert.equal('987158034397061298850', (await xole.totalRewarded()).toString()); - //Exceed available balance await assertThrows(xole.convertToSharingToken(toWei(20001), 0, usdtOLEDexData), 'Exceed available balance'); @@ -186,7 +196,8 @@ contract("XOLE", async accounts => { await ole.mint(admin, toWei(10000)); await ole.approve(xole.address, toWei(10000)); let lastbk = await web3.eth.getBlock('latest'); - await xole.create_lock(toWei(10000), lastbk.timestamp + WEEK); + await advanceBlockAndSetTime(lastbk.timestamp - 10); + await xole.create_lock(toWei(10000), lastbk.timestamp + 2 * WEEK); assert.equal('10000000000000000000000', (await usdt.balanceOf(xole.address)).toString()); await xole.convertToSharingToken(toWei(1000), 0, daiUsdtDexData); m.log("xOLE USDT balance:", await usdt.balanceOf(xole.address)); @@ -210,9 +221,10 @@ contract("XOLE", async accounts => { await ole.mint(admin, toWei(10000)); await ole.approve(xole.address, toWei(10000)); let lastbk = await web3.eth.getBlock('latest'); - await xole.create_lock(toWei(10000), lastbk.timestamp + WEEK); + await advanceBlockAndSetTime(lastbk.timestamp - 10); + await xole.create_lock(toWei(10000), lastbk.timestamp + 2 * WEEK + 10); assert.equal('10000000000000000000000', (await usdt.balanceOf(xole.address)).toString()); - await xole.convertToSharingToken(toWei(1000), 0, "0x01" + "000000" + "03" + addressToBytes(dai.address) + addressToBytes(usdt.address) + addressToBytes(ole.address)); + await xole.convertToSharingToken(toWei(1000), 0, "0x01" + "000000" + "03" + addressToBytes(dai.address) + addressToBytes(usdt.address) + addressToBytes(ole.address)); m.log("xOLE USDT balance:", await usdt.balanceOf(xole.address)); assert.equal('10000000000000000000000', (await usdt.balanceOf(xole.address)).toString()); @@ -241,8 +253,8 @@ contract("XOLE", async accounts => { let timeToMove = lastbk.timestamp + (WEEK - lastbk.timestamp % WEEK); m.log("Move time to start of the week", new Date(timeToMove)); - step("John stake 500 1 weeks"); - await xole.create_lock(toWei(500), timeToMove + WEEK + 60, {from: john}); + step("John stake 500 2 weeks"); + await xole.create_lock(toWei(500), timeToMove + 2 * WEEK + 60, {from: john}); step("Tom stake 500 2 weeks"); await xole.create_lock(toWei(500), timeToMove + (2 * WEEK) + 60 * 60, {from: tom}); assertPrint("Total staked:", toWei(1000), await xole.totalLocked()); @@ -250,9 +262,6 @@ contract("XOLE", async accounts => { await xole.convertToSharingToken(toWei(1), 0, daiOLEDexData); assertPrint("Dev Fund:", '498495030004550854', await xole.devFund()); assertPrint("Total to share:", '498495030004550855', await xole.totalRewarded()); - assertPrint("John earned:", '246682021973748000', await xole.earned(john)); - //Tom earned more 2.08% than John - assertPrint("Tom earned:", '251813008030801958', await xole.earned(tom)); }) it("John Deposit for 1 weeks, Tom 2 weeks increase amount yet", async () => { @@ -267,8 +276,8 @@ contract("XOLE", async accounts => { let timeToMove = lastbk.timestamp + (WEEK - lastbk.timestamp % WEEK); m.log("Move time to start of the week", new Date(timeToMove)); - step("John stake 500 1 weeks"); - await xole.create_lock(toWei(500), timeToMove + WEEK + 10, {from: john}); + step("John stake 500 2 weeks"); + await xole.create_lock(toWei(500), timeToMove + 2 * WEEK + 10, {from: john}); step("Tom stake 500 2 weeks"); await xole.create_lock(toWei(500), timeToMove + (2 * WEEK) + 60 * 60, {from: tom}); await xole.increase_amount(toWei(500), {from: tom}); @@ -278,9 +287,6 @@ contract("XOLE", async accounts => { await xole.convertToSharingToken(toWei(1), 0, daiOLEDexData); assertPrint("Dev Fund:", '498495030004550854', await xole.devFund()); assertPrint("Total to share:", '498495030004550855', await xole.totalRewarded()); - assertPrint("John earned:", '163892369149313000', await xole.earned(john)); - //Tom earned more 104.16% than John - assertPrint("Tom earned:", '334602660855237420', await xole.earned(tom)); }) it("John Deposit for 1 weeks, Tom 2 weeks increase unlock time to 4 weeks", async () => { @@ -294,8 +300,8 @@ contract("XOLE", async accounts => { let timeToMove = lastbk.timestamp + (WEEK - lastbk.timestamp % WEEK); m.log("Move time to start of the week", new Date(timeToMove)); - step("John stake 500 1 weeks"); - await xole.create_lock(toWei(500), timeToMove + WEEK + 60, {from: john}); + step("John stake 500 2 weeks"); + await xole.create_lock(toWei(500), timeToMove + 2 * WEEK + 60, {from: john}); step("Tom stake 500 2 weeks"); lastbk = await web3.eth.getBlock('latest'); await xole.create_lock(toWei(500), timeToMove + (2 * WEEK) + 60 * 60, {from: tom}); @@ -306,9 +312,6 @@ contract("XOLE", async accounts => { await xole.convertToSharingToken(toWei(1), 0, daiOLEDexData); assertPrint("Dev Fund:", '498495030004550854', await xole.devFund()); assertPrint("Total to share:", '498495030004550855', await xole.totalRewarded()); - assertPrint("John earned:", '241706279094526000', await xole.earned(john)); - //Tom earned more 6.24% than John - assertPrint("Tom earned:", '256788750910024422', await xole.earned(tom)); }) it("John Deposit for 1 weeks, Tom 2 weeks redraw, share again", async () => { @@ -319,18 +322,16 @@ contract("XOLE", async accounts => { await ole.approve(xole.address, toWei(500), {from: john}); await ole.approve(xole.address, toWei(1000), {from: tom}); let lastbk = await web3.eth.getBlock('latest'); + await advanceBlockAndSetTime(lastbk.timestamp - 10); let timeToMove = lastbk.timestamp + (WEEK - lastbk.timestamp % WEEK); m.log("Move time to start of the week", new Date(timeToMove)); - step("John stake 500 1 weeks"); - await xole.create_lock(toWei(500), timeToMove + WEEK + 10, {from: john}); + step("John stake 500 2 weeks"); + await xole.create_lock(toWei(500), timeToMove + 2 * WEEK + 10, {from: john}); step("Tom stake 500 2 weeks"); await xole.create_lock(toWei(500), timeToMove + (2 * WEEK) + 60 * 60, {from: tom}); step("New reward 1"); await xole.convertToSharingToken(toWei(1), 0, daiOLEDexData); - assertPrint("John earned:", '246682021973748000', await xole.earned(john)); - //Tom earned more 2.08% than John - assertPrint("Tom earned:", '251813008030801958', await xole.earned(tom)); m.log("Tom balance=", (await xole.balanceOf(tom)).toString()); m.log("John balance=", (await xole.balanceOf(john)).toString()); @@ -343,13 +344,10 @@ contract("XOLE", async accounts => { m.log("lastbk.timestamp after=", lastbk.timestamp); await xole.withdraw({from: tom}); - assertPrint("Total Extra Token:", toWei(500), await xole.totalSupply()); + assertPrint("Total Extra Token:", "520800000000000000000", await xole.totalSupply()); assertPrint("Tom Extra Token:", 0, await xole.balanceOf(tom)); await xole.convertToSharingToken(toWei(1), 0, daiOLEDexData); - //241706279094526000+498495030004550855 - assertPrint("John earned:", '745167097231345500', await xole.earned(john)); - assertPrint("Tom earned:", '0', await xole.earned(tom)); }) it("John and Tom stakes, Tom stakes more, shares fees", async () => { m.log("process.env.FASTMODE", process.env.FASTMODE); @@ -366,21 +364,18 @@ contract("XOLE", async accounts => { await ole.approve(xole.address, toWei(300), {from: tom}); let lastbk = await web3.eth.getBlock('latest'); + await advanceBlockAndSetTime(lastbk.timestamp - 10); step("John stake 500"); - await xole.create_lock(toWei(500), lastbk.timestamp + WEEK, {from: john}); + await xole.create_lock(toWei(500), lastbk.timestamp + 2 * WEEK + 10, {from: john}); assertPrint("John staked:", toWei(500), (await xole.locked(john)).amount); step("Tom stake 300"); - await xole.create_lock(toWei(300), lastbk.timestamp + WEEK, {from: tom}); + await xole.create_lock(toWei(300), lastbk.timestamp + 2 * WEEK + 10, {from: tom}); assertPrint("Tom staked:", toWei(300), (await xole.locked(tom)).amount); assertPrint("Total staked:", toWei(800), await xole.totalLocked()); step("New reward 1"); await xole.convertToSharingToken(toWei(1), 0, daiOLEDexData); assertPrint("Dev Fund:", '498495030004550854', await xole.devFund()); assertPrint("Total to share:", '498495030004550855', await xole.totalRewarded()); - assertPrint("John earned:", '311559393752844000', await xole.earned(john)); - assertPrint("Tom earned:", '186935636251706400', await xole.earned(tom)); - assertPrint("Total of John and Tom", '498495030004550400', - (await xole.earned(john)).add(await xole.earned(tom))); step("Tom stake more 200"); await ole.approve(xole.address, toWei(200), {from: tom}); @@ -392,43 +387,31 @@ contract("XOLE", async accounts => { step("New reward 1"); await xole.convertToSharingToken(toWei(1), 0, daiOLEDexData); assertPrint("Dev Fund:", '996980105262148814', await xole.devFund()); - assertPrint("John earned:", '560801931381642500', await xole.earned(john)); - assertPrint("Tom earned:", '436178173880504900', await xole.earned(tom)); // Block time insensitive step("Advancing block time ..."); timeMachine.advanceTimeAndBlock(1000); assertPrint("Dev Fund:", '996980105262148814', await xole.devFund()); - assertPrint("John earned:", '560801931381642500', await xole.earned(john)); - assertPrint("Tom earned:", '436178173880504900', await xole.earned(tom)); step("John stack more, but earning should not change because no new reward"); await ole.approve(xole.address, toWei(1000), {from: john}); await xole.increase_amount(toWei(1000), {from: john}); assertPrint("Total staked:", toWei(2000), await xole.totalLocked()); assertPrint("Dev Fund:", '996980105262148814', await xole.devFund()); - assertPrint("John earned:", '560801931381642500', await xole.earned(john)); - assertPrint("Tom earned:", '436178173880504900', await xole.earned(tom)); step("New reward 200"); await xole.convertToSharingToken(toWei(200), 0, daiOLEDexData); assertPrint("Dev Fund:", '100494603912584309258', await xole.devFund()); - assertPrint("John earned:", '75184019786873262500', await xole.earned(john)); - assertPrint("Tom earned:", '25310584125711044900', await xole.earned(tom)); await advanceBlockAndSetTime(lastbk.timestamp + 3 * WEEK); step("John exits, but earning should not change because no new reward"); await xole.withdraw({from: john}); assertPrint("Total staked:", toWei(500), await xole.totalLocked()); assertPrint("Dev Fund:", '100494603912584309258', await xole.devFund()); - assertPrint("John earned:", '0', await xole.earned(john)); - assertPrint("Tom earned:", '25310584125711044900', await xole.earned(tom)); step("New reward 100"); await xole.convertToSharingToken(toWei(100), 0, daiOLEDexData); assertPrint("Dev Fund:", '150094767100146587308', await xole.devFund()); - assertPrint("John earned:", '0', await xole.earned(john)); - assertPrint("Tom earned:", '74910747313273322900', await xole.earned(tom)); step("Tom exit, and more reward"); await xole.withdraw({from: tom}); @@ -436,24 +419,138 @@ contract("XOLE", async accounts => { step("John stack more, but earning should not change because no new reward"); await ole.approve(xole.address, toWei(1000), {from: john}); lastbk = await web3.eth.getBlock('latest'); - await xole.create_lock(toWei(1000), lastbk.timestamp + WEEK, {from: john}); - assertPrint("John earned:", '0', await xole.earned(john)); + await xole.create_lock(toWei(1000), lastbk.timestamp + 3 * WEEK, {from: john}); step("New reward 100"); await xole.convertToSharingToken(toWei(100), 0, daiOLEDexData); assertPrint("Dev Fund:", '199596275059873518079', await xole.devFund()); - assertPrint("John earned:", '49501507959726930000', await xole.earned(john)); - await advanceMultipleBlocksAndTime(10); lastbk = await web3.eth.getBlock('latest'); - await xole.increase_unlock_time(lastbk.timestamp + 2 * WEEK, {from: john}); + await xole.increase_unlock_time(lastbk.timestamp + 5 * WEEK, {from: john}); assertPrint("Dev Fund:", '199596275059873518079', await xole.devFund()); - assertPrint("John earned:", '49501507959726930000', await xole.earned(john)); step("New reward 100"); await xole.convertToSharingToken(toWei(100), 0, daiOLEDexData); assertPrint("Dev Fund:", '248999421985512891445', await xole.devFund()); - assertPrint("John earned:", '98904654885366303000', await xole.earned(john)); + + }) + + it("Convert dexData is 0x ", async () => { + await dai.mint(xole.address, toWei(1000)); + await xole.setShareToken(dai.address); + assert.equal('1000000000000000000000', (await xole.shareableTokenAmount()).toString()); + assert.equal('0', (await xole.claimableTokenAmount()).toString()); + + await xole.convertToSharingToken(toWei(500), 0, '0x'); + + m.log("xOLE dai balance:", await dai.balanceOf(xole.address)); + assert.equal('1000000000000000000000', (await dai.balanceOf(xole.address)).toString()); + + + m.log("xOLE totalRewarded:", await xole.totalRewarded()); + assert.equal('250000000000000000000', (await xole.totalRewarded()).toString()); + + m.log("xOLE devFund:", await xole.devFund()); + assert.equal('250000000000000000000', (await xole.devFund()).toString()); + + assert.equal('500000000000000000000', (await xole.shareableTokenAmount()).toString()); + assert.equal('250000000000000000000', (await xole.claimableTokenAmount()).toString()); + + + await dai.mint(xole.address, toWei(1000)); + + assert.equal('1500000000000000000000', (await xole.shareableTokenAmount()).toString()); + assert.equal('250000000000000000000', (await xole.claimableTokenAmount()).toString()); + + // withdraw devFund + await xole.withdrawDevFund({from: dev}); + assert.equal('250000000000000000000', (await dai.balanceOf(dev)).toString()); + + await xole.convertToSharingToken(toWei(1500), 0, '0x'); + + assert.equal('1750000000000000000000', (await dai.balanceOf(xole.address)).toString()); + assert.equal('1000000000000000000000', (await xole.totalRewarded()).toString()); + assert.equal('750000000000000000000', (await xole.devFund()).toString()); + + await assertThrows(xole.convertToSharingToken(toWei(1), 0, '0x'), 'Exceed share token balance'); + + // withdraw devFund + await xole.withdrawDevFund({from: dev}); + assert.equal('1000000000000000000000', (await dai.balanceOf(dev)).toString()); + // withdraw communityFund + await xole.withdrawCommunityFund(communityAcc); + assert.equal('1000000000000000000000', (await dai.balanceOf(communityAcc)).toString()); + + assert.equal('0', (await xole.shareableTokenAmount()).toString()); + assert.equal('0', (await xole.claimableTokenAmount()).toString()); + }) + + it("Convert minBuy limit test", async () => { + await dai.mint(xole.address, toWei(1000)); + await ole.mint(admin, toWei(10000)); + await ole.approve(xole.address, toWei(10000)); + let lastbk = await web3.eth.getBlock('latest'); + await advanceBlockAndSetTime(lastbk.timestamp - 10); + await xole.create_lock(toWei(10000), lastbk.timestamp + 2 * WEEK + 10); + assert.equal('10000000000000000000000', (await usdt.balanceOf(xole.address)).toString()); + await assertThrows(xole.convertToSharingToken(toWei(1000), '10906610893880149131582', daiUsdtDexData), 'buy amount less than min'); + + await assertThrows(xole.convertToSharingToken(toWei(1000), "895794058774498675512", + "0x01" + "000000" + "03" + addressToBytes(dai.address) + addressToBytes(usdt.address) + addressToBytes(ole.address)), 'buy amount less than min'); + + }) + + it("Convert shareToken=oleToken test", async () => { + await ole.mint(admin, toWei(10000)); + await ole.approve(xole.address, toWei(10000)); + let lastbk = await web3.eth.getBlock('latest'); + await advanceBlockAndSetTime(lastbk.timestamp - 10); + await xole.create_lock(toWei(1), lastbk.timestamp + 2 * WEEK + 10); + + assert.equal('0', (await xole.shareableTokenAmount()).toString()); + assert.equal('0', (await xole.claimableTokenAmount()).toString()); + await ole.mint(xole.address, toWei(10000)); + assert.equal("10000000000000000000000", (await xole.shareableTokenAmount()).toString()); + assert.equal('0', (await xole.claimableTokenAmount()).toString()); + // increase not effect shareableAmount + await xole.increase_amount(toWei(1)); + assert.equal("10000000000000000000000", (await xole.shareableTokenAmount()).toString()); + assert.equal('0', (await xole.claimableTokenAmount()).toString()); + // convert dai to ole + await dai.mint(xole.address, toWei(1000)); + await xole.convertToSharingToken(toWei(1000), '0', daiOLEDexData); + assert.equal("10000000000000000000000", (await xole.shareableTokenAmount()).toString()); + assert.equal('493579017198530649425', (await xole.claimableTokenAmount()).toString()); + assert.equal('493579017198530649425', (await xole.devFund()).toString()); + // convert usdt to ole + await usdt.mint(xole.address, toWei(1000)); + await xole.convertToSharingToken(toWei(1000), '0', usdtOLEDexData); + assert.equal("10000000000000000000000", (await xole.shareableTokenAmount()).toString()); + assert.equal('987158034397061298850', (await xole.claimableTokenAmount()).toString()); + assert.equal('987158034397061298850', (await xole.devFund()).toString()); + // convert ole + await xole.convertToSharingToken(toWei(1000), '0', '0x'); + assert.equal("9000000000000000000000", (await xole.shareableTokenAmount()).toString()); + assert.equal('1487158034397061298850', (await xole.claimableTokenAmount()).toString()); + assert.equal('1487158034397061298850', (await xole.devFund()).toString()); + // withdrawCommunityFund + await xole.withdrawCommunityFund(communityAcc); + assert.equal('1487158034397061298850', (await ole.balanceOf(communityAcc)).toString()); + assert.equal("9000000000000000000000", (await xole.shareableTokenAmount()).toString()); + assert.equal('0', (await xole.claimableTokenAmount()).toString()); + assert.equal('1487158034397061298850', (await xole.devFund()).toString()); + // user withdraw ole, no effect + lastbk = await web3.eth.getBlock('latest'); + end = lastbk.timestamp + WEEK * 3; + await advanceBlockAndSetTime(end + 60 * 60 * 24); + await xole.withdraw({from: admin}); + assert.equal('0', (await xole.balanceOf(admin)).toString()); + assert.equal('10000000000000000000000', (await ole.balanceOf(admin)).toString()); + // convert all + await xole.convertToSharingToken(toWei(9000), '0', '0x'); + assert.equal("0", (await xole.shareableTokenAmount()).toString()); + assert.equal('4500000000000000000000', (await xole.claimableTokenAmount()).toString()); + assert.equal('5987158034397061298850', (await xole.devFund()).toString()); }) @@ -465,6 +562,78 @@ contract("XOLE", async accounts => { web3.eth.abi.encodeParameters(['uint256'], [1]), 0) assert.equal(1, await xole0.devFundRatio()); await assertThrows(xole0.setDevFundRatio(1), 'caller must be admin'); + }) + + it("Admin setDexAgg test", async () => { + let newDexAgg = accounts[3]; + let timeLock = await utils.createTimelock(admin); + let xole0 = await utils.createXOLE(ole.address, timeLock.address, dev, dexAgg.address, accounts[0]); + await timeLock.executeTransaction(xole0.address, 0, 'setDexAgg(address)', + web3.eth.abi.encodeParameters(['address'], [newDexAgg]), 0) + assert.equal(newDexAgg, await xole0.dexAgg()); + await assertThrows(xole0.setDexAgg(newDexAgg), 'caller must be admin'); + }) + + it("Admin setShareToken test", async () => { + let shareToken = ole.address; + let timeLock = await utils.createTimelock(admin); + let xole0 = await utils.createXOLE(ole.address, timeLock.address, dev, dexAgg.address, accounts[0]); + await timeLock.executeTransaction(xole0.address, 0, 'setShareToken(address)', + web3.eth.abi.encodeParameters(['address'], [shareToken]), 0) + assert.equal(shareToken, await xole0.shareToken()); + await assertThrows(xole0.setShareToken(shareToken), 'caller must be admin'); + + await ole.mint(xole0.address, toWei(10000)); + await xole0.convertToSharingToken(toWei(10000), 0, '0x'); + // Withdraw fund firstly + await assertThrows(timeLock.executeTransaction(xole0.address, 0, 'setShareToken(address)', + web3.eth.abi.encodeParameters(['address'], [shareToken]), 0), 'Transaction execution reverted'); + + }) + + it("Admin setOleLpStakeToken test", async () => { + let oleLpStakeToken = ole.address; + let timeLock = await utils.createTimelock(admin); + let xole0 = await utils.createXOLE(ole.address, timeLock.address, dev, dexAgg.address, accounts[0]); + await timeLock.executeTransaction(xole0.address, 0, 'setOleLpStakeToken(address)', + web3.eth.abi.encodeParameters(['address'], [oleLpStakeToken]), 0) + assert.equal(oleLpStakeToken, await xole0.oleLpStakeToken()); + await assertThrows(xole0.setOleLpStakeToken(oleLpStakeToken), 'caller must be admin'); + }) + + it("Admin setOleLpStakeAutomator test", async () => { + let oleLpStakeAutomator = ole.address; + let timeLock = await utils.createTimelock(admin); + let xole0 = await utils.createXOLE(ole.address, timeLock.address, dev, dexAgg.address, accounts[0]); + await timeLock.executeTransaction(xole0.address, 0, 'setOleLpStakeAutomator(address)', + web3.eth.abi.encodeParameters(['address'], [oleLpStakeAutomator]), 0) + assert.equal(oleLpStakeAutomator, await xole0.oleLpStakeAutomator()); + await assertThrows(xole0.setOleLpStakeAutomator(oleLpStakeAutomator), 'caller must be admin'); + }) + + it("Admin withdrawCommunityFund test", async () => { + let to = accounts[7]; + let timeLock = await utils.createTimelock(admin); + let xole0 = await utils.createXOLE(ole.address, timeLock.address, dev, dexAgg.address, accounts[0]); + + await assertThrows(xole0.withdrawCommunityFund(to), 'caller must be admin'); + let claimableTokenAmount = await xole0.claimableTokenAmount(); + assert.equal(0, claimableTokenAmount); + // to is 0x address + await assertThrows(timeLock.executeTransaction(xole0.address, 0, 'withdrawCommunityFund(address)', + web3.eth.abi.encodeParameters(['address'], ["0x0000000000000000000000000000000000000000"]), 0, {from: admin}), 'Transaction execution reverted'); + // fund is 0 + await assertThrows(timeLock.executeTransaction(xole0.address, 0, 'withdrawCommunityFund(address)', + web3.eth.abi.encodeParameters(['address'], [to]), 0, {from: admin}), 'Transaction execution reverted'); + + await timeLock.executeTransaction(xole0.address, 0, 'setShareToken(address)', + web3.eth.abi.encodeParameters(['address'], [ole.address]), 0); + + await ole.mint(xole0.address, toWei(10000)); + await xole0.convertToSharingToken(toWei(10000), 0, '0x'); + + await timeLock.executeTransaction(xole0.address, 0, 'withdrawCommunityFund(address)', + web3.eth.abi.encodeParameters(['address'], [to]), 0, {from: admin}); }) @@ -476,7 +645,6 @@ contract("XOLE", async accounts => { web3.eth.abi.encodeParameters(['address'], [newDev]), 0) assert.equal(newDev, await xole0.dev()); await assertThrows(xole0.setDev(newDev), 'caller must be admin'); - }) it("Admin convertToSharingToken test", async () => { 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..28d39b2 --- /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(), 7); + approxPrecisionAssertPrint("Check borrow rate", '1585489599', await erc20Pool.borrowRatePerBlock(), 7); + assert.equal(toETH(await erc20Pool.supplyRatePerBlock()), 0); + assert.equal((await erc20Pool.exchangeRateStored()).toString(), 1e18); + approxPrecisionAssertPrint("reserves", '334855403136000000', reserves.toString(), 7); + + //reduce reserves + await erc20Pool.reduceReserves(accounts[1], '34855403136000000'); + let reservesAfterReduce = await erc20Pool.totalReserves(); + approxPrecisionAssertPrint("reservesAfterReduce", '300000000000000000', reservesAfterReduce.toString(), 7); + approxPrecisionAssertPrint("Check borrow rate", '1585489599', (await erc20Pool.borrowRatePerBlock()).toString(), 7); + assert.equal(toETH(await erc20Pool.supplyRatePerBlock()), 0); + assert.equal((await erc20Pool.exchangeRateStored()).toString(), 1e18); + approxPrecisionAssertPrint("balanceOf", '34855403136000000', (await testToken.balanceOf(accounts[1])).toString(), 7); + approxPrecisionAssertPrint("cash", '300000000000000000', (await erc20Pool.getCash()).toString(), 7); + // add reserves + await erc20Pool.addReserves('100000000000000000'); + approxPrecisionAssertPrint("totalReserves", '400000000000000000', (await erc20Pool.totalReserves()).toString(), 7); + approxPrecisionAssertPrint("cash", '400000000000000000', (await erc20Pool.getCash()).toString(), 7); + + }) + + 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/OleLpStakeAutomatorTest.js b/test/OleLpStakeAutomatorTest.js new file mode 100644 index 0000000..6b7408d --- /dev/null +++ b/test/OleLpStakeAutomatorTest.js @@ -0,0 +1,651 @@ +const OLEToken = artifacts.require("OLEToken"); +const { + assertPrint, + approxAssertPrint, + createEthDexAgg, + createUniswapV2Factory, + createXOLE, assertThrows, toWei +} = require("./utils/OpenLevUtil"); +const m = require('mocha-logger'); +const {advanceMultipleBlocksAndTime, advanceBlockAndSetTime, toBN} = require("./utils/EtheUtil"); +const utils = require("./utils/OpenLevUtil"); +const timeMachine = require("ganache-time-traveler"); +const UniswapV2Factory = artifacts.require("UniswapV2Factory"); +const UniswapV2Router = artifacts.require("UniswapV2Router02"); + +const OleLpStakeAutomatorDelegator = artifacts.require("OleLpStakeAutomatorDelegator"); + +const OleLpStakeAutomator = artifacts.require("OleLpStakeAutomator"); +const TestToken = artifacts.require("MockERC20"); + +contract("xOLE", async accounts => { + + let DAY = 86400; + let WEEK = 7 * DAY; + + let _1000 = "1000000000000000000000"; + let _500 = "500000000000000000000"; + + let bob = accounts[0]; + let alice = accounts[1]; + let admin = accounts[2]; + let dev = accounts[3]; + + let ole; + let oleWethLpToken; + let oleUsdtLpToken; + let xole; + let weth; + let usdt; + let factory; + let router; + let nativeAutomator; + let erc20Automator; + let snapshotId; + beforeEach(async () => { + let snapshot = await timeMachine.takeSnapshot(); + snapshotId = snapshot['result']; + ole = await OLEToken.new(admin, accounts[0], "Open Leverage Token", "OLE"); + await ole.mint(bob, _1000); + await ole.mint(alice, _1000); + usdt = await TestToken.new('USDT', 'USDT'); + await usdt.mint(admin, _1000); + await usdt.mint(bob, _1000); + await usdt.mint(alice, _1000); + // uniswap + weth = await utils.createWETH(); + factory = await UniswapV2Factory.new("0x0000000000000000000000000000000000000000"); + router = await UniswapV2Router.new(factory.address, weth.address); + let block = await web3.eth.getBlock("latest"); + + await ole.approve(router.address, utils.toWei(1), {from: admin}); + await web3.eth.sendTransaction({from: accounts[9], to: admin, value: utils.toWei(1)}); + await router.addLiquidityETH(ole.address, utils.toWei(1), utils.toWei(1), utils.toWei(1), admin, block.timestamp + 60, { + from: admin, + value: utils.toWei(1) + }); + + await ole.approve(router.address, utils.toWei(1), {from: admin}); + await usdt.approve(router.address, utils.toWei(1), {from: admin}); + await router.addLiquidity(ole.address, usdt.address, utils.toWei(1), utils.toWei(1), utils.toWei(1), utils.toWei(1), admin, block.timestamp + 60, { + from: admin + }); + + //xole + xole = await createXOLE(ole.address, admin, dev, "0x0000000000000000000000000000000000000000", admin); + // native automator + oleWethLpToken = await factory.getPair(ole.address, weth.address); + nativeAutomator = await OleLpStakeAutomator.new(); + nativeAutomator = await OleLpStakeAutomator.at((await OleLpStakeAutomatorDelegator.new(xole.address, ole.address, weth.address, oleWethLpToken, weth.address, router.address, admin, nativeAutomator.address)).address); + // erc20 automator + oleUsdtLpToken = await factory.getPair(ole.address, usdt.address); + erc20Automator = await OleLpStakeAutomator.new(); + erc20Automator = await OleLpStakeAutomator.at((await OleLpStakeAutomatorDelegator.new(xole.address, ole.address, usdt.address, oleUsdtLpToken, weth.address, router.address, admin, erc20Automator.address)).address); + // init + let lastbk = await web3.eth.getBlock('latest'); + m.log("lastbk", lastbk.timestamp); + let timeToMove = lastbk.timestamp + (WEEK - lastbk.timestamp % WEEK) + 10; + m.log("timeToMove", timeToMove); + m.log("Move time to start of the week", new Date(timeToMove * 1000)); + await advanceBlockAndSetTime(timeToMove); + + }); + + afterEach(async () => { + await timeMachine.revertToSnapshot(snapshotId); + }); + it("Native automator createLockBoth test", async () => { + await xole.setOleLpStakeToken(oleWethLpToken, {from: admin}); + await xole.setOleLpStakeAutomator(nativeAutomator.address, {from: admin}); + let oleAmount = toWei(1); + let otherAmount = toWei(1); + let unlockTime = (await web3.eth.getBlock('latest')).timestamp + 3 * WEEK + DAY; + m.log("unlockTime", unlockTime); + await ole.approve(nativeAutomator.address, oleAmount, {from: alice}); + //minAmount check + await assertThrows(nativeAutomator.createLockBoth(oleAmount, 0, unlockTime, toWei(2), toWei(2), { + from: alice, + value: otherAmount + }), 'INSUFFICIENT'); + //back remainder check + await ole.approve(nativeAutomator.address, toWei(2), {from: alice}); + await nativeAutomator.createLockBoth(toWei(2), 0, unlockTime, oleAmount, otherAmount, { + from: alice, + value: otherAmount + }); + let ethBalanceBefore = await web3.eth.getBalance(bob); + await ole.approve(nativeAutomator.address, oleAmount, {from: bob}); + let r = await nativeAutomator.createLockBoth(oleAmount, 0, unlockTime, oleAmount, otherAmount, { + from: bob, + value: toWei(2) + }); + m.log("createLockBoth Gas Used:", r.receipt.gasUsed); + + let ethBalanceAfter = await web3.eth.getBalance(bob); + assertPrint("Bob back 1 eth", toBN(ethBalanceBefore).sub(toBN(ethBalanceAfter)).lt(toWei(2)), true); + // xole check + assertPrint("Alice's balance of ole", "999000000000000000000", await ole.balanceOf(alice)); + assertPrint("Alice's balance of xole", "1020800000000000000", await xole.balanceOf(alice)); + + assertPrint("Bob's balance of ole", "999000000000000000000", await ole.balanceOf(bob)); + assertPrint("Bob's balance of xole", "1020800000000000000", await xole.balanceOf(bob)); + }) + + it("Erc20 automator createLockBoth test", async () => { + + await xole.setOleLpStakeToken(oleUsdtLpToken, {from: admin}); + await xole.setOleLpStakeAutomator(erc20Automator.address, {from: admin}); + let oleAmount = toWei(1); + let otherAmount = toWei(1); + let unlockTime = (await web3.eth.getBlock('latest')).timestamp + 3 * WEEK + DAY; + await ole.approve(erc20Automator.address, oleAmount, {from: alice}); + await usdt.approve(erc20Automator.address, toWei(2), {from: alice}); + //minAmount check + await assertThrows(erc20Automator.createLockBoth(oleAmount, otherAmount, unlockTime, toWei(2), toWei(2), { + from: alice + }), 'INSUFFICIENT'); + //back remainder check + await ole.approve(erc20Automator.address, toWei(2), {from: alice}); + await usdt.approve(erc20Automator.address, toWei(2), {from: alice}); + await erc20Automator.createLockBoth(toWei(2), otherAmount, unlockTime, oleAmount, otherAmount, { + from: alice + }); + await ole.approve(erc20Automator.address, oleAmount, {from: bob}); + await usdt.approve(erc20Automator.address, toWei(2), {from: bob}); + await erc20Automator.createLockBoth(oleAmount, toWei(2), unlockTime, oleAmount, otherAmount, { + from: bob + }); + // xole check + assertPrint("Alice's balance of ole", "999000000000000000000", await ole.balanceOf(alice)); + assertPrint("Alice's balance of xole", "1020800000000000000", await xole.balanceOf(alice)); + + assertPrint("Bob's balance of ole", "999000000000000000000", await ole.balanceOf(bob)); + assertPrint("Bob's balance of xole", "1020800000000000000", await xole.balanceOf(bob)); + }) + it("Native automator createLockOLE test", async () => { + await xole.setOleLpStakeToken(oleWethLpToken, {from: admin}); + await xole.setOleLpStakeAutomator(nativeAutomator.address, {from: admin}); + let oleAmount = toWei(1); + let unlockTime = (await web3.eth.getBlock('latest')).timestamp + 3 * WEEK + DAY; + await ole.approve(nativeAutomator.address, oleAmount, {from: alice}); + + //minAmount check + await assertThrows(nativeAutomator.createLockOLE(oleAmount, unlockTime, toWei(1), toWei(1), { + from: alice + }), 'INSUFFICIENT'); + //back remainder check + await ole.approve(nativeAutomator.address, toWei(2), {from: alice}); + + let ethBalanceBefore = await web3.eth.getBalance(alice); + let r = await nativeAutomator.createLockOLE(toWei(2), unlockTime, toWei(0), toWei(0), { + from: alice + }); + m.log("createLockOLE Gas Used:", r.receipt.gasUsed); + + let ethBalanceAfter = await web3.eth.getBalance(alice); + assertPrint("Alice back 0.2 eth", toBN(ethBalanceAfter).sub(toBN(ethBalanceBefore)).gt(toWei(0)), true); + + //xole check + assertPrint("Alice's balance of ole", "998000000000000000000", await ole.balanceOf(alice)); + assertPrint("Alice's balance of xole", "510399999999999998", await xole.balanceOf(alice)); + + }) + + it("Erc20 automator createLockOLE test", async () => { + + await xole.setOleLpStakeToken(oleUsdtLpToken, {from: admin}); + await xole.setOleLpStakeAutomator(erc20Automator.address, {from: admin}); + let oleAmount = toWei(1); + let unlockTime = (await web3.eth.getBlock('latest')).timestamp + 3 * WEEK + DAY; + await ole.approve(erc20Automator.address, oleAmount, {from: alice}); + + //minAmount check + await assertThrows(erc20Automator.createLockOLE(oleAmount, unlockTime, toWei(1), toWei(1), { + from: alice + }), 'INSUFFICIENT'); + //back remainder check + await ole.approve(erc20Automator.address, toWei(2), {from: alice}); + + await erc20Automator.createLockOLE(toWei(2), unlockTime, toWei(0), toWei(0), { + from: alice + }); + + //xole check + assertPrint("Alice's balance of ole", "998000000000000000000", await ole.balanceOf(alice)); + assertPrint("Alice's balance of usdt", "1000248873309964947421", await usdt.balanceOf(alice)); + assertPrint("Alice's balance of xole", "510399999999999998", await xole.balanceOf(alice)); + + }) + + it("Native automator createLockOther test", async () => { + await xole.setOleLpStakeToken(oleWethLpToken, {from: admin}); + await xole.setOleLpStakeAutomator(nativeAutomator.address, {from: admin}); + let otherAmount = toWei(1); + let unlockTime = (await web3.eth.getBlock('latest')).timestamp + 3 * WEEK + DAY; + + //minAmount check + await assertThrows(nativeAutomator.createLockOther(0, unlockTime, toWei(1), toWei(1), { + from: alice, + value: otherAmount + }), 'INSUFFICIENT'); + + //back remainder check + let r = await nativeAutomator.createLockOther(0, unlockTime, toWei(0), toWei(0), { + from: alice, + value: toWei(2) + }); + m.log("createLockOther Gas Used:", r.receipt.gasUsed); + + //xole check + assertPrint("Alice's balance of ole", "1000248873309964947421", await ole.balanceOf(alice)); + assertPrint("Alice's balance of xole", "510399999999999998", await xole.balanceOf(alice)); + + }) + + it("Erc20 automator createLockOther test", async () => { + + await xole.setOleLpStakeToken(oleUsdtLpToken, {from: admin}); + await xole.setOleLpStakeAutomator(erc20Automator.address, {from: admin}); + let otherAmount = toWei(1); + let unlockTime = (await web3.eth.getBlock('latest')).timestamp + 3 * WEEK + DAY; + await usdt.approve(erc20Automator.address, otherAmount, {from: alice}); + + //minAmount check + await assertThrows(erc20Automator.createLockOther(otherAmount, unlockTime, toWei(1), toWei(1), { + from: alice + }), 'INSUFFICIENT'); + //back remainder check + await usdt.approve(erc20Automator.address, toWei(2), {from: alice}); + + await erc20Automator.createLockOther(toWei(2), unlockTime, toWei(0), toWei(0), { + from: alice + }); + + //xole check + assertPrint("Alice's balance of ole", "1000248873309964947421", await ole.balanceOf(alice)); + assertPrint("Alice's balance of xole", "510399999999999998", await xole.balanceOf(alice)); + + }) + + it("Native automator increaseAmountBoth test", async () => { + await xole.setOleLpStakeToken(oleWethLpToken, {from: admin}); + await xole.setOleLpStakeAutomator(nativeAutomator.address, {from: admin}); + let oleAmount = toWei(1); + let otherAmount = toWei(1); + let unlockTime = (await web3.eth.getBlock('latest')).timestamp + 3 * WEEK + DAY; + m.log("unlockTime", unlockTime); + //back remainder check + await ole.approve(nativeAutomator.address, toWei(2), {from: alice}); + await nativeAutomator.createLockBoth(toWei(2), 0, unlockTime, oleAmount, otherAmount, { + from: alice, + value: otherAmount + }); + + //minAmount check + await ole.approve(nativeAutomator.address, toWei(2), {from: alice}); + await assertThrows(nativeAutomator.increaseAmountBoth(oleAmount, 0, toWei(2), toWei(2), { + from: alice, + value: otherAmount + }), 'INSUFFICIENT'); + + let r = await nativeAutomator.increaseAmountBoth(toWei(2), 0, oleAmount, otherAmount, { + from: alice, + value: otherAmount + }); + m.log("increaseAmountBoth Gas Used:", r.receipt.gasUsed); + + // xole check + assertPrint("Alice's balance of ole", "998000000000000000000", await ole.balanceOf(alice)); + assertPrint("Alice's balance of xole", "2041600000000000000", await xole.balanceOf(alice)); + + }) + + it("Erc20 automator increaseAmountBoth test", async () => { + await xole.setOleLpStakeToken(oleUsdtLpToken, {from: admin}); + await xole.setOleLpStakeAutomator(erc20Automator.address, {from: admin}); + let oleAmount = toWei(1); + let otherAmount = toWei(1); + let unlockTime = (await web3.eth.getBlock('latest')).timestamp + 3 * WEEK + DAY; + m.log("unlockTime", unlockTime); + //back remainder check + await ole.approve(erc20Automator.address, toWei(2), {from: alice}); + await usdt.approve(erc20Automator.address, toWei(2), {from: alice}); + + await erc20Automator.createLockBoth(toWei(2), otherAmount, unlockTime, oleAmount, otherAmount, { + from: alice + }); + + //minAmount check + await ole.approve(erc20Automator.address, toWei(2), {from: alice}); + await usdt.approve(erc20Automator.address, toWei(2), {from: alice}); + await assertThrows(erc20Automator.increaseAmountBoth(oleAmount, otherAmount, toWei(2), toWei(2), { + from: alice + }), 'INSUFFICIENT'); + + await erc20Automator.increaseAmountBoth(toWei(2), otherAmount, oleAmount, otherAmount, { + from: alice + }); + + // xole check + assertPrint("Alice's balance of ole", "998000000000000000000", await ole.balanceOf(alice)); + assertPrint("Alice's balance of usdt", "998000000000000000000", await usdt.balanceOf(alice)); + assertPrint("Alice's balance of xole", "2041600000000000000", await xole.balanceOf(alice)); + + }) + + it("Native automator increaseAmountOLE test", async () => { + await xole.setOleLpStakeToken(oleWethLpToken, {from: admin}); + await xole.setOleLpStakeAutomator(nativeAutomator.address, {from: admin}); + let oleAmount = toWei(1); + let otherAmount = toWei(1); + let unlockTime = (await web3.eth.getBlock('latest')).timestamp + 3 * WEEK + DAY; + //back remainder check + await ole.approve(nativeAutomator.address, toWei(2), {from: alice}); + await nativeAutomator.createLockBoth(toWei(2), 0, unlockTime, oleAmount, otherAmount, { + from: alice, + value: otherAmount + }); + + //minAmount check + await ole.approve(nativeAutomator.address, toWei(2), {from: alice}); + await assertThrows(nativeAutomator.increaseAmountOLE(oleAmount, toWei(1), toWei(1), { + from: alice + }), 'INSUFFICIENT'); + //back remainder check + let ethBalanceBefore = await web3.eth.getBalance(alice); + await ole.approve(nativeAutomator.address, toWei(2), {from: alice}); + let r = await nativeAutomator.increaseAmountOLE(toWei(2), toWei(0), toWei(0), { + from: alice + }); + m.log("increaseAmountOLE Gas Used:", r.receipt.gasUsed); + + let ethBalanceAfter = await web3.eth.getBalance(alice); + assertPrint("Alice back 0.2 eth", toBN(ethBalanceAfter).sub(toBN(ethBalanceBefore)).gt(toWei(0)), true); + + //xole check + assertPrint("Alice's balance of ole", "997000000000000000000", await ole.balanceOf(alice)); + assertPrint("Alice's balance of xole", "1701333333333333332", await xole.balanceOf(alice)); + + }) + + it("Erc20 automator increaseAmountOLE test", async () => { + await xole.setOleLpStakeToken(oleUsdtLpToken, {from: admin}); + await xole.setOleLpStakeAutomator(erc20Automator.address, {from: admin}); + let oleAmount = toWei(1); + let otherAmount = toWei(1); + let unlockTime = (await web3.eth.getBlock('latest')).timestamp + 3 * WEEK + DAY; + //back remainder check + await ole.approve(erc20Automator.address, toWei(2), {from: alice}); + await usdt.approve(erc20Automator.address, toWei(2), {from: alice}); + await erc20Automator.createLockBoth(toWei(2), otherAmount, unlockTime, oleAmount, otherAmount, { + from: alice + }); + + //minAmount check + await ole.approve(erc20Automator.address, toWei(2), {from: alice}); + await assertThrows(erc20Automator.increaseAmountOLE(oleAmount, toWei(1), toWei(1), { + from: alice + }), 'INSUFFICIENT'); + //back remainder check + await ole.approve(erc20Automator.address, toWei(2), {from: alice}); + await erc20Automator.increaseAmountOLE(toWei(2), toWei(0), toWei(0), { + from: alice + }); + //xole check + assertPrint("Alice's balance of ole", "997000000000000000000", await ole.balanceOf(alice)); + assertPrint("Alice's balance of usdt", "999220442664887109331", await usdt.balanceOf(alice)); + assertPrint("Alice's balance of xole", "1701333333333333332", await xole.balanceOf(alice)); + + }) + + it("Native automator increaseAmountOther test", async () => { + await xole.setOleLpStakeToken(oleWethLpToken, {from: admin}); + await xole.setOleLpStakeAutomator(nativeAutomator.address, {from: admin}); + let oleAmount = toWei(1); + let otherAmount = toWei(1); + let unlockTime = (await web3.eth.getBlock('latest')).timestamp + 3 * WEEK + DAY; + //back remainder check + await ole.approve(nativeAutomator.address, toWei(2), {from: alice}); + await nativeAutomator.createLockBoth(toWei(2), 0, unlockTime, oleAmount, otherAmount, { + from: alice, + value: otherAmount + }); + + //minAmount check + await assertThrows(nativeAutomator.increaseAmountOther(0, toWei(1), toWei(1), { + from: alice, + value: otherAmount + }), 'INSUFFICIENT'); + //back remainder check + + let r = await nativeAutomator.increaseAmountOther(0, toWei(0), toWei(0), { + from: alice, + value: toWei(2) + }); + m.log("increaseAmountOther Gas Used:", r.receipt.gasUsed); + + //xole check + assertPrint("Alice's balance of xole", "1701333333333333332", await xole.balanceOf(alice)); + assertPrint("Alice's balance of ole", "999220442664887109331", await ole.balanceOf(alice)); + }) + + it("Erc20 automator increaseAmountOther test", async () => { + await xole.setOleLpStakeToken(oleUsdtLpToken, {from: admin}); + await xole.setOleLpStakeAutomator(erc20Automator.address, {from: admin}); + let oleAmount = toWei(1); + let otherAmount = toWei(1); + let unlockTime = (await web3.eth.getBlock('latest')).timestamp + 3 * WEEK + DAY; + //back remainder check + await ole.approve(erc20Automator.address, toWei(2), {from: alice}); + await usdt.approve(erc20Automator.address, toWei(2), {from: alice}); + + await erc20Automator.createLockBoth(toWei(2), otherAmount, unlockTime, oleAmount, otherAmount, { + from: alice + }); + + //minAmount check + await usdt.approve(erc20Automator.address, toWei(2), {from: alice}); + await assertThrows(erc20Automator.increaseAmountOther(otherAmount, toWei(1), toWei(1), { + from: alice, + }), 'INSUFFICIENT'); + //back remainder check + await usdt.approve(erc20Automator.address, toWei(2), {from: alice}); + await erc20Automator.increaseAmountOther(toWei(2), toWei(0), toWei(0), { + from: alice + }); + //xole check + assertPrint("Alice's balance of xole", "1701333333333333332", await xole.balanceOf(alice)); + assertPrint("Alice's balance of ole", "999220442664887109331", await ole.balanceOf(alice)); + }) + + it("Native automator withdrawBoth test", async () => { + await xole.setOleLpStakeToken(oleWethLpToken, {from: admin}); + await xole.setOleLpStakeAutomator(nativeAutomator.address, {from: admin}); + let oleAmount = toWei(1); + let otherAmount = toWei(1); + let unlockTime = (await web3.eth.getBlock('latest')).timestamp + 3 * WEEK + DAY; + //back remainder check + await ole.approve(nativeAutomator.address, toWei(2), {from: alice}); + await nativeAutomator.createLockBoth(toWei(2), 0, unlockTime, oleAmount, otherAmount, { + from: alice, + value: otherAmount + }); + await advanceBlockAndSetTime(unlockTime + WEEK); + //minAmount check + await assertThrows(nativeAutomator.withdrawBoth(toWei(2), toWei(2), { + from: alice + }), 'INSUFFICIENT'); + let ethBalanceBefore = await web3.eth.getBalance(alice); + + let r = await nativeAutomator.withdrawBoth(0, 0, { + from: alice + }); + m.log("withdrawBoth Gas Used:", r.receipt.gasUsed); + + let ethBalanceAfter = await web3.eth.getBalance(alice); + assertPrint("Alice back 1 eth", toBN(ethBalanceAfter).sub(toBN(ethBalanceBefore)).gt(toWei(0)), true); + //xole check + assertPrint("Alice's balance of xole", "0", await xole.balanceOf(alice)); + assertPrint("Alice's balance of ole", "1000000000000000000000", await ole.balanceOf(alice)); + + }) + + it("Erc20 automator withdrawBoth test", async () => { + await xole.setOleLpStakeToken(oleUsdtLpToken, {from: admin}); + await xole.setOleLpStakeAutomator(erc20Automator.address, {from: admin}); + let oleAmount = toWei(1); + let otherAmount = toWei(1); + let unlockTime = (await web3.eth.getBlock('latest')).timestamp + 3 * WEEK + DAY; + //back remainder check + await ole.approve(erc20Automator.address, toWei(2), {from: alice}); + await usdt.approve(erc20Automator.address, toWei(2), {from: alice}); + await erc20Automator.createLockBoth(toWei(2), otherAmount, unlockTime, oleAmount, otherAmount, { + from: alice + }); + await advanceBlockAndSetTime(unlockTime + WEEK); + //minAmount check + await assertThrows(erc20Automator.withdrawBoth(toWei(2), toWei(2), { + from: alice + }), 'INSUFFICIENT'); + + await erc20Automator.withdrawBoth(0, 0, { + from: alice + }); + //xole check + assertPrint("Alice's balance of xole", "0", await xole.balanceOf(alice)); + assertPrint("Alice's balance of usdt", "1000000000000000000000", await usdt.balanceOf(alice)); + assertPrint("Alice's balance of ole", "1000000000000000000000", await ole.balanceOf(alice)); + + }) + + it("Native automator withdrawOle test", async () => { + await xole.setOleLpStakeToken(oleWethLpToken, {from: admin}); + await xole.setOleLpStakeAutomator(nativeAutomator.address, {from: admin}); + let oleAmount = toWei(1); + let otherAmount = toWei(1); + let unlockTime = (await web3.eth.getBlock('latest')).timestamp + 3 * WEEK + DAY; + //back remainder check + await ole.approve(nativeAutomator.address, toWei(2), {from: alice}); + await nativeAutomator.createLockBoth(toWei(2), 0, unlockTime, oleAmount, otherAmount, { + from: alice, + value: otherAmount + }); + await advanceBlockAndSetTime(unlockTime + WEEK); + //minAmount check + await assertThrows(nativeAutomator.withdrawOle(toWei(2), toWei(2), { + from: alice + }), 'INSUFFICIENT'); + + let r = await nativeAutomator.withdrawOle(0, 0, { + from: alice + }); + m.log("withdrawOle Gas Used:", r.receipt.gasUsed); + + //xole check + assertPrint("Alice's balance of xole", "0", await xole.balanceOf(alice)); + assertPrint("Alice's balance of ole", "1000499248873309964947", await ole.balanceOf(alice)); + + }) + + it("Erc20 automator withdrawOle test", async () => { + await xole.setOleLpStakeToken(oleUsdtLpToken, {from: admin}); + await xole.setOleLpStakeAutomator(erc20Automator.address, {from: admin}); + let oleAmount = toWei(1); + let otherAmount = toWei(1); + let unlockTime = (await web3.eth.getBlock('latest')).timestamp + 3 * WEEK + DAY; + //back remainder check + await ole.approve(erc20Automator.address, toWei(2), {from: alice}); + await usdt.approve(erc20Automator.address, toWei(2), {from: alice}); + await erc20Automator.createLockBoth(toWei(2), otherAmount, unlockTime, oleAmount, otherAmount, { + from: alice + }); + await advanceBlockAndSetTime(unlockTime + WEEK); + //minAmount check + await assertThrows(erc20Automator.withdrawOle(toWei(2), toWei(2), { + from: alice + }), 'INSUFFICIENT'); + + await erc20Automator.withdrawOle(0, 0, { + from: alice + }); + //xole check + assertPrint("Alice's balance of xole", "0", await xole.balanceOf(alice)); + assertPrint("Alice's balance of ole", "1000499248873309964947", await ole.balanceOf(alice)); + + }) + + it("Native automator withdrawOther test", async () => { + await xole.setOleLpStakeToken(oleWethLpToken, {from: admin}); + await xole.setOleLpStakeAutomator(nativeAutomator.address, {from: admin}); + let oleAmount = toWei(1); + let otherAmount = toWei(1); + let unlockTime = (await web3.eth.getBlock('latest')).timestamp + 3 * WEEK + DAY; + //back remainder check + await ole.approve(nativeAutomator.address, toWei(2), {from: alice}); + await nativeAutomator.createLockBoth(toWei(2), 0, unlockTime, oleAmount, otherAmount, { + from: alice, + value: otherAmount + }); + await advanceBlockAndSetTime(unlockTime + WEEK); + //minAmount check + await assertThrows(nativeAutomator.withdrawOther(toWei(2), toWei(2), { + from: alice + }), 'INSUFFICIENT'); + + + let ethBalanceBefore = await web3.eth.getBalance(alice); + let r = await nativeAutomator.withdrawOther(0, 0, { + from: alice + }); + m.log("withdrawOther Gas Used:", r.receipt.gasUsed); + + let ethBalanceAfter = await web3.eth.getBalance(alice); + assertPrint("Alice back 1.4 eth", toBN(ethBalanceAfter).sub(toBN(ethBalanceBefore)).gt(toWei(1)), true); + + //xole check + assertPrint("Alice's balance of xole", "0", await xole.balanceOf(alice)); + + }) + + it("Erc20 automator withdrawOther test", async () => { + await xole.setOleLpStakeToken(oleUsdtLpToken, {from: admin}); + await xole.setOleLpStakeAutomator(erc20Automator.address, {from: admin}); + let oleAmount = toWei(1); + let otherAmount = toWei(1); + let unlockTime = (await web3.eth.getBlock('latest')).timestamp + 3 * WEEK + DAY; + //back remainder check + await ole.approve(erc20Automator.address, toWei(2), {from: alice}); + await usdt.approve(erc20Automator.address, toWei(2), {from: alice}); + await erc20Automator.createLockBoth(toWei(2), otherAmount, unlockTime, oleAmount, otherAmount, { + from: alice + }); + await advanceBlockAndSetTime(unlockTime + WEEK); + //minAmount check + await assertThrows(erc20Automator.withdrawOther(toWei(2), toWei(2), { + from: alice + }), 'INSUFFICIENT'); + + await erc20Automator.withdrawOther(0, 0, { + from: alice + }); + + //xole check + assertPrint("Alice's balance of xole", "0", await xole.balanceOf(alice)); + assertPrint("Alice's balance of usdt", "1000499248873309964947", await usdt.balanceOf(alice)); + + }) + + /*** Admin Test ***/ + + it("Admin initialize test", async () => { + await assertThrows(nativeAutomator.initialize(admin, admin, admin, admin, admin, admin, {from: alice}), 'NAD'); + await nativeAutomator.initialize(admin, admin, admin, admin, admin, admin, {from: admin}); + }) + + it("Admin setImplementation test", async () => { + let automator = await OleLpStakeAutomatorDelegator.at(nativeAutomator.address); + await assertThrows(automator.setImplementation(admin, {from: alice}), 'caller must be admin'); + await automator.setImplementation(admin, {from: admin}); + }); + +}) 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 826a158..f62013c 100644 --- a/test/OpenLevV1UniV3Test1.js +++ b/test/OpenLevV1UniV3Test1.js @@ -30,6 +30,7 @@ contract("OpenLev UniV3", async accounts => { let admin = accounts[0]; let saver = accounts[1]; let trader = accounts[2]; + let trader2 = accounts[4]; let dev = accounts[3]; let liquidator2 = accounts[9]; @@ -156,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}); @@ -275,6 +276,64 @@ contract("OpenLev UniV3", async accounts => { checkAmount("Trader Borrows Token Balance is Zero", 0, await token1.balanceOf(trader), 18); }) + it("LONG Token0, Deposit Token0, Liquidate, Reduce insurance ", async () => { + let pairId = 0; + + // provide some funds for trader and saver + await utils.mint(token0, trader, 400); + await utils.mint(token1, trader2, 1000000); + + m.log("Trader", last8(trader), "minted", await token0.symbol(), await token0.balanceOf(trader)); + + await utils.mint(token1, saver, 10000); + await utils.mint(token0, saver, 10000); + m.log("Saver", last8(saver), "minted", await token1.symbol(), await token1.balanceOf(saver)); + + // Trader to approve openLev to spend + let deposit = utils.toWei(400); + let deposit2 = utils.toWei(1000000); + await token0.approve(openLev.address, deposit, {from: trader}); + await token1.approve(openLev.address, deposit2, {from: trader2}); + + // Saver deposit to pool1 + let saverSupply = utils.toWei(4000); + let pool1 = await LPool.at((await openLev.markets(pairId)).pool1); + await token1.approve(await pool1.address, saverSupply, {from: saver}); + await pool1.mint(saverSupply, {from: saver}); + + let borrow = utils.toWei(1000); + m.log("toBorrow from Pool 1: \t", borrow); + await openLev.marginTrade(0, false, false, deposit, borrow, 0, Uni3DexData, {from: trader}); + await openLev.marginTrade(0, false, true, deposit2, borrow, 0, Uni3DexData, {from: trader2}); + + await advanceMultipleBlocks(1000); + await gotPair.setPrice(token0.address, token1.address, "400000000000000000"); + await gotPair.setPreviousPrice(token0.address, token1.address, "400000000000000000"); + let slot0 = await gotPair.slot0(); + m.log("uniV3Price: ", slot0.sqrtPriceX96.toString()); + m.log("uniV3Tick: ", slot0.tick.toString()); + + marginRatio_2 = await openLev.marginRatio(trader, 0, 0, Uni3DexData, {from: saver}); + m.log("Margin Ratio:", marginRatio_2.current / 100, "%"); + + let trade = await openLev.activeTrades(trader, 0, 0); + m.log("Trade.held:", trade.held); + m.log("Trade.deposited:", trade.deposited); + + m.log("Liquidating trade ... "); + let tx_liquidate = await openLev.liquidate(trader, 0, 0, 0, utils.maxUint(), Uni3DexData, {from: liquidator2}); + + assertPrint("Deposit Decrease", '395800000000000000000', tx_liquidate.logs[0].args.depositDecrease); + assertPrint("Deposit Return", '0', tx_liquidate.logs[0].args.depositReturn); + + assertPrint("Insurance of Pool0:", '2755128454053090685', (await openLev.markets(pairId)).pool0Insurance); + assertPrint("Insurance of Pool1:", '519400035826617452742', (await openLev.markets(pairId)).pool1Insurance); + assertPrint("TotalHeld of Token0:", '89891509577740192199543', (await openLev.totalHelds(token0.address))); + assertPrint("TotalHeld of Token1:", '519400035826617452742', (await openLev.totalHelds(token1.address))); + assertPrint("Balance of Token0:", '89891509577740192199543', (await token0.balanceOf(openLev.address))); + assertPrint("Balance of Token1:", '519400035826617452742', (await token1.balanceOf(openLev.address))); + }) + it("LONG Token0, Deposit Token0, Liquidate, Blow up", async () => { let pairId = 0; @@ -432,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'); }) @@ -467,19 +526,51 @@ contract("OpenLev UniV3", async accounts => { await openLev.setPendingAdmin(timeLock.address); await timeLock.executeTransaction(openLev.address, 0, 'acceptAdmin()', web3.eth.abi.encodeParameters([], []), 0) - // await openLev.acceptAdmin(); let pool1Insurance = (await openLev.markets(pairId)).pool1Insurance; + let totalHeld = await openLev.totalHelds(token0.address); + assert.equal(pool1Insurance.toString(), (await openLev.totalHelds(token1.address)).toString()); + m.log("pool1Insurance", pool1Insurance); await timeLock.executeTransaction(openLev.address, 0, 'moveInsurance(uint16,uint8,address,uint256)', web3.eth.abi.encodeParameters(['uint16', 'uint8', 'address', 'uint256'], [pairId, 1, accounts[5], pool1Insurance]), 0) - + assert.equal("0", (await openLev.totalHelds(token1.address))); assert.equal("0", (await openLev.markets(pairId)).pool1Insurance); assert.equal(pool1Insurance, (await token1.balanceOf(accounts[5])).toString()); + //close + await openLev.closeTrade(0, 0, toBN(totalHeld).div(toBN(2)), 0, Uni3DexData, {from: trader}); + let pool0Insurance = (await openLev.markets(pairId)).pool0Insurance; + m.log("pool0Insurance", pool0Insurance); + let marginRatio_0 = await openLev.marginRatio(trader, 0, 0, Uni3DexData); + await timeLock.executeTransaction(openLev.address, 0, 'moveInsurance(uint16,uint8,address,uint256)', + web3.eth.abi.encodeParameters(['uint16', 'uint8', 'address', 'uint256'], [pairId, 0, accounts[5], pool0Insurance]), 0) + let marginRatio_1 = await openLev.marginRatio(trader, 0, 0, Uni3DexData); + m.log("marginRatio_0.current", marginRatio_0.current); + m.log("marginRatio_1.current", marginRatio_1.current); + + assert.equal(marginRatio_1.current.toString(), marginRatio_0.current.toString()); + + assert.equal((await openLev.totalHelds(token0.address)).toString(), (await openLev.activeTrades(trader, pairId, false)).held.toString()); + assert.equal("0", (await openLev.markets(pairId)).pool0Insurance); + assert.equal(pool0Insurance, (await token0.balanceOf(accounts[5])).toString()); + + await openLev.closeTrade(0, 0, (await openLev.activeTrades(trader, pairId, false)).held, 0, Uni3DexData, {from: trader}); + assert.equal("0", (await openLev.activeTrades(trader, pairId, false)).held.toString()); + assert.equal("0", (await pool1.borrowBalanceCurrent(trader)).toString()); + await assertThrows(openLev.moveInsurance(pairId, 1, accounts[5], pool1Insurance), 'caller must be admin'); }) + 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/RetroactiveAirdropLockTest.js b/test/RetroactiveAirdropLockTest.js new file mode 100644 index 0000000..ea45bd9 --- /dev/null +++ b/test/RetroactiveAirdropLockTest.js @@ -0,0 +1,129 @@ +const {toBN} = require("./utils/EtheUtil"); + +const {toWei, lastBlockTime, toETH, firstStr, assertThrows} = require("./utils/OpenLevUtil"); +const RetroactiveAirdropLock = artifacts.require("RetroactiveAirdropLock"); +const OLEToken = artifacts.require("OLEToken"); + + +const m = require('mocha-logger'); + +const timeMachine = require('ganache-time-traveler'); +const utils = require("./utils/OpenLevUtil"); +const {from} = require("truffle/build/987.bundled"); + +contract("RetroactiveAirdropLock", async accounts => { + let oleToken; + let currentBlockTime; + let timeLock; + + beforeEach(async () => { + oleToken = await OLEToken.new(accounts[0], accounts[0], 'TEST', 'TEST'); + currentBlockTime = parseInt(await lastBlockTime()); + timeLock = await utils.createTimelock(accounts[0]); + }); + + it("Claim before start time", async () => { + let lock = await RetroactiveAirdropLock.new(oleToken.address, timeLock.address, currentBlockTime + 1000000, currentBlockTime + 2000000, currentBlockTime + 3000000); + await timeLock.executeTransaction(lock.address, 0, 'setReleaseBatch(address[],uint256[])', + web3.eth.abi.encodeParameters(['address[]', 'uint256[]'], [[accounts[1]], [toWei(100000)]]), 0); + await oleToken.transfer(lock.address, toWei(100000)); + await assertThrows(lock.release({from: accounts[1]}), 'not time to unlock'); + }); + + it("Claim after end time and before expire time", async () => { + let lock = await RetroactiveAirdropLock.new(oleToken.address, timeLock.address, '1599372311', currentBlockTime, currentBlockTime + 10000000); + await timeLock.executeTransaction(lock.address, 0, 'setReleaseBatch(address[],uint256[])', + web3.eth.abi.encodeParameters(['address[]', 'uint256[]'], [[accounts[1]], [toWei(100000)]]), 0); + await oleToken.transfer(lock.address, toWei(100000)); + await lock.release({from: accounts[1]}); + assert.equal((await lock.releaseAbleAmount(accounts[1])), 0); + assert.equal(toBN(100000).mul(toBN(1e18)).toString(), (await oleToken.balanceOf(accounts[1])).toString()); + }); + + it("Claim after expire time", async () => { + let lock = await RetroactiveAirdropLock.new(oleToken.address, timeLock.address, '1599372311', currentBlockTime - 10000000, currentBlockTime - 1); + await timeLock.executeTransaction(lock.address, 0, 'setReleaseBatch(address[],uint256[])', + web3.eth.abi.encodeParameters(['address[]', 'uint256[]'], [[accounts[1]], [toWei(100000)]]), 0); + await oleToken.transfer(lock.address, toWei(100000)); + await assertThrows(lock.release({from: accounts[1]}), 'time expired'); + }); + + it("Claim address is non beneficiary: ", async () => { + let lock = await RetroactiveAirdropLock.new(oleToken.address, timeLock.address, '1599372311', currentBlockTime, currentBlockTime + 10000000); + await timeLock.executeTransaction(lock.address, 0, 'setReleaseBatch(address[],uint256[])', + web3.eth.abi.encodeParameters(['address[]', 'uint256[]'], [[accounts[1]], [toWei(100000)]]), 0); + await oleToken.transfer(lock.address, toWei(100000)); + await assertThrows(lock.release({from: accounts[2]}), 'beneficiary does not exist'); + }); + + it("Claim many times, is the amount correct each time: ", async () => { + let lock = await RetroactiveAirdropLock.new(oleToken.address, timeLock.address, currentBlockTime - 100000, currentBlockTime + 100000, currentBlockTime + 200000); + await timeLock.executeTransaction(lock.address, 0, 'setReleaseBatch(address[],uint256[])', + web3.eth.abi.encodeParameters(['address[]', 'uint256[]'], [[accounts[1]], [toWei(100000)]]), 0); + await oleToken.transfer(lock.address, toWei(100000)); + await lock.release({from: accounts[1]}); + assert.equal(50000, (await oleToken.balanceOf(accounts[1])).div(toBN(1e18))); + + m.log("Wait for 50000 seconds ...."); + let takeSnapshot = await timeMachine.takeSnapshot(); + let shotId = takeSnapshot['result']; + await timeMachine.advanceTime(50000); + await lock.release({from: accounts[1]}); + assert.equal(75000, (await oleToken.balanceOf(accounts[1])).div(toBN(1e18))); + + m.log("Wait for 50000 seconds again...."); + await timeMachine.advanceTime(50000); + await lock.release({from: accounts[1]}); + assert.equal(toBN(100000).mul(toBN(1e18)).toString(), (await oleToken.balanceOf(accounts[1])).toString()); + // check lastUpdateTime + let accountReleaseVar = await lock.releaseVars(accounts[1]); + assert.equal(currentBlockTime + 100000, accountReleaseVar[1]); + await timeMachine.revertToSnapshot(shotId); + }); + + it("If the claim is completed, is it wrong to claim again: ", async () => { + let lock = await RetroactiveAirdropLock.new(oleToken.address, timeLock.address, '1599372311', currentBlockTime - 1, currentBlockTime + 30000); + await timeLock.executeTransaction(lock.address, 0, 'setReleaseBatch(address[],uint256[])', + web3.eth.abi.encodeParameters(['address[]', 'uint256[]'], [[accounts[1]], [toWei(100000)]]), 0); + await oleToken.transfer(lock.address, toWei(100000)); + await lock.release({from: accounts[1]}); + assert.equal(toWei(100000).toString(), (await oleToken.balanceOf(accounts[1])).toString()); + await assertThrows(lock.release({from: accounts[1]}), 'no releasable amount'); + }); + + it("Two accounts, two addresses, partial claim: ", async () => { + let lock = await RetroactiveAirdropLock.new(oleToken.address, timeLock.address, '1599372311', currentBlockTime, currentBlockTime + 60000); + await timeLock.executeTransaction(lock.address, 0, 'setReleaseBatch(address[],uint256[])', + web3.eth.abi.encodeParameters(['address[]', 'uint256[]'], [[accounts[1], accounts[2]], [toWei(100000), toWei(100000)]]), 0); + await oleToken.transfer(lock.address, toWei(100000)); + await lock.release({from: accounts[1]}); + await oleToken.transfer(lock.address, toWei(100000)); + await lock.release({from: accounts[2]}) + + assert.equal(toWei(100000).toString(), (await oleToken.balanceOf(accounts[1])).toString()); + assert.equal(toWei(100000).toString(), (await oleToken.balanceOf(accounts[2])).toString()); + }); + + it("Withdraw not by timeLock", async () => { + let lock = await RetroactiveAirdropLock.new(oleToken.address, timeLock.address, '1599372311', currentBlockTime, currentBlockTime + 10000000); + await oleToken.transfer(lock.address, toWei(100000)); + await assertThrows(lock.withdraw(accounts[1], {from: accounts[1]}), 'caller must be admin'); + }); + + it("Set release not by timeLock", async () => { + let lock = await RetroactiveAirdropLock.new(oleToken.address, timeLock.address, '1599372311', currentBlockTime, currentBlockTime + 10000000); + await oleToken.transfer(lock.address, toWei(100000)); + await assertThrows(lock.setReleaseBatch([accounts[1]], [toWei(100000)], {from: accounts[3]}), 'caller must be admin'); + }); + + it("Withdraw by timeLock", async () => { + let lock = await RetroactiveAirdropLock.new(oleToken.address, timeLock.address, '1599372311', currentBlockTime, currentBlockTime + 10000000); + await oleToken.transfer(lock.address, toWei(100000)); + let beforeWithdraw = await oleToken.balanceOf(accounts[1]); + await timeLock.executeTransaction(lock.address, 0, 'withdraw(address)', web3.eth.abi.encodeParameters(['address'], [accounts[1]]), 0); + let afterWithdraw = await oleToken.balanceOf(accounts[1]); + assert.equal(toBN(100000).mul(toBN(1e18)), afterWithdraw - beforeWithdraw); + }); +}) + + diff --git a/test/ZGovernorTest.js b/test/ZGovernorTest.js index c6e8096..f51d3b2 100644 --- a/test/ZGovernorTest.js +++ b/test/ZGovernorTest.js @@ -34,7 +34,8 @@ contract("GovernorAlphaTest", async accounts => { tlAdmin = await MockTLAdmin.new(timelock.address); xole = await createXOLE(ole.address, admin, admin, "0x0000000000000000000000000000000000000000"); - + await xole.setShareToken(ole.address); + await xole.setOleLpStakeToken(ole.address, {from: admin}); gov = await GovernorAlpha.new(timelock.address, xole.address, admin); await timelock.setPendingAdmin(gov.address, {from: admin}); await gov.__acceptAdmin({from: admin}); @@ -403,7 +404,7 @@ contract("GovernorAlphaTest", async accounts => { await ole.mint(proposeAccount, toWei(240000)); await ole.approve(xole.address, toWei(240000), {from: proposeAccount}); lastbk = await web3.eth.getBlock('latest'); - await xole.create_lock(toWei(240000), lastbk.timestamp + (DAY * 7), {from: proposeAccount}); + await xole.create_lock(toWei(240000), lastbk.timestamp + (DAY * 28), {from: proposeAccount}); let lastBlockNum = await web3.eth.getBlockNumber(); await advanceMultipleBlocksAndTime(1); let vote = await xole.getPriorVotes(proposeAccount, lastBlockNum); 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/test/xOLELockTest.js b/test/xOLELockTest.js new file mode 100644 index 0000000..6c0ef9f --- /dev/null +++ b/test/xOLELockTest.js @@ -0,0 +1,253 @@ +const OLEToken = artifacts.require("OLEToken"); +const { + assertPrint, + approxAssertPrint, + createEthDexAgg, + createUniswapV2Factory, + createXOLE, assertThrows, toWei +} = require("./utils/OpenLevUtil"); +const m = require('mocha-logger'); +const {advanceMultipleBlocksAndTime, advanceBlockAndSetTime, toBN} = require("./utils/EtheUtil"); +const timeMachine = require("ganache-time-traveler"); + +contract("xOLE", async accounts => { + + let H = 3600; + let DAY = 86400; + let WEEK = 7 * DAY; + let MAXTIME = 126144000; + let TOL = 120 / WEEK; + + let decimals = "000000000000000000"; + let _1000 = "1000000000000000000000"; + let _500 = "500000000000000000000"; + + let bob = accounts[0]; + let alice = accounts[1]; + let admin = accounts[2]; + let dev = accounts[3]; + + let uniswapFactory; + + let ole; + let stakeLpToken; + let xole; + + let stages = {}; + let snapshotId; + beforeEach(async () => { + ole = await OLEToken.new(admin, accounts[0], "Open Leverage Token", "OLE"); + await ole.mint(bob, _1000); + await ole.mint(alice, _1000); + + uniswapFactory = await createUniswapV2Factory(admin); + let dexAgg = await createEthDexAgg(uniswapFactory.address, "0x0000000000000000000000000000000000000000", admin); + xole = await createXOLE(ole.address, admin, dev, dexAgg.address, admin); + stakeLpToken = ole.address; + await xole.setOleLpStakeToken(stakeLpToken, {from: admin}); + let lastbk = await web3.eth.getBlock('latest'); + let timeToMove = lastbk.timestamp + (WEEK - lastbk.timestamp % WEEK); + m.log("Move time to start of the week", new Date(timeToMove)); + await advanceBlockAndSetTime(timeToMove); + let snapshot = await timeMachine.takeSnapshot(); + snapshotId = snapshot['result']; + }) + + afterEach(async () => { + await timeMachine.revertToSnapshot(snapshotId); + }); + + it("Create lock, increase amount, increase lock time", async () => { + await ole.approve(xole.address, _1000 + "0", {"from": alice}); + await ole.approve(xole.address, _1000 + "0", {"from": bob}); + let lastbk = await web3.eth.getBlock('latest'); + await advanceBlockAndSetTime(lastbk.timestamp - 10); + let end = lastbk.timestamp + 2 * WEEK; + m.log("Alice locked start ", lastbk.timestamp); + m.log("Alice locked end ", end); + m.log("Alice creates lock with 500 till time ", end, new Date(end)); + await xole.create_lock(_500, end, {"from": alice}); + assertPrint("Alice locked amount", _500, (await xole.locked(alice)).amount); + approxAssertPrint("Alice locked end", end, (await xole.locked(alice)).end); + approxAssertPrint("xOLE Total supply", "510400000000000000000", await xole.totalSupply()); + approxAssertPrint("Alice's balance of xOLE", "510400000000000000000", await xole.balanceOf(alice)); + assertPrint("Bob's balance of xOLE", "0", await xole.balanceOf(bob)); + + await advanceBlockAndSetTime(lastbk.timestamp - 10); + m.log("Alice increase amount with 500"); + await xole.increase_amount(_500, {"from": alice}); + assertPrint("Alice locked amount", _1000, (await xole.locked(alice)).amount); + approxAssertPrint("Alice locked end", end, (await xole.locked(alice)).end); // end isn't changed + approxAssertPrint("xOLE Total supply", "1020800000000000000000", await xole.totalSupply()); + approxAssertPrint("Alice's balance of xOLE", "1020800000000000000000", await xole.balanceOf(alice)); + assertPrint("Bob's balance of xOLE", "0", await xole.balanceOf(bob)); + + await advanceBlockAndSetTime(lastbk.timestamp - 10); + m.log("Alice increase lock time by 1 week"); + await xole.increase_unlock_time(end + WEEK, {"from": alice}); + assertPrint("Alice locked amount", _1000, (await xole.locked(alice)).amount); + approxAssertPrint("Alice locked end", end + WEEK, (await xole.locked(alice)).end); // end isn't changed + approxAssertPrint("xOLE Total supply", "1041600000000000000000", await xole.totalSupply()); + approxAssertPrint("Alice's balance of xOLE", "1041600000000000000000", await xole.balanceOf(alice)); + assertPrint("Bob's balance of xOLE", "0", await xole.balanceOf(bob)); + }) + + it("Create lock for other, and withdraw", async () => { + await ole.approve(xole.address, _1000 + "0", {"from": alice}); + + let lastbk = await web3.eth.getBlock('latest'); + await advanceBlockAndSetTime(lastbk.timestamp - 10); + let end = lastbk.timestamp + 2 * WEEK; + m.log("Alice creates lock to bob with 500 till time ", end, new Date(end)); + await xole.create_lock_for(bob, _500, end, {"from": alice}); + let bobBalance = await xole.balanceOf(bob); + let aliceBalance = await xole.balanceOf(alice); + assertPrint("Bob's balance of xOLE", "510400000000000000000", bobBalance); + assertPrint("Alice's balance of xOLE", "0", aliceBalance); + await advanceBlockAndSetTime(end + 60 * 60 * 24); + await xole.withdraw({from: bob}); + let withdrawAmount = await (await OLEToken.at(stakeLpToken)).balanceOf(bob); + assertPrint("Bob's stakeToken amount", "1500000000000000000000", withdrawAmount); + }) + + it("Create lock for other, and withdraw", async () => { + await ole.approve(xole.address, _1000 + "0", {"from": alice}); + + let lastbk = await web3.eth.getBlock('latest'); + await advanceBlockAndSetTime(lastbk.timestamp - 10); + let end = lastbk.timestamp + 2 * WEEK; + m.log("Alice creates lock to bob with 500 till time ", end, new Date(end)); + await xole.create_lock_for(bob, _500, end, {"from": alice}); + let bobBalance = await xole.balanceOf(bob); + let aliceBalance = await xole.balanceOf(alice); + assertPrint("Bob's balance of xOLE", "510400000000000000000", bobBalance); + assertPrint("Alice's balance of xOLE", "0", aliceBalance); + await advanceBlockAndSetTime(end + 60 * 60 * 24); + await xole.withdraw({from: bob}); + let withdrawAmount = await (await OLEToken.at(stakeLpToken)).balanceOf(bob); + assertPrint("Bob's stakeToken amount", "1500000000000000000000", withdrawAmount); + }) + it("Increase amount lock for other, and withdraw", async () => { + await ole.approve(xole.address, _1000 + "0", {"from": alice}); + + let lastbk = await web3.eth.getBlock('latest'); + await advanceBlockAndSetTime(lastbk.timestamp - 10); + let end = lastbk.timestamp + 2 * WEEK; + m.log("Alice creates lock to bob with 500 till time ", end, new Date(end)); + await xole.create_lock(_500, end, {"from": alice}); + let aliceBalance = await xole.balanceOf(alice); + assertPrint("Alice's balance of xOLE", "510400000000000000000", aliceBalance); + await ole.approve(xole.address, _1000 + "0", {"from": bob}); + await xole.increase_amount_for(alice, _1000, {"from": bob}); + aliceBalance = await xole.balanceOf(alice); + assertPrint("Alice's balance of xOLE", "1531200000000000000000", aliceBalance); + await advanceBlockAndSetTime(end + 60 * 60 * 24); + await xole.withdraw({from: alice}); + let withdrawAmount = await (await OLEToken.at(stakeLpToken)).balanceOf(alice); + assertPrint("Alice's stakeToken amount", "2000000000000000000000", withdrawAmount); + }) + it("Increase amount lock for other, and withdraw", async () => { + await ole.approve(xole.address, _1000 + "0", {"from": alice}); + + let lastbk = await web3.eth.getBlock('latest'); + await advanceBlockAndSetTime(lastbk.timestamp - 10); + let end = lastbk.timestamp + 2 * WEEK; + m.log("Alice creates lock to bob with 500 till time ", end, new Date(end)); + await xole.create_lock(_500, end, {"from": alice}); + let aliceBalance = await xole.balanceOf(alice); + assertPrint("Alice's balance of xOLE", "510400000000000000000", aliceBalance); + await advanceBlockAndSetTime(end + 60 * 60 * 24); + await assertThrows(xole.withdraw_automator(alice), 'Not automator'); + await xole.setOleLpStakeAutomator(bob, {from: admin}); + await xole.withdraw_automator(alice, {from: bob}); + let withdrawAmount = await (await OLEToken.at(stakeLpToken)).balanceOf(bob); + assertPrint("Bob's stakeToken amount", "1500000000000000000000", withdrawAmount); + }) + it("Lock to get voting powers, and withdraw", async () => { + + if (process.env.FASTMODE === 'true') { + m.log("Skipping this test for FAST Mode"); + return; + } + + await ole.approve(xole.address, _1000 + "0", {"from": alice}); + await ole.approve(xole.address, _1000 + "0", {"from": bob}); + + assertPrint("Total Supply", "0", await xole.totalSupply()); + assertPrint("Alice's Balance", "0", await xole.balanceOf(alice)); + assertPrint("Bob's Balance", "0", await xole.balanceOf(bob)); + + let lastbk = await web3.eth.getBlock('latest'); + stages["before_deposits"] = {bknum: lastbk.number, bltime: lastbk.timestamp}; + + await advanceBlockAndSetTime(lastbk.timestamp - 10); + let end = lastbk.timestamp + 2 * WEEK; + m.log("Alice creates lock with 1000 till time ", end, new Date(end)); + await xole.create_lock(_1000, end, {"from": alice}); + lastbk = await web3.eth.getBlock('latest'); + stages["alice_deposit"] = {bknum: lastbk.number, bltime: lastbk.timestamp}; + + approxAssertPrint("xOLE Total supply", "1020800000000000000000", await xole.totalSupply()); + approxAssertPrint("Alice's balance of xOLE", "1020800000000000000000", await xole.balanceOf(alice)); + assertPrint("Bob's balance of xOLE", "0", await xole.balanceOf(bob)); + + lastbk = await web3.eth.getBlock('latest'); + await advanceBlockAndSetTime(lastbk.timestamp - 10); + end = lastbk.timestamp + WEEK * 3 + 100; + + m.log("Bob creates lock till time ", end, new Date(end)); + await xole.create_lock(_1000, end, {"from": bob}); + lastbk = await web3.eth.getBlock('latest'); + stages["bob_deposit"] = {bknum: lastbk.number, bltime: lastbk.timestamp}; + m.log("xole.totalSupply()", (await xole.totalSupply()).toString()); + approxAssertPrint("xOLE Total supply", "2062400000000000000000", await xole.totalSupply()); + approxAssertPrint("Alice's balance of xOLE", "1020800000000000000000", await xole.balanceOf(alice)); + approxAssertPrint("Bob's balance of xOLE", "1041600000000000000000", await xole.balanceOf(bob)); + + await advanceBlockAndSetTime(end + 60 * 60 * 24); + lastbk = await web3.eth.getBlock('latest'); + stages["check_point"] = {bknum: lastbk.number, bltime: lastbk.timestamp}; + approxAssertPrint("xOLE Total supply", "2062400000000000000000", await xole.totalSupply()); + assertPrint("Alice's balance of xOLE", "1020800000000000000000", await xole.balanceOf(alice)); + approxAssertPrint("Bob's balance of xOLE", "1041600000000000000000", await xole.balanceOf(bob)); + // + m.log("Alice withdraw"); + await xole.withdraw({from: alice}); + approxAssertPrint("Alice's balance of ole", _1000, await ole.balanceOf(alice)); + + approxAssertPrint("xOLE Total supply", "1041600000000000000000", await xole.totalSupply()); + assertPrint("Alice's balance of xOLE", "0", await xole.balanceOf(alice)); + approxAssertPrint("Bob's balance of xOLE", "1041600000000000000000", await xole.balanceOf(bob)); + + + }) + + it("Create lock 2 weeks", async () => { + await ole.approve(xole.address, _1000 + "0", {"from": alice}); + let lastbk = await web3.eth.getBlock('latest'); + await advanceBlockAndSetTime(lastbk.timestamp + 1); + let end = lastbk.timestamp + 1 * WEEK; + m.log("Alice creates lock to bob with 500 till time ", end, new Date(end)); + await assertThrows(xole.create_lock(_500, end, {"from": alice}), 'Can only lock until time in the future'); + end = lastbk.timestamp + 2 * WEEK; + await assertThrows(xole.create_lock(_500, end, {"from": alice}), 'Can only lock until time in the future'); + await advanceBlockAndSetTime(lastbk.timestamp - 1); + await xole.create_lock(_500, end, {"from": alice}); + assertPrint("Alice's balance of xOLE", "510400000000000000000", await xole.balanceOf(alice)); + // increase time before unlock time + await advanceBlockAndSetTime(end - 1 * WEEK + 1); + await assertThrows(xole.increase_unlock_time(end + 1 * WEEK, {"from": alice}), 'Can only lock until time in the future'); + await advanceBlockAndSetTime(end - 1 * WEEK - 10); + await xole.increase_unlock_time(end + 3 * WEEK, {"from": alice}); + assertPrint("Alice's balance of xOLE", "531200000000000000000", await xole.balanceOf(alice)); + approxAssertPrint("Alice locked end", end + 3 * WEEK, (await xole.locked(alice)).end); + // increase time after unlock time + await advanceBlockAndSetTime(end + 3 * WEEK + 1); + lastbk = await web3.eth.getBlock('latest'); + await assertThrows(xole.increase_unlock_time(lastbk.timestamp + 1 * WEEK, {"from": alice}), 'Can only lock until time in the future'); + await xole.increase_unlock_time(lastbk.timestamp + 4 * WEEK, {"from": alice}); + approxAssertPrint("Alice locked end", lastbk.timestamp + 4 * WEEK, (await xole.locked(alice)).end); + assertPrint("Alice's balance of xOLE", "520800000000000000000", await xole.balanceOf(alice)); + }) + +}) diff --git a/test/xOLETest.js b/test/xOLETest.js deleted file mode 100644 index 9748cdc..0000000 --- a/test/xOLETest.js +++ /dev/null @@ -1,147 +0,0 @@ -const OLEToken = artifacts.require("OLEToken"); -const { - assertPrint, - approxAssertPrint, - createEthDexAgg, - createUniswapV2Factory, - createXOLE -} = require("./utils/OpenLevUtil"); -const m = require('mocha-logger'); -const {advanceMultipleBlocksAndTime, advanceBlockAndSetTime, toBN} = require("./utils/EtheUtil"); - -contract("xOLE", async accounts => { - - let H = 3600; - let DAY = 86400; - let WEEK = 7 * DAY; - let MAXTIME = 126144000; - let TOL = 120 / WEEK; - - let decimals = "000000000000000000"; - let _1000 = "1000000000000000000000"; - let _500 = "500000000000000000000"; - - let bob = accounts[0]; - let alice = accounts[1]; - let admin = accounts[2]; - let dev = accounts[3]; - - let uniswapFactory; - - let ole - let xole; - - let stages = {}; - - beforeEach(async () => { - ole = await OLEToken.new(admin, accounts[0], "Open Leverage Token", "OLE"); - await ole.mint(bob, _1000); - await ole.mint(alice, _1000); - - uniswapFactory = await createUniswapV2Factory(admin); - let dexAgg = await createEthDexAgg(uniswapFactory.address, "0x0000000000000000000000000000000000000000", admin); - xole = await createXOLE(ole.address, admin, dev, dexAgg.address, admin); - - let lastbk = await web3.eth.getBlock('latest'); - let timeToMove = lastbk.timestamp + (WEEK - lastbk.timestamp % WEEK); - m.log("Move time to start of the week", new Date(timeToMove)); - await advanceBlockAndSetTime(timeToMove); - }) - - it("Create lock, increase amount, increase lock time", async () => { - await ole.approve(xole.address, _1000 + "0", {"from": alice}); - await ole.approve(xole.address, _1000 + "0", {"from": bob}); - - let lastbk = await web3.eth.getBlock('latest'); - let end = lastbk.timestamp + WEEK; - m.log("Alice creates lock with 500 till time ", end, new Date(end)); - await xole.create_lock(_500, end, {"from": alice}); - assertPrint("Alice locked amount", _500, (await xole.locked(alice)).amount); - approxAssertPrint("Alice locked end", end, (await xole.locked(alice)).end); - approxAssertPrint("xOLE Total supply", "500000000000000000000", await xole.totalSupply()); - approxAssertPrint("Alice's balance of xOLE", "500000000000000000000", await xole.balanceOf(alice)); - assertPrint("Bob's balance of xOLE", "0", await xole.balanceOf(bob)); - - await advanceMultipleBlocksAndTime(10); - m.log("Alice increase amount with 500"); - await xole.increase_amount(_500, {"from": alice}); - assertPrint("Alice locked amount", _1000, (await xole.locked(alice)).amount); - approxAssertPrint("Alice locked end", end, (await xole.locked(alice)).end); // end isn't changed - approxAssertPrint("xOLE Total supply", "1000000000000000000000", await xole.totalSupply()); - approxAssertPrint("Alice's balance of xOLE", "1000000000000000000000", await xole.balanceOf(alice)); - assertPrint("Bob's balance of xOLE", "0", await xole.balanceOf(bob)); - - await advanceMultipleBlocksAndTime(10); - m.log("Alice increase lock time by 1 week"); - await xole.increase_unlock_time(end + WEEK, {"from": alice}); - assertPrint("Alice locked amount", _1000, (await xole.locked(alice)).amount); - approxAssertPrint("Alice locked end", end + WEEK, (await xole.locked(alice)).end); // end isn't changed - approxAssertPrint("xOLE Total supply", "1000000000000000000000", await xole.totalSupply()); - approxAssertPrint("Alice's balance of xOLE", "1000000000000000000000", await xole.balanceOf(alice)); - assertPrint("Bob's balance of xOLE", "0", await xole.balanceOf(bob)); - }) - - - it("Lock to get voting powers, and withdraw", async () => { - - if (process.env.FASTMODE === 'true') { - m.log("Skipping this test for FAST Mode"); - return; - } - - await ole.approve(xole.address, _1000 + "0", {"from": alice}); - await ole.approve(xole.address, _1000 + "0", {"from": bob}); - - assertPrint("Totol Supply", "0", await xole.totalSupply()); - assertPrint("Alice's Balance", "0", await xole.balanceOf(alice)); - assertPrint("Bob's Balance", "0", await xole.balanceOf(bob)); - - let lastbk = await web3.eth.getBlock('latest'); - stages["before_deposits"] = {bknum: lastbk.number, bltime: lastbk.timestamp}; - - let end = lastbk.timestamp + WEEK; - m.log("Alice creates lock with 1000 till time ", end, new Date(end)); - await xole.create_lock(_1000, end, {"from": alice}); - lastbk = await web3.eth.getBlock('latest'); - stages["alice_deposit"] = {bknum: lastbk.number, bltime: lastbk.timestamp}; - - approxAssertPrint("xOLE Total supply", "1000000000000000000000", await xole.totalSupply()); - approxAssertPrint("Alice's balance of xOLE", "1000000000000000000000", await xole.balanceOf(alice)); - assertPrint("Bob's balance of xOLE", "0", await xole.balanceOf(bob)); - - - await advanceMultipleBlocksAndTime(1000); - - approxAssertPrint("xOLE Total supply", "1000000000000000000000", await xole.totalSupply()); - approxAssertPrint("Alice's balance of xOLE", "1000000000000000000000", await xole.balanceOf(alice)); - assertPrint("Bob's balance of xOLE", "0", await xole.balanceOf(bob)); - - lastbk = await web3.eth.getBlock('latest'); - end = lastbk.timestamp + WEEK * 3; - m.log("Bob creates lock till time ", end, new Date(end)); - await xole.create_lock(_1000, end, {"from": bob}); - lastbk = await web3.eth.getBlock('latest'); - stages["bob_deposit"] = {bknum: lastbk.number, bltime: lastbk.timestamp}; - m.log("xole.totalSupply()", (await xole.totalSupply()).toString()); - approxAssertPrint("xOLE Total supply", "2020800000000000000000", await xole.totalSupply()); - approxAssertPrint("Alice's balance of xOLE", "1000000000000000000000", await xole.balanceOf(alice)); - approxAssertPrint("Bob's balance of xOLE", "1020800000000000000000", await xole.balanceOf(bob)); - - await advanceBlockAndSetTime(end + 60 * 60 * 24); - lastbk = await web3.eth.getBlock('latest'); - stages["check_point"] = {bknum: lastbk.number, bltime: lastbk.timestamp}; - approxAssertPrint("xOLE Total supply", "2020800000000000000000", await xole.totalSupply()); - assertPrint("Alice's balance of xOLE", "1000000000000000000000", await xole.balanceOf(alice)); - approxAssertPrint("Bob's balance of xOLE", "1020800000000000000000", await xole.balanceOf(bob)); - // - m.log("Alice withdraw"); - await xole.withdraw({from: alice}); - - approxAssertPrint("xOLE Total supply", "1020800000000000000000", await xole.totalSupply()); - assertPrint("Alice's balance of xOLE", "0", await xole.balanceOf(alice)); - approxAssertPrint("Bob's balance of xOLE", "1020800000000000000000", await xole.balanceOf(bob)); - - - }) - -}) 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. + +