diff --git a/packages/ERTP/package.json b/packages/ERTP/package.json index 320d75c2646c..5996ecb8f66e 100644 --- a/packages/ERTP/package.json +++ b/packages/ERTP/package.json @@ -41,11 +41,11 @@ "@agoric/nat": "^4.1.0", "@agoric/notifier": "^0.3.35", "@agoric/store": "^0.6.10", + "@agoric/swingset-vat": "^0.25.1", "@endo/marshal": "^0.6.3", "@endo/promise-kit": "^0.2.37" }, "devDependencies": { - "@agoric/swingset-vat": "^0.25.1", "@endo/bundle-source": "^2.1.1", "ava": "^3.12.1", "fast-check": "^2.21.0" diff --git a/packages/ERTP/src/payment.js b/packages/ERTP/src/payment.js index 4f5c7809ee29..3a87d9c22990 100644 --- a/packages/ERTP/src/payment.js +++ b/packages/ERTP/src/payment.js @@ -1,15 +1,21 @@ // @ts-check -import { Far } from '@endo/marshal'; +import { defineKind } from '@agoric/swingset-vat/src/storeModule.js'; /** * @template {AssetKind} K * @param {string} allegedName * @param {Brand} brand - * @returns {Payment} + * @returns {() => Payment} */ -export const makePayment = (allegedName, brand) => { - return Far(`${allegedName} payment`, { - getAllegedBrand: () => brand, - }); +export const makePaymentMaker = (allegedName, brand) => { + const makePayment = defineKind( + `${allegedName} payment`, + () => ({}), + () => ({ + getAllegedBrand: () => brand, + }), + ); + return makePayment; }; +harden(makePaymentMaker); diff --git a/packages/ERTP/src/paymentLedger.js b/packages/ERTP/src/paymentLedger.js index 649c9673467d..9c3d31ad210a 100644 --- a/packages/ERTP/src/paymentLedger.js +++ b/packages/ERTP/src/paymentLedger.js @@ -4,11 +4,11 @@ import { assert, details as X } from '@agoric/assert'; import { E } from '@endo/eventual-send'; import { isPromise } from '@endo/promise-kit'; import { Far, assertCopyArray } from '@endo/marshal'; -import { makeWeakStore, fit } from '@agoric/store'; - +import { fit } from '@agoric/store'; +import { makeScalarBigWeakMapStore } from '@agoric/swingset-vat/src/storeModule.js'; import { AmountMath } from './amountMath.js'; -import { makePayment } from './payment.js'; -import { makePurse } from './purse.js'; +import { makePaymentMaker } from './payment.js'; +import { makePurseMaker } from './purse.js'; import '@agoric/store/exported.js'; @@ -31,6 +31,8 @@ export const makePaymentLedger = ( displayInfo, optShutdownWithFailure = undefined, ) => { + const makePayment = makePaymentMaker(allegedName, brand); + /** @type {ShutdownWithFailure} */ const shutdownLedgerWithFailure = reason => { // TODO This should also destroy ledger state. @@ -47,7 +49,7 @@ export const makePaymentLedger = ( }; /** @type {WeakStore} */ - const paymentLedger = makeWeakStore('payment'); + const paymentLedger = makeScalarBigWeakMapStore('payment'); /** @type {(left: Amount, right: Amount) => Amount } */ const add = (left, right) => AmountMath.add(left, right, brand); @@ -142,7 +144,7 @@ export const makePaymentLedger = ( payments.forEach(payment => paymentLedger.delete(payment)); newPayments = newPaymentBalances.map(balance => { - const newPayment = makePayment(allegedName, brand); + const newPayment = makePayment(); paymentLedger.init(newPayment, balance); return newPayment; }); @@ -257,7 +259,7 @@ export const makePaymentLedger = ( */ const mintPayment = newAmount => { newAmount = coerce(newAmount); - const payment = makePayment(allegedName, brand); + const payment = makePayment(); paymentLedger.init(payment, newAmount); return payment; }; @@ -319,7 +321,7 @@ export const makePaymentLedger = ( ); const newPurseBalance = subtract(currentBalance, amount); - const payment = makePayment(allegedName, brand); + const payment = makePayment(); try { // COMMIT POINT Move the withdrawn assets from this purse into // payment. Total assets must remain conserved. @@ -337,6 +339,13 @@ export const makePaymentLedger = ( withdraw, }; + const makeEmptyPurse = makePurseMaker( + allegedName, + assetKind, + brand, + purseMethods, + ); + /** @type {Issuer} */ const issuer = Far(`${allegedName} issuer`, { isLive, @@ -350,8 +359,7 @@ export const makePaymentLedger = ( getAllegedName: () => allegedName, getAssetKind: () => assetKind, getDisplayInfo: () => displayInfo, - makeEmptyPurse: () => - makePurse(allegedName, assetKind, brand, purseMethods), + makeEmptyPurse, }); /** @type {Mint} */ diff --git a/packages/ERTP/src/purse.js b/packages/ERTP/src/purse.js index ae51e1dd5330..8cb54c5b26e0 100644 --- a/packages/ERTP/src/purse.js +++ b/packages/ERTP/src/purse.js @@ -1,43 +1,60 @@ import { makeNotifierKit } from '@agoric/notifier'; -import { Far } from '@endo/marshal'; +import { defineKind } from '@agoric/swingset-vat/src/storeModule.js'; import { AmountMath } from './amountMath.js'; -export const makePurse = (allegedName, assetKind, brand, purseMethods) => { - let currentBalance = AmountMath.makeEmpty(brand, assetKind); +export const makePurseMaker = (allegedName, assetKind, brand, purseMethods) => { + const makePurseKit = defineKind( + allegedName, + () => { + const currentBalance = AmountMath.makeEmpty(brand, assetKind); - /** @type {NotifierRecord} */ - const { notifier: balanceNotifier, updater: balanceUpdater } = - makeNotifierKit(currentBalance); + /** @type {NotifierRecord} */ + const { notifier: balanceNotifier, updater: balanceUpdater } = + makeNotifierKit(currentBalance); - const updatePurseBalance = newPurseBalance => { - currentBalance = newPurseBalance; - balanceUpdater.updateState(currentBalance); - }; - - /** @type {Purse} */ - const purse = Far(`${allegedName} purse`, { - deposit: (srcPayment, optAmountShape = undefined) => { - // Note COMMIT POINT within deposit. - return purseMethods.deposit( + return { currentBalance, - updatePurseBalance, - srcPayment, - optAmountShape, - ); + balanceNotifier, + balanceUpdater, + }; }, - withdraw: amount => - // Note COMMIT POINT within withdraw. - purseMethods.withdraw(currentBalance, updatePurseBalance, amount), - getCurrentAmount: () => currentBalance, - getCurrentAmountNotifier: () => balanceNotifier, - getAllegedBrand: () => brand, - // eslint-disable-next-line no-use-before-define - getDepositFacet: () => depositFacet, - }); - - const depositFacet = Far(`${allegedName} depositFacet`, { - receive: purse.deposit, - }); + state => { + const { balanceNotifier, balanceUpdater } = state; + const updatePurseBalance = newPurseBalance => { + state.currentBalance = newPurseBalance; + balanceUpdater.updateState(state.currentBalance); + }; - return purse; + /** @type {Purse} */ + const purse = { + deposit: (srcPayment, optAmountShape = undefined) => { + // Note COMMIT POINT within deposit. + return purseMethods.deposit( + state.currentBalance, + updatePurseBalance, + srcPayment, + optAmountShape, + ); + }, + withdraw: amount => + // Note COMMIT POINT within withdraw. + purseMethods.withdraw( + state.currentBalance, + updatePurseBalance, + amount, + ), + getCurrentAmount: () => state.currentBalance, + getCurrentAmountNotifier: () => balanceNotifier, + getAllegedBrand: () => brand, + // eslint-disable-next-line no-use-before-define + getDepositFacet: () => depositFacet, + }; + const depositFacet = { + receive: purse.deposit, + }; + return { purse, depositFacet }; + }, + ); + return () => makePurseKit().purse; }; +harden(makePurseMaker); diff --git a/packages/ERTP/test/unitTests/test-issuerObj.js b/packages/ERTP/test/unitTests/test-issuerObj.js index d71f4c3bf405..60917b96fc65 100644 --- a/packages/ERTP/test/unitTests/test-issuerObj.js +++ b/packages/ERTP/test/unitTests/test-issuerObj.js @@ -434,7 +434,7 @@ test('issuer.combine bad payments', async t => { await t.throwsAsync( () => E(issuer).combine(payments), { - message: '"payment" not found: "[Alleged: other fungible payment]"', + message: /.* "\[Alleged: other fungible payment\]"/, }, 'payment from other mint is not found', ); diff --git a/packages/governance/test/unitTests/test-buildParamManager.js b/packages/governance/test/unitTests/test-buildParamManager.js index 6c36ad1403f9..fea6eea59faa 100644 --- a/packages/governance/test/unitTests/test-buildParamManager.js +++ b/packages/governance/test/unitTests/test-buildParamManager.js @@ -213,7 +213,7 @@ test('Invitation', async t => { t.is(paramManager.getAmount('Amt'), drachmaAmount); const invitationActualAmount = paramManager.getInvitationAmount('Invite').value; - t.is(invitationActualAmount, invitationAmount.value); + t.deepEqual(invitationActualAmount, invitationAmount.value); // @ts-ignore invitationActualAmount's type is unknown t.is(invitationActualAmount[0].description, 'simple'); diff --git a/packages/governance/test/unitTests/test-typedParamManager.js b/packages/governance/test/unitTests/test-typedParamManager.js index b8dd8c239751..51c1918f687f 100644 --- a/packages/governance/test/unitTests/test-typedParamManager.js +++ b/packages/governance/test/unitTests/test-typedParamManager.js @@ -220,7 +220,7 @@ test('Invitation', async t => { t.is(paramManager.getCurrency(), drachmaBrand); t.is(paramManager.getAmt(), drachmaAmount); const invitationActualAmount = paramManager.getInvite().value; - t.is(invitationActualAmount, invitationAmount.value); + t.deepEqual(invitationActualAmount, invitationAmount.value); t.is(invitationActualAmount[0].description, 'simple'); t.is(await paramManager.getInternalParamValue('Invite'), invitation); 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 }); };