From 541e993abac8076a34bbe6e5c4175e87051eb3c2 Mon Sep 17 00:00:00 2001 From: Kate Sills Date: Wed, 18 Aug 2021 16:05:14 -0700 Subject: [PATCH] chore: add old tests --- .../test-calcDeltaY-calcDeltaX.js | 43 +++ .../propertyBased/test-calcDeltaY-property.js | 39 +++ .../propertyBased/test-largeValues.js | 28 ++ .../propertyBased/test-reduction.js | 43 +++ .../propertyBased/test-smallValues.js | 26 ++ .../contracts/constantProduct/runTest.js | 38 +++ .../contracts/constantProduct/setupMints.js | 19 ++ .../constantProduct/test-calcDeltaY.js | 69 +++++ .../test-compareBondingCurves.js | 286 +++++++++++++++++ .../test-compareNewSwapPrice.js | 292 ++++++++++++++++++ .../constantProduct/test-edgeCases.js | 25 ++ .../contracts/constantProduct/test-getXY.js | 50 +++ .../constantProduct/test-newBondingCurve.js | 177 +++++++++++ 13 files changed, 1135 insertions(+) create mode 100644 packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-calcDeltaY-calcDeltaX.js create mode 100644 packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-calcDeltaY-property.js create mode 100644 packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-largeValues.js create mode 100644 packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-reduction.js create mode 100644 packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-smallValues.js create mode 100644 packages/zoe/test/unitTests/contracts/constantProduct/runTest.js create mode 100644 packages/zoe/test/unitTests/contracts/constantProduct/setupMints.js create mode 100644 packages/zoe/test/unitTests/contracts/constantProduct/test-calcDeltaY.js create mode 100644 packages/zoe/test/unitTests/contracts/constantProduct/test-compareBondingCurves.js create mode 100644 packages/zoe/test/unitTests/contracts/constantProduct/test-compareNewSwapPrice.js create mode 100644 packages/zoe/test/unitTests/contracts/constantProduct/test-edgeCases.js create mode 100644 packages/zoe/test/unitTests/contracts/constantProduct/test-getXY.js create mode 100644 packages/zoe/test/unitTests/contracts/constantProduct/test-newBondingCurve.js diff --git a/packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-calcDeltaY-calcDeltaX.js b/packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-calcDeltaY-calcDeltaX.js new file mode 100644 index 00000000000..66d37b059fa --- /dev/null +++ b/packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-calcDeltaY-calcDeltaX.js @@ -0,0 +1,43 @@ +// @ts-check + +// eslint-disable-next-line import/no-extraneous-dependencies +import { test } from '@agoric/zoe/tools/prepare-test-env-ava'; + +// eslint-disable-next-line import/no-extraneous-dependencies +import jsc from 'jsverify'; +import { AmountMath } from '@agoric/ertp'; + +import { + calcDeltaYSellingX, + calcDeltaXSellingX, +} from '../../../../../src/contracts/constantProduct/core'; +import { setupMintKits } from '../setupMints'; + +const doTest = (x, y, deltaX) => { + const { run, bld } = setupMintKits(); + const runX = run(x); + const bldY = bld(y); + const runDeltaX = run(deltaX); + const deltaY = calcDeltaYSellingX(runX, bldY, runDeltaX); + const newDeltaX = calcDeltaXSellingX(runX, bldY, deltaY); + + // Pass through again, should always get the same answer. + const newDeltaY = calcDeltaYSellingX(runX, bldY, newDeltaX); + + return AmountMath.isEqual(deltaY, newDeltaY); +}; + +test('jsverify constant product calcDeltaYSellingX', t => { + const runPoolAllocationArbitrary = jsc.suchthat(jsc.nat(), u => u > 1); + const secondaryPoolAllocationArbitrary = jsc.suchthat(jsc.nat(), u => u > 1); + const runValueInArbitrary = jsc.suchthat(jsc.nat(), u => u > 1); + + const zeroOut = jsc.forall( + runPoolAllocationArbitrary, + secondaryPoolAllocationArbitrary, + runValueInArbitrary, + doTest, + ); + + t.true(jsc.check(zeroOut)); +}); diff --git a/packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-calcDeltaY-property.js b/packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-calcDeltaY-property.js new file mode 100644 index 00000000000..83178865c8e --- /dev/null +++ b/packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-calcDeltaY-property.js @@ -0,0 +1,39 @@ +// @ts-check + +// eslint-disable-next-line import/no-extraneous-dependencies +import { test } from '@agoric/zoe/tools/prepare-test-env-ava'; + +// eslint-disable-next-line import/no-extraneous-dependencies +import jsc from 'jsverify'; +import { AmountMath } from '@agoric/ertp'; + +import { calcDeltaYSellingX } from '../../../../../src/contracts/constantProduct/core'; +import { setupMintKits } from '../setupMints'; + +const doTest = (x, y, deltaX) => { + const { run, bld } = setupMintKits(); + const runX = run(x); + const bldY = bld(y); + const runDeltaX = run(deltaX); + const deltaY = calcDeltaYSellingX(runX, bldY, runDeltaX); + const oldK = BigInt(runX.value) * BigInt(bldY.value); + const newX = AmountMath.add(runX, runDeltaX); + const newY = AmountMath.subtract(bldY, deltaY); + const newK = BigInt(newX.value) * BigInt(newY.value); + return newK >= oldK; +}; + +test('jsverify constant product calcDeltaYSellingX', t => { + const runPoolAllocationArbitrary = jsc.suchthat(jsc.nat(), u => u > 1); + const secondaryPoolAllocationArbitrary = jsc.suchthat(jsc.nat(), u => u > 1); + const runValueInArbitrary = jsc.suchthat(jsc.nat(), u => u > 1); + + const zeroOut = jsc.forall( + runPoolAllocationArbitrary, + secondaryPoolAllocationArbitrary, + runValueInArbitrary, + doTest, + ); + + t.true(jsc.check(zeroOut)); +}); diff --git a/packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-largeValues.js b/packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-largeValues.js new file mode 100644 index 00000000000..4899dde63f8 --- /dev/null +++ b/packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-largeValues.js @@ -0,0 +1,28 @@ +// @ts-check + +// eslint-disable-next-line import/no-extraneous-dependencies +import { test } from '@agoric/zoe/tools/prepare-test-env-ava'; + +import jsc from 'jsverify'; + +import { runTest } from '../runTest'; + +// larger values than this seem to take a really long time and the +// test hangs +test('jsverify constant product large values', t => { + const runPoolAllocationArbitrary = jsc.suchthat(jsc.nat(), u => u > 30468); + const secondaryPoolAllocationArbitrary = jsc.suchthat( + jsc.nat(), + u => u > 30468, + ); + const runValueInArbitrary = jsc.suchthat(jsc.nat(), u => u < 30468 && u > 0); + + const constantProduct = jsc.forall( + runPoolAllocationArbitrary, + secondaryPoolAllocationArbitrary, + runValueInArbitrary, + runTest, + ); + + t.true(jsc.check(constantProduct)); +}); diff --git a/packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-reduction.js b/packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-reduction.js new file mode 100644 index 00000000000..21cb0ee8078 --- /dev/null +++ b/packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-reduction.js @@ -0,0 +1,43 @@ +// @ts-check + +// eslint-disable-next-line import/no-extraneous-dependencies +import { test } from '@agoric/zoe/tools/prepare-test-env-ava'; + +// eslint-disable-next-line import/no-extraneous-dependencies +import jsc from 'jsverify'; +import { AmountMath } from '@agoric/ertp'; + +import { + calcDeltaYSellingX, + calcDeltaXSellingX, +} from '../../../../../src/contracts/constantProduct/core'; +import { setupMintKits } from '../setupMints'; + +// Not currently functional +const doTest = (x, y, deltaX) => { + const { run, bld } = setupMintKits(); + const runX = run(x); + const bldY = bld(y); + const runDeltaX = run(deltaX); + const deltaY = calcDeltaYSellingX(runX, bldY, runDeltaX); + const newDeltaX = calcDeltaXSellingX(runX, bldY, deltaY); + + const reduction = AmountMath.subtract(runDeltaX, newDeltaX); + + return AmountMath.isGTE(run(23), reduction); +}; + +test('jsverify constant product calcDeltaYSellingX', t => { + const runPoolAllocationArbitrary = jsc.suchthat(jsc.nat(), u => u > 1); + const secondaryPoolAllocationArbitrary = jsc.suchthat(jsc.nat(), u => u > 1); + const runValueInArbitrary = jsc.suchthat(jsc.nat(), u => u > 1); + + const zeroOut = jsc.forall( + runPoolAllocationArbitrary, + secondaryPoolAllocationArbitrary, + runValueInArbitrary, + doTest, + ); + + t.true(jsc.check(zeroOut)); +}); diff --git a/packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-smallValues.js b/packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-smallValues.js new file mode 100644 index 00000000000..282a1730084 --- /dev/null +++ b/packages/zoe/test/unitTests/contracts/constantProduct/propertyBased/test-smallValues.js @@ -0,0 +1,26 @@ +// @ts-check + +// eslint-disable-next-line import/no-extraneous-dependencies +import { test } from '@agoric/zoe/tools/prepare-test-env-ava'; + +import jsc from 'jsverify'; + +import { runTest } from '../runTest'; + +test('jsverify constant product small values', t => { + const runPoolAllocationArbitrary = jsc.suchthat(jsc.nat(), u => u > 10000); + const secondaryPoolAllocationArbitrary = jsc.suchthat( + jsc.nat(), + u => u > 10000, + ); + const runValueInArbitrary = jsc.suchthat(jsc.nat(), u => u < 10000 && u > 0); + + const constantProduct = jsc.forall( + runPoolAllocationArbitrary, + secondaryPoolAllocationArbitrary, + runValueInArbitrary, + runTest, + ); + + t.true(jsc.check(constantProduct)); +}); diff --git a/packages/zoe/test/unitTests/contracts/constantProduct/runTest.js b/packages/zoe/test/unitTests/contracts/constantProduct/runTest.js new file mode 100644 index 00000000000..2ee879b1ed4 --- /dev/null +++ b/packages/zoe/test/unitTests/contracts/constantProduct/runTest.js @@ -0,0 +1,38 @@ +// @ts-check + +import { Nat } from '@agoric/nat'; + +import { + DEFAULT_POOL_FEE, + DEFAULT_PROTOCOL_FEE, +} from '../../../../src/contracts/constantProduct/defaults'; +import { specifyRunIn } from '../../../../src/contracts/constantProduct/specifyRunIn'; +import { checkKInvariantSellingX } from '../../../../src/contracts/constantProduct/invariants'; +import { setupMintKits } from './setupMints'; + +export const runTest = ( + runPoolAllocationNat, + secondaryPoolAllocationNat, + runValueInNat, +) => { + const { bld, run } = setupMintKits(); + const runAmountIn = run(Nat(runValueInNat)); + const runPoolAllocation = run(Nat(runPoolAllocationNat)); + const bldPoolAllocation = bld(Nat(secondaryPoolAllocationNat)); + + const result = specifyRunIn( + runAmountIn, + runPoolAllocation, + bldPoolAllocation, + DEFAULT_PROTOCOL_FEE, + DEFAULT_POOL_FEE, + ); + // console.log(result); + + return checkKInvariantSellingX( + runPoolAllocation, + bldPoolAllocation, + result.deltaRun, + result.deltaSecondary, + ); +}; diff --git a/packages/zoe/test/unitTests/contracts/constantProduct/setupMints.js b/packages/zoe/test/unitTests/contracts/constantProduct/setupMints.js new file mode 100644 index 00000000000..5b0d5ee5246 --- /dev/null +++ b/packages/zoe/test/unitTests/contracts/constantProduct/setupMints.js @@ -0,0 +1,19 @@ +// @ts-check + +import { AmountMath, makeIssuerKit, AssetKind } from '@agoric/ertp'; + +export const setupMintKits = () => { + const runKit = makeIssuerKit( + 'RUN', + AssetKind.NAT, + harden({ decimalPlaces: 6 }), + ); + const bldKit = makeIssuerKit( + 'BLD', + AssetKind.NAT, + harden({ decimalPlaces: 6 }), + ); + const run = value => AmountMath.make(runKit.brand, value); + const bld = value => AmountMath.make(bldKit.brand, value); + return { runKit, bldKit, run, bld }; +}; diff --git a/packages/zoe/test/unitTests/contracts/constantProduct/test-calcDeltaY.js b/packages/zoe/test/unitTests/contracts/constantProduct/test-calcDeltaY.js new file mode 100644 index 00000000000..6a4c8157f34 --- /dev/null +++ b/packages/zoe/test/unitTests/contracts/constantProduct/test-calcDeltaY.js @@ -0,0 +1,69 @@ +// @ts-check + +// eslint-disable-next-line import/no-extraneous-dependencies +import { test } from '@agoric/zoe/tools/prepare-test-env-ava'; +import { AmountMath } from '@agoric/ertp'; + +import { calcDeltaYSellingX } from '../../../../src/contracts/constantProduct/core'; +import { setupMintKits } from './setupMints'; + +// the brands of x and y shouldn't matter (test this explicitly in a +// separate test) +const doTest = (t, x, y, deltaX, expectedDeltaY) => { + const { run, bld } = setupMintKits(); + const result = calcDeltaYSellingX(run(x), bld(y), run(deltaX)); + t.true( + AmountMath.isEqual(result, bld(expectedDeltaY)), + `${result.value} equals ${expectedDeltaY}`, + ); +}; + +// deltaXPlusX is 0 +test('0, 0, 0, 0', t => { + t.throws(() => doTest(t, 0, 0, 0, 0), { + message: 'No infinite ratios! Denominator was 0/"[Alleged: RUN brand]"', + }); +}); + +test('0, 0, 1, 0', t => { + doTest(t, 0, 0, 1, 0); +}); + +test('1, 0, 0, 0', t => { + doTest(t, 1, 0, 0, 0); +}); + +// deltaXPlusX is 0 +test('0, 1, 0, 0', t => { + t.throws(() => doTest(t, 0, 1, 0, 0), { + message: 'No infinite ratios! Denominator was 0/"[Alleged: RUN brand]"', + }); +}); + +test('1, 1, 0, 0', t => { + doTest(t, 1, 1, 0, 0); +}); + +test('1, 1, 1, 0', t => { + doTest(t, 1, 1, 1, 0); +}); + +test('1, 2, 1, 1', t => { + doTest(t, 1, 2, 1, 1); +}); + +test('2, 3, 4, 2', t => { + doTest(t, 2, 3, 4, 2); +}); + +test('928861206, 130870247, 746353662, 58306244', t => { + doTest(t, 928861206n, 130870247n, 746353662n, 58306244n); +}); + +test('9, 3, 17, 1', t => { + doTest(t, 9, 3, 17, 1); +}); + +test('10000, 5000, 209, 102', t => { + doTest(t, 10000, 5000, 209, 102); +}); diff --git a/packages/zoe/test/unitTests/contracts/constantProduct/test-compareBondingCurves.js b/packages/zoe/test/unitTests/contracts/constantProduct/test-compareBondingCurves.js new file mode 100644 index 00000000000..190fa120039 --- /dev/null +++ b/packages/zoe/test/unitTests/contracts/constantProduct/test-compareBondingCurves.js @@ -0,0 +1,286 @@ +// @ts-check +// eslint-disable-next-line import/no-extraneous-dependencies +import { test } from '@agoric/zoe/tools/prepare-test-env-ava'; +import { BASIS_POINTS } from '../../../../src/contracts/constantProduct/defaults'; + +import { swapIn } from '../../../../src/contracts/constantProduct/swapIn'; +import { swapOut } from '../../../../src/contracts/constantProduct/swapOut'; +import { setupMintKits } from './setupMints'; +import { makeRatio } from '../../../../src/contractSupport'; + +// This assumes run is swapped in. The test should function the same +// regardless of what brand is the amountIn, because no run fee is +// charged. +const prepareSwapInTest = ({ inputReserve, outputReserve, inputValue }) => { + const { run, bld, runKit, bldKit } = setupMintKits(); + const amountGiven = run(inputValue); + const poolAllocation = harden({ + Central: run(inputReserve), + Secondary: bld(outputReserve), + }); + const amountWanted = bld(3n); + const protocolFeeRatio = makeRatio(0n, runKit.brand, BASIS_POINTS); + const poolFeeRatio = makeRatio(3n, bldKit.brand, BASIS_POINTS); + + const args = [ + amountGiven, + poolAllocation, + amountWanted, + protocolFeeRatio, + poolFeeRatio, + ]; + return harden({ + args, + run, + bld, + }); +}; + +const testGetPrice = (t, inputs, expectedOutput) => { + const { args, bld } = prepareSwapInTest(inputs); + const result = swapIn(...args); + t.deepEqual(result.swapperGets, bld(expectedOutput)); +}; + +const getInputPriceThrows = (t, inputs, message) => { + t.throws( + _ => { + const { args } = prepareSwapInTest(inputs); + return swapIn(...args); + }, + { + message, + }, + ); +}; + +// This assumes run is swapped in. The test should function the same +// regardless of what brand is the amountIn, because no run fee is +// charged. +const prepareSwapOutTest = ({ inputReserve, outputReserve, outputValue }) => { + const { run, bld, runKit } = setupMintKits(); + const amountGiven = run(10000n); // hard-coded + const poolAllocation = harden({ + Central: run(inputReserve), + Secondary: bld(outputReserve), + }); + const amountWanted = bld(outputValue); + const protocolFeeRatio = makeRatio(0n, runKit.brand, BASIS_POINTS); + const poolFeeRatio = makeRatio(30n, runKit.brand, BASIS_POINTS); + + const args = [ + amountGiven, + poolAllocation, + amountWanted, + protocolFeeRatio, + poolFeeRatio, + ]; + return harden({ + args, + run, + bld, + }); +}; + +const testGetOutputPrice = (t, inputs, expectedInput) => { + const { args, run } = prepareSwapOutTest(inputs); + const result = swapOut(...args); + t.deepEqual(result.swapperGives, run(expectedInput)); +}; + +const getOutputPriceThrows = (t, inputs, message) => { + const { args } = prepareSwapOutTest(inputs); + t.throws(_ => swapOut(...args), { + message, + }); +}; + +// If these tests of `getInputPrice` fail, it would indicate that we have +// diverged from the calculation in the Uniswap paper. +test('getInputPrice no reserves', t => { + const input = { + inputReserve: 0n, + outputReserve: 0n, + inputValue: 1n, + }; + const message = + '"poolAllocation.Central" was not greater than 0: {"brand":"[Alleged: RUN brand]","value":"[0n]"}'; + getInputPriceThrows(t, input, message); +}); + +test('getInputPrice ok 2', t => { + const input = { + inputReserve: 5984n, + outputReserve: 3028n, + inputValue: 1398n, + }; + const expectedOutput = 572n; + testGetPrice(t, input, expectedOutput); +}); + +test('getInputPrice ok 3', t => { + const input = { + inputReserve: 8160n, + outputReserve: 7743n, + inputValue: 6635n, + }; + const expectedOutput = 3466n; + testGetPrice(t, input, expectedOutput); +}); + +test('getInputPrice ok 4', t => { + const input = { + inputReserve: 10n, + outputReserve: 10n, + inputValue: 1000n, + }; + const expectedOutput = 9n; + testGetPrice(t, input, expectedOutput); +}); + +test('getInputPrice ok 5', t => { + const input = { + inputReserve: 100n, + outputReserve: 50n, + inputValue: 17n, + }; + const expectedOutput = 7n; + testGetPrice(t, input, expectedOutput); +}); + +test('getInputPrice ok 6', t => { + const input = { + outputReserve: 117n, + inputReserve: 43n, + inputValue: 7n, + }; + const expectedOutput = 16n; + testGetPrice(t, input, expectedOutput); +}); + +test('getInputPrice negative', t => { + const input = { + outputReserve: 117n, + inputReserve: 43n, + inputValue: -7n, + }; + const message = 'value "[-7n]" must be a Nat or an array'; + getInputPriceThrows(t, input, message); +}); + +test('getInputPrice bad reserve 1', t => { + const input = { + outputReserve: 0n, + inputReserve: 43n, + inputValue: 347n, + }; + const message = + '"poolAllocation.Secondary" was not greater than 0: {"brand":"[Alleged: BLD brand]","value":"[0n]"}'; + getInputPriceThrows(t, input, message); +}); + +test('getInputPrice bad reserve 2', t => { + const input = { + outputReserve: 50n, + inputReserve: 0n, + inputValue: 828n, + }; + const message = + '"poolAllocation.Central" was not greater than 0: {"brand":"[Alleged: RUN brand]","value":"[0n]"}'; + getInputPriceThrows(t, input, message); +}); + +test('getInputPrice zero input', t => { + const input = { + outputReserve: 50n, + inputReserve: 320n, + inputValue: 0n, + }; + const message = + '"allocation.In" was not greater than 0: {"brand":"[Alleged: RUN brand]","value":"[0n]"}'; + getInputPriceThrows(t, input, message); +}); + +test('getInputPrice big product', t => { + const input = { + outputReserve: 100000000n, + inputReserve: 100000000n, + inputValue: 1000n, + }; + const expectedOutput = 996n; + testGetPrice(t, input, expectedOutput); +}); + +test('getOutputPrice ok', t => { + const input = { + outputReserve: 117n, + inputReserve: 43n, + outputValue: 37n, + }; + const expectedOutput = 20n; + testGetOutputPrice(t, input, expectedOutput); +}); + +test('getOutputPrice zero output reserve', t => { + const input = { + outputReserve: 0n, + inputReserve: 43n, + outputValue: 37n, + }; + const message = + '"poolAllocation.Secondary" was not greater than 0: {"brand":"[Alleged: BLD brand]","value":"[0n]"}'; + getOutputPriceThrows(t, input, message); +}); + +test('getOutputPrice zero input reserve', t => { + const input = { + outputReserve: 92n, + inputReserve: 0n, + outputValue: 37n, + }; + const message = + '"poolAllocation.Central" was not greater than 0: {"brand":"[Alleged: RUN brand]","value":"[0n]"}'; + getOutputPriceThrows(t, input, message); +}); + +test('getOutputPrice too much output', t => { + const input = { + outputReserve: 1024n, + inputReserve: 1132n, + outputValue: 20923n, + }; + const message = + 'The poolAllocation {"brand":"[Alleged: BLD brand]","value":"[1024n]"} did not have enough to satisfy the wanted amountOut {"brand":"[Alleged: BLD brand]","value":"[20923n]"}'; + getOutputPriceThrows(t, input, message); +}); + +test('getOutputPrice too much output 2', t => { + const input = { + outputReserve: 345n, + inputReserve: 1132n, + outputValue: 345n, + }; + const message = + 'The poolAllocation {"brand":"[Alleged: BLD brand]","value":"[345n]"} did not have enough to satisfy the wanted amountOut {"brand":"[Alleged: BLD brand]","value":"[345n]"}'; + getOutputPriceThrows(t, input, message); +}); + +test('getOutputPrice big product', t => { + const input = { + outputReserve: 100000000n, + inputReserve: 100000000n, + outputValue: 1000n, + }; + const expectedOutput = 1004n; + testGetOutputPrice(t, input, expectedOutput); +}); + +test('getOutputPrice minimum price', t => { + const input = { + outputReserve: 10n, + inputReserve: 1n, + outputValue: 1n, + }; + const expectedOutput = 1n; + testGetOutputPrice(t, input, expectedOutput); +}); diff --git a/packages/zoe/test/unitTests/contracts/constantProduct/test-compareNewSwapPrice.js b/packages/zoe/test/unitTests/contracts/constantProduct/test-compareNewSwapPrice.js new file mode 100644 index 00000000000..839eef1bde5 --- /dev/null +++ b/packages/zoe/test/unitTests/contracts/constantProduct/test-compareNewSwapPrice.js @@ -0,0 +1,292 @@ +// @ts-check + +// eslint-disable-next-line import/no-extraneous-dependencies +import { test } from '@agoric/zoe/tools/prepare-test-env-ava'; +import { AmountMath, makeIssuerKit } from '@agoric/ertp'; + +import { swapIn } from '../../../../src/contracts/constantProduct/swapIn'; +import { + calcDeltaXSellingX, + calcDeltaYSellingX, + swapInNoFees, +} from '../../../../src/contracts/constantProduct/core'; +import { makeRatio } from '../../../../src/contractSupport'; + +const BASIS_POINTS = 10000n; +const POOL_FEE = 24n; +const PROTOCOL_FEE = 6n; + +// Moola is central + +const setupMints = () => { + const moolaKit = makeIssuerKit('moola'); + const bucksKit = makeIssuerKit('bucks'); + const simoleanKit = makeIssuerKit('simolean'); + + const moola = value => AmountMath.make(moolaKit.brand, value); + const bucks = value => AmountMath.make(bucksKit.brand, value); + const simoleans = value => AmountMath.make(simoleanKit.brand, value); + + return { + moolaKit, + bucksKit, + simoleanKit, + moola, + bucks, + simoleans, + }; +}; + +function protocolFee(input) { + return floorDivide(multiply(input, 6n), BASIS_POINTS); +} + +const doTest = () => {}; + +test('newSwap getPriceGivenAvailableInput specify central', async t => { + const { moola, bucks, moolaKit, bucksKit } = setupMints(); + const poolAllocation = { + Central: moola(800000n), + Secondary: bucks(300000n), + }; + const amountGiven = moola(10000n); + const amountWanted = bucks(1n); + + const protocolFeeRatio = makeRatio( + PROTOCOL_FEE, + moolaKit.brand, + BASIS_POINTS, + ); + const poolFeeRatio = makeRatio(POOL_FEE, bucksKit.brand, BASIS_POINTS); + + // This is reduced, if any reduction occurs. + const noFeesResult = swapInNoFees({ amountGiven, poolAllocation }); + t.deepEqual(noFeesResult.amountIn, moola(9999n)); + t.deepEqual(noFeesResult.amountOut, bucks(3703n)); + + const noReductionResult = calcDeltaYSellingX( + poolAllocation.Central, + poolAllocation.Secondary, + amountGiven, + ); + t.deepEqual(noReductionResult, bucks(3703n)); + + const reduced = calcDeltaXSellingX( + poolAllocation.Central, + poolAllocation.Secondary, + noReductionResult, + ); + t.deepEqual(reduced, moola(9999n)); + + const result = swapIn( + amountGiven, + poolAllocation, + amountWanted, + protocolFeeRatio, + poolFeeRatio, + ); + // swapperGives is 9999n + // t.deepEqual(result.swapperGives, moola(9997n)); + // Same + t.deepEqual(result.swapperGets, bucks(3692n)); + // Protocol fee is 6n + // t.deepEqual(result.protocolFee, moola(5n)); +}); + +test('newSwap getPriceGivenAvailableInput secondary', async t => { + const { moola, bucks, moolaKit, bucksKit } = setupMints(); + const poolAllocation = { + Central: moola(800000n), + Secondary: bucks(500000n), + }; + const amountGiven = bucks(10000n); + const amountWanted = moola(1n); + + const protocolFeeRatio = makeRatio( + PROTOCOL_FEE, + moolaKit.brand, + BASIS_POINTS, + ); + const poolFeeRatio = makeRatio(POOL_FEE, bucksKit.brand, BASIS_POINTS); + + const result = swapIn( + amountGiven, + poolAllocation, + amountWanted, + protocolFeeRatio, + poolFeeRatio, + ); + + const newSwapResult = { + amountIn: bucks(10000n), + amountOut: moola(15640n), + protocolFee: moola(9n), + }; + + // same + t.deepEqual(result.swapperGives, newSwapResult.amountIn); + // SwapperGets one less: 15639n + // t.deepEqual(result.swapperGets, newSwapResult.amountOut); + // Swapper pays one more: 10n + // t.deepEqual(result.protocolFee, newSwapResult.protocolFee); +}); + +test('newSwap getPriceGivenRequiredOutput specify central', async t => { + const initMoola = 700000n; + const initBucks = 500000n; + const { bucks, moola, bucksBrand, pricer } = setupPricer( + initMoola, + initBucks, + ); + + const output = 10000n; + const pFeePre = protocolFee(output); + const poolChange = output + pFeePre; + const valueIn = priceFromTargetOutput(poolChange, initMoola, initBucks, 24n); + const valueOut = outputFromInputPrice(initBucks, initMoola, valueIn, 24n); + const pFee = protocolFee(valueOut); + t.deepEqual(pricer.getPriceGivenRequiredOutput(bucksBrand, moola(output)), { + amountIn: bucks(valueIn), + amountOut: moola(valueOut - pFee), + protocolFee: moola(pFee), + }); + t.truthy( + (initMoola - valueOut) * (initBucks + valueIn + pFee) > + initBucks * initMoola, + ); +}); + +test('newSwap getPriceGivenRequiredOutput specify secondary', async t => { + const initMoola = 700000n; + const initBucks = 500000n; + const { bucks, moola, moolaBrand, pricer } = setupPricer( + initMoola, + initBucks, + ); + + const output = 10000n; + const valueIn = priceFromTargetOutput(output, initBucks, initMoola, 24n); + const valueOut = outputFromInputPrice(initMoola, initBucks, valueIn, 24n); + const pFee = protocolFee(valueIn); + t.deepEqual(pricer.getPriceGivenRequiredOutput(moolaBrand, bucks(output)), { + amountIn: moola(valueIn + pFee), + amountOut: bucks(valueOut), + protocolFee: moola(pFee), + }); + t.truthy( + (initMoola - valueOut) * (initBucks + valueIn + pFee) > + initBucks * initMoola, + ); +}); + +test('newSwap getPriceGivenAvailableInput twoPools', async t => { + const initMoola = 800000n; + const initBucks = 500000n; + const initSimoleans = 300000n; + const { bucks, moola, simoleans, simoleansBrand, pricer } = setupPricer( + initMoola, + initBucks, + initSimoleans, + ); + + // get price given input from simoleans to bucks through moola, presuming + // there will be no price improvement + const input = 10000n; + const moolaOut = outputFromInputPrice(initBucks, initMoola, input, 12n); + const feeOut = floorDivide(multiply(moolaOut, 6), BASIS_POINTS); + const simOut = outputFromInputPrice( + initMoola, + initSimoleans, + moolaOut - feeOut, + 12n, + ); + t.deepEqual( + pricer.getPriceGivenAvailableInput(bucks(input), simoleansBrand), + { + amountIn: bucks(input), + amountOut: simoleans(simOut), + protocolFee: moola(feeOut), + centralAmount: moola(moolaOut), + }, + ); +}); + +test('newSwap getPriceGivenRequiredOutput twoPools', async t => { + const initMoola = 800000n; + const initBucks = 500000n; + const initSimoleans = 300000n; + const { bucks, moola, simoleans, simoleansBrand, pricer } = setupPricer( + initMoola, + initBucks, + initSimoleans, + ); + + // get price given desired output from simoleans to bucks through moola, + // choosing 10001 so there will be no price improvement + const output = 10001n; + const moolaIn = priceFromTargetOutput(output, initBucks, initMoola, 12n); + const fee = floorDivide(multiply(moolaIn, 6), BASIS_POINTS); + const simIn = priceFromTargetOutput( + moolaIn + fee, + initMoola, + initSimoleans, + 12n, + ); + t.deepEqual( + pricer.getPriceGivenRequiredOutput(simoleansBrand, bucks(output)), + { + amountIn: simoleans(simIn), + amountOut: bucks(output), + protocolFee: moola(fee), + centralAmount: moola(moolaIn), + }, + ); +}); + +test('newSwap getPriceGivenOutput central extreme', async t => { + const initMoola = 700000n; + const initBucks = 500000n; + const { bucks, moola, bucksBrand, pricer } = setupPricer( + initMoola, + initBucks, + ); + + const output = 690000n; + const pFeePre = protocolFee(output); + const poolChange = output + pFeePre; + const valueIn = priceFromTargetOutput(poolChange, initMoola, initBucks, 24n); + const valueOut = outputFromInputPrice(initBucks, initMoola, valueIn, 24n); + const pFee = protocolFee(valueOut); + t.deepEqual(pricer.getPriceGivenRequiredOutput(bucksBrand, moola(output)), { + amountIn: bucks(valueIn), + amountOut: moola(valueOut - pFee), + protocolFee: moola(pFee), + }); + + t.truthy( + (initMoola - valueOut) * (initBucks + valueIn + pFee) > + initBucks * initMoola, + ); +}); + +test('newSwap getPriceGivenInput secondary extreme', async t => { + const moolaPool = 800000n; + const bucksPool = 500000n; + const { bucks, moola, moolaBrand, pricer } = setupPricer( + moolaPool, + bucksPool, + ); + + const input = 690000n; + const valueOut = outputFromInputPrice(bucksPool, moolaPool, input, 24n); + const pFee = protocolFee(valueOut); + const valueIn = priceFromTargetOutput(valueOut, moolaPool, bucksPool, 24n); + t.deepEqual(pricer.getPriceGivenAvailableInput(bucks(input), moolaBrand), { + amountIn: bucks(valueIn), + amountOut: moola(valueOut - pFee), + protocolFee: moola(pFee), + }); + t.truthy( + (moolaPool - valueOut) * (bucksPool + valueIn) > bucksPool * moolaPool, + ); +}); diff --git a/packages/zoe/test/unitTests/contracts/constantProduct/test-edgeCases.js b/packages/zoe/test/unitTests/contracts/constantProduct/test-edgeCases.js new file mode 100644 index 00000000000..d6fb4e61262 --- /dev/null +++ b/packages/zoe/test/unitTests/contracts/constantProduct/test-edgeCases.js @@ -0,0 +1,25 @@ +// @ts-check + +// eslint-disable-next-line import/no-extraneous-dependencies +import { test } from '@agoric/zoe/tools/prepare-test-env-ava'; + +import { runTest } from './runTest'; + +// Parameters are: +// runPoolAllocationNat, +// secondaryPoolAllocationNat, +// runValueInNat, + +test('specifyRunIn 101, 101, 0', t => { + t.throws(() => runTest(101, 101, 0), { + message: 'runAmountIn cannot be empty', + }); +}); + +test('specifyRunIn 101, 101, 2', t => { + t.true(runTest(101, 101, 2)); +}); + +test('specifyRunIn 101, 101, 3', t => { + t.true(runTest(101, 101, 3)); +}); diff --git a/packages/zoe/test/unitTests/contracts/constantProduct/test-getXY.js b/packages/zoe/test/unitTests/contracts/constantProduct/test-getXY.js new file mode 100644 index 00000000000..a154628b861 --- /dev/null +++ b/packages/zoe/test/unitTests/contracts/constantProduct/test-getXY.js @@ -0,0 +1,50 @@ +// @ts-check + +// eslint-disable-next-line import/no-extraneous-dependencies +import { test } from '@agoric/zoe/tools/prepare-test-env-ava'; + +import { getXY } from '../../../../src/contracts/constantProduct/getXY'; +import { setupMintKits } from './setupMints'; + +// There's no difference between SwapIn and SwapOut for this function +test('swap Central for Secondary', t => { + const { run, bld } = setupMintKits(); + + const amountGiven = run(2000n); + const poolAllocation = { + Central: run(102902920n), + Secondary: bld(203838393n), + }; + const amountWanted = bld(2819n); + const { x, y, deltaX, wantedDeltaY } = getXY( + amountGiven, + poolAllocation, + amountWanted, + ); + + t.deepEqual(x, poolAllocation.Central); + t.deepEqual(y, poolAllocation.Secondary); + t.deepEqual(deltaX, amountGiven); + t.deepEqual(wantedDeltaY, amountWanted); +}); + +test('swap Secondary for Central', t => { + const { run, bld } = setupMintKits(); + + const amountGiven = bld(2000n); + const poolAllocation = { + Central: run(102902920n), + Secondary: bld(203838393n), + }; + const amountWanted = run(2819n); + const { x, y, deltaX, wantedDeltaY } = getXY( + amountGiven, + poolAllocation, + amountWanted, + ); + + t.deepEqual(x, poolAllocation.Secondary); + t.deepEqual(y, poolAllocation.Central); + t.deepEqual(deltaX, amountGiven); + t.deepEqual(wantedDeltaY, amountWanted); +}); diff --git a/packages/zoe/test/unitTests/contracts/constantProduct/test-newBondingCurve.js b/packages/zoe/test/unitTests/contracts/constantProduct/test-newBondingCurve.js new file mode 100644 index 00000000000..784a2ebf70a --- /dev/null +++ b/packages/zoe/test/unitTests/contracts/constantProduct/test-newBondingCurve.js @@ -0,0 +1,177 @@ +// @ts-check + +// eslint-disable-next-line import/no-extraneous-dependencies +import { test } from '@agoric/zoe/tools/prepare-test-env-ava'; + +import { amountMath } from '@agoric/ertp'; + +import { assertAmountsEqual } from '../../../zoeTestHelpers'; +import { getCurrentPrice } from '../fixedNewSwap'; + +import { + DEFAULT_POOL_FEE, + DEFAULT_PROTOCOL_FEE, +} from '../../../../src/contracts/constantProduct/defaults'; +import { specifyRunIn } from '../../../../src/contracts/constantProduct/specifyRunIn'; +import { + assertKInvariantSellingX, + assertPoolFee, + assertProtocolFee, +} from '../../../../src/contracts/constantProduct/invariants'; +import { calcDeltaYSellingX } from '../../../../src/contracts/constantProduct/core'; +import { setupMintKits } from './setupMints'; + +const conductTestSpecifyRunIn = ( + mintKits, + runPoolAllocationValue, + bldPoolAllocationValue, + runValueIn, + expected, + t, + protocolFeeBP = DEFAULT_PROTOCOL_FEE, + poolFeeBP = DEFAULT_POOL_FEE, +) => { + const { runKit, bldKit } = mintKits; + + const bldPoolAllocation = amountMath.make( + bldKit.brand, + bldPoolAllocationValue, + ); + const runPoolAllocation = amountMath.make( + runKit.brand, + runPoolAllocationValue, + ); + + const runAmountIn = amountMath.make(runKit.brand, runValueIn); + + const result = specifyRunIn( + runAmountIn, + runPoolAllocation, + bldPoolAllocation, + protocolFeeBP, + poolFeeBP, + ); + + Object.entries(expected).forEach(([property, amount]) => { + assertAmountsEqual(t, result[property], amount, property); + }); +}; + +test('test bug scenario', async t => { + const mintKits = setupMintKits(); + const { run, bld } = mintKits; + const bldPoolAllocationValue = 2196247730468n; + const runPoolAllocationValue = 50825056949339n; + const runValueIn = 73000000n; + + const expected = { + protocolFee: run(43800n), + poolFee: bld(7571n), + amountIn: run(72999997n), // buggy newswap quotes 72999951n + amountOut: bld(3145001n), // buggy newswap quotes 3145005n + deltaRun: run(72956197n), + deltaSecondary: bld(3152572n), + newRunPool: run(50825129905536n), + newSecondaryPool: bld(2196244577896n), + inReturnedToUser: run(3n), + }; + + conductTestSpecifyRunIn( + mintKits, + runPoolAllocationValue, + bldPoolAllocationValue, + runValueIn, + expected, + t, + ); +}); + +test('test small values', async t => { + const mintKits = setupMintKits(); + const { run, bld } = mintKits; + const bldPoolAllocationValue = 40000n; + const runPoolAllocationValue = 500000n; + const runValueIn = 5839n; + + const expected = { + protocolFee: run(4n), + poolFee: bld(2n), + amountIn: run(5834n), + amountOut: bld(459n), + deltaRun: run(5830n), + deltaSecondary: bld(461n), + newRunPool: run(505830n), + newSecondaryPool: bld(39539n), + inReturnedToUser: run(5n), + }; + + conductTestSpecifyRunIn( + mintKits, + runPoolAllocationValue, + bldPoolAllocationValue, + runValueIn, + expected, + t, + ); +}); + +test.failing('test bug scenario against fixed newSwap', async t => { + const mintKits = setupMintKits(); + const { run, bld } = mintKits; + const bldPoolAllocationValue = 2196247730468n; + const runPoolAllocationValue = 50825056949339n; + const runValueIn = 73000000n; + + // const expected = { + // protocolFee: run(43800n), + // poolFee: bld(7567n), // 7566 + // amountIn: run(72999997n), // buggy newswap quotes 72999951n + // amountOut: bld(3145005n), // buggy newswap quotes 3145005n - the same + // deltaRun: run(72956197n), + // deltaSecondary: bld(3152572n), + // newRunPool: run(50825129905536n), + // newSecondaryPool: bld(2196244577896n), + // inReturnedToUser: run(3n), + // }; + + const { amountIn, amountOut, protocolFee } = getCurrentPrice( + run(runPoolAllocationValue), + bld(bldPoolAllocationValue), + run(runValueIn), + DEFAULT_PROTOCOL_FEE, + DEFAULT_POOL_FEE, + ); + + // amountIn: run(72999997n) - amountIn is the same + // amountOut: bld(3145007n) - amount out is higher + // protocolFee: run(43773n) - protocolFee is less + + const runPoolAllocation = run(runPoolAllocationValue); + const bldPoolAllocation = bld(bldPoolAllocationValue); + const deltaX = amountMath.subtract(amountIn, protocolFee); + + // This includes the pool fee so it's only checking that including + // the pool fee, k is increasing. + assertKInvariantSellingX( + run(runPoolAllocationValue), + bld(bldPoolAllocationValue), + amountMath.subtract(amountIn, protocolFee), + amountOut, + ); + + const deltaY = calcDeltaYSellingX( + runPoolAllocation, + bldPoolAllocation, + deltaX, + ); + + const poolFee = amountMath.subtract(deltaY, amountOut); + + // This is violated: 5.996 BP not 6 + t.notThrows(() => + assertProtocolFee(protocolFee, amountIn, DEFAULT_PROTOCOL_FEE), + ); + + // This is violated 23.999444263463527 not 24 + t.notThrows(() => assertPoolFee(poolFee, amountOut, DEFAULT_POOL_FEE)); +});