From a0ae9b68b2f63d442d794f0edfc4651c82c58d4f Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 21 Jan 2025 18:33:15 -0600 Subject: [PATCH] WIP: fix --- packages/fast-usdc/src/exos/liquidity-pool.js | 58 +++++++++---------- packages/fast-usdc/src/pool-share-math.js | 58 ++++++++++++++++--- 2 files changed, 77 insertions(+), 39 deletions(-) diff --git a/packages/fast-usdc/src/exos/liquidity-pool.js b/packages/fast-usdc/src/exos/liquidity-pool.js index 34b16ce807c..1c2cd28746e 100644 --- a/packages/fast-usdc/src/exos/liquidity-pool.js +++ b/packages/fast-usdc/src/exos/liquidity-pool.js @@ -8,6 +8,7 @@ import { M } from '@endo/patterns'; import { Fail, q } from '@endo/errors'; import { borrowCalc, + checkPoolBalance, depositCalc, makeParity, repayCalc, @@ -29,32 +30,7 @@ import { * @import {PoolStats} from '../types.js'; */ -const { add, isEqual, isGTE, makeEmpty } = AmountMath; - -/** @param {Brand} brand */ -const makeDust = brand => AmountMath.make(brand, 1n); - -/** - * Verifies that the total pool balance (unencumbered + encumbered) matches the - * shareWorth numerator. The total pool balance consists of: - * 1. unencumbered balance - USDC available in the pool for borrowing - * 2. encumbered balance - USDC currently lent out - * - * A negligible `dust` amount is used to initialize shareWorth with a non-zero - * denominator. It must remain in the pool at all times. - * - * @param {ZCFSeat} poolSeat - * @param {ShareWorth} shareWorth - * @param {Brand} USDC - * @param {Amount<'nat'>} encumberedBalance - */ -const checkPoolBalance = (poolSeat, shareWorth, USDC, encumberedBalance) => { - const unencumberedBalance = poolSeat.getAmountAllocated('USDC', USDC); - const dust = makeDust(USDC); - const grossBalance = add(add(unencumberedBalance, dust), encumberedBalance); - isEqual(grossBalance, shareWorth.numerator) || - Fail`🚨 pool balance ${q(unencumberedBalance)} and encumbered balance ${q(encumberedBalance)} inconsistent with shareWorth ${q(shareWorth)}`; -}; +const { add, isGTE, makeEmpty } = AmountMath; /** * @typedef {{ @@ -127,7 +103,7 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => { (shareMint, node) => { const { brand: PoolShares } = shareMint.getIssuerRecord(); const proposalShapes = makeProposalShapes({ USDC, PoolShares }); - const shareWorth = makeParity(makeDust(USDC), PoolShares); + const shareWorth = makeParity(USDC, PoolShares); const { zcfSeat: poolSeat } = zcf.makeEmptySeatKit(); const { zcfSeat: feeSeat } = zcf.makeEmptySeatKit(); const poolMetricsRecorderKit = tools.makeRecorderKit( @@ -215,7 +191,11 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => { poolStats, shareWorth, } = this.state; - checkPoolBalance(poolSeat, shareWorth, USDC, encumberedBalance); + checkPoolBalance( + poolSeat.getCurrentAllocation(), + shareWorth, + encumberedBalance, + ); const fromSeatAllocation = fromSeat.getCurrentAllocation(); // Validate allocation equals amounts and Principal <= encumberedBalance @@ -272,8 +252,20 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => { /** @type {USDCProposalShapes['deposit']} */ // @ts-expect-error ensured by proposalShape const proposal = lp.getProposal(); - checkPoolBalance(poolSeat, shareWorth, USDC, encumberedBalance); + checkPoolBalance( + poolSeat.getCurrentAllocation(), + shareWorth, + encumberedBalance, + ); const post = depositCalc(shareWorth, proposal); + console.log( + '@@@deposit', + poolSeat.getCurrentAllocation(), + shareWorth, + encumberedBalance, + proposal, + post, + ); // COMMIT POINT const mint = shareMint.mintGains(post.payouts); @@ -308,8 +300,12 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => { // @ts-expect-error ensured by proposalShape const proposal = lp.getProposal(); const { zcfSeat: burn } = zcf.makeEmptySeatKit(); - checkPoolBalance(poolSeat, shareWorth, USDC, encumberedBalance); - const post = withdrawCalc(shareWorth, proposal); + const post = withdrawCalc( + poolSeat.getCurrentAllocation(), + encumberedBalance, + shareWorth, + proposal, + ); // COMMIT POINT try { diff --git a/packages/fast-usdc/src/pool-share-math.js b/packages/fast-usdc/src/pool-share-math.js index 1d9994a985d..5445a869415 100644 --- a/packages/fast-usdc/src/pool-share-math.js +++ b/packages/fast-usdc/src/pool-share-math.js @@ -7,7 +7,8 @@ import { } from '@agoric/zoe/src/contractSupport/ratio.js'; import { Fail, q } from '@endo/errors'; -const { getValue, add, isEmpty, isEqual, isGTE, subtract } = AmountMath; +const { keys } = Object; +const { add, isEmpty, isEqual, isGTE, make, makeEmpty, subtract } = AmountMath; /** * @import {Amount, Brand, DepositFacet, NatValue, Payment} from '@agoric/ertp'; @@ -18,8 +19,7 @@ const { getValue, add, isEmpty, isEqual, isGTE, subtract } = AmountMath; /** * Invariant: shareWorth is the pool balance divided by shares outstanding. * - * Use `makeParity(make(USDC, epsilon), PoolShares)` for an initial - * value, for some negligible `epsilon` such as 1n. + * Use `makeParity(USDC, PoolShares)` for an initial value. * * @typedef {Ratio} ShareWorth */ @@ -27,12 +27,12 @@ const { getValue, add, isEmpty, isEqual, isGTE, subtract } = AmountMath; /** * Make a 1-to-1 ratio between amounts of 2 brands. * - * @param {Amount<'nat'>} numerator + * @param {Brand<'nat'>} numeratorBrand * @param {Brand<'nat'>} denominatorBrand */ -export const makeParity = (numerator, denominatorBrand) => { - const value = getValue(numerator.brand, numerator); - return makeRatio(value, numerator.brand, value, denominatorBrand); +export const makeParity = (numeratorBrand, denominatorBrand) => { + const dust = 1n; + return makeRatio(dust, numeratorBrand, dust, denominatorBrand); }; /** @@ -95,14 +95,54 @@ export const depositCalc = (shareWorth, { give, want }) => { }); }; +/** + * Verifies that the total pool balance (unencumbered + encumbered) matches the + * shareWorth numerator. The total pool balance consists of: + * 1. unencumbered balance - USDC available in the pool for borrowing + * 2. encumbered balance - USDC currently lent out + * + * A negligible `dust` amount is used to initialize shareWorth with a non-zero + * denominator. It must remain in the pool at all times. + * + * @param {Allocation} poolAlloc + * @param {ShareWorth} shareWorth + * @param {Amount<'nat'>} encumberedBalance + */ +export const checkPoolBalance = (poolAlloc, shareWorth, encumberedBalance) => { + const { brand: usdcBrand } = encumberedBalance; + const unencumberedBalance = poolAlloc.USDC || makeEmpty(usdcBrand); + const kwds = keys(poolAlloc); + kwds.length === 0 || + (kwds.length === 1 && kwds[0] === 'USDC') || + Fail`unexpected pool allocations: ${poolAlloc}`; + const dust = make(usdcBrand, 1n); + const grossBalance = add(add(unencumberedBalance, dust), encumberedBalance); + isEqual(grossBalance, shareWorth.numerator) || + Fail`🚨 pool balance ${q(unencumberedBalance)} and encumbered balance ${q(encumberedBalance)} inconsistent with shareWorth ${q(shareWorth)}`; + return harden({ unencumberedBalance, grossBalance }); +}; + /** * Compute payout from a withdraw proposal, along with updated shareWorth * + * @param {Allocation} poolAlloc + * @param {Amount<'nat'>} encumberedBalance * @param {ShareWorth} shareWorth * @param {USDCProposalShapes['withdraw']} proposal * @returns {{ shareWorth: ShareWorth, payouts: { USDC: Amount<'nat'> }}} */ -export const withdrawCalc = (shareWorth, { give, want }) => { +export const withdrawCalc = ( + poolAlloc, + encumberedBalance, + shareWorth, + { give, want }, +) => { + const { unencumberedBalance } = checkPoolBalance( + poolAlloc, + shareWorth, + encumberedBalance, + ); + assert(!isEmpty(give.PoolShare)); assert(!isEmpty(want.USDC)); @@ -112,6 +152,8 @@ export const withdrawCalc = (shareWorth, { give, want }) => { const { denominator: sharesOutstanding, numerator: poolBalance } = shareWorth; !isGTE(want.USDC, poolBalance) || Fail`cannot withdraw ${q(want.USDC)}; only ${q(poolBalance)} in pool`; + !isGTE(want.USDC, unencumberedBalance) || + Fail`cannot withdraw ${q(want.USDC)}; only ${q(unencumberedBalance)} currently available; ${q(poolBalance)} should be available soon`; const balancePost = subtract(poolBalance, payout); // giving more shares than are outstanding is impossible, // so it's not worth a custom diagnostic. subtract will fail