Skip to content

Commit

Permalink
feat: publish econ stats from AMM (#5420)
Browse files Browse the repository at this point in the history
* feat: public econ stats from AMM

closes: #4648

add notifier and subscription to state

* test: integrate tests with changes from #5377

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
Chris-Hibbert and mergify[bot] authored May 24, 2022
1 parent 1f849a3 commit 87a9e62
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 29 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 @@ -62,6 +62,7 @@ export const makeAddIssuer = (zcf, isInSecondaries, brandToLiquidityMint) => {
* @param {ZCFSeat} reserveLiquidityTokenSeat seat that holds liquidity tokens
* from adding pool liquidity. It is expected to be collected by the Reserve.
* @param {WeakStore<Brand,ZCFMint>} brandToLiquidityMint
* @param {() => void} updateMetrics
*/
export const makeAddPoolInvitation = (
zcf,
Expand All @@ -73,6 +74,7 @@ export const makeAddPoolInvitation = (
protocolSeat,
reserveLiquidityTokenSeat,
brandToLiquidityMint,
updateMetrics,
) => {
const makePool = definePoolKind(
zcf,
Expand All @@ -92,6 +94,7 @@ export const makeAddPoolInvitation = (
const poolFacets = makePool(liquidityZcfMint, poolSeat, secondaryBrand);

initPool(secondaryBrand, poolFacets);
updateMetrics();
return { liquidityZcfMint, poolFacets };
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import {
makeRatio,
} from '@agoric/zoe/src/contractSupport/ratio.js';

import './types.js';
import './internal-types.js';

import { BASIS_POINTS } from './defaults.js';

const { details: X } = assert;
Expand Down
30 changes: 26 additions & 4 deletions packages/run-protocol/src/vpool-xyk-amm/multipoolMarketMaker.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
// @ts-check

import { makeWeakStore } from '@agoric/store';
import { makeStore, makeWeakStore } 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';
import { makeAddIssuer, makeAddPoolInvitation } from './addPool.js';
import { publicPrices } from './pool.js';
import {
makeMakeAddLiquidityInvitation,
makeMakeAddLiquidityAtRateInvitation,
makeMakeAddLiquidityInvitation,
} from './addLiquidity.js';
import { makeMakeRemoveLiquidityInvitation } from './removeLiquidity.js';

Expand All @@ -28,6 +29,11 @@ import {

const { quote: q, details: X } = assert;

/**
* @typedef {object} MetricsNotification
* @property {Brand[]} XYK brands of pools that use an X*Y=K pricing policy
*/

/**
* Multipool AMM is a rewrite of Uniswap that supports multiple liquidity pools,
* and direct exchanges across pools. Please see the documentation for more:
Expand Down Expand Up @@ -133,8 +139,8 @@ 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;
Expand All @@ -146,6 +152,16 @@ const start = async (zcf, privateArgs) => {

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

/** @type {SubscriptionRecord<MetricsNotification>} */
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 @@ -165,6 +181,7 @@ const start = async (zcf, privateArgs) => {
protocolSeat,
reserveLiquidityTokenSeat,
secondaryBrandToLiquidityMint,
updateMetrics,
);
const addIssuer = makeAddIssuer(
zcf,
Expand All @@ -184,6 +201,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 @@ -266,6 +286,8 @@ const start = async (zcf, privateArgs) => {
getAllPoolBrands: () =>
Object.values(zcf.getTerms().brands).filter(isSecondary),
getProtocolPoolBalance: () => protocolSeat.getCurrentAllocation(),
getMetrics: () => metricsSubscription,
getPoolMetrics,
}),
);

Expand Down
30 changes: 29 additions & 1 deletion 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 @@ -42,6 +42,8 @@ export const publicPrices = prices => {
* @typedef {{
* updater: IterationObserver<any>,
* notifier: Notifier<any>,
* metricsPublication: IterationObserver<PoolMetricsNotification>,
* metricsSubscription: Subscription<PoolMetricsNotification>
* poolSeat: ZCFSeat,
* liqTokenSupply: bigint,
* }} MutableState
Expand All @@ -54,6 +56,11 @@ export const publicPrices = prices => {
* singlePool: VirtualPool,
* },
* }} MethodContext
*
* @typedef {object} PoolMetricsNotification
* @property {Amount} centralAmount
* @property {Amount} secondaryAmount
* @property {NatValue} liquidityTokens - outstanding tokens
*/

export const updateUpdaterState = (updater, pool) =>
Expand Down Expand Up @@ -131,8 +138,20 @@ const helperBehavior = {
);
zcfSeat.exit();
updateUpdaterState(updater, pool);
facets.helper.updateMetrics();
return 'Added liquidity.';
},
/** @param {MethodContext} context */
updateMetrics: context => {
const { state, facets } = context;
const payload = harden({
centralAmount: facets.pool.getCentralAmount(),
secondaryAmount: facets.pool.getSecondaryAmount(),
liquidityTokens: state.liqTokenSupply,
});

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

const poolBehavior = {
Expand Down Expand Up @@ -233,6 +252,7 @@ const poolBehavior = {

userSeat.exit();
updateUpdaterState(state.updater, facets.pool);
facets.helper.updateMetrics();
return 'Liquidity successfully removed.';
},
getNotifier: ({ state: { notifier } }) => notifier,
Expand All @@ -242,6 +262,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 @@ -290,6 +311,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 @@ -312,6 +334,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 @@ -335,6 +361,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
8 changes: 8 additions & 0 deletions packages/run-protocol/src/vpool-xyk-amm/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
* @property {Amount<'nat'>} newX
*/

/**
* @typedef {import('./multipoolMarketMaker.js').MetricsNotification} MetricsNotification
* @typedef {import('./pool.js').PoolMetricsNotification} PoolMetricsNotification
*/

/**
* @typedef {object} DoublePoolSwapResult
* @property {Amount<'nat'>} swapperGives
Expand Down Expand Up @@ -77,6 +82,7 @@
* @property {() => PriceAuthority} getToCentralPriceAuthority
* @property {() => PriceAuthority} getFromCentralPriceAuthority
* @property {() => VirtualPool} getVPool
* @property {() => Subscription<PoolMetricsNotification>} getMetrics
*/

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

/**
Expand Down
Loading

0 comments on commit 87a9e62

Please sign in to comment.