From 2c27386c34539aa31d0d6f30e930ea3d1e3b2c9c Mon Sep 17 00:00:00 2001 From: mingda Date: Tue, 27 Oct 2020 02:53:23 +0800 Subject: [PATCH] init funding test --- .../DODOVendorMachine/impl/DVMFunding.sol | 42 ++--- .../DODOVendorMachine/impl/DVMStorage.sol | 9 +- .../DODOVendorMachine/impl/DVMTrader.sol | 18 ++- contracts/SmartRoute/SmartRoute.sol | 2 +- contracts/helper/NaiveFeeRateModel.sol | 2 +- test/DVM/funding.test.ts | 144 ++++++++++++++++++ test/DVM/trader.test.ts | 121 +++++++++++++++ test/DVM/trader.ts | 85 ----------- test/utils/DVMContext.ts | 2 + 9 files changed, 305 insertions(+), 120 deletions(-) create mode 100644 test/DVM/funding.test.ts create mode 100644 test/DVM/trader.test.ts delete mode 100644 test/DVM/trader.ts diff --git a/contracts/DODOVendorMachine/impl/DVMFunding.sol b/contracts/DODOVendorMachine/impl/DVMFunding.sol index 04fbc61..65be8bb 100644 --- a/contracts/DODOVendorMachine/impl/DVMFunding.sol +++ b/contracts/DODOVendorMachine/impl/DVMFunding.sol @@ -23,27 +23,27 @@ contract DVMFunding is DVMStorage { if (baseReserve == 0 && quoteReserve == 0) { mintAmount = baseInput; } - // // case 2. supply when quote reserve is 0 - // if (baseReserve > 0 && quoteReserve == 0) { - // uint256 mintRatio = DecimalMath.divFloor(baseInput, baseReserve); - // mintAmount = DecimalMath.mulFloor(_VAULT_.totalSupply(), mintRatio); - // } - // // case 3. normal case - // if (baseReserve > 0 && quoteReserve > 0) { - // uint256 baseInputRatio = DecimalMath.divFloor(baseInput, baseReserve); - // uint256 quoteInputRatio = DecimalMath.divFloor(quoteInput, quoteReserve); - // uint256 mintRatio = baseInputRatio > quoteInputRatio ? quoteInputRatio : baseInputRatio; - // // 在提币的时候向下取整。因此永远不会出现,balance为0但totalsupply不为0的情况 - // // 但有可能出现,reserve>0但totalSupply=0的场景 - // uint256 totalShare = _VAULT_.totalSupply(); - // if (totalShare > 0) { - // mintAmount = DecimalMath.mulFloor(totalShare, mintRatio); - // } else { - // mintAmount = baseInput; - // } - // } - // _VAULT_.mint(account, mintAmount); - // _VAULT_.sync(); + // case 2. supply when quote reserve is 0 + if (baseReserve > 0 && quoteReserve == 0) { + uint256 mintRatio = DecimalMath.divFloor(baseInput, baseReserve); + mintAmount = DecimalMath.mulFloor(_VAULT_.totalSupply(), mintRatio); + } + // case 3. normal case + if (baseReserve > 0 && quoteReserve > 0) { + uint256 baseInputRatio = DecimalMath.divFloor(baseInput, baseReserve); + uint256 quoteInputRatio = DecimalMath.divFloor(quoteInput, quoteReserve); + uint256 mintRatio = baseInputRatio > quoteInputRatio ? quoteInputRatio : baseInputRatio; + // 在提币的时候向下取整。因此永远不会出现,balance为0但totalsupply不为0的情况 + // 但有可能出现,reserve>0但totalSupply=0的场景 + uint256 totalShare = _VAULT_.totalSupply(); + if (totalShare > 0) { + mintAmount = DecimalMath.mulFloor(totalShare, mintRatio); + } else { + mintAmount = baseInput; + } + } + _VAULT_.mint(account, mintAmount); + _VAULT_.sync(); } function sellShares( diff --git a/contracts/DODOVendorMachine/impl/DVMStorage.sol b/contracts/DODOVendorMachine/impl/DVMStorage.sol index 7be080d..9061dce 100644 --- a/contracts/DODOVendorMachine/impl/DVMStorage.sol +++ b/contracts/DODOVendorMachine/impl/DVMStorage.sol @@ -46,7 +46,6 @@ contract DVMStorage is InitializableOwnable, ReentrancyGuard { IFeeRateModel public _MT_FEE_RATE_MODEL_; uint256 public _K_; uint256 public _I_; - uint256 public _BASE0_; DVMVault public _VAULT_; DVMVault public _PROTECTION_VAULT_; @@ -59,13 +58,9 @@ contract DVMStorage is InitializableOwnable, ReentrancyGuard { } // ============ Helper Functions ============ - function _updateBase0() internal { + function getBase0() public view returns (uint256) { uint256 fairAmount = DecimalMath.divFloor(_VAULT_._QUOTE_RESERVE_(), _I_); - _BASE0_ = DODOMath._SolveQuadraticFunctionForTarget( - _VAULT_._BASE_RESERVE_(), - _K_, - fairAmount - ); + return DODOMath._SolveQuadraticFunctionForTarget(_VAULT_._BASE_RESERVE_(), _K_, fairAmount); } // ============ Version Control ============ diff --git a/contracts/DODOVendorMachine/impl/DVMTrader.sol b/contracts/DODOVendorMachine/impl/DVMTrader.sol index 6fd4ab0..f374d5e 100644 --- a/contracts/DODOVendorMachine/impl/DVMTrader.sol +++ b/contracts/DODOVendorMachine/impl/DVMTrader.sol @@ -25,7 +25,6 @@ contract DVMTrader is DVMStorage { _VAULT_.transferQuoteOut(_MAINTAINER_, mtFee); } _VAULT_.sync(); - _updateBase0(); // 这里需要想想,原则上不需要update B0. 但精度问题,或者用户往合约里充值,可能导致需要updateBase0 return receiveQuoteAmount; } @@ -38,7 +37,6 @@ contract DVMTrader is DVMStorage { _VAULT_.transferBaseOut(_MAINTAINER_, mtFee); } _VAULT_.sync(); - _updateBase0(); return receiveBaseAmount; } @@ -47,10 +45,11 @@ contract DVMTrader is DVMStorage { view returns (uint256 receiveQuoteAmount, uint256 mtFee) { + uint256 B0 = getBase0(); uint256 B2 = _VAULT_._BASE_RESERVE_(); uint256 B1 = B2.add(payBaseAmount); - require(_BASE0_ >= B1, "DODO_BASE_BALANCE_NOT_ENOUGH"); - uint256 Q = DODOMath._GeneralIntegrate(_BASE0_, B1, B2, _I_, _K_); + require(B0 >= B1, "DODO_BASE_BALANCE_NOT_ENOUGH"); + uint256 Q = DODOMath._GeneralIntegrate(B0, B1, B2, _I_, _K_); uint256 lpFeeRate = _LP_FEE_RATE_MODEL_.getFeeRate(Q); uint256 mtFeeRate = _MT_FEE_RATE_MODEL_.getFeeRate(Q); mtFee = DecimalMath.mulCeil(Q, mtFeeRate); @@ -63,10 +62,11 @@ contract DVMTrader is DVMStorage { view returns (uint256 receiveBaseAmount, uint256 mtFee) { + uint256 B0 = getBase0(); uint256 B1 = _VAULT_._BASE_RESERVE_(); uint256 fairAmount = DecimalMath.divFloor(payQuoteAmount, _I_); uint256 newBaseReserve = DODOMath._SolveQuadraticFunctionForTrade( - _BASE0_, + B0, B1, fairAmount, false, @@ -79,4 +79,12 @@ contract DVMTrader is DVMStorage { receiveBaseAmount = deltaBase.sub(mtFee).sub(DecimalMath.mulCeil(deltaBase, lpFeeRate)); return (receiveBaseAmount, mtFee); } + + function getMidPrice() public view returns (uint256 midPrice) { + uint256 B0 = getBase0(); + uint256 B1 = _VAULT_._BASE_RESERVE_(); + uint256 offsetRatio = DecimalMath.ONE.mul(B0).div(B1).mul(B0).div(B1); + uint256 offset = DecimalMath.ONE.sub(_K_).add(DecimalMath.mulFloor(offsetRatio, _K_)); + return DecimalMath.mulFloor(_I_, offset); + } } diff --git a/contracts/SmartRoute/SmartRoute.sol b/contracts/SmartRoute/SmartRoute.sol index 57f9718..f1a7712 100644 --- a/contracts/SmartRoute/SmartRoute.sol +++ b/contracts/SmartRoute/SmartRoute.sol @@ -46,7 +46,7 @@ contract SmartRoute is Ownable { address(DVMController(DVM)._VAULT_()), quoteAmount ); - receiveAmount = DVMController(DVM).sellBase(to); + receiveAmount = DVMController(DVM).sellQuote(to); require(receiveAmount >= minReceive, "RECEIVE_NOT_ENOUGU"); return receiveAmount; } diff --git a/contracts/helper/NaiveFeeRateModel.sol b/contracts/helper/NaiveFeeRateModel.sol index eff1ea2..7b5727d 100644 --- a/contracts/helper/NaiveFeeRateModel.sol +++ b/contracts/helper/NaiveFeeRateModel.sol @@ -15,7 +15,7 @@ contract NaiveFeeRateModel is Ownable, IFeeRateModel { uint256 public _FEE_RATE_; constructor(uint256 feeRate) public { - feeRate = _FEE_RATE_; + _FEE_RATE_ = feeRate; } function setFeeRate(uint256 newFeeRate) external { diff --git a/test/DVM/funding.test.ts b/test/DVM/funding.test.ts new file mode 100644 index 0000000..0f90ac4 --- /dev/null +++ b/test/DVM/funding.test.ts @@ -0,0 +1,144 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +// import * as assert from 'assert'; + +import { decimalStr } from '../utils/Converter'; +import { logGas } from '../utils/Log'; +import { DVMContext, getDVMContext } from '../utils/DVMContext'; +import { assert } from 'chai'; +import BigNumber from 'bignumber.js'; + +let lp: string; +let trader: string; + +async function init(ctx: DVMContext): Promise { + lp = ctx.SpareAccounts[0]; + trader = ctx.SpareAccounts[1]; + await ctx.approveRoute(lp); + await ctx.approveRoute(trader); + + await ctx.mintTestToken(lp, decimalStr("10"), decimalStr("1000")); + await ctx.mintTestToken(trader, decimalStr("10"), decimalStr("1000")); +} + +describe("Funding", () => { + let snapshotId: string; + let ctx: DVMContext; + + before(async () => { + ctx = await getDVMContext(); + await init(ctx); + }); + + beforeEach(async () => { + snapshotId = await ctx.EVM.snapshot(); + }); + + afterEach(async () => { + await ctx.EVM.reset(snapshotId); + }); + + describe("buy shares", () => { + + it("buy shares from init states", async () => { + + await logGas(ctx.Route.methods + .depositToDVM(ctx.DVM.options.address, lp, decimalStr("10"), decimalStr("0")) + , ctx.sendParam(lp), "buy shares"); + + // vault balances + assert.equal( + await ctx.BASE.methods.balanceOf(ctx.Vault.options.address).call(), + decimalStr("10") + ); + assert.equal( + await ctx.QUOTE.methods.balanceOf(ctx.Vault.options.address).call(), + decimalStr("0") + ); + assert.equal( + await ctx.Vault.methods._BASE_RESERVE_().call(), + decimalStr("10") + ) + assert.equal( + await ctx.Vault.methods._QUOTE_RESERVE_().call(), + decimalStr("0") + ) + + // shares number + assert.equal(await ctx.Vault.methods.balanceOf(lp).call(), decimalStr("10")) + }); + + it("buy shares from init states with quote != 0", async () => { + await ctx.Route.methods + .depositToDVM(ctx.DVM.options.address, lp, decimalStr("10"), decimalStr("100")) + .send(ctx.sendParam(lp)); + assert.equal(await ctx.Vault.methods.balanceOf(lp).call(), decimalStr("10")) + assert.equal(await ctx.DVM.methods.getMidPrice().call(), "102078438912577213500") + }) + + it("buy shares with balanced input", async () => { + await ctx.Route.methods + .depositToDVM(ctx.DVM.options.address, lp, decimalStr("10"), decimalStr("0")) + .send(ctx.sendParam(lp)); + await ctx.Route.methods.sellQuoteOnDVM(ctx.DVM.options.address, trader, decimalStr("200"), decimalStr("1")).send(ctx.sendParam(trader)) + + var vaultBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(ctx.Vault.options.address).call()) + var vaultQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(ctx.Vault.options.address).call()) + var increaseRatio = new BigNumber("0.1") + + await ctx.Route.methods.depositToDVM(ctx.DVM.options.address, trader, vaultBaseBalance.multipliedBy(increaseRatio).toFixed(0), vaultQuoteBalance.multipliedBy(increaseRatio).toFixed(0)).send(ctx.sendParam(trader)) + + assert.equal( + await ctx.BASE.methods.balanceOf(ctx.Vault.options.address).call(), + "8856412162577279149" + ); + assert.equal( + await ctx.QUOTE.methods.balanceOf(ctx.Vault.options.address).call(), + "219999999999999999800" + ); + + assert.equal(await ctx.Vault.methods.balanceOf(trader).call(), "999999999999999990") + }) + + it("buy shares with unbalanced input (less quote)", async () => { + await ctx.Route.methods + .depositToDVM(ctx.DVM.options.address, lp, decimalStr("10"), decimalStr("0")) + .send(ctx.sendParam(lp)); + await ctx.Route.methods.sellQuoteOnDVM(ctx.DVM.options.address, trader, decimalStr("200"), decimalStr("1")).send(ctx.sendParam(trader)) + + var vaultBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(ctx.Vault.options.address).call()) + var vaultQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(ctx.Vault.options.address).call()) + var increaseRatio = new BigNumber("0.1") + await ctx.Route.methods.depositToDVM( + ctx.DVM.options.address, + trader, + vaultBaseBalance.multipliedBy(increaseRatio).toFixed(0), + vaultQuoteBalance.multipliedBy(increaseRatio).div(2).toFixed(0) + ).send(ctx.sendParam(trader)) + assert.equal(await ctx.Vault.methods.balanceOf(trader).call(), "499999999999999990") + }) + + it("buy shares with unbalanced input (less base)", async () => { + await ctx.Route.methods + .depositToDVM(ctx.DVM.options.address, lp, decimalStr("10"), decimalStr("0")) + .send(ctx.sendParam(lp)); + await ctx.Route.methods.sellQuoteOnDVM(ctx.DVM.options.address, trader, decimalStr("200"), decimalStr("1")).send(ctx.sendParam(trader)) + + var vaultBaseBalance = new BigNumber(await ctx.BASE.methods.balanceOf(ctx.Vault.options.address).call()) + var vaultQuoteBalance = new BigNumber(await ctx.QUOTE.methods.balanceOf(ctx.Vault.options.address).call()) + var increaseRatio = new BigNumber("0.1") + await ctx.Route.methods.depositToDVM( + ctx.DVM.options.address, + trader, + vaultBaseBalance.multipliedBy(increaseRatio).div(2).toFixed(0), + vaultQuoteBalance.multipliedBy(increaseRatio).toFixed(0) + ).send(ctx.sendParam(trader)) + assert.equal(await ctx.Vault.methods.balanceOf(trader).call(), "499999999999999990") + }) + }); +}); diff --git a/test/DVM/trader.test.ts b/test/DVM/trader.test.ts new file mode 100644 index 0000000..6580bc0 --- /dev/null +++ b/test/DVM/trader.test.ts @@ -0,0 +1,121 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +// import * as assert from 'assert'; + +import { decimalStr } from '../utils/Converter'; +import { logGas } from '../utils/Log'; +import { DVMContext, getDVMContext } from '../utils/DVMContext'; +import { assert } from 'chai'; + +let lp: string; +let trader: string; + +async function init(ctx: DVMContext): Promise { + lp = ctx.SpareAccounts[0]; + trader = ctx.SpareAccounts[1]; + await ctx.approveRoute(lp); + await ctx.approveRoute(trader); + + await ctx.mintTestToken(lp, decimalStr("10"), decimalStr("1000")); + await ctx.mintTestToken(trader, decimalStr("10"), decimalStr("1000")); + + await ctx.Route.methods + .depositToDVM(ctx.DVM.options.address, lp, decimalStr("10"), decimalStr("0")) + .send(ctx.sendParam(lp)); + + console.log(await ctx.Vault.methods.getVaultBalance().call()) + + console.log("deposit") +} + +describe("Trader", () => { + let snapshotId: string; + let ctx: DVMContext; + + before(async () => { + ctx = await getDVMContext(); + await init(ctx); + }); + + beforeEach(async () => { + snapshotId = await ctx.EVM.snapshot(); + }); + + afterEach(async () => { + await ctx.EVM.reset(snapshotId); + }); + + describe("trade", () => { + it("buy & sell", async () => { + + console.log("BASE0 before buy", await ctx.DVM.methods._BASE0_().call()) + + // buy + await logGas(ctx.Route.methods.sellQuoteOnDVM(ctx.DVM.options.address, trader, decimalStr("200"), decimalStr("1")), ctx.sendParam(trader), "buy base token") + console.log("BASE0 after buy", await ctx.DVM.methods._BASE0_().call()) + // trader balances + assert.equal( + await ctx.BASE.methods.balanceOf(trader).call(), + "11946763594380080787" + ); + assert.equal( + await ctx.QUOTE.methods.balanceOf(trader).call(), + decimalStr("800") + ); + // vault balances + assert.equal( + await ctx.BASE.methods.balanceOf(ctx.Vault.options.address).call(), + "8051283784161162863" + ); + assert.equal( + await ctx.QUOTE.methods.balanceOf(ctx.Vault.options.address).call(), + decimalStr("200") + ); + // maintainer balances + assert.equal( + await ctx.BASE.methods.balanceOf(ctx.Maintainer).call(), + "1952621458756350" + ); + assert.equal( + await ctx.QUOTE.methods.balanceOf(ctx.Maintainer).call(), + decimalStr("0") + ); + + // sell + await logGas(ctx.Route.methods.sellBaseOnDVM(ctx.DVM.options.address, trader, decimalStr("1"), decimalStr("100")), ctx.sendParam(trader), "sell base token") + console.log("BASE0 after sell", await ctx.DVM.methods._BASE0_().call()) + // trader balances + assert.equal( + await ctx.BASE.methods.balanceOf(trader).call(), + "10946763594380080787" + ); + assert.equal( + await ctx.QUOTE.methods.balanceOf(trader).call(), + "903421810640399874603" + ); + // vault balances + assert.equal( + await ctx.BASE.methods.balanceOf(ctx.Vault.options.address).call(), + "9051283784161162863" + ); + assert.equal( + await ctx.QUOTE.methods.balanceOf(ctx.Vault.options.address).call(), + "96474456349930717298" + ); + // maintainer balances + assert.equal( + await ctx.BASE.methods.balanceOf(ctx.Maintainer).call(), + "1952621458756350" + ); + assert.equal( + await ctx.QUOTE.methods.balanceOf(ctx.Maintainer).call(), + "103733009669408099" + ); + }); + }); +}); diff --git a/test/DVM/trader.ts b/test/DVM/trader.ts deleted file mode 100644 index bc6b435..0000000 --- a/test/DVM/trader.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - - Copyright 2020 DODO ZOO. - SPDX-License-Identifier: Apache-2.0 - -*/ - -// import * as assert from 'assert'; - -import { decimalStr } from '../utils/Converter'; -import { logGas } from '../utils/Log'; -import { DVMContext, getDVMContext } from '../utils/DVMContext'; -import { DVM_VAULT_NAME, getContractWithAddress } from '../utils/Contracts'; -import { Contract } from 'web3-eth-contract'; - -let lp: string; -let trader: string; -let vault: Contract - -async function init(ctx: DVMContext): Promise { - lp = ctx.SpareAccounts[0]; - trader = ctx.SpareAccounts[1]; - await ctx.approveRoute(lp); - await ctx.approveRoute(trader); - - console.log("approve") - - await ctx.mintTestToken(lp, decimalStr("10"), decimalStr("1000")); - await ctx.mintTestToken(trader, decimalStr("10"), decimalStr("1000")); - - console.log("mint") - - var vaultAddress = await ctx.DVM.methods._VAULT_().call(); - vault = getContractWithAddress(DVM_VAULT_NAME, vaultAddress) - - await ctx.Route.methods - .depositToDVM(ctx.DVM.options.address, lp, decimalStr("10"), decimalStr("0")) - .send(ctx.sendParam(lp)); - - console.log(await vault.methods.getVaultBalance().call()) - - console.log("deposit") -} - -describe("Trader", () => { - let snapshotId: string; - let ctx: DVMContext; - - before(async () => { - ctx = await getDVMContext(); - await init(ctx); - }); - - beforeEach(async () => { - snapshotId = await ctx.EVM.snapshot(); - }); - - afterEach(async () => { - await ctx.EVM.reset(snapshotId); - }); - - describe("trade", () => { - it("buy when R equals ONE", async () => { - await logGas(ctx.Route.methods.sellBaseOnDVM(ctx.DVM.options.address, trader, decimalStr("1"), decimalStr("90")), ctx.sendParam(trader), "buy base token when balanced") - // trader balances - console.log( - await ctx.BASE.methods.balanceOf(trader).call(), - decimalStr("11") - ); - console.log( - await ctx.QUOTE.methods.balanceOf(trader).call(), - "898581839502056240973" - ); - // maintainer balances - console.log( - await ctx.BASE.methods.balanceOf(ctx.Maintainer).call(), - decimalStr("0.001") - ); - console.log( - await ctx.QUOTE.methods.balanceOf(ctx.Maintainer).call(), - decimalStr("0") - ); - }); - }); -}); diff --git a/test/utils/DVMContext.ts b/test/utils/DVMContext.ts index fa6adb8..17d5d7d 100644 --- a/test/utils/DVMContext.ts +++ b/test/utils/DVMContext.ts @@ -105,6 +105,8 @@ export class DVMContext { var vendorMachines = await this.DVMFactory.methods.getVendorMachine(this.BASE.options.address, this.QUOTE.options.address).call() this.DVM = contracts.getContractWithAddress(contracts.DVM_CONTROLLER_NAME, vendorMachines[0]) + this.Vault = contracts.getContractWithAddress(contracts.DVM_VAULT_NAME, await this.DVM.methods._VAULT_().call()) + console.log(log.blueText("[Init DVM context]")); }