Skip to content

Commit

Permalink
WIP: fix
Browse files Browse the repository at this point in the history
  • Loading branch information
dckc committed Jan 22, 2025
1 parent a027d97 commit a0ae9b6
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 39 deletions.
58 changes: 27 additions & 31 deletions packages/fast-usdc/src/exos/liquidity-pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { M } from '@endo/patterns';
import { Fail, q } from '@endo/errors';
import {
borrowCalc,
checkPoolBalance,
depositCalc,
makeParity,
repayCalc,
Expand All @@ -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 {{
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down
58 changes: 50 additions & 8 deletions packages/fast-usdc/src/pool-share-math.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -18,21 +19,20 @@ 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
*/

/**
* 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);
};

/**
Expand Down Expand Up @@ -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));

Expand All @@ -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
Expand Down

0 comments on commit a0ae9b6

Please sign in to comment.