Skip to content

Commit

Permalink
feat: add invariant helper and constant product use example (#3090)
Browse files Browse the repository at this point in the history
  • Loading branch information
katelynsills authored May 13, 2021
1 parent cdf158b commit f533f76
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 13 deletions.
1 change: 1 addition & 0 deletions packages/zoe/src/contractSupport/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export {
withdrawFromSeat,
saveAllIssuers,
offerTo,
checkZCF,
} from './zoeHelpers';

export {
Expand Down
21 changes: 21 additions & 0 deletions packages/zoe/src/contractSupport/zoeHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -476,3 +476,24 @@ export const offerTo = async (

return harden({ userSeatPromise, deposited: depositedPromiseKit.promise });
};

/**
* Create a wrapped version of zcf that asserts an invariant
* before performing a reallocation.
*
* @param {ContractFacet} zcf
* @param {(stagings: SeatStaging[]) => void} assertFn - an assertion
* that must be true for the reallocate to occur
* @returns {ContractFacet}
*/
export const checkZCF = (zcf, assertFn) => {
const checkedZCF = harden({
...zcf,
reallocate: (...stagings) => {
assertFn(stagings);
// @ts-ignore The types aren't right for spreading
zcf.reallocate(...stagings);
},
});
return checkedZCF;
};
38 changes: 38 additions & 0 deletions packages/zoe/src/contracts/multipoolAutoswap/constantProduct.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// @ts-check

import { assert, details as X } from '@agoric/assert';
import { natSafeMath } from '../../contractSupport';

// A pool seat has Central and Secondary keywords, and a swap seat has
// In and Out keywords
const isPoolSeat = allocation => {
return allocation.Central !== undefined || allocation.Secondary !== undefined;
};

const calcK = allocation => {
return natSafeMath.multiply(
allocation.Secondary.value,
allocation.Central.value,
);
};

/**
*
* @param {SeatStaging[]} stagings
*/
export const assertConstantProduct = stagings => {
stagings.forEach(seatStaging => {
const seat = seatStaging.getSeat();
const priorAllocation = seat.getCurrentAllocation();
const stagedAllocation = seatStaging.getStagedAllocation();
if (isPoolSeat(stagedAllocation)) {
const oldK = calcK(priorAllocation);
const newK = calcK(stagedAllocation);
console.log('oldK', oldK, 'newK', newK);
assert(
newK >= oldK,
X`the product of the pool tokens must not decrease as the result of a trade. ${oldK} decreased to ${newK}`,
);
}
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import { makeWeakStore } from '@agoric/store';
import { Far } from '@agoric/marshal';

import { AssetKind, makeIssuerKit, AmountMath } from '@agoric/ertp';
import { assertIssuerKeywords } from '../../contractSupport';
import { assertIssuerKeywords, checkZCF } from '../../contractSupport';
import { makeAddPool } from './pool';
import { makeGetCurrentPrice } from './getCurrentPrice';
import { makeMakeSwapInvitation } from './swap';
import { makeMakeAddLiquidityInvitation } from './addLiquidity';
import { makeMakeRemoveLiquidityInvitation } from './removeLiquidity';
import { assertConstantProduct } from './constantProduct';

import '../../../exported';

Expand Down Expand Up @@ -126,7 +127,7 @@ const start = zcf => {
makeSwapInInvitation,
makeSwapOutInvitation,
} = makeMakeSwapInvitation(
zcf,
checkZCF(zcf, assertConstantProduct),
isSecondary,
isCentral,
getPool,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// @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 { checkZCF } from '../../../../src/contractSupport';
import { assertConstantProduct } from '../../../../src/contracts/multipoolAutoswap/constantProduct';
import { setupZCFTest } from '../../zcf/setupZcfTest';

test('constantProduct invariant', async t => {
const { zcf } = await setupZCFTest();

const checkedZCF = checkZCF(zcf, assertConstantProduct);

const { zcfSeat: poolSeat } = checkedZCF.makeEmptySeatKit();
const { zcfSeat: swapSeat } = checkedZCF.makeEmptySeatKit();

// allocate some secondary and central to the poolSeat
const centralMint = await checkedZCF.makeZCFMint('Central');
const { brand: centralBrand } = centralMint.getIssuerRecord();
const secondaryMint = await checkedZCF.makeZCFMint('Secondary');
const { brand: secondaryBrand } = secondaryMint.getIssuerRecord();
centralMint.mintGains(
{ Central: AmountMath.make(centralBrand, 10n ** 6n) },
poolSeat,
);
secondaryMint.mintGains(
{ Secondary: AmountMath.make(secondaryBrand, 10n ** 6n) },
poolSeat,
);

const poolSeatAllocation = poolSeat.getCurrentAllocation();
t.deepEqual(poolSeatAllocation, {
Central: AmountMath.make(centralBrand, 10n ** 6n),
Secondary: AmountMath.make(secondaryBrand, 10n ** 6n),
});

// const oldK =
// poolSeatAllocation.Secondary.value * poolSeatAllocation.Central.value;

// const newK = 0;

// Let's give the swap user all the tokens and take
// nothing, a clear violation of the constant product
t.throws(
() =>
checkedZCF.reallocate(
poolSeat.stage({
Central: AmountMath.make(centralBrand, 0n),
Secondary: AmountMath.make(secondaryBrand, 0n),
}),
swapSeat.stage({
In: poolSeatAllocation.Central,
Out: poolSeatAllocation.Secondary,
}),
),
{
message:
'the product of the pool tokens must not decrease as the result of a trade. "[1000000000000n]" decreased to "[0n]"',
},
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,26 @@ import { test } from '@agoric/zoe/tools/prepare-test-env-ava';
import bundleSource from '@agoric/bundle-source';
import { makeIssuerKit, amountMath } from '@agoric/ertp';
import { E } from '@agoric/eventual-send';
import fakeVatAdmin from '../../../tools/fakeVatAdmin';
import fakeVatAdmin from '../../../../tools/fakeVatAdmin';

// noinspection ES6PreferShortImport
import { makeZoe } from '../../../src/zoeService/zoe';
import { setup } from '../setupBasicMints';
import { makeZoe } from '../../../../src/zoeService/zoe';
import { setup } from '../../setupBasicMints';
import {
makeTrader,
updatePoolState,
scaleForAddLiquidity,
scaleForRemoveLiquidity,
priceFromTargetOutput,
} from '../../autoswapJig';
import { assertPayoutDeposit, assertAmountsEqual } from '../../zoeTestHelpers';
import buildManualTimer from '../../../tools/manualTimer';
import { getAmountOut } from '../../../src/contractSupport';
} from '../../../autoswapJig';
import {
assertPayoutDeposit,
assertAmountsEqual,
} from '../../../zoeTestHelpers';
import buildManualTimer from '../../../../tools/manualTimer';
import { getAmountOut } from '../../../../src/contractSupport';

const multipoolAutoswapRoot = `${__dirname}/../../../src/contracts/multipoolAutoswap/multipoolAutoswap`;
const multipoolAutoswapRoot = `${__dirname}/../../../../src/contracts/multipoolAutoswap/multipoolAutoswap`;

test('multipoolAutoSwap with valid offers', async t => {
const { moolaR, simoleanR, moola, simoleans } = setup();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
import { test } from '@agoric/zoe/tools/prepare-test-env-ava';
import { makeNotifierKit } from '@agoric/notifier';
import { makeIssuerKit, amountMath, AssetKind } from '@agoric/ertp';
import { makePriceAuthority } from '../../../src/contracts/multipoolAutoswap/priceAuthority';
import { setup } from '../setupBasicMints';
import buildManualTimer from '../../../tools/manualTimer';
import { makePriceAuthority } from '../../../../src/contracts/multipoolAutoswap/priceAuthority';
import { setup } from '../../setupBasicMints';
import buildManualTimer from '../../../../tools/manualTimer';

test('multipoolAutoSwap PriceAuthority exception path', async t => {
const { moolaR, simoleanR } = setup();
Expand Down

0 comments on commit f533f76

Please sign in to comment.