diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 253367b..dc56b34 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, cronos ] + branches: [ main, tax-token-support, bsc, financing_payoff ] pull_request: branches: [ main ] diff --git a/contracts/OpenLevInterface.sol b/contracts/OpenLevInterface.sol index 6159a41..1184903 100644 --- a/contracts/OpenLevInterface.sol +++ b/contracts/OpenLevInterface.sol @@ -139,14 +139,14 @@ interface OpenLevInterface { 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; function marginRatio(address owner, uint16 marketId, bool longToken, bytes memory dexData) external view returns (uint current, uint cAvg, uint hAvg, uint32 limit); 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); diff --git a/contracts/OpenLevV1.sol b/contracts/OpenLevV1.sol index 4d6cd7b..0b00897 100644 --- a/contracts/OpenLevV1.sol +++ b/contracts/OpenLevV1.sol @@ -178,6 +178,7 @@ contract OpenLevV1 is DelegateInterface, Adminable, ReentrancyGuard, OpenLevInte 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]; Types.MarketVars memory marketVars = toMarketVar(longToken, false, markets[marketId]); + bool depositToken = trade.depositToken; //verify require(closeHeld <= trade.held, "CBH"); @@ -204,7 +205,7 @@ 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"); @@ -244,10 +245,39 @@ contract OpenLevV1 is DelegateInterface, Adminable, ReentrancyGuard, OpenLevInte OpenLevV1Lib.updatePriceInternal(address(marketVars.buyToken), address(marketVars.sellToken), dexData); } - emit TradeClosed(msg.sender, marketId, longToken, trade.depositToken, closeHeld, closeTradeVars.depositDecrease, closeTradeVars.depositReturn, closeTradeVars.fees, + emit TradeClosed(msg.sender, marketId, longToken, depositToken, closeHeld, closeTradeVars.depositDecrease, closeTradeVars.depositReturn, closeTradeVars.fees, closeTradeVars.token0Price, closeTradeVars.dexDetail); } + /// @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)); + 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. /// @dev For trades without sufficient funds to repay, use insurance. /// @param owner Owner of the trade to liquidate. @@ -331,31 +361,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. @@ -391,13 +397,6 @@ contract OpenLevV1 is DelegateInterface, Adminable, ReentrancyGuard, OpenLevInte ); } - /// @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); - } - /// @notice Update price on Dex. /// @param dexData Index and fee rate for the trading Dex. function updatePrice(uint16 marketId, bytes memory dexData) external override { diff --git a/contracts/OpenLevV1Lib.sol b/contracts/OpenLevV1Lib.sol index f331935..52a4a8a 100644 --- a/contracts/OpenLevV1Lib.sol +++ b/contracts/OpenLevV1Lib.sol @@ -386,4 +386,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/test/OpenLevV1UniV2Test1.js b/test/OpenLevV1UniV2Test1.js index 70899dc..cf1d106 100644 --- a/test/OpenLevV1UniV2Test1.js +++ b/test/OpenLevV1UniV2Test1.js @@ -325,8 +325,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'); @@ -380,9 +378,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'); await advanceMultipleBlocksAndTime(1000); diff --git a/test/PayOffTradeTaxTokenTest.js b/test/PayOffTradeTaxTokenTest.js new file mode 100644 index 0000000..47acaf4 --- /dev/null +++ b/test/PayOffTradeTaxTokenTest.js @@ -0,0 +1,270 @@ +const utils = require("./utils/OpenLevUtil"); +const {Uni2DexData, assertThrows} = require("./utils/OpenLevUtil"); +const {advanceMultipleBlocksAndTime, toBN, advanceMultipleBlocks} = require("./utils/EtheUtil"); +const Controller = artifacts.require("ControllerV1"); +const ControllerDelegator = artifacts.require("ControllerDelegator"); +const OpenLevV1 = artifacts.require("OpenLevV1"); +const OpenLevDelegator = artifacts.require("OpenLevDelegator"); +const m = require('mocha-logger'); +const {from} = require("truffle/build/987.bundled"); +const LPool = artifacts.require("LPool"); +const TestToken = artifacts.require("MockERC20"); +const MockTaxToken = artifacts.require("MockTaxToken"); +const UniswapV2Factory = artifacts.require("UniswapV2Factory"); +const UniswapV2Router = artifacts.require("UniswapV2Router02"); +const OpenLevV1Lib = artifacts.require("OpenLevV1Lib") + +// list all cases for tax token since there is no smaller unit to divide. +contract("OpenLev payoff trade tax token", async accounts => { + // components + let openLev; + let ole; + let treasury; + let factory; + let router; + let gotPair; + let dexAgg; + let pool0; + let poolEth; + + // roles + let admin = accounts[0]; + let saver = accounts[1]; + let trader = accounts[2]; + + let dev = accounts[3]; + let liquidator2 = accounts[8]; + let token0; + let delegatee; + let weth; + + let pairId = 0; + + beforeEach(async () => { + weth = await utils.createWETH(); + ole = await TestToken.new('OpenLevERC20', 'OLE'); + factory = await UniswapV2Factory.new("0x0000000000000000000000000000000000000000"); + router = await UniswapV2Router.new(factory.address, weth.address); + token0 = await MockTaxToken.new('TokenA', 'TKA', 5, 2, router.address); + + await web3.eth.sendTransaction({from: accounts[9], to: admin, value: utils.toWei(1)}); + await token0.approve(router.address, utils.toWei(1)); + let block = await web3.eth.getBlock("latest"); + await router.addLiquidityETH(token0.address, utils.toWei(1), utils.toWei(1), utils.toWei(1), admin, block.timestamp + 60, {from: admin, value: utils.toWei(1)}); + + dexAgg = await utils.createEthDexAgg(factory.address, "0x0000000000000000000000000000000000000000", accounts[0]); + xole = await utils.createXOLE(ole.address, admin, dev, dexAgg.address); + + let instance = await Controller.new(); + let controller = await ControllerDelegator.new( + ole.address, + xole.address, + weth.address, + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + dexAgg.address, + "0x01", + admin, + instance.address); + controller = await Controller.at(controller.address); + + openLevV1Lib = await OpenLevV1Lib.new(); + await OpenLevV1.link("OpenLevV1Lib", openLevV1Lib.address); + delegatee = await OpenLevV1.new(); + + openLev = await OpenLevDelegator.new(controller.address, dexAgg.address, [token0.address, weth.address], weth.address, xole.address, [1, 2], accounts[0], delegatee.address); + openLev = await OpenLevV1.at(openLev.address); + await openLev.setCalculateConfig(30, 33, 3000, 5, 25, 25, (30e18) + '', 300, 10, 60); + await controller.setOpenLev(openLev.address); + await controller.setLPoolImplementation((await utils.createLPoolImpl()).address); + await controller.setInterestParam(toBN(90e16).div(toBN(2102400)), toBN(10e16).div(toBN(2102400)), toBN(20e16).div(toBN(2102400)), 50e16 + ''); + await dexAgg.setOpenLev(openLev.address); + let dexData = Uni2DexData + "011170000000011170000000011170000000"; + await controller.createLPoolPair(token0.address, weth.address, 3000, dexData); // 30% margin ratio by default + + market = await openLev.markets(0); + let pool0Address = market.pool0; + let poolEthAddress = market.pool1; + pool0 = await LPool.at(pool0Address); + poolEth = await LPool.at(poolEthAddress); + + await token0.approve(pool0.address, utils.toWei(1)); + await pool0.mint(utils.toWei(1)); + await poolEth.mintEth({from: saver, value: utils.toWei(1)}); + await token0.transfer(trader, utils.toWei(1)); + await token0.approve(openLev.address, utils.toWei(1), {from: trader}); + // set weth to trader + await weth.mint(trader, utils.toWei(1)); + // set tax rate = 7% + await openLev.setTaxRate(pairId, token0.address, 0, 70000); + await advanceMultipleBlocksAndTime(30); + }); + + it("if repay token is a tax Token need pay tax deductions with twice ", async () => { + let deposit = toBN(1e15); + let borrow = toBN(1e15); + m.log("-- marginTrade..."); + await openLev.marginTrade(pairId, true, false, deposit, borrow, 0, Uni2DexData, {from: trader}); + let tradeBefore = await openLev.activeTrades(trader, pairId, 1); + let borrowedBefore = await pool0.borrowBalanceCurrent(trader); + let token0BalanceBefore = await token0.balanceOf(trader); + m.log("current held =", tradeBefore.held); + m.log("current borrowed =", borrowedBefore); + m.log("current tax token balance = ", token0BalanceBefore); + assert.equal(tradeBefore.held.toString(), "1717213647663711"); + assert.equal(borrowedBefore, 1000000000000000); + assert.equal(token0BalanceBefore, 999001927118839757); + + m.log("-- payoffTrade..."); + let payoffTradeTx = await openLev.payoffTrade(pairId, true, {from: trader}); + let tradeAfter = await openLev.activeTrades(trader, pairId, 1); + let borrowedAfter = await pool0.borrowBalanceCurrent(trader); + let token0BalanceAfter = await token0.balanceOf(trader); + m.log("current held =", tradeAfter.held); + m.log("current borrowed =", borrowedAfter); + m.log("current tax token balance = ", token0BalanceAfter); + assert.equal(tradeAfter.held, 0); + assert.equal(borrowedAfter.toString(), '0'); + assert.equal(token0BalanceAfter.toString(), "997846836928621941"); + + m.log("-- check event..."); + let depositToken = payoffTradeTx.logs[0].args.depositToken; + let depositDecrease = payoffTradeTx.logs[0].args.depositDecrease; + let closeAmount = payoffTradeTx.logs[0].args.closeAmount; + assert.equal(depositToken, false); + assert.equal(depositDecrease.toString(), "924210463605232"); + assert.equal(closeAmount.toString(), "1717213647663711"); + }) + + it("if transfer in with tax token amount can't pay it all off, will fail", async () => { + let deposit = toBN(1e15); + let borrow = toBN(1e15); + m.log("-- marginTrade..."); + await openLev.marginTrade(pairId, true, false, deposit, borrow, 0, Uni2DexData, {from: trader}); + let tradeBefore = await openLev.activeTrades(trader, pairId, 1); + let borrowedBefore = await pool0.borrowBalanceCurrent(trader); + let token0BalanceBefore = await token0.balanceOf(trader); + m.log("current held =", tradeBefore.held); + m.log("current borrowed =", borrowedBefore); + m.log("current tax token balance = ", token0BalanceBefore); + assert.equal(tradeBefore.held.toString(), "1717213647663711"); + assert.equal(borrowedBefore, 1000000000000000); + assert.equal(token0BalanceBefore, 999001927118839757); + + let transferOutAmount = toBN(997900000000000000); + m.log("transfer out tak token,amount = ", transferOutAmount); + await token0.transfer(saver, transferOutAmount, {from: trader}); + let token0AfterTransferOutBalance = await token0.balanceOf(trader); + m.log("current tax token balance =", token0AfterTransferOutBalance) + assert.equal(token0AfterTransferOutBalance, 1102477199838617); + + m.log("-- payoffTrade..."); + await assertThrows(openLev.payoffTrade(pairId, true, {from: trader}), 'TFF'); + m.log("payoffTrade fail --- TFF, test pass.") + }) + + it("if repay token is eth, repay weth will fail", async () => { + let deposit = toBN(1e16); + let borrow = toBN(1e16); + + m.log("-- marginTrade..."); + await openLev.marginTrade(pairId, false, true, deposit, borrow, 0, Uni2DexData, {from: trader, value: deposit}); + let tradeBefore = await openLev.activeTrades(trader, pairId, 0); + let borrowedBefore = await poolEth.borrowBalanceCurrent(trader); + let token0BalanceBefore = await token0.balanceOf(trader); + let wethBalance = await weth.balanceOf(trader); + + m.log("current held =", tradeBefore.held); + m.log("current borrowed =", borrowedBefore); + m.log("current token0 balance = ", token0BalanceBefore); + m.log("current weth balance =", wethBalance); + assert.equal(tradeBefore.held.toString(), "18128352683015392"); + assert.equal(borrowedBefore, 10000000000000000); + assert.equal(token0BalanceBefore, 1000009746426173664); + + await assertThrows(openLev.payoffTrade(pairId, false, {from: trader}), 'IRP'); + }) + + it("if repay token is eth, repay current borrow Amount will fail", async () => { + let deposit = toBN(1e16); + let borrow = toBN(1e16); + + m.log("-- marginTrade..."); + await openLev.marginTrade(pairId, false, true, deposit, borrow, 0, Uni2DexData, {from: trader, value: deposit}); + let tradeBefore = await openLev.activeTrades(trader, pairId, 0); + let borrowed = await poolEth.borrowBalanceCurrent(trader); + let token0Balance = await token0.balanceOf(trader); + let ethBalance = await web3.eth.getBalance(trader); + + m.log("current held =", tradeBefore.held); + m.log("current borrowed =", borrowed); + m.log("current token0 balance = ", token0Balance); + m.log("current eth balance =", ethBalance); + assert.equal(tradeBefore.held.toString(), "18128352683015392"); + assert.equal(borrowed, 10000000000000000); + assert.equal(token0Balance, 1000009746426173664); + + await advanceMultipleBlocks(1); + m.log("advance 1 blocks"); + await assertThrows(openLev.payoffTrade(pairId, false, {from: trader, value: borrowed}), 'IRP'); + }) + + it("if repay token is eth, need to repay 1/100000 more of the borrow amount, and received tax token is less than held", async () => { + let deposit = toBN(1e16); + let borrow = toBN(1e16); + let token0Balance = await token0.balanceOf(trader); + m.log("current token0 balance = ", token0Balance); + + m.log("-- marginTrade..."); + await openLev.marginTrade(pairId, false, true, deposit, borrow, 0, Uni2DexData, {from: trader, value: deposit}); + let tradeBefore = await openLev.activeTrades(trader, pairId, 0); + let borrowedBefore = await poolEth.borrowBalanceCurrent(trader); + let token0BalanceBefore = await token0.balanceOf(trader); + let wethBalanceBefore = await weth.balanceOf(trader); + let ethBalanceBefore = await web3.eth.getBalance(trader); + + m.log("current held =", tradeBefore.held); + m.log("current borrowed =", borrowedBefore); + m.log("current token0 balance = ", token0BalanceBefore); + m.log("current weth balance = ", wethBalanceBefore); + m.log("trader eth balance = ", ethBalanceBefore); + assert.equal(tradeBefore.held.toString(), "18128352683015392"); + assert.equal(borrowedBefore, 10000000000000000); + assert.equal(token0BalanceBefore, 1000009746426173664); + + m.log("-- payoffTrade..."); + let borrowReturn = toBN(borrowedBefore * (1 + 1e-5)) + m.log("transfer eth amount = ", borrowReturn); + let gas_price = 10000000000; + let payoffTradeTx = await openLev.payoffTrade(pairId, false, {from: trader, value: borrowReturn, gasPrice: gas_price}); + let tradeAfter = await openLev.activeTrades(trader, pairId, 0); + let borrowedAfter = await poolEth.borrowBalanceCurrent(trader); + let token0BalanceAfter = await token0.balanceOf(trader); + let wethBalanceAfter = await weth.balanceOf(trader); + let ethBalanceAfter = await web3.eth.getBalance(trader); + + m.log("current held =", tradeAfter.held); + m.log("current borrowed =", borrowedAfter); + m.log("current token0 balance = ", token0BalanceAfter); + m.log("current weth balance = ", wethBalanceAfter); + m.log("trader eth balance = ", ethBalanceAfter); + assert.equal(tradeAfter.held, 0); + assert.equal(borrowedAfter.toString(), '0'); + assert.equal(token0BalanceAfter.toString(), "1016878331585893332"); + assert.equal(wethBalanceAfter.toString(), wethBalanceBefore.toString()); + m.log("weth amount of before payoffTrade equals to after payoffTrade. ") + let gasUsed = payoffTradeTx.receipt.gasUsed; + m.log("payoffTrade gas used = ", gasUsed * gas_price); + assert.equal((toBN(ethBalanceBefore).sub(toBN(ethBalanceAfter))).toString(), ((toBN(gasUsed).mul(toBN(gas_price))).add(toBN(borrowReturn))).toString()); + m.log("eth balance of after payoffTrade = balance of before payoffTrade + gas used + repay amount") + + m.log("-- check event..."); + let depositToken = payoffTradeTx.logs[0].args.depositToken; + let depositDecrease = payoffTradeTx.logs[0].args.depositDecrease; + let closeAmount = payoffTradeTx.logs[0].args.closeAmount; + assert.equal(depositToken, true); + assert.equal(depositDecrease.toString(), "9940000000000000"); + assert.equal(closeAmount.toString(), "18128352683015392"); + }) + +}) \ No newline at end of file diff --git a/test/PayOffTradeTest.js b/test/PayOffTradeTest.js new file mode 100644 index 0000000..ea10321 --- /dev/null +++ b/test/PayOffTradeTest.js @@ -0,0 +1,174 @@ +const utils = require("./utils/OpenLevUtil"); +const { + last8, + Uni2DexData, + assertThrows, +} = require("./utils/OpenLevUtil"); +const {advanceMultipleBlocksAndTime, toBN} = require("./utils/EtheUtil"); +const OpenLevV1 = artifacts.require("OpenLevV1"); +const OpenLevDelegator = artifacts.require("OpenLevDelegator"); +const TestToken = artifacts.require("MockERC20"); +const m = require('mocha-logger'); +const LPool = artifacts.require("LPool"); +const OpenLevV1Lib = artifacts.require("OpenLevV1Lib") + +contract("OpenLev payoff trade", async accounts => { + + // components + let openLev; + let ole; + let treasury; + let uniswapFactory; + let gotPair; + let dexAgg; + // roles + let admin = accounts[0]; + let saver = accounts[1]; + let trader = accounts[2]; + let dev = accounts[3]; + let token0; + let token1; + let controller; + let delegatee; + let weth; + + beforeEach(async () => { + + // runs once before the first test in this block + controller = await utils.createController(admin); + m.log("Created Controller", last8(controller.address)); + + ole = await TestToken.new('OpenLevERC20', 'OLE'); + token0 = await TestToken.new('TokenA', 'TKA'); + token1 = await TestToken.new('TokenB', 'TKB'); + weth = await utils.createWETH(); + + uniswapFactory = await utils.createUniswapV2Factory(); + gotPair = await utils.createUniswapV2Pool(uniswapFactory, token0, token1); + dexAgg = await utils.createEthDexAgg(uniswapFactory.address, "0x0000000000000000000000000000000000000000", accounts[0]); + xole = await utils.createXOLE(ole.address, admin, dev, dexAgg.address); + openLevV1Lib = await OpenLevV1Lib.new(); + await OpenLevV1.link("OpenLevV1Lib", openLevV1Lib.address); + delegatee = await OpenLevV1.new(); + + openLev = await OpenLevDelegator.new(controller.address, dexAgg.address, [token0.address, token1.address], weth.address, xole.address, [1, 2], accounts[0], delegatee.address); + openLev = await OpenLevV1.at(openLev.address); + await openLev.setCalculateConfig(30, 33, 3000, 5, 25, 25, (30e18) + '', 300, 10, 60); + await controller.setOpenLev(openLev.address); + await controller.setLPoolImplementation((await utils.createLPoolImpl()).address); + await controller.setInterestParam(toBN(90e16).div(toBN(2102400)), toBN(10e16).div(toBN(2102400)), toBN(20e16).div(toBN(2102400)), 50e16 + ''); + await dexAgg.setOpenLev(openLev.address); + + let createPoolTx = await controller.createLPoolPair(token0.address, token1.address, 3000, Uni2DexData); // 30% margin ratio by default + m.log("Create Market Gas Used: ", createPoolTx.receipt.gasUsed); + }); + + it("current held is zero, transaction fail ", async () => { + let pairId = 0; + await utils.mint(token1, trader, 10000); + let saverSupply = utils.toWei(1000); + let pool1 = await LPool.at((await openLev.markets(0)).pool1); + await token1.approve(pool1.address, utils.toWei(10000), {from: trader}); + await token1.approve(openLev.address, utils.toWei(10000), {from: trader}); + await pool1.mint(saverSupply, {from: trader}); + m.log("mint token1 to pool1, amount = ", saverSupply) + await advanceMultipleBlocksAndTime(1000); + await openLev.updatePrice(pairId, Uni2DexData); + m.log("updatePrice ---"); + + let deposit = utils.toWei(1); + let borrow = utils.toWei(1); + await openLev.marginTrade(pairId, false, true, deposit, borrow, 0, Uni2DexData, {from: trader}); + let tradeBefore = await openLev.activeTrades(trader, pairId, 0); + m.log("finish marginTrade, current held = ", tradeBefore.held) + assert.equal(tradeBefore.held.toString(), "1987978478630008709"); + + await openLev.closeTrade(pairId, false, tradeBefore.held, 0, Uni2DexData, {from: trader}); + let tradeAfter = await openLev.activeTrades(trader, 0, 0); + m.log("finish closeTrade, current held = ", tradeAfter.held) + assert.equal(tradeAfter.held, 0); + m.log("start payoffTrade, current held is zero ---") + await assertThrows(openLev.payoffTrade(pairId, false, {from: trader}), 'HI0'); + m.log("payoffTrade fail --- HI0, test pass.") + }) + + it("not enough to repay current borrow, transaction fail ", async () => { + let pairId = 0; + await utils.mint(token1, trader, 1001); + m.log("mint 1001 amount token1 to trader") + let saverSupply = utils.toWei(1000); + let pool1 = await LPool.at((await openLev.markets(0)).pool1); + await token1.approve(pool1.address, utils.toWei(10000), {from: trader}); + await token1.approve(openLev.address, utils.toWei(10000), {from: trader}); + await pool1.mint(saverSupply, {from: trader}); + m.log("trader mint 1000 token1 to pool1") + m.log("trader token1 balance = ", utils.toETH(await token1.balanceOf(trader))); + await advanceMultipleBlocksAndTime(1000); + await openLev.updatePrice(pairId, Uni2DexData); + m.log("updatePrice ---"); + + let deposit = utils.toWei(1); + let borrow = utils.toWei(1); + m.log("start marginTrade, deposit token1 amount = ", utils.toETH(deposit)) + await openLev.marginTrade(pairId, false, true, deposit, borrow, 0, Uni2DexData, {from: trader}); + m.log("finish marginTrade, trader current token1 balance is ---", utils.toETH(await token1.balanceOf(trader))) + await assertThrows(openLev.payoffTrade(pairId, false, {from: trader}), 'TFF'); + m.log("payoffTrade fail --- TFF, test pass.") + }) + + it("after payoff trade finished, account current borrow and held is zero, receive held token ", async () => { + let pairId = 0; + await utils.mint(token1, trader, 10000); + let saverSupply = utils.toWei(1000); + let pool1 = await LPool.at((await openLev.markets(0)).pool1); + await token1.approve(pool1.address, utils.toWei(10000), {from: trader}); + await token1.approve(openLev.address, utils.toWei(10000), {from: trader}); + await pool1.mint(saverSupply, {from: trader}); + await advanceMultipleBlocksAndTime(1000); + await openLev.updatePrice(pairId, Uni2DexData); + m.log("updatePrice ---"); + + let deposit = utils.toWei(1); + let borrow = utils.toWei(1); + await openLev.marginTrade(pairId, false, true, deposit, borrow, 0, Uni2DexData, {from: trader}); + + let tradeBefore = await openLev.activeTrades(trader, pairId, 0); + let borrowedBefore = utils.toETH(await pool1.borrowBalanceCurrent(trader)); + let token0BalanceBefore = utils.toETH(await token0.balanceOf(trader)); + let token1BalanceBefore = utils.toETH(await token1.balanceOf(trader)); + m.log("before payoffTrade ---"); + m.log("current held =", tradeBefore.held); + m.log("current borrowed =", borrowedBefore); + m.log("current token0 balance = ", token0BalanceBefore); + m.log("current token1 balance = ", token1BalanceBefore); + assert.equal(tradeBefore.held.toString(), "1987978478630008709"); + assert.equal(borrowedBefore, 1); + assert.equal(token0BalanceBefore, 0); + assert.equal(token1BalanceBefore, 8999); + + let payoffTradeTx = await openLev.payoffTrade(pairId, false, {from: trader}); + + let tradeAfter = await openLev.activeTrades(trader, 0, 0); + let borrowedAfter = await pool1.borrowBalanceCurrent(trader); + let token0BalanceAfter = await token0.balanceOf(trader); + let token1BalanceAfter = await token1.balanceOf(trader); + m.log("after payoffTrade ---"); + m.log("current held =", tradeAfter.held); + m.log("current borrowed =", borrowedAfter); + m.log("current token0 balance = ", token0BalanceAfter); + m.log("current token1 balance = ", token1BalanceAfter); + assert.equal(tradeAfter.held, 0); + assert.equal(borrowedAfter, 0); + assert.equal(token0BalanceAfter, 1987978478630008709); + assert.equal(token1BalanceAfter, 8997999999571870243534); + + m.log("-- check event..."); + let depositToken = payoffTradeTx.logs[0].args.depositToken; + let depositDecrease = payoffTradeTx.logs[0].args.depositDecrease; + let closeAmount = payoffTradeTx.logs[0].args.closeAmount; + assert.equal(depositToken, true); + assert.equal(depositDecrease.toString(), "994000000000000000"); + assert.equal(closeAmount.toString(), "1987978478630008709"); + }) + +}) \ No newline at end of file diff --git a/test/integration/wholeProcessTest.js b/test/integration/wholeProcessTest.js index d8bf5e1..a460a43 100644 --- a/test/integration/wholeProcessTest.js +++ b/test/integration/wholeProcessTest.js @@ -60,10 +60,8 @@ contract("OpenLev integration test ", async accounts => { 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); - } + 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); diff --git a/truffle-config.js b/truffle-config.js index 8bd5fcc..e3a02dc 100644 --- a/truffle-config.js +++ b/truffle-config.js @@ -46,7 +46,7 @@ module.exports = { host: "127.0.0.1", // Localhost (default: none) port: 8545, // Standard Ethereum port (default: none) network_id: "*", - gas: 6000000, + gas : 8000000, disableConfirmationListener: true }, local: {