Skip to content

Commit

Permalink
feat: public econ stats from AMM
Browse files Browse the repository at this point in the history
closes: #4648
  • Loading branch information
Chris-Hibbert committed May 23, 2022
1 parent b2f9b67 commit adad2f7
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 6 deletions.
3 changes: 3 additions & 0 deletions packages/run-protocol/src/vpool-xyk-amm/addPool.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const { details: X } = assert;
* @param {IssuerKit} quoteIssuerKit
* @param {import('./multipoolMarketMaker.js').AMMParamGetters} params retrieve governed params
* @param {ZCFSeat} protocolSeat seat that holds collected fees
* @param {() => void} updateMetrics
*/
export const makeAddPool = (
zcf,
Expand All @@ -26,6 +27,7 @@ export const makeAddPool = (
quoteIssuerKit,
params,
protocolSeat,
updateMetrics,
) => {
const makePool = definePoolKind(
zcf,
Expand Down Expand Up @@ -76,6 +78,7 @@ export const makeAddPool = (
const pool = makePool(liquidityZCFMint, poolSeat, secondaryBrand);
// @ts-expect-error xxx fix types
initPool(secondaryBrand, pool);
updateMetrics();
return liquidityZCFMint.getIssuerRecord().issuer;
};

Expand Down
22 changes: 19 additions & 3 deletions packages/run-protocol/src/vpool-xyk-amm/multipoolMarketMaker.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// @ts-check

import { makeWeakStore } from '@agoric/store';
import { makeStore } from '@agoric/store';
import { Far } from '@endo/marshal';

import { AssetKind, makeIssuerKit } from '@agoric/ertp';
import { handleParamGovernance, ParamTypes } from '@agoric/governance';
import { makeSubscriptionKit } from '@agoric/notifier';

import { assertIssuerKeywords } from '@agoric/zoe/src/contractSupport/index.js';
import { E } from '@endo/far';
Expand Down Expand Up @@ -140,15 +141,24 @@ const start = async (zcf, privateArgs) => {
)}`,
);

/** @type {WeakStore<Brand,PoolFacets>} */
const secondaryBrandToPool = makeWeakStore('secondaryBrand');
/** @type {Store<Brand,PoolFacets>} */
const secondaryBrandToPool = makeStore('secondaryBrand');
const getPool = brand => secondaryBrandToPool.get(brand).pool;
const getPoolHelper = brand => secondaryBrandToPool.get(brand).helper;
const initPool = secondaryBrandToPool.init;
const isSecondary = secondaryBrandToPool.has;

const quoteIssuerKit = makeIssuerKit('Quote', AssetKind.SET);

const { publication: metricsPublication, subscription: metricsSubscription } =
makeSubscriptionKit();
const updateMetrics = () => {
metricsPublication.updateState(
harden({ XYK: Array.from(secondaryBrandToPool.keys()) }),
);
};
updateMetrics();

// For now, this seat collects protocol fees. It needs to be connected to
// something that will extract the fees.
const { zcfSeat: protocolSeat } = zcf.makeEmptySeatKit();
Expand All @@ -164,6 +174,7 @@ const start = async (zcf, privateArgs) => {
quoteIssuerKit,
params,
protocolSeat,
updateMetrics,
);
/** @param {Brand} brand */
const getPoolAllocation = brand =>
Expand All @@ -177,6 +188,9 @@ const start = async (zcf, privateArgs) => {
};
};

/** @param {Brand} brand */
const getPoolMetrics = brand => getPool(brand).getMetrics();

/**
* @param {Brand} brandIn
* @param {Brand} [brandOut]
Expand Down Expand Up @@ -258,6 +272,8 @@ const start = async (zcf, privateArgs) => {
getAllPoolBrands: () =>
Object.values(zcf.getTerms().brands).filter(isSecondary),
getProtocolPoolBalance: () => protocolSeat.getCurrentAllocation(),
getMetrics: () => metricsSubscription,
getPoolMetrics,
}),
);

Expand Down
23 changes: 21 additions & 2 deletions packages/run-protocol/src/vpool-xyk-amm/pool.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @ts-check

import { AmountMath, isNatValue } from '@agoric/ertp';
import { makeNotifierKit } from '@agoric/notifier';
import { makeNotifierKit, makeSubscriptionKit } from '@agoric/notifier';

import {
calcLiqValueToMint,
Expand Down Expand Up @@ -66,7 +66,7 @@ const updateUpdaterState = (updater, pool) =>
const helperBehavior = {
/** @type {import('@agoric/vat-data/src/types').PlusContext<MethodContext, AddLiquidityActual>} */
addLiquidityActual: (
{ state },
{ state, facets },
pool,
zcfSeat,
secondaryAmount,
Expand Down Expand Up @@ -114,8 +114,18 @@ const helperBehavior = {
}
zcfSeat.exit();
updateUpdaterState(updater, pool);
facets.helper.updateMetrics();
return 'Added liquidity.';
},
updateMetrics: ({ state, facets }) => {
const payload = harden({
Central: facets.pool.getCentralAmount(),
Secondary: facets.pool.getSecondaryAmount(),
Liquidity: state.liqTokenSupply,
});

state.metricsPublication.updateState(payload);
},
};

const poolBehavior = {
Expand Down Expand Up @@ -216,6 +226,7 @@ const poolBehavior = {

userSeat.exit();
updateUpdaterState(state.updater, facets.pool);
facets.helper.updateMetrics();
return 'Liquidity successfully removed.';
},
getNotifier: ({ state: { notifier } }) => notifier,
Expand All @@ -225,6 +236,7 @@ const poolBehavior = {
getToCentralPriceAuthority: ({ state }) => state.toCentralPriceAuthority,
getFromCentralPriceAuthority: ({ state }) => state.fromCentralPriceAuthority,
getVPool: ({ facets }) => facets.singlePool,
getMetrics: ({ state }) => state.metricsSubscription,
};

/** @param {MethodContext} context */
Expand Down Expand Up @@ -273,6 +285,7 @@ const finish = context => {
context.state.toCentralPriceAuthority = toCentralPriceAuthority;
// @ts-expect-error declared read-only, set value once
context.state.fromCentralPriceAuthority = fromCentralPriceAuthority;
context.facets.helper.updateMetrics();
};

/**
Expand All @@ -295,6 +308,10 @@ export const definePoolKind = (
const { brand: liquidityBrand, issuer: liquidityIssuer } =
liquidityZcfMint.getIssuerRecord();
const { notifier, updater } = makeNotifierKit();
const {
publication: metricsPublication,
subscription: metricsSubscription,
} = makeSubscriptionKit();

// XXX why does the paramAccessor have to be repackaged as a Far object?
const params = Far('pool param accessor', {
Expand All @@ -318,6 +335,8 @@ export const definePoolKind = (
quoteIssuerKit,
timer,
paramAccessor: params,
metricsPublication,
metricsSubscription,
};
};

Expand Down
3 changes: 2 additions & 1 deletion packages/run-protocol/src/vpool-xyk-amm/singlePool.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const getPools = pool => ({

export const singlePool = {
allocateGainsAndLosses: (context, seat, prices) => {
const { pool } = context.facets;
const { pool, helper } = context.facets;
const { poolSeat, zcf, protocolSeat } = context.state;
seat.decrementBy(harden({ In: prices.swapperGives }));
seat.incrementBy(harden({ Out: prices.swapperGets }));
Expand All @@ -34,6 +34,7 @@ export const singlePool = {
zcf.reallocate(poolSeat, seat, protocolSeat);
seat.exit();
pool.updateState();
helper.updateMetrics();
return `Swap successfully completed.`;
},

Expand Down
3 changes: 3 additions & 0 deletions packages/run-protocol/src/vpool-xyk-amm/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
* @property {() => PriceAuthority} getToCentralPriceAuthority
* @property {() => PriceAuthority} getFromCentralPriceAuthority
* @property {() => VirtualPool} getVPool
* @property {() => Subscription<unknown>} getMetrics
*/

/**
Expand Down Expand Up @@ -118,6 +119,8 @@
* Prices and notifications about changing prices.
* @property {() => Brand[]} getAllPoolBrands
* @property {() => Allocation} getProtocolPoolBalance
* @property {() => Subscription<unknown>} getMetrics
* @property {(brand: Brand) => Subscription<unknown>} getPoolMetrics
*/

/**
Expand Down
63 changes: 63 additions & 0 deletions packages/run-protocol/test/amm/vpool-xyk-amm/test-xyk-amm-swap.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
ceilMultiplyBy,
natSafeMath,
} from '@agoric/zoe/src/contractSupport/index.js';
import { makeNotifierFromAsyncIterable } from '@agoric/notifier';
import {
assertAmountsEqual,
assertPayoutAmount,
Expand Down Expand Up @@ -445,6 +446,12 @@ test('amm doubleSwap', async t => {

const ammInstance = await amm.instance;

const metricsSub = await E(amm.ammPublicFacet).getMetrics();
const metricsNotifier = makeNotifierFromAsyncIterable(metricsSub);

const state0 = await E(metricsNotifier).getUpdateSince();
t.deepEqual(state0.value, { XYK: [] });

const aliceAddLiquidityInvitation = E(
amm.ammPublicFacet,
).makeAddLiquidityInvitation();
Expand All @@ -453,6 +460,10 @@ test('amm doubleSwap', async t => {
moolaR.issuer,
'Moola',
);

const state1 = await E(metricsNotifier).getUpdateSince(state0.updateCount);
t.deepEqual(state1.value, { XYK: [moolaR.brand] });

const moolaLiquidityBrand = await E(moolaLiquidityIssuer).getBrand();
const moolaLiquidity = value => AmountMath.make(moolaLiquidityBrand, value);

Expand All @@ -465,6 +476,9 @@ test('amm doubleSwap', async t => {
const simoleanLiquidity = value =>
AmountMath.make(simoleanLiquidityBrand, value);

const state2 = await E(metricsNotifier).getUpdateSince(state1.updateCount);
t.deepEqual(state2.value, { XYK: [moolaR.brand, simoleanR.brand] });

const issuerKeywordRecord = await E(zoe).getIssuers(ammInstance);
t.deepEqual(
issuerKeywordRecord,
Expand Down Expand Up @@ -1204,6 +1218,13 @@ test('amm adding liquidity', async t => {
centralR,
timer,
);

const metricsSub = await E(amm.ammPublicFacet).getMetrics();
const metricsNotifier = makeNotifierFromAsyncIterable(metricsSub);

const state0 = await E(metricsNotifier).getUpdateSince();
t.deepEqual(state0.value, { XYK: [] });

const moolaLiquidityIssuer = await E(amm.ammPublicFacet).addPool(
// verify that addPool works with ERef<Issuer>.
Promise.resolve(moolaR.issuer),
Expand Down Expand Up @@ -1238,6 +1259,21 @@ test('amm adding liquidity', async t => {
`The poolAllocation object values for moola should be empty`,
);

const state1 = await E(metricsNotifier).getUpdateSince(state0.updateCount);
t.deepEqual(state1.value, { XYK: [moolaR.brand] });

const poolMetricsSub = await E(amm.ammPublicFacet).getPoolMetrics(
moolaR.brand,
);
const poolMetricsNotifier = makeNotifierFromAsyncIterable(poolMetricsSub);

const poolState0 = await E(poolMetricsNotifier).getUpdateSince();
t.deepEqual(poolState0.value, {
Central: AmountMath.makeEmpty(centralR.brand),
Secondary: moola(0n),
Liquidity: 0n,
});

// add initial liquidity at 10000:50000
const {
Central: c1,
Expand All @@ -1252,6 +1288,15 @@ test('amm adding liquidity', async t => {
`poolAllocation after initialization`,
);

const poolState1 = await E(poolMetricsNotifier).getUpdateSince(
poolState0.updateCount,
);
t.deepEqual(poolState1.value, {
Central: AmountMath.make(centralR.brand, 50_000n),
Secondary: moola(10_000n),
Liquidity: 5_0000n,
});

// Add liquidity. Offer 20_000:70_000.
const {
Central: c2,
Expand All @@ -1267,6 +1312,15 @@ test('amm adding liquidity', async t => {
`poolAllocation after initialization`,
);

const poolState2 = await E(poolMetricsNotifier).getUpdateSince(
poolState1.updateCount,
);
t.deepEqual(poolState2.value, {
Central: AmountMath.make(centralR.brand, 119_996n),
Secondary: moola(29_989n),
Liquidity: 134_115n,
});

// Add liquidity. Offer 12_000:100_000.
const {
Central: c3,
Expand All @@ -1281,4 +1335,13 @@ test('amm adding liquidity', async t => {
allocation(219985n, 0n, 41973n),
`poolAllocation after initialization`,
);

const poolState3 = await E(poolMetricsNotifier).getUpdateSince(
poolState2.updateCount,
);
t.deepEqual(poolState3.value, {
Central: AmountMath.make(centralR.brand, 219_985n),
Secondary: moola(41_973n),
Liquidity: 214_795n,
});
});

0 comments on commit adad2f7

Please sign in to comment.