diff --git a/packages/run-protocol/src/vpool-xyk-amm/pool.js b/packages/run-protocol/src/vpool-xyk-amm/pool.js index 19d9a460524b..ec26515db497 100644 --- a/packages/run-protocol/src/vpool-xyk-amm/pool.js +++ b/packages/run-protocol/src/vpool-xyk-amm/pool.js @@ -1,9 +1,9 @@ // @ts-check import { E } from '@endo/eventual-send'; -import { Far } from '@endo/marshal'; import { AssetKind, AmountMath, isNatValue } from '@agoric/ertp'; import { makeNotifierKit } from '@agoric/notifier'; +import { defineKind } from '@agoric/swingset-vat/src/storeModule.js'; import { calcLiqValueToMint, @@ -43,210 +43,257 @@ export const makeAddPool = ( getPoolFeeBP, protocolSeat, ) => { - const makePool = (liquidityZcfMint, poolSeat, secondaryBrand) => { - let liqTokenSupply = 0n; - - const { brand: liquidityBrand, issuer: liquidityIssuer } = - liquidityZcfMint.getIssuerRecord(); - const { notifier, updater } = makeNotifierKit(); - - const updateState = pool => - // TODO: when governance can change the interest rate, include it here - updater.updateState({ - central: pool.getCentralAmount(), - secondary: pool.getSecondaryAmount(), - }); - - const addLiquidityActual = ( - pool, - zcfSeat, - secondaryAmount, - poolCentralAmount, - feeSeat, - ) => { - // addLiquidity can't be called until the pool has been created. We verify - // that the asset is NAT before creating a pool. - - const liquidityValueOut = calcLiqValueToMint( - liqTokenSupply, - zcfSeat.getStagedAllocation().Central.value, - poolCentralAmount.value, - ); - - const liquidityAmountOut = AmountMath.make( - liquidityBrand, - liquidityValueOut, - ); - liquidityZcfMint.mintGains( - harden({ Liquidity: liquidityAmountOut }), - poolSeat, - ); - liqTokenSupply += liquidityValueOut; - - poolSeat.incrementBy( - zcfSeat.decrementBy( - harden({ - Central: zcfSeat.getStagedAllocation().Central, - Secondary: secondaryAmount, - }), - ), - ); + const updateUpdaterState = (updater, pool) => + // TODO: when governance can change the interest rate, include it here + updater.updateState({ + central: pool.getCentralAmount(), + secondary: pool.getSecondaryAmount(), + }); - zcfSeat.incrementBy( - poolSeat.decrementBy(harden({ Liquidity: liquidityAmountOut })), - ); - if (feeSeat) { - zcf.reallocate(poolSeat, zcfSeat, feeSeat); - } else { - zcf.reallocate(poolSeat, zcfSeat); - } - zcfSeat.exit(); - updateState(pool); - return 'Added liquidity.'; - }; - - /** @type {XYKPool} */ - const pool = Far('pool', { - getLiquiditySupply: () => liqTokenSupply, - getLiquidityIssuer: () => liquidityIssuer, - getPoolSeat: () => poolSeat, - getCentralAmount: () => - poolSeat.getAmountAllocated('Central', centralBrand), - getSecondaryAmount: () => - poolSeat.getAmountAllocated('Secondary', secondaryBrand), - - addLiquidity: zcfSeat => { - const centralIn = zcfSeat.getStagedAllocation().Central; - assert(isNatValue(centralIn.value), 'User Central'); - const secondaryIn = zcfSeat.getStagedAllocation().Secondary; - assert(isNatValue(secondaryIn.value), 'User Secondary'); - - if (liqTokenSupply === 0n) { - return addLiquidityActual(pool, zcfSeat, secondaryIn, centralIn); - } - - const centralPoolAmount = pool.getCentralAmount(); - const secondaryPoolAmount = pool.getSecondaryAmount(); - assert(isNatValue(centralPoolAmount.value), 'Pool Central'); - assert(isNatValue(secondaryPoolAmount.value), 'Pool Secondary'); - - // To calculate liquidity, we'll need to calculate alpha from the primary - // token's value before, and the value that will be added to the pool - const secondaryRequired = AmountMath.make( - secondaryBrand, - calcSecondaryRequired( - centralIn.value, - centralPoolAmount.value, - secondaryPoolAmount.value, - secondaryIn.value, - ), - ); + const addLiquidityActualToState = ( + state, + pool, + zcfSeat, + secondaryAmount, + poolCentralAmount, + feeSeat, + ) => { + const { poolSeat, liquidityBrand, liquidityZcfMint, updater } = state; + + // addLiquidity can't be called until the pool has been created. We + // verify that the asset is NAT before creating a pool. + + const liquidityValueOut = calcLiqValueToMint( + state.liqTokenSupply, + zcfSeat.getStagedAllocation().Central.value, + poolCentralAmount.value, + ); - // Central was specified precisely so offer must provide enough secondary. - assert( - AmountMath.isGTE(secondaryIn, secondaryRequired), - 'insufficient Secondary deposited', - ); + const liquidityAmountOut = AmountMath.make( + liquidityBrand, + liquidityValueOut, + ); + liquidityZcfMint.mintGains( + harden({ Liquidity: liquidityAmountOut }), + poolSeat, + ); + state.liqTokenSupply += liquidityValueOut; + + poolSeat.incrementBy( + zcfSeat.decrementBy( + harden({ + Central: zcfSeat.getStagedAllocation().Central, + Secondary: secondaryAmount, + }), + ), + ); - return addLiquidityActual(pool, zcfSeat, secondaryRequired, centralIn); - }, - removeLiquidity: userSeat => { - const liquidityIn = userSeat.getAmountAllocated( - 'Liquidity', - liquidityBrand, - ); - const liquidityValueIn = liquidityIn.value; - assert(isNatValue(liquidityValueIn), 'User Liquidity'); - const centralTokenAmountOut = AmountMath.make( - centralBrand, - calcValueToRemove( - liqTokenSupply, - pool.getCentralAmount().value, - liquidityValueIn, - ), - ); + zcfSeat.incrementBy( + poolSeat.decrementBy(harden({ Liquidity: liquidityAmountOut })), + ); + if (feeSeat) { + zcf.reallocate(poolSeat, zcfSeat, feeSeat); + } else { + zcf.reallocate(poolSeat, zcfSeat); + } + zcfSeat.exit(); + updateUpdaterState(updater, pool); + return 'Added liquidity.'; + }; - const tokenKeywordAmountOut = AmountMath.make( - secondaryBrand, - calcValueToRemove( - liqTokenSupply, - pool.getSecondaryAmount().value, - liquidityValueIn, - ), - ); + const makePool = defineKind( + 'pool', + (liquidityZcfMint, poolSeat, secondaryBrand) => { + const { brand: liquidityBrand, issuer: liquidityIssuer } = + liquidityZcfMint.getIssuerRecord(); + const { notifier, updater } = makeNotifierKit(); - liqTokenSupply -= liquidityValueIn; + return { + liqTokenSupply: 0n, + liquidityIssuer, + poolSeat, + liquidityBrand, + secondaryBrand, + liquidityZcfMint, + updater, + notifier, + vPool: undefined, + toCentralPriceAuthority: undefined, + fromCentralPriceAuthority: undefined, + }; + }, + + state => { + const { + liquidityIssuer, + poolSeat, + liquidityBrand, + secondaryBrand, + updater, + notifier, + } = state; + + /** @type {XYKPool} */ + const pool = { + getLiquiditySupply: () => state.liqTokenSupply, + getLiquidityIssuer: () => liquidityIssuer, + getPoolSeat: () => poolSeat, + getCentralAmount: () => + poolSeat.getAmountAllocated('Central', centralBrand), + getSecondaryAmount: () => + poolSeat.getAmountAllocated('Secondary', secondaryBrand), + + addLiquidity: zcfSeat => { + const centralIn = zcfSeat.getStagedAllocation().Central; + assert(isNatValue(centralIn.value), 'User Central'); + const secondaryIn = zcfSeat.getStagedAllocation().Secondary; + assert(isNatValue(secondaryIn.value), 'User Secondary'); + + if (state.liqTokenSupply === 0n) { + return addLiquidityActualToState( + state, + pool, + zcfSeat, + secondaryIn, + centralIn, + ); + } + + const centralPoolAmount = pool.getCentralAmount(); + const secondaryPoolAmount = pool.getSecondaryAmount(); + assert(isNatValue(centralPoolAmount.value), 'Pool Central'); + assert(isNatValue(secondaryPoolAmount.value), 'Pool Secondary'); + + // To calculate liquidity, we'll need to calculate alpha from the + // primary token's value before, and the value that will be added to + // the pool + const secondaryRequired = AmountMath.make( + secondaryBrand, + calcSecondaryRequired( + centralIn.value, + centralPoolAmount.value, + secondaryPoolAmount.value, + secondaryIn.value, + ), + ); + + // Central was specified precisely so offer must provide enough + // secondary. + assert( + AmountMath.isGTE(secondaryIn, secondaryRequired), + 'insufficient Secondary deposited', + ); + + return addLiquidityActualToState( + state, + pool, + zcfSeat, + secondaryRequired, + centralIn, + ); + }, + removeLiquidity: userSeat => { + const liquidityIn = userSeat.getAmountAllocated( + 'Liquidity', + liquidityBrand, + ); + const liquidityValueIn = liquidityIn.value; + assert(isNatValue(liquidityValueIn), 'User Liquidity'); + const centralTokenAmountOut = AmountMath.make( + centralBrand, + calcValueToRemove( + state.liqTokenSupply, + pool.getCentralAmount().value, + liquidityValueIn, + ), + ); + + const tokenKeywordAmountOut = AmountMath.make( + secondaryBrand, + calcValueToRemove( + state.liqTokenSupply, + pool.getSecondaryAmount().value, + liquidityValueIn, + ), + ); + + state.liqTokenSupply -= liquidityValueIn; + + poolSeat.incrementBy( + userSeat.decrementBy(harden({ Liquidity: liquidityIn })), + ); + userSeat.incrementBy( + poolSeat.decrementBy( + harden({ + Central: centralTokenAmountOut, + Secondary: tokenKeywordAmountOut, + }), + ), + ); + zcf.reallocate(userSeat, poolSeat); + + userSeat.exit(); + updateUpdaterState(updater, pool); + return 'Liquidity successfully removed.'; + }, + getNotifier: () => notifier, + updateState: () => updateUpdaterState(updater, pool), + getToCentralPriceAuthority: () => state.toCentralPriceAuthority, + getFromCentralPriceAuthority: () => state.fromCentralPriceAuthority, + // eslint-disable-next-line no-use-before-define + getVPool: () => { + return state.vPool; + }, + }; + return pool; + }, + + (state, pool) => { + const { secondaryBrand, notifier } = state; + + const vPool = makeSinglePool( + zcf, + pool, + getProtocolFeeBP, + getPoolFeeBP, + protocolSeat, + (...args) => addLiquidityActualToState(state, ...args), + ); + state.vPool = vPool; - poolSeat.incrementBy( - userSeat.decrementBy(harden({ Liquidity: liquidityIn })), + const getInputPriceForPA = (amountIn, brandOut) => + vPool.externalFacet.getInputPrice( + amountIn, + AmountMath.makeEmpty(brandOut), ); - userSeat.incrementBy( - poolSeat.decrementBy( - harden({ - Central: centralTokenAmountOut, - Secondary: tokenKeywordAmountOut, - }), - ), + const getOutputPriceForPA = (brandIn, amountout) => + vPool.externalFacet.getInputPrice( + AmountMath.makeEmpty(brandIn), + amountout, ); - zcf.reallocate(userSeat, poolSeat); - - userSeat.exit(); - updateState(pool); - return 'Liquidity successfully removed.'; - }, - getNotifier: () => notifier, - updateState: () => updateState(pool), - // eslint-disable-next-line no-use-before-define - getToCentralPriceAuthority: () => toCentralPriceAuthority, - // eslint-disable-next-line no-use-before-define - getFromCentralPriceAuthority: () => fromCentralPriceAuthority, - // eslint-disable-next-line no-use-before-define - getVPool: () => vPool, - }); - - const vPool = makeSinglePool( - zcf, - pool, - getProtocolFeeBP, - getPoolFeeBP, - protocolSeat, - addLiquidityActual, - ); - const getInputPriceForPA = (amountIn, brandOut) => - vPool.externalFacet.getInputPrice( - amountIn, - AmountMath.makeEmpty(brandOut), + state.toCentralPriceAuthority = makePriceAuthority( + getInputPriceForPA, + getOutputPriceForPA, + secondaryBrand, + centralBrand, + timer, + zcf, + notifier, + quoteIssuerKit, ); - const getOutputPriceForPA = (brandIn, amountout) => - vPool.externalFacet.getInputPrice( - AmountMath.makeEmpty(brandIn), - amountout, + state.fromCentralPriceAuthority = makePriceAuthority( + getInputPriceForPA, + getOutputPriceForPA, + centralBrand, + secondaryBrand, + timer, + zcf, + notifier, + quoteIssuerKit, ); - - const toCentralPriceAuthority = makePriceAuthority( - getInputPriceForPA, - getOutputPriceForPA, - secondaryBrand, - centralBrand, - timer, - zcf, - notifier, - quoteIssuerKit, - ); - const fromCentralPriceAuthority = makePriceAuthority( - getInputPriceForPA, - getOutputPriceForPA, - centralBrand, - secondaryBrand, - timer, - zcf, - notifier, - quoteIssuerKit, - ); - - return pool; - }; + }, + ); /** * Allows users to add new liquidity pools. `secondaryIssuer` and @@ -292,3 +339,4 @@ export const makeAddPool = ( return addPool; }; +harden(makeAddPool); diff --git a/packages/run-protocol/src/vpool-xyk-amm/singlePool.js b/packages/run-protocol/src/vpool-xyk-amm/singlePool.js index 889a75796ca9..d35a1a64523b 100644 --- a/packages/run-protocol/src/vpool-xyk-amm/singlePool.js +++ b/packages/run-protocol/src/vpool-xyk-amm/singlePool.js @@ -100,5 +100,5 @@ export const makeSinglePool = ( addLiquidityActual, }); - return { externalFacet, internalFacet }; + return harden({ externalFacet, internalFacet }); };