Skip to content

Commit

Permalink
chore(fast-usdc): prune testBorrow, testRepay methods (#10607)
Browse files Browse the repository at this point in the history
closes: #10388
refs: #10511

## Description

Prune `testBorrow`, `testRepay` methods along with contract tests that depend on them.

 - Several tests are largely covered by share-pool-math tests
   - 1 was not, so I made a pool-share-math test to cover _repay succeeds with no Pool or Contract Fee_.
 - Several tests basically checked the interface guards of `lp.borrower.borrow` / `lp.repayer.repay`.
    These are internal APIs with static types.
   - testing consistency between interface guards and static types
     might have some value, but, I suggest, not enough for the cost

### Scaling / Documentation / Upgrade Considerations

none

### Security / Testing Considerations

small loss in test coverage - mostly in test redundancy

`makeTestPushInvitation` remains on the public facet. Getting rid of it in due course remains critical.
I expect / hope we can get rid of it in #10606 (cc @samsiegart ) .

Ideally, the liquidity-pool exo would have unit test coverage. I looked into that but found that I would have to build substantial ZCF / Zoe test tooling. Since `liquidity-pool.js` is just 360 lines of straightforward Zoe API usage, I suggest we postpone that under...

 - #10558
  • Loading branch information
mergify[bot] authored Dec 3, 2024
2 parents ef82fbf + d96a45e commit 11c895a
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 268 deletions.
44 changes: 5 additions & 39 deletions packages/fast-usdc/src/fast-usdc.contract.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { AssetKind } from '@agoric/ertp';
import {
assertAllDefined,
deeplyFulfilledObject,
makeTracer,
} from '@agoric/internal';
import { assertAllDefined, makeTracer } from '@agoric/internal';
import { observeIteration, subscribeEach } from '@agoric/notifier';
import {
CosmosChainInfoShape,
Expand All @@ -12,20 +8,19 @@ import {
registerChainsAndAssets,
withOrchestration,
} from '@agoric/orchestration';
import { makeZoeTools } from '@agoric/orchestration/src/utils/zoe-tools.js';
import { provideSingleton } from '@agoric/zoe/src/contractSupport/durability.js';
import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js';
import { makeZoeTools } from '@agoric/orchestration/src/utils/zoe-tools.js';
import { depositToSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js';
import { E } from '@endo/far';
import { M, objectMap } from '@endo/patterns';
import { M } from '@endo/patterns';
import { prepareAdvancer } from './exos/advancer.js';
import { prepareLiquidityPoolKit } from './exos/liquidity-pool.js';
import { prepareSettler } from './exos/settler.js';
import { prepareStatusManager } from './exos/status-manager.js';
import { prepareTransactionFeedKit } from './exos/transaction-feed.js';
import { defineInertInvitation } from './utils/zoe.js';
import { FastUSDCTermsShape, FeeConfigShape } from './type-guards.js';
import * as flows from './fast-usdc.flows.js';
import { FastUSDCTermsShape, FeeConfigShape } from './type-guards.js';
import { defineInertInvitation } from './utils/zoe.js';

const trace = makeTracer('FastUsdc');

Expand Down Expand Up @@ -151,35 +146,6 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
async makeOperatorInvitation(operatorId) {
return feedKit.creator.makeOperatorInvitation(operatorId);
},
/**
* @param {{ USDC: Amount<'nat'>}} amounts
*/
testBorrow(amounts) {
console.log('🚧🚧 UNTIL: borrow is integrated (#10388) 🚧🚧', amounts);
const { zcfSeat: tmpAssetManagerSeat } = zcf.makeEmptySeatKit();
poolKit.borrower.borrow(tmpAssetManagerSeat, amounts);
return tmpAssetManagerSeat.getCurrentAllocation();
},
/**
*
* @param {RepayAmountKWR} amounts
* @param {RepayPaymentKWR} payments
* @returns {Promise<AmountKeywordRecord>}
*/
async testRepay(amounts, payments) {
console.log('🚧🚧 UNTIL: repay is integrated (#10388) 🚧🚧', amounts);
const { zcfSeat: tmpAssetManagerSeat } = zcf.makeEmptySeatKit();
await depositToSeat(
zcf,
tmpAssetManagerSeat,
await deeplyFulfilledObject(
objectMap(payments, pmt => E(terms.issuers.USDC).getAmountOf(pmt)),
),
payments,
);
poolKit.repayer.repay(tmpAssetManagerSeat, amounts);
return tmpAssetManagerSeat.getCurrentAllocation();
},
});

const publicFacet = zone.exo('Fast USDC Public', undefined, {
Expand Down
229 changes: 0 additions & 229 deletions packages/fast-usdc/test/fast-usdc.contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,235 +317,6 @@ const makeLP = async (
return me;
};

test.skip('LP borrow - TODO: move to exo test', async t => {
const common = await commonSetup(t);
const {
brands: { usdc },
utils,
} = common;

const { instance, creatorFacet, zoe, metricsSub, terms } =
await startContract(common);

const usdcPurse = purseOf(terms.issuers.USDC, utils);
const lps = {
alice: makeLP('Alice', usdcPurse(100n), zoe, instance),
};
// seed pool with funds
await E(lps.alice).deposit(t, 100n);

const { value } = await E(metricsSub).getUpdateSince();
const { shareWorth, encumberedBalance } = value;
const poolSeatAllocation = subtract(
subtract(shareWorth.numerator, encumberedBalance),
usdc.make(1n),
);
t.log('Attempting to borrow entire pool seat allocation', poolSeatAllocation);
await t.throwsAsync(
E(creatorFacet).testBorrow({ USDC: poolSeatAllocation }),
{
message: /Cannot borrow/,
},
'borrow fails when requested equals pool seat allocation',
);

await t.throwsAsync(
E(creatorFacet).testBorrow({ USDC: usdc.make(200n) }),
{
message: /Cannot borrow/,
},
'borrow fails when requested exceeds pool seat allocation',
);

await t.throwsAsync(E(creatorFacet).testBorrow({ USDC: usdc.make(0n) }), {
message: /arg 1: USDC: value: "\[0n\]" - Must be >= "\[1n\]"/,
});

await t.throwsAsync(
E(creatorFacet).testBorrow(
// @ts-expect-error intentionally incorrect KW
{ Fee: usdc.make(1n) },
),
{
message: /Must have missing properties \["USDC"\]/,
},
);

// LPs can still withdraw (contract did not shutdown)
await E(lps.alice).withdraw(t, 0.5);

const amt = await E(creatorFacet).testBorrow({ USDC: usdc.make(30n) });
t.deepEqual(amt, { USDC: usdc.make(30n) }, 'borrow succeeds');

await eventLoopIteration();
t.like(await E(metricsSub).getUpdateSince(), {
value: {
encumberedBalance: {
value: 30n,
},
totalBorrows: {
value: 30n,
},
totalRepays: {
value: 0n,
},
},
});
});

test.skip('LP repay - TODO: move to exo test', async t => {
const common = await commonSetup(t);
const {
commonPrivateArgs,
brands: { usdc },
utils,
} = common;

const { instance, creatorFacet, zoe, metricsSub, terms } =
await startContract(common);
const usdcPurse = purseOf(terms.issuers.USDC, utils);
const lps = {
alice: makeLP('Alice', usdcPurse(100n), zoe, instance),
};
// seed pool with funds
await E(lps.alice).deposit(t, 100n);

// borrow funds from pool to increase encumbered balance
await E(creatorFacet).testBorrow({ USDC: usdc.make(50n) });
const feeTools = makeFeeTools(commonPrivateArgs.feeConfig);
{
t.log('cannot repay more than encumbered balance');
const repayAmounts = feeTools.calculateSplit(usdc.make(100n));
const repayPayments = await deeplyFulfilledObject(
objectMap(repayAmounts, utils.pourPayment),
);
await t.throwsAsync(
E(creatorFacet).testRepay(repayAmounts, repayPayments),
{
message: /Cannot repay. Principal .* exceeds encumberedBalance/,
},
);
}

{
const pmt = utils.pourPayment(usdc.make(50n));
await t.throwsAsync(
E(creatorFacet).testRepay(
// @ts-expect-error intentionally incorrect KWR
{ USDC: usdc.make(50n) },
{ USDC: pmt },
),
{
message:
/Must have missing properties \["Principal","PoolFee","ContractFee"\]/,
},
);
}
{
const pmt = utils.pourPayment(usdc.make(50n));
await t.throwsAsync(
E(creatorFacet).testRepay(
// @ts-expect-error intentionally incorrect KWR
{ Principal: usdc.make(50n) },
{ Principal: pmt },
),
{
message: /Must have missing properties \["PoolFee","ContractFee"\]/,
},
);
}
{
const amts = {
Principal: usdc.make(0n),
ContractFee: usdc.make(0n),
PoolFee: usdc.make(0n),
};
const pmts = await deeplyFulfilledObject(
objectMap(amts, utils.pourPayment),
);
await t.throwsAsync(E(creatorFacet).testRepay(amts, pmts), {
message: /arg 1: Principal: value: "\[0n\]" - Must be >= "\[1n\]"/,
});
}

{
t.log('repay fails when amounts do not match seat allocation');
const amts = {
Principal: usdc.make(25n),
ContractFee: usdc.make(1n),
PoolFee: usdc.make(2n),
};
const pmts = await deeplyFulfilledObject(
harden({
Principal: utils.pourPayment(usdc.make(24n)),
ContractFee: utils.pourPayment(usdc.make(1n)),
PoolFee: utils.pourPayment(usdc.make(2n)),
}),
);
await t.throwsAsync(E(creatorFacet).testRepay(amts, pmts), {
message: /Cannot repay. From seat allocation .* does not equal amounts/,
});
}

{
t.log('repay succeeds with no Pool or Contract Fee');
const amts = {
Principal: usdc.make(25n),
ContractFee: usdc.make(0n),
PoolFee: usdc.make(0n),
};
const pmts = await deeplyFulfilledObject(
objectMap(amts, utils.pourPayment),
);
const repayResult = await E(creatorFacet).testRepay(amts, pmts);

for (const r of Object.values(repayResult)) {
t.is(r.value, 0n, 'testRepay consumes all payments');
}
}

const amts = {
Principal: usdc.make(25n),
ContractFee: usdc.make(1n),
PoolFee: usdc.make(2n),
};
const pmts = await deeplyFulfilledObject(objectMap(amts, utils.pourPayment));
const repayResult = await E(creatorFacet).testRepay(amts, pmts);

for (const r of Object.values(repayResult)) {
t.is(r.value, 0n, 'testRepay consumes all payments');
}

await eventLoopIteration();
t.like(await E(metricsSub).getUpdateSince(), {
value: {
encumberedBalance: {
value: 0n,
},
totalBorrows: {
value: 50n,
},
totalRepays: {
value: 50n,
},
totalContractFees: {
value: 1n,
},
totalPoolFees: {
value: 2n,
},
shareWorth: {
numerator: {
value: 103n, // 100n (alice lp) + 1n (dust) + 2n (pool fees)
},
},
},
});

// LPs can still withdraw (contract did not shutdown)
await E(lps.alice).withdraw(t, 1);
});

const makeEVM = (template = MockCctpTxEvidences.AGORIC_PLUS_OSMO()) => {
const [settleAddr] = template.aux.recipientAddress.split('?');
let nonce = 0;
Expand Down
30 changes: 30 additions & 0 deletions packages/fast-usdc/test/pool-share-math.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,3 +494,33 @@ test('repay fails when seat allocation does not equal amounts', t => {
},
);
});

test('repay succeeds with no Pool or Contract Fee', t => {
const { USDC } = brands;
const encumberedBalance = make(USDC, 100n);
const shareWorth = makeParity(make(USDC, 1n), brands.PoolShares);

const amounts = {
Principal: make(USDC, 25n),
ContractFee: make(USDC, 0n),
PoolFee: make(USDC, 0n),
};
const poolStats = {
...makeInitialPoolStats(),
totalBorrows: make(USDC, 100n),
};
const fromSeatAllocation = amounts;
const actual = repayCalc(
shareWorth,
fromSeatAllocation,
amounts,
encumberedBalance,
poolStats,
);
t.like(actual, {
shareWorth,
encumberedBalance: {
value: 75n,
},
});
});

0 comments on commit 11c895a

Please sign in to comment.