From aadf5eb8446710011048df5638b159b2feddaf3d Mon Sep 17 00:00:00 2001 From: owen05 Date: Tue, 10 Nov 2020 18:37:51 +0800 Subject: [PATCH] route testing --- contracts/SmartRoute/SmartApprove.sol | 7 +- contracts/SmartRoute/SmartSwap.sol | 49 ++- package-lock.json | 33 +- package.json | 2 +- route-test.sh | 2 + test/Route/Route.test.ts | 362 +++++++++++------ .../utils-v1/{Context.ts => Context-route.ts} | 177 ++++++--- test/utils-v1/Converter.ts | 6 + test/utils-v1/dodoHelper.ts | 366 ++++++++++++++++++ 9 files changed, 802 insertions(+), 202 deletions(-) create mode 100644 route-test.sh rename test/utils-v1/{Context.ts => Context-route.ts} (51%) create mode 100644 test/utils-v1/dodoHelper.ts diff --git a/contracts/SmartRoute/SmartApprove.sol b/contracts/SmartRoute/SmartApprove.sol index 3b6ccd3..0fcc38b 100644 --- a/contracts/SmartRoute/SmartApprove.sol +++ b/contracts/SmartRoute/SmartApprove.sol @@ -11,7 +11,6 @@ import {IERC20} from "../intf/IERC20.sol"; import {SafeERC20} from "../lib/SafeERC20.sol"; import {Ownable} from "../lib/Ownable.sol"; - contract SmartApprove is Ownable { using SafeERC20 for IERC20; address public smartSwap; @@ -20,9 +19,6 @@ contract SmartApprove is Ownable { smartSwap = _smartSwap; } - - event Test1(uint256 amount); - function claimTokens( IERC20 token, address who, @@ -30,7 +26,6 @@ contract SmartApprove is Ownable { uint256 amount ) external { require(msg.sender == smartSwap, "Not SmartSwap Address, Access restricted"); - emit Test1(amount); - // token.safeTransferFrom(who, dest, amount); + token.safeTransferFrom(who, dest, amount); } } diff --git a/contracts/SmartRoute/SmartSwap.sol b/contracts/SmartRoute/SmartSwap.sol index 0856450..aba2dde 100644 --- a/contracts/SmartRoute/SmartSwap.sol +++ b/contracts/SmartRoute/SmartSwap.sol @@ -30,7 +30,7 @@ contract SmartSwap is Ownable { IERC20 indexed toToken, address indexed sender, uint256 fromAmount, - uint256 toAmount + uint256 returnAmount ); event ExternalRecord(address indexed to, address indexed sender); @@ -39,7 +39,6 @@ contract SmartSwap is Ownable { smartApprove = ISmartApprove(_smartApprove); } - function dodoSwap( IERC20 fromToken, IERC20 toToken, @@ -50,37 +49,35 @@ contract SmartSwap is Ownable { uint256[] memory starts, uint256[] memory gasLimitsAndValues ) public payable returns (uint256 returnAmount) { - require(minReturnAmount > 0, "haha hihi Min return should be bigger then 0."); + require(minReturnAmount > 0, "Min return should be bigger then 0."); require(callPairs.length > 0, "pairs should exists."); - // if (fromToken != ETH_ADDRESS) { - // // smartApprove.claimTokens(fromToken, msg.sender, address(this), fromTokenAmount); - // } + if (fromToken != ETH_ADDRESS) { + smartApprove.claimTokens(fromToken, msg.sender, address(this), fromTokenAmount); + } - // for (uint256 i = 0; i < callPairs.length; i++) { - // require(callPairs[i] != address(smartApprove), "Access denied"); - // require( - // callPairs[i].externalCall( - // gasLimitsAndValues[i] & ((1 << 128) - 1), - // callDataConcat, - // starts[i], - // starts[i + 1] - starts[i], - // gasLimitsAndValues[i] >> 128 - // ) - // ); - // } + for (uint256 i = 0; i < callPairs.length; i++) { + require(callPairs[i] != address(smartApprove), "Access denied"); + require( + callPairs[i].externalCall( + gasLimitsAndValues[i] & ((1 << 128) - 1), + callDataConcat, + starts[i], + starts[i + 1] - starts[i], + gasLimitsAndValues[i] >> 128 + ),"Swap Transaction Error!" + ); + } - // // Return back all unswapped - // fromToken.universalTransfer(msg.sender, fromToken.universalBalanceOf(address(this))); + fromToken.universalTransfer(msg.sender, fromToken.universalBalanceOf(address(this))); + returnAmount = toToken.universalBalanceOf(address(this)); - // returnAmount = toToken.universalBalanceOf(address(this)); - - // require(returnAmount >= minReturnAmount, "Return amount is not enough"); - // toToken.universalTransfer(msg.sender, returnAmount); - emit Swapped(fromToken, toToken, msg.sender, fromTokenAmount, fromTokenAmount); - // emit Swapped(fromToken, toToken, msg.sender, fromTokenAmount, returnAmount); + require(returnAmount >= minReturnAmount, "Return amount is not enough"); + toToken.universalTransfer(msg.sender, returnAmount); + emit Swapped(fromToken, toToken, msg.sender, fromTokenAmount, returnAmount); } + //TODO:change function externalSwap( IERC20 fromToken, IERC20 toToken, diff --git a/package-lock.json b/package-lock.json index e1bfe80..af6f651 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1526,7 +1526,8 @@ "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true }, "assign-symbols": { "version": "1.0.0", @@ -2255,6 +2256,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, "requires": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", @@ -2284,7 +2286,8 @@ "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true }, "checkpoint-store": { "version": "1.1.0", @@ -2787,6 +2790,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, "requires": { "type-detect": "^4.0.0" } @@ -4998,7 +5002,8 @@ "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=" + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true }, "get-intrinsic": { "version": "1.0.1", @@ -6116,6 +6121,12 @@ "integrity": "sha1-74y/QI9uSCaGYzRTBcaswLd4cC4=", "dev": true }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, "lodash.toarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", @@ -7156,7 +7167,8 @@ "pathval": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=" + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true }, "pbkdf2": { "version": "3.1.1", @@ -8904,6 +8916,16 @@ "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" }, + "truffle-assertions": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/truffle-assertions/-/truffle-assertions-0.9.2.tgz", + "integrity": "sha512-9g2RhaxU2F8DeWhqoGQvL/bV8QVoSnQ6PY+ZPvYRP5eF7+/8LExb4mjLx/FeliLTjc3Tv1SABG05Gu5qQ/ErmA==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "lodash.isequal": "^4.5.0" + } + }, "truffle-hdwallet-provider": { "version": "1.0.17", "resolved": "https://registry.npmjs.org/truffle-hdwallet-provider/-/truffle-hdwallet-provider-1.0.17.tgz", @@ -8994,7 +9016,8 @@ "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true }, "type-is": { "version": "1.6.18", diff --git a/package.json b/package.json index 4c759df..de2e88b 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "babel-cli": "^6.26.0", "babel-eslint": "^10.1.0", "bignumber.js": "^9.0.0", - "chai": "^4.2.0", "chai-bignumber": "^3.0.0", "debug": "^4.1.1", "dotenv-flow": "^3.1.0", @@ -50,6 +49,7 @@ }, "devDependencies": { "@truffle/hdwallet-provider": "^1.0.36", + "chai": "^4.2.0", "ganache-cli": "^6.9.1", "prettier": "^2.0.5", "prettier-plugin-solidity": "^1.0.0-alpha.52", diff --git a/route-test.sh b/route-test.sh new file mode 100644 index 0000000..89655ff --- /dev/null +++ b/route-test.sh @@ -0,0 +1,2 @@ +truffle compile --all +truffle test ./test/Route/Route.test.ts \ No newline at end of file diff --git a/test/Route/Route.test.ts b/test/Route/Route.test.ts index 79f8a23..e4d6e99 100644 --- a/test/Route/Route.test.ts +++ b/test/Route/Route.test.ts @@ -5,86 +5,145 @@ */ -// import * as assert from 'assert'; - +import * as assert from 'assert'; import BigNumber from 'bignumber.js'; -import { DODOContext, getDODOContext } from '../utils-v1/Context'; -import { decimalStr,MAX_UINT256 } from '../utils-v1/Converter'; +import { DODOContext, getDODOContext } from '../utils-v1/Context-route'; +import { decimalStr,MAX_UINT256,fromWei,mweiStr} from '../utils-v1/Converter'; import { logGas } from '../utils-v1/Log'; +import { DODOHelper } from '../utils-v1/dodoHelper'; let lp: string; let trader: string; -async function init(ctx: DODOContext): Promise { - await ctx.setOraclePrice(decimalStr("100")); - +async function initDODO_USDT(ctx: DODOContext): Promise { + await ctx.setOraclePrice(ctx.DODO_USDT_ORACLE,mweiStr("0.1")); lp = ctx.spareAccounts[0]; trader = ctx.spareAccounts[1]; - await ctx.approveDODO(lp); - await ctx.approveDODO(trader); + + let DODO = ctx.DODO; + let USDT = ctx.USDT; + let DODO_USDT = ctx.DODO_USDT; + await ctx.approvePair(DODO,USDT,DODO_USDT.options.address,lp); + await ctx.approvePair(DODO,USDT,DODO_USDT.options.address,trader); - await ctx.mintTestToken(lp, decimalStr("10"), decimalStr("1000")); - await ctx.mintTestToken(trader, decimalStr("10"), decimalStr("1000")); + await ctx.mintToken(DODO,USDT, lp, decimalStr("10000"), mweiStr("1000")); + await ctx.mintToken(DODO,USDT,trader, decimalStr("100"), mweiStr("0")); - await ctx.DODO.methods - .depositBaseTo(lp, decimalStr("10")) + await DODO_USDT.methods + .depositBaseTo(lp, decimalStr("10000")) .send(ctx.sendParam(lp)); - await ctx.DODO.methods - .depositQuoteTo(lp, decimalStr("1000")) + await DODO_USDT.methods + .depositQuoteTo(lp, mweiStr("1000")) .send(ctx.sendParam(lp)); - } - -async function calcRoute(ctx: DODOContext) { - let fromTokenAmount = decimalStr("1"); - //路径 - let routes = [ - { - address: ctx.BASE.options.address, - decimals: 18 - }, - { - address: ctx.QUOTE.options.address, - decimals: 18 - } - ] +async function initUSDT_USDC(ctx: DODOContext): Promise { + await ctx.setOraclePrice(ctx.USDT_USDC_ORACLE,decimalStr("1")); + lp = ctx.spareAccounts[0]; + trader = ctx.spareAccounts[1]; - //路径上交易对 - let pairs = [ - { - pair: ctx.DODO.options.address, - base: ctx.BASE.options.address - } - ] + let USDT = ctx.USDT; + let USDC = ctx.USDC; + let USDT_USDC = ctx.USDT_USDC; + + await ctx.approvePair(USDT,USDC,USDT_USDC.options.address,lp); + await ctx.mintToken(USDT,USDC,lp, mweiStr("1000"), mweiStr("1000")); + + await USDT_USDC.methods + .depositBaseTo(lp, mweiStr("1000")) + .send(ctx.sendParam(lp)); + await USDT_USDC.methods + .depositQuoteTo(lp, mweiStr("1000")) + .send(ctx.sendParam(lp)); +} + +async function initWETH_USDC(ctx: DODOContext): Promise { + await ctx.setOraclePrice(ctx.WETH_USDC_ORACLE,mweiStr("450")); + lp = ctx.spareAccounts[0]; + trader = ctx.spareAccounts[1]; + + let WETH = ctx.WETH; + let USDC = ctx.USDC; + let WETH_USDC = ctx.WETH_USDC; + + await ctx.approvePair(WETH,USDC,WETH_USDC.options.address,lp); + await ctx.mintToken(WETH,USDC,lp, decimalStr("1000"), mweiStr("450000")); + + await WETH_USDC.methods + .depositBaseTo(lp, decimalStr("1000")) + .send(ctx.sendParam(lp)); + await WETH_USDC.methods + .depositQuoteTo(lp, mweiStr("450000")) + .send(ctx.sendParam(lp)); +} + +//mock sdk logic +async function calcRoute(ctx: DODOContext,fromTokenAmount:string,slippage:number,routes:any[],pairs:any[]) { + let swapAmount = fromTokenAmount + let callPairs: string[] = [] let datas: string = "" let starts: number[] = [] let gAndV: number[] = [] - let swapAmount = fromTokenAmount for (let i = 0; i < pairs.length; i++) { - let curPair = pairs[i] - callPairs.push(curPair.pair) - //TODO: hardcode - let curContact =ctx.DODO; - let curData = '' - if (curPair.base === routes[i].address) { - curData = await curContact.methods.sellBaseToken(swapAmount, 0, []).encodeABI() - swapAmount = await curContact.methods.querySellBaseToken(swapAmount).call(); - } else { - curData = await curContact.methods.buyBaseToken(swapAmount, 0, []).encodABI() - swapAmount = await curContact.methods.queryBuyBaseToken(swapAmount).call(); + if(i == 0){ + starts.push(0); } - starts.push(datas.length) - gAndV.push(0) + let curPair = pairs[i] + let curContact =pairs[i].pairContract; + let curData = ''; + let curApproveData = ''; + + if (curPair.base === routes[i].address) { + curApproveData = await pairs[i].baseContract.methods.approve(curPair.pair,swapAmount).encodeABI() + curApproveData = curApproveData.substring(2,curApproveData.length) + datas += curApproveData + starts.push(datas.length/2) + gAndV.push(0) + callPairs.push(pairs[i].baseContract.options.address); + curData = await curContact.methods.sellBaseToken(swapAmount, 0, "0x").encodeABI() + console.log(i + ":b-for-swapAmount:",swapAmount); + swapAmount = await curContact.methods.querySellBaseToken(swapAmount).call(); + console.log(i + ":a-for-swapAmount:",swapAmount); + } else { + //TODO: approve的逻辑? + curApproveData = await pairs[i].quoteContract.methods.approve(curPair.pair,swapAmount).encodeABI() + curApproveData = curApproveData.substring(2,curApproveData.length) + datas += curApproveData + starts.push(datas.length/2) + gAndV.push(0) + callPairs.push(pairs[i].quoteContract.options.address); + console.log(i + ":b-for-swapAmount:",swapAmount); + let baseDecimal = await pairs[i].baseContract.methods.decimals().call(); + let quoteDecimal = await pairs[i].quoteContract.methods.decimals().call(); + let curPairDetail = { + B: new BigNumber(await curContact.methods._BASE_BALANCE_().call() / 10 ** baseDecimal), + Q: new BigNumber(await curContact.methods._QUOTE_BALANCE_().call() / 10 ** quoteDecimal), + B0: new BigNumber(await curContact.methods._TARGET_BASE_TOKEN_AMOUNT_().call() / 10 ** baseDecimal), + Q0: new BigNumber(await curContact.methods._TARGET_QUOTE_TOKEN_AMOUNT_().call() / 10 ** quoteDecimal), + RStatus: await curContact.methods._R_STATUS_().call(), + OraclePrice: new BigNumber(await curContact.methods.getOraclePrice().call() / 10 ** (18-baseDecimal + quoteDecimal)), + k: new BigNumber(parseInt(ctx.k) / 1e18), + mtFeeRate: new BigNumber(parseInt(ctx.mtFeeRate) / 1e18), + lpFeeRate: new BigNumber(parseInt(ctx.lpFeeRate) / 1e18) + } + let dodoHelper = new DODOHelper(curPairDetail) + let tmpamount = dodoHelper.queryBuyQuote(new BigNumber(fromWei(swapAmount,'mwei'))).toString(); + swapAmount = decimalStr(tmpamount); + curData = await curContact.methods.buyBaseToken(swapAmount, 0, "0x").encodeABI() + console.log(i + ":a-for-swapAmount:",swapAmount); + } + curData = curData.substring(2,curData.length) datas += curData + starts.push(datas.length/2) + gAndV.push(0) + callPairs.push(curPair.pair) } - - let toAmount = new BigNumber(swapAmount).multipliedBy(0.99).toFixed(0, BigNumber.ROUND_DOWN) - + datas = "0x" + datas; + let toAmount = new BigNumber(swapAmount).multipliedBy(1-slippage).toFixed(0, BigNumber.ROUND_DOWN) return ctx.SmartSwap.methods.dodoSwap( - ctx.BASE.options.address, - ctx.QUOTE.options.address, + routes[0].address, + routes[routes.length-1].address, fromTokenAmount, toAmount, callPairs, @@ -100,7 +159,9 @@ describe("Trader", () => { before(async () => { ctx = await getDODOContext(); - await init(ctx); + await initDODO_USDT(ctx); + await initUSDT_USDC(ctx); + await initWETH_USDC(ctx); }); beforeEach(async () => { @@ -111,61 +172,138 @@ describe("Trader", () => { await ctx.EVM.reset(snapshotId); }); - describe("hit currently pair", () => { - it("base to quote", async () => { - var beforeBalance = await ctx.BASE.methods.balanceOf(trader).call() - // await ctx.BASE.methods.approve(ctx.SmartApprove.options.address,MAX_UINT256).send(ctx.sendParam(trader)) - console.log("beforeBalance",beforeBalance) - await logGas(await calcRoute(ctx), ctx.sendParam(trader), "buy token") - var afterBalance = await ctx.BASE.methods.balanceOf(trader).call() - console.log("afterBalance",afterBalance) - // // trader balances - // assert.equal( - // await ctx.BASE.methods.balanceOf(trader).call(), - // decimalStr("11") - // ); - // assert.equal( - // await ctx.QUOTE.methods.balanceOf(trader).call(), - // "898581839502056240973" - // ); + describe("route calc test", () => { + it("DODO to USDT directly swap", async () => { + var b_DODO = await ctx.DODO.methods.balanceOf(trader).call() + var b_USDT = await ctx.USDT.methods.balanceOf(trader).call() + console.log("Before DODO:" + fromWei(b_DODO,'ether') + "; USDT:" + fromWei(b_USDT,'mwei')); + //approve DODO entry + await ctx.DODO.methods.approve(ctx.SmartApprove.options.address,MAX_UINT256).send(ctx.sendParam(trader)) + //set route path + var routes = [{ + address: ctx.DODO.options.address, + decimals: 18 + }, + { + address: ctx.USDT.options.address, + decimals: 6 + }]; + + var pairs = [{ + pair: ctx.DODO_USDT.options.address, + base: ctx.DODO.options.address, + /*only for test*/ + pairContract: ctx.DODO_USDT, + baseContract: ctx.DODO, + quoteContract: ctx.USDT + /**************/ + }]; + + var tx = await logGas(await calcRoute(ctx,decimalStr('10'),0.1,routes,pairs), ctx.sendParam(trader), "route swap") + // console.log(tx.events['Swapped']); + var a_DODO = await ctx.DODO.methods.balanceOf(trader).call() + var a_USDT = await ctx.USDT.methods.balanceOf(trader).call() + console.log("After DODO:" + fromWei(a_DODO,'ether') + "; USDT:" + fromWei(a_USDT,'mwei')); + }); + + + it("DODO to USDC two hops swap", async () => { + var b_DODO = await ctx.DODO.methods.balanceOf(trader).call() + var b_USDC = await ctx.USDC.methods.balanceOf(trader).call() + console.log("Before DODO:" + fromWei(b_DODO,'ether') + "; USDC:" + fromWei(b_USDC,'mwei')); + //approve DODO entry + await ctx.DODO.methods.approve(ctx.SmartApprove.options.address,MAX_UINT256).send(ctx.sendParam(trader)) + //set route path + var routes = [{ + address: ctx.DODO.options.address, + decimals: 18 + },{ + address: ctx.USDT.options.address, + decimals: 6 + },{ + address: ctx.USDC.options.address, + decimals: 6 + }]; + + var pairs = [{ + pair: ctx.DODO_USDT.options.address, + base: ctx.DODO.options.address, + /*only for test*/ + pairContract: ctx.DODO_USDT, + baseContract: ctx.DODO, + quoteContract: ctx.USDT + /**************/ + },{ + pair: ctx.USDT_USDC.options.address, + base: ctx.USDT.options.address, + /*only for test*/ + pairContract: ctx.USDT_USDC, + baseContract: ctx.USDT, + quoteContract: ctx.USDC + /**************/ + }]; + + var tx = await logGas(await calcRoute(ctx,decimalStr('10'),0.1,routes,pairs), ctx.sendParam(trader), "route swap") + // console.log(tx.events['Swapped']); + var a_DODO = await ctx.DODO.methods.balanceOf(trader).call() + var a_USDC = await ctx.USDC.methods.balanceOf(trader).call() + console.log("After DODO:" + fromWei(a_DODO,'ether') + "; USDC:" + fromWei(a_USDC,'mwei')); + }); + + + + it("DODO to WETH three hops swap", async () => { + var b_DODO = await ctx.DODO.methods.balanceOf(trader).call() + var b_WETH = await ctx.WETH.methods.balanceOf(trader).call() + console.log("Before DODO:" + fromWei(b_DODO,'ether') + "; WETH:" + fromWei(b_WETH,'ether')); + //approve DODO entry + await ctx.DODO.methods.approve(ctx.SmartApprove.options.address,MAX_UINT256).send(ctx.sendParam(trader)) + //set route path + var routes = [{ + address: ctx.DODO.options.address, + decimals: 18 + },{ + address: ctx.USDT.options.address, + decimals: 6 + },{ + address: ctx.USDC.options.address, + decimals: 6 + },{ + address: ctx.WETH.options.address, + decimals: 18 + }]; + + var pairs = [{ + pair: ctx.DODO_USDT.options.address, + base: ctx.DODO.options.address, + /*only for test*/ + pairContract: ctx.DODO_USDT, + baseContract: ctx.DODO, + quoteContract: ctx.USDT + /**************/ + },{ + pair: ctx.USDT_USDC.options.address, + base: ctx.USDT.options.address, + /*only for test*/ + pairContract: ctx.USDT_USDC, + baseContract: ctx.USDT, + quoteContract: ctx.USDC + /**************/ + },{ + pair: ctx.WETH_USDC.options.address, + base: ctx.WETH.options.address, + /*only for test*/ + pairContract: ctx.WETH_USDC, + baseContract: ctx.WETH, + quoteContract: ctx.USDC + /**************/ + }]; + + var tx = await logGas(await calcRoute(ctx,decimalStr('10'),0.1,routes,pairs), ctx.sendParam(trader), "route swap") + // console.log(tx.events['Swapped']); + var a_DODO = await ctx.DODO.methods.balanceOf(trader).call() + var a_WETH = await ctx.WETH.methods.balanceOf(trader).call() + console.log("After DODO:" + fromWei(a_DODO,'ether') + "; WETH:" + fromWei(a_WETH,'ether')); }); }); - - - // describe("Revert cases", () => { - // it("price limit", async () => { - // await assert.rejects( - // ctx.DODO.methods - // .buyBaseToken(decimalStr("1"), decimalStr("100"), "0x") - // .send(ctx.sendParam(trader)), - // /BUY_BASE_COST_TOO_MUCH/ - // ); - // await assert.rejects( - // ctx.DODO.methods - // .sellBaseToken(decimalStr("1"), decimalStr("100"), "0x") - // .send(ctx.sendParam(trader)), - // /SELL_BASE_RECEIVE_NOT_ENOUGH/ - // ); - // }); - - // it("base balance limit", async () => { - // await assert.rejects( - // ctx.DODO.methods - // .buyBaseToken(decimalStr("11"), decimalStr("10000"), "0x") - // .send(ctx.sendParam(trader)), - // /DODO_BASE_BALANCE_NOT_ENOUGH/ - // ); - - // await ctx.DODO.methods - // .buyBaseToken(decimalStr("1"), decimalStr("200"), "0x") - // .send(ctx.sendParam(trader)); - - // await assert.rejects( - // ctx.DODO.methods - // .buyBaseToken(decimalStr("11"), decimalStr("10000"), "0x") - // .send(ctx.sendParam(trader)), - // /DODO_BASE_BALANCE_NOT_ENOUGH/ - // ); - // }); - // }); }); diff --git a/test/utils-v1/Context.ts b/test/utils-v1/Context-route.ts similarity index 51% rename from test/utils-v1/Context.ts rename to test/utils-v1/Context-route.ts index f00ba44..68a6a02 100644 --- a/test/utils-v1/Context.ts +++ b/test/utils-v1/Context-route.ts @@ -49,47 +49,48 @@ export let DefaultDODOContextInitConfig = { export class DODOContext { EVM: EVM; Web3: Web3; - DODO: Contract; DODOZoo: Contract; - BASE: Contract; - BaseCapital: Contract; - QUOTE: Contract; - QuoteCapital: Contract; - ORACLE: Contract; - SmartSwap: Contract; - SmartApprove: Contract; Deployer: string; Supervisor: string; Maintainer: string; spareAccounts: string[]; + lpFeeRate: string; + mtFeeRate: string; + k: string; + + //token + DODO:Contract; + USDT:Contract; + USDC:Contract; + WETH:Contract; + //pair + DODO_USDT: Contract; + USDT_USDC: Contract; + WETH_USDC: Contract; + DODO_USDT_ORACLE: Contract; + USDT_USDC_ORACLE: Contract; + WETH_USDC_ORACLE: Contract; + //SmartRoute + SmartSwap: Contract; + SmartApprove: Contract; constructor() {} async init(config: DODOContextInitConfig) { + this.k = config.k; + this.mtFeeRate = config.mtFeeRate; + this.lpFeeRate = config.lpFeeRate; + this.EVM = new EVM(); this.Web3 = getDefaultWeb3(); var cloneFactory = await contracts.newContract( contracts.CLONE_FACTORY_CONTRACT_NAME ); - - this.BASE = await contracts.newContract( - contracts.TEST_ERC20_CONTRACT_NAME, - ["TestBase", 18] - ); - this.QUOTE = await contracts.newContract( - contracts.TEST_ERC20_CONTRACT_NAME, - ["TestQuote", 18] - ); - this.ORACLE = await contracts.newContract( - contracts.NAIVE_ORACLE_CONTRACT_NAME - ); - const allAccounts = await this.Web3.eth.getAccounts(); this.Deployer = allAccounts[0]; this.Supervisor = allAccounts[1]; this.Maintainer = allAccounts[2]; this.spareAccounts = allAccounts.slice(3, 10); - var DODOTemplate = await contracts.newContract( contracts.DODO_CONTRACT_NAME ); @@ -101,13 +102,66 @@ export class DODOContext { this.Supervisor, ] ); - + //发币 + this.DODO = await contracts.newContract( + contracts.TEST_ERC20_CONTRACT_NAME, + ["DODO", 18] + ); + this.USDT = await contracts.newContract( + contracts.TEST_ERC20_CONTRACT_NAME, + ["USDT", 6] + ); + this.USDC = await contracts.newContract( + contracts.TEST_ERC20_CONTRACT_NAME, + ["USDC", 6] + ); + this.WETH = await contracts.newContract( + contracts.TEST_ERC20_CONTRACT_NAME, + ["WETH", 18] + ); + //创建交易对 + //DODO-USDT + this.DODO_USDT_ORACLE = await contracts.newContract( + contracts.NAIVE_ORACLE_CONTRACT_NAME + ); await this.DODOZoo.methods .breedDODO( this.Maintainer, - this.BASE.options.address, - this.QUOTE.options.address, - this.ORACLE.options.address, + this.DODO.options.address, + this.USDT.options.address, + this.DODO_USDT_ORACLE.options.address, + config.lpFeeRate, + config.mtFeeRate, + config.k, + config.gasPriceLimit + ) + .send(this.sendParam(this.Deployer)); + //USDT-USDC + this.USDT_USDC_ORACLE = await contracts.newContract( + contracts.NAIVE_ORACLE_CONTRACT_NAME + ); + await this.DODOZoo.methods + .breedDODO( + this.Maintainer, + this.USDT.options.address, + this.USDC.options.address, + this.USDT_USDC_ORACLE.options.address, + config.lpFeeRate, + config.mtFeeRate, + config.k, + config.gasPriceLimit + ) + .send(this.sendParam(this.Deployer)); + //WETH-USDC + this.WETH_USDC_ORACLE = await contracts.newContract( + contracts.NAIVE_ORACLE_CONTRACT_NAME + ); + await this.DODOZoo.methods + .breedDODO( + this.Maintainer, + this.WETH.options.address, + this.USDC.options.address, + this.WETH_USDC_ORACLE.options.address, config.lpFeeRate, config.mtFeeRate, config.k, @@ -115,30 +169,50 @@ export class DODOContext { ) .send(this.sendParam(this.Deployer)); - this.DODO = contracts.getContractWithAddress( + this.DODO_USDT = contracts.getContractWithAddress( contracts.DODO_CONTRACT_NAME, await this.DODOZoo.methods - .getDODO(this.BASE.options.address, this.QUOTE.options.address) + .getDODO(this.DODO.options.address, this.USDT.options.address) .call() ); - await this.DODO.methods + + this.USDT_USDC = contracts.getContractWithAddress( + contracts.DODO_CONTRACT_NAME, + await this.DODOZoo.methods + .getDODO(this.USDT.options.address, this.USDC.options.address) + .call() + ); + this.WETH_USDC = contracts.getContractWithAddress( + contracts.DODO_CONTRACT_NAME, + await this.DODOZoo.methods + .getDODO(this.WETH.options.address, this.USDC.options.address) + .call() + ); + + await this.DODO_USDT.methods .enableBaseDeposit() .send(this.sendParam(this.Deployer)); - await this.DODO.methods + await this.DODO_USDT.methods .enableQuoteDeposit() .send(this.sendParam(this.Deployer)); - await this.DODO.methods.enableTrading().send(this.sendParam(this.Deployer)); + await this.DODO_USDT.methods.enableTrading().send(this.sendParam(this.Deployer)); - this.BaseCapital = contracts.getContractWithAddress( - contracts.DODO_LP_TOKEN_CONTRACT_NAME, - await this.DODO.methods._BASE_CAPITAL_TOKEN_().call() - ); - this.QuoteCapital = contracts.getContractWithAddress( - contracts.DODO_LP_TOKEN_CONTRACT_NAME, - await this.DODO.methods._QUOTE_CAPITAL_TOKEN_().call() - ); + await this.USDT_USDC.methods + .enableBaseDeposit() + .send(this.sendParam(this.Deployer)); + await this.USDT_USDC.methods + .enableQuoteDeposit() + .send(this.sendParam(this.Deployer)); + await this.USDT_USDC.methods.enableTrading().send(this.sendParam(this.Deployer)); + + await this.WETH_USDC.methods + .enableBaseDeposit() + .send(this.sendParam(this.Deployer)); + await this.WETH_USDC.methods + .enableQuoteDeposit() + .send(this.sendParam(this.Deployer)); + await this.WETH_USDC.methods.enableTrading().send(this.sendParam(this.Deployer)); - /*v1.5*/ this.SmartApprove = await contracts.newContract( contracts.SMART_APPROVE ); @@ -149,7 +223,6 @@ export class DODOContext { ); await this.SmartApprove.methods.setSmartSwap(this.SmartSwap.options.address).send(this.sendParam(this.Deployer)); - /*****/ console.log(log.blueText("[Init dodo context]")); } @@ -163,25 +236,25 @@ export class DODOContext { }; } - async setOraclePrice(price: string) { - await this.ORACLE.methods + async setOraclePrice(oracle:Contract,price: string) { + await oracle.methods .setPrice(price) .send(this.sendParam(this.Deployer)); } - async mintTestToken(to: string, base: string, quote: string) { - await this.BASE.methods.mint(to, base).send(this.sendParam(this.Deployer)); - await this.QUOTE.methods - .mint(to, quote) + async mintToken(tokenBase:Contract,tokenQuote:Contract,to: string, base: string, quote: string) { + await tokenBase.methods.mint(to, base).send(this.sendParam(this.Deployer)); + await tokenQuote.methods + .mint(to,  quote) .send(this.sendParam(this.Deployer)); } - async approveDODO(account: string) { - await this.BASE.methods - .approve(this.DODO.options.address, MAX_UINT256) + async approvePair(tokenBase:Contract,tokenQuote:Contract, approveTarget:string,account: string) { + await tokenBase.methods + .approve(approveTarget, MAX_UINT256) .send(this.sendParam(account)); - await this.QUOTE.methods - .approve(this.DODO.options.address, MAX_UINT256) + await tokenQuote.methods + .approve(approveTarget, MAX_UINT256) .send(this.sendParam(account)); } } diff --git a/test/utils-v1/Converter.ts b/test/utils-v1/Converter.ts index c7108b9..f4af089 100644 --- a/test/utils-v1/Converter.ts +++ b/test/utils-v1/Converter.ts @@ -1,4 +1,5 @@ import BigNumber from "bignumber.js"; +import { getDefaultWeb3 } from './EVM'; export const MAX_UINT256 = "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" @@ -12,4 +13,9 @@ export function mweiStr(value: string): string { export function gweiStr(gwei: string): string { return new BigNumber(gwei).multipliedBy(10 ** 9).toFixed(0, BigNumber.ROUND_DOWN) +} + +export function fromWei(value:string,unit:any): string { + var web3 = getDefaultWeb3(); + return web3.utils.fromWei(value,unit); } \ No newline at end of file diff --git a/test/utils-v1/dodoHelper.ts b/test/utils-v1/dodoHelper.ts new file mode 100644 index 0000000..ca83e23 --- /dev/null +++ b/test/utils-v1/dodoHelper.ts @@ -0,0 +1,366 @@ +import { BigNumber } from 'bignumber.js'; + +export const RStatusOne = 0; +export const RStatusAboveOne = 1; +export const RStatusBelowOne = 2; + +export class DODOHelper { + // unstable + public B!: BigNumber; // DODO._BASE_BALANCE_() / 10^baseDecimals + public Q!: BigNumber; // DODO._QUOTE_BALANCE_() / 10^quoteDecimals + public B0!: BigNumber; // DODO._TARGET_BASE_TOKEN_AMOUNT_() / 10^baseDecimals + public Q0!: BigNumber; // DODO._TARGET_QUOTE_TOKEN_AMOUNT_() / 10^quoteDecimals + public RStatus!: number; // DODO._R_STATUS_() + public OraclePrice!: BigNumber; // DODO.getOraclePrice() / 10^(18-baseDecimals+quoteDecimals) + + // stable + public k!: BigNumber; // DODO._K_()/10^18 + public mtFeeRate!: BigNumber; // DODO._MT_FEE_RATE_()/10^18 + public lpFeeRate!: BigNumber; // DODO._LP_FEE_RATE_()/10^18 + + constructor(pairDetail:any) { + this.B = pairDetail.B + this.Q = pairDetail.Q + this.B0 = pairDetail.B0 + this.Q0 = pairDetail.Q0 + this.RStatus = pairDetail.RStatus + this.OraclePrice = pairDetail.OraclePrice + this.k = pairDetail.k + this.mtFeeRate = pairDetail.mtFeeRate + this.lpFeeRate = pairDetail.lpFeeRate + } + + // return mid price + public getMidPrice(): BigNumber { + if (this.RStatus === RStatusOne) { + return this.OraclePrice; + } + if (this.RStatus === RStatusAboveOne) { + let R = this.B0.div(this.B); + R = R.multipliedBy(R) + .multipliedBy(this.k) + .minus(this.k) + .plus(new BigNumber(1)); + return this.OraclePrice.multipliedBy(R); + } + if (this.RStatus === RStatusBelowOne) { + let R = this.Q0.div(this.Q); + R = R.multipliedBy(R) + .multipliedBy(this.k) + .minus(this.k) + .plus(new BigNumber(1)); + return this.OraclePrice.div(R); + } + return this.OraclePrice; + } + + // return the targetBase and targetQuote assuming system balanced + public getExpectedTarget(): { base: BigNumber; quote: BigNumber } { + let baseTarget: BigNumber; + let quoteTarget: BigNumber; + baseTarget = this.B0; + quoteTarget = this.Q0; + if (this.RStatus === RStatusOne) { + baseTarget = this.B0; + quoteTarget = this.Q0; + } + if (this.RStatus === RStatusAboveOne) { + quoteTarget = this.Q0; + baseTarget = solveQuadraticFunctionForTarget(this.B, this.k, this.Q.minus(this.Q0).div(this.OraclePrice)); + } + if (this.RStatus === RStatusBelowOne) { + baseTarget = this.B0; + quoteTarget = solveQuadraticFunctionForTarget( + this.Q, + this.k, + this.B.minus(this.B0).multipliedBy(this.OraclePrice) + ); + } + return { + base: baseTarget, + quote: quoteTarget + }; + } + + // return paid quote amount (fee deducted) + public queryBuyBase(amount: BigNumber) { + let mtFee = amount.multipliedBy(this.mtFeeRate); + let lpFee = amount.multipliedBy(this.lpFeeRate); + amount = amount.plus(mtFee).plus(lpFee); + let target = this.getExpectedTarget(); + let quote = new BigNumber(0); + if (this.RStatus === RStatusOne) { + quote = this.ROneBuyBase(amount, target.base); + } else if (this.RStatus === RStatusAboveOne) { + quote = this.RAboveBuyBase(amount, target.base); + } else { + let backOneBase = this.B.minus(target.base); + let backOneQuote = target.quote.minus(this.Q); + if (amount.isLessThanOrEqualTo(backOneBase)) { + quote = this.RBelowBuyBase(amount, target.quote); + } else { + quote = backOneQuote.plus(this.ROneBuyBase(amount.minus(backOneBase), target.base)); + } + } + + return quote + } + + // return received quote amount (fee deducted) + public querySellBase(amount: BigNumber) { + let result: BigNumber; + let target = this.getExpectedTarget(); + if (this.RStatus === RStatusOne) { + result = this.ROneSellBase(amount, target.quote); + } else if (this.RStatus === RStatusBelowOne) { + result = this.RBelowSellBase(amount, target.quote); + } else { + let backOneBase = target.base.minus(this.B); + let backOneQuote = this.Q.minus(target.quote); + if (amount.isLessThanOrEqualTo(backOneBase)) { + result = this.RAboveSellBase(amount, target.base); + } else { + result = backOneQuote.plus(this.ROneSellBase(amount.minus(backOneBase), target.quote)); + } + } + let mtFee = result.multipliedBy(this.mtFeeRate); + let lpFee = result.multipliedBy(this.lpFeeRate); + + const quote = result.minus(mtFee).minus(lpFee); + + return quote + } + + // return paid base amount (fee deducted) + public queryBuyQuote(amount: BigNumber): BigNumber { + let mtFee = amount.multipliedBy(this.mtFeeRate); + let lpFee = amount.multipliedBy(this.lpFeeRate); + amount = amount.plus(mtFee).plus(lpFee); + let target = this.getExpectedTarget(); + if (this.RStatus === RStatusOne) { + return this.ROneBuyQuote(amount, target.quote); + } else if (this.RStatus === RStatusBelowOne) { + return this.RBelowBuyQuote(amount, target.quote); + } else { + let backOneBase = target.base.minus(this.B); + let backOneQuote = this.Q.minus(target.quote); + if (amount.isLessThanOrEqualTo(backOneQuote)) { + return this.RAboveBuyQuote(amount, target.base); + } else { + return backOneBase.plus(this.ROneBuyQuote(amount.minus(backOneQuote), target.quote)); + } + } + } + + // return received base amount (fee deducted) + public querySellQuote(amount: BigNumber): BigNumber { + let result: BigNumber; + let target = this.getExpectedTarget(); + if (this.RStatus === RStatusOne) { + result = this.ROneSellQuote(amount, target.base); + } else if (this.RStatus === RStatusAboveOne) { + result = this.RAboveSellQuote(amount, target.base); + } else { + let backOneBase = this.B.minus(target.base); + let backOneQuote = target.quote.minus(this.Q); + if (amount.isLessThanOrEqualTo(backOneQuote)) { + result = this.RBelowSellQuote(amount, target.quote); + } else { + result = backOneBase.plus(this.ROneSellQuote(amount.minus(backOneQuote), target.base)); + } + } + let mtFee = result.multipliedBy(this.mtFeeRate); + let lpFee = result.multipliedBy(this.lpFeeRate); + return result.minus(mtFee).minus(lpFee); + } + + public getWithdrawBasePenalty(amount: BigNumber): BigNumber { + if (this.RStatus === RStatusAboveOne) { + let baseTarget = solveQuadraticFunctionForTarget(this.B, this.k, this.Q.minus(this.Q0).div(this.OraclePrice)); + let baseTargetWithdraw = solveQuadraticFunctionForTarget( + this.B.minus(amount), + this.k, + this.Q.minus(this.Q0).div(this.OraclePrice) + ); + let penalty = baseTarget.minus(baseTargetWithdraw).minus(amount); + return penalty; + } else { + return new BigNumber(0); + } + } + + public getWithdrawQuotePenalty(amount: BigNumber): BigNumber { + if (this.RStatus === RStatusBelowOne) { + let quoteTarget = solveQuadraticFunctionForTarget( + this.Q, + this.k, + this.B.minus(this.B0).multipliedBy(this.OraclePrice) + ); + let quoteTargetWithdraw = solveQuadraticFunctionForTarget( + this.Q.minus(amount), + this.k, + this.B.minus(this.B0).multipliedBy(this.OraclePrice) + ); + let penalty = quoteTarget.minus(quoteTargetWithdraw).minus(amount); + return penalty; + } else { + return new BigNumber(0); + } + } + + // =========== helper ROne =========== + + public ROneBuyBase(amount: BigNumber, targetBase: BigNumber): BigNumber { + if (amount.isGreaterThanOrEqualTo(targetBase)) { + throw new Error('ROne Buy Base Amount Exceed Limitation'); + } + return integrate(targetBase, targetBase, targetBase.minus(amount), this.OraclePrice, this.k); + } + + public ROneBuyQuote(amount: BigNumber, targetQuote: BigNumber): BigNumber { + if (amount.isGreaterThanOrEqualTo(targetQuote)) { + throw new Error('ROne Buy Quote Amount Exceed Limitation'); + } + return integrate( + targetQuote, + targetQuote, + targetQuote.minus(amount), + new BigNumber(1).div(this.OraclePrice), + this.k + ); + } + + public ROneSellBase(amount: BigNumber, targetQuote: BigNumber): BigNumber { + let newQ = solveQuadraticFunctionForTrade(targetQuote, targetQuote, this.OraclePrice, amount.negated(), this.k); + return targetQuote.minus(newQ); + } + + public ROneSellQuote(amount: BigNumber, targetBase: BigNumber): BigNumber { + let newB = solveQuadraticFunctionForTrade( + targetBase, + targetBase, + new BigNumber(1).div(this.OraclePrice), + amount.negated(), + this.k + ); + return targetBase.minus(newB); + } + + // =========== helper RAbove =========== + + public RAboveBuyBase(amount: BigNumber, targetBase: BigNumber): BigNumber { + if (amount.isGreaterThanOrEqualTo(this.B)) { + throw new Error('RAbove Buy Base Amount Exceed Limitation'); + } + return integrate(targetBase, this.B, this.B.minus(amount), this.OraclePrice, this.k); + } + + public RAboveSellBase(amount: BigNumber, targetBase: BigNumber): BigNumber { + if (amount.plus(this.B).isGreaterThan(targetBase)) { + throw new Error('RAbove Sell Base Amount Exceed Limitation'); + } + return integrate(targetBase, this.B.plus(amount), this.B, this.OraclePrice, this.k); + } + + public RAboveBuyQuote(amount: BigNumber, targetBase: BigNumber): BigNumber { + let newB = solveQuadraticFunctionForTrade( + targetBase, + this.B, + new BigNumber(1).div(this.OraclePrice), + amount, + this.k + ); + return newB.minus(this.B); + } + + public RAboveSellQuote(amount: BigNumber, targetBase: BigNumber): BigNumber { + let newB = solveQuadraticFunctionForTrade( + targetBase, + this.B, + new BigNumber(1).div(this.OraclePrice), + amount.negated(), + this.k + ); + return this.B.minus(newB); + } + + // =========== helper RBelow =========== + + public RBelowBuyQuote(amount: BigNumber, targetQuote: BigNumber): BigNumber { + if (amount.isGreaterThanOrEqualTo(this.Q)) { + throw new Error('RBelow Buy Quote Amount Exceed Limitation'); + } + return integrate(targetQuote, this.Q, this.Q.minus(amount), new BigNumber(1).div(this.OraclePrice), this.k); + } + + public RBelowSellQuote(amount: BigNumber, targetQuote: BigNumber): BigNumber { + if (amount.plus(this.Q).isGreaterThan(targetQuote)) { + throw new Error('RBelow Sell Quote Amount Exceed Limitation'); + } + return integrate(targetQuote, this.Q.plus(amount), this.Q, new BigNumber(1).div(this.OraclePrice), this.k); + } + + public RBelowBuyBase(amount: BigNumber, targetQuote: BigNumber): BigNumber { + let newQ = solveQuadraticFunctionForTrade(targetQuote, this.Q, this.OraclePrice, amount, this.k); + return newQ.minus(this.Q); + } + + public RBelowSellBase(amount: BigNumber, targetQuote: BigNumber): BigNumber { + let newQ = solveQuadraticFunctionForTrade(targetQuote, this.Q, this.OraclePrice, amount.negated(), this.k); + return this.Q.minus(newQ); + } +} + +export const integrate = (V0: BigNumber, V1: BigNumber, V2: BigNumber, i: BigNumber, k: BigNumber): BigNumber => { + let fairAmount = i.multipliedBy(V1.minus(V2)); + let penalty = V0.multipliedBy(V0) + .div(V1) + .div(V2) + .multipliedBy(k); + return fairAmount.multipliedBy(new BigNumber(1).minus(k).plus(penalty)); +}; + +export const solveQuadraticFunctionForTrade = ( + V0: BigNumber, + V1: BigNumber, + i: BigNumber, + delta: BigNumber, + k: BigNumber +): BigNumber => { + // -b = (1-k)V1-kV0^2/V1+i*delta + let minusB = new BigNumber(1).minus(k).multipliedBy(V1); + minusB = minusB.minus( + k + .multipliedBy(V0) + .multipliedBy(V0) + .div(V1) + ); + minusB = minusB.plus(i.multipliedBy(delta)); + + // sqrt(b*b+4(1-k)kQ0*Q0) + let squareRoot = new BigNumber(4) + .multipliedBy(new BigNumber(1).minus(k)) + .multipliedBy(k) + .multipliedBy(V0) + .multipliedBy(V0); + squareRoot = minusB + .multipliedBy(minusB) + .plus(squareRoot) + .sqrt(); + + // 2(1-k) + let denominator = new BigNumber(2).multipliedBy(new BigNumber(1).minus(k)); + + return minusB.plus(squareRoot).div(denominator); +}; + +export const solveQuadraticFunctionForTarget = (V1: BigNumber, k: BigNumber, fairAmount: BigNumber): BigNumber => { + // V0 = V1+V1*(sqrt-1)/2k + let sqrt = new BigNumber(4) + .multipliedBy(k) + .multipliedBy(fairAmount) + .div(V1); + sqrt = new BigNumber(1).plus(sqrt).sqrt(); + let premium = sqrt.minus(new BigNumber(1)).div(k.multipliedBy(new BigNumber(2))); + return V1.multipliedBy(new BigNumber(1).plus(premium)); +};