Skip to content

Commit

Permalink
refactor: use a single zcf.atomicReallocate for repaying
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris-Hibbert committed Feb 26, 2025
1 parent f83e5a9 commit f01f6b4
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 117 deletions.
46 changes: 17 additions & 29 deletions packages/fast-usdc/src/exos/liquidity-pool.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { AmountMath, AmountShape, RatioShape } from '@agoric/ertp';
import {
fromOnly,
toOnly,
makeRecorderTopic,
RecorderKitShape,
TopicsRecordShape,
} from '@agoric/zoe/src/contractSupport/index.js';
import { SeatShape } from '@agoric/zoe/src/typeGuards.js';
import { M } from '@endo/patterns';
import { Fail, q } from '@endo/errors';
import { TransferPartShape } from '@agoric/zoe/src/contractSupport/atomicTransfer.js';
import {
borrowCalc,
checkPoolBalance,
Expand Down Expand Up @@ -83,7 +86,7 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
}),
repayer: M.interface('repayer', {
repay: M.call(
SeatShape,
TransferPartShape,
harden({
Principal: makeNatAmountShape(USDC, 1n),
PoolFee: makeNatAmountShape(USDC, 0n),
Expand Down Expand Up @@ -179,7 +182,6 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
* @param {Amount<'nat'>} amount
*/
returnToPool(borrowSeat, amount) {
const { zcfSeat: repaySeat } = zcf.makeEmptySeatKit();
const returnAmounts = harden({
Principal: amount,
PoolFee: makeEmpty(USDC),
Expand All @@ -188,21 +190,18 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
const borrowSeatAllocation = borrowSeat.getCurrentAllocation();
isGTE(borrowSeatAllocation.USDC, amount) ||
Fail`⚠️ borrowSeatAllocation ${q(borrowSeatAllocation)} less than amountKWR ${q(amount)}`;
// arrange payments in a format repay is expecting
zcf.atomicRearrange(
harden([[borrowSeat, repaySeat, { USDC: amount }, returnAmounts]]),
);
this.facets.repayer.repay(repaySeat, returnAmounts);

const transferSourcePart = fromOnly(borrowSeat, { USDC: amount });
this.facets.repayer.repay(transferSourcePart, returnAmounts);
borrowSeat.exit();
repaySeat.exit();
},
},
repayer: {
/**
* @param {ZCFSeat} fromSeat
* @param {RepayAmountKWR} amounts
* @param {TransferPart} sourceTransfer
* @param {RepayAmountKWR} split
*/
repay(fromSeat, amounts) {
repay(sourceTransfer, split) {
const {
encumberedBalance,
feeSeat,
Expand All @@ -215,32 +214,21 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
shareWorth,
encumberedBalance,
);

const fromSeatAllocation = fromSeat.getCurrentAllocation();
// Validate allocation equals amounts and Principal <= encumberedBalance
// Validate Principal <= encumberedBalance and produce poolStats after
const post = repayCalc(
shareWorth,
fromSeatAllocation,
amounts,
split,
encumberedBalance,
poolStats,
);

const { ContractFee, ...rest } = amounts;

// COMMIT POINT
// UNTIL #10684: ability to terminate an incarnation w/o terminating the contract
zcf.atomicRearrange(
harden([
[
fromSeat,
poolSeat,
rest,
{ USDC: add(amounts.PoolFee, amounts.Principal) },
],
[fromSeat, feeSeat, { ContractFee }, { USDC: ContractFee }],
]),
);
zcf.atomicRearrange([
sourceTransfer,
toOnly(poolSeat, { USDC: add(split.PoolFee, split.Principal) }),
toOnly(feeSeat, { USDC: split.ContractFee }),
]);

Object.assign(this.state, post);
this.facets.external.publishPoolMetrics();
Expand Down
7 changes: 3 additions & 4 deletions packages/fast-usdc/src/exos/settler.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { E } from '@endo/far';
import { M } from '@endo/patterns';

import { decodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js';
import { fromOnly } from '@agoric/zoe/src/contractSupport/index.js';
import { PendingTxStatus } from '../constants.js';
import { makeFeeTools } from '../utils/fees.js';
import {
Expand Down Expand Up @@ -323,10 +324,8 @@ export const prepareSettler = (
harden({ In: received }),
),
);
zcf.atomicRearrange(
harden([[settlingSeat, settlingSeat, { In: received }, split]]),
);
repayer.repay(settlingSeat, split);
const transferPart = fromOnly(settlingSeat, { In: received });
repayer.repay(transferPart, split);
settlingSeat.exit();

// update status manager, marking tx `DISBURSED`
Expand Down
32 changes: 10 additions & 22 deletions packages/fast-usdc/src/pool-share-math.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,35 +201,23 @@ export const borrowCalc = (

/**
* @param {ShareWorth} shareWorth
* @param {Allocation} fromSeatAllocation
* @param {RepayAmountKWR} amounts
* @param {RepayAmountKWR} split
* @param {Amount<'nat'>} encumberedBalance aka 'outstanding borrows'
* @param {PoolStats} poolStats
* @throws {Error} if allocations do not match amounts or Principal exceeds encumberedBalance
* @throws {Error} if Principal exceeds encumberedBalance
*/
export const repayCalc = (
shareWorth,
fromSeatAllocation,
amounts,
encumberedBalance,
poolStats,
) => {
(isEqual(fromSeatAllocation.Principal, amounts.Principal) &&
isEqual(fromSeatAllocation.PoolFee, amounts.PoolFee) &&
isEqual(fromSeatAllocation.ContractFee, amounts.ContractFee)) ||
Fail`Cannot repay. From seat allocation ${q(fromSeatAllocation)} does not equal amounts ${q(amounts)}.`;

isGTE(encumberedBalance, amounts.Principal) ||
Fail`Cannot repay. Principal ${q(amounts.Principal)} exceeds encumberedBalance ${q(encumberedBalance)}.`;
export const repayCalc = (shareWorth, split, encumberedBalance, poolStats) => {
isGTE(encumberedBalance, split.Principal) ||
Fail`Cannot repay. Principal ${q(split.Principal)} exceeds encumberedBalance ${q(encumberedBalance)}.`;

return harden({
shareWorth: withFees(shareWorth, amounts.PoolFee),
encumberedBalance: subtract(encumberedBalance, amounts.Principal),
shareWorth: withFees(shareWorth, split.PoolFee),
encumberedBalance: subtract(encumberedBalance, split.Principal),
poolStats: {
...poolStats,
totalRepays: add(poolStats.totalRepays, amounts.Principal),
totalPoolFees: add(poolStats.totalPoolFees, amounts.PoolFee),
totalContractFees: add(poolStats.totalContractFees, amounts.ContractFee),
totalRepays: add(poolStats.totalRepays, split.Principal),
totalPoolFees: add(poolStats.totalPoolFees, split.PoolFee),
totalContractFees: add(poolStats.totalContractFees, split.ContractFee),
},
});
};
22 changes: 7 additions & 15 deletions packages/fast-usdc/test/exos/settler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,8 @@ const makeTestContext = async t => {
};

const repayer = zone.exo('Repayer Mock', undefined, {
repay(fromSeat: ZCFSeat, amounts: AmountKeywordRecord) {
callLog.push(harden({ method: 'repay', fromSeat, amounts }));
repay(sourceTransfer: TransferPart, amounts: AmountKeywordRecord) {
callLog.push(harden({ method: 'repay', sourceTransfer, amounts }));
},
});

Expand Down Expand Up @@ -261,8 +261,8 @@ test('happy path: disburse to LPs; StatusManager removes tx', async t => {

t.log('Funds were disbursed to LP.');
const calls = peekCalls();
t.is(calls.length, 3);
const [withdraw, rearrange, repay] = calls;
t.is(calls.length, 2);
const [withdraw, repay] = calls;

t.deepEqual(
withdraw,
Expand All @@ -284,16 +284,6 @@ test('happy path: disburse to LPs; StatusManager removes tx', async t => {
Principal: usdc.make(146999999n),
});

t.like(
rearrange,
{ method: 'atomicRearrange' },
'2. settler called atomicRearrange ',
);
t.is(rearrange.parts.length, 1);
const [s1, s2, a1, a2] = rearrange.parts[0];
t.is(s1, s2, 'src and dest seat are the same');
t.deepEqual([a1, a2], [{ In }, expectedSplit]);

t.like(
repay,
{
Expand All @@ -302,7 +292,9 @@ test('happy path: disburse to LPs; StatusManager removes tx', async t => {
},
'3. settler called repay() on liquidity pool repayer facet',
);
t.is(repay.fromSeat, s1);
t.like(repay.sourceTransfer[2], {
In: usdc.units(150),
});

t.deepEqual(
statusManager.lookupPending(
Expand Down
56 changes: 9 additions & 47 deletions packages/fast-usdc/test/pool-share-math.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,15 +425,8 @@ test('basic repay calculation', t => {
};
const encumberedBalance = make(USDC, 200n);
const poolStats = makeInitialPoolStats();
const fromSeatAllocation = amounts;

const result = repayCalc(
shareWorth,
fromSeatAllocation,
amounts,
encumberedBalance,
poolStats,
);
const result = repayCalc(shareWorth, amounts, encumberedBalance, poolStats);

t.deepEqual(
result.encumberedBalance,
Expand Down Expand Up @@ -489,23 +482,13 @@ test('repay fails when principal exceeds encumbered balance', t => {

const fromSeatAllocation = amounts;

t.throws(
() =>
repayCalc(
shareWorth,
fromSeatAllocation,
amounts,
encumberedBalance,
poolStats,
),
{
message: /Cannot repay. Principal .* exceeds encumberedBalance/,
},
);
t.throws(() => repayCalc(shareWorth, amounts, encumberedBalance, poolStats), {
message: /Cannot repay. Principal .* exceeds encumberedBalance/,
});

t.notThrows(
() =>
repayCalc(shareWorth, fromSeatAllocation, amounts, make(USDC, 200n), {
repayCalc(shareWorth, amounts, make(USDC, 200n), {
...makeInitialPoolStats(),
totalBorrows: make(USDC, 200n),
}),
Expand All @@ -528,24 +511,9 @@ test('repay fails when seat allocation does not equal amounts', t => {
totalBorrows: make(USDC, 100n),
};

const fromSeatAllocation = {
...amounts,
ContractFee: make(USDC, 1n),
};

t.throws(
() =>
repayCalc(
shareWorth,
fromSeatAllocation,
amounts,
encumberedBalance,
poolStats,
),
{
message: /Cannot repay. From seat allocation .* does not equal amounts/,
},
);
t.throws(() => repayCalc(shareWorth, amounts, encumberedBalance, poolStats), {
message: /Cannot repay. Principal .* exceeds encumberedBalance/,
});
});

test('repay succeeds with no Pool or Contract Fee', t => {
Expand All @@ -563,13 +531,7 @@ test('repay succeeds with no Pool or Contract Fee', t => {
totalBorrows: make(USDC, 100n),
};
const fromSeatAllocation = amounts;
const actual = repayCalc(
shareWorth,
fromSeatAllocation,
amounts,
encumberedBalance,
poolStats,
);
const actual = repayCalc(shareWorth, amounts, encumberedBalance, poolStats);
t.like(actual, {
shareWorth,
encumberedBalance: {
Expand Down

0 comments on commit f01f6b4

Please sign in to comment.