Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add invariant helper and constant product example #3090

Merged
merged 3 commits into from
May 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 => {
katelynsills marked this conversation as resolved.
Show resolved Hide resolved
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