From d5aeadae53c4488b7b4995d181a27428f2565c4c Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Tue, 19 Nov 2024 22:04:48 -0500 Subject: [PATCH 1/4] test: `FeeTools` against `MockCctpTxEvidences` - document `MockCctpTxEvidences` with `FeeConfig` used `commonPrivateArgs.feeConfig` (contract tests) - increase `makeTestFeeConfig` `maxVariable` value so fees are more visible in tests - add `debugString` to `feeToolsScenario` macro for easier maintenance --- packages/fast-usdc/test/exos/advancer.test.ts | 2 +- packages/fast-usdc/test/fixtures.ts | 2 +- packages/fast-usdc/test/mocks.ts | 2 +- packages/fast-usdc/test/utils/fees.test.ts | 45 +++++++++++++++++-- 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/packages/fast-usdc/test/exos/advancer.test.ts b/packages/fast-usdc/test/exos/advancer.test.ts index dc23436af50..b3ca8f0e050 100644 --- a/packages/fast-usdc/test/exos/advancer.test.ts +++ b/packages/fast-usdc/test/exos/advancer.test.ts @@ -194,7 +194,7 @@ test('updates status to OBSERVED on insufficient pool funds', async t => { t.deepEqual(inspectLogs(0), [ 'Insufficient pool funds', - 'Requested {"brand":"[Alleged: USDC brand]","value":"[199999899n]"} but only have {"brand":"[Alleged: USDC brand]","value":"[1n]"}', + 'Requested {"brand":"[Alleged: USDC brand]","value":"[294999999n]"} but only have {"brand":"[Alleged: USDC brand]","value":"[1n]"}', ]); }); diff --git a/packages/fast-usdc/test/fixtures.ts b/packages/fast-usdc/test/fixtures.ts index fff1f942d96..0a520918906 100644 --- a/packages/fast-usdc/test/fixtures.ts +++ b/packages/fast-usdc/test/fixtures.ts @@ -43,7 +43,7 @@ export const MockCctpTxEvidences: Record< txHash: '0xd81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761799', tx: { - amount: 200000000n, + amount: 300000000n, forwardingAddress: 'noble1x0ydg69dh6fqvr27xjvp6maqmrldam6yfelktz', }, aux: { diff --git a/packages/fast-usdc/test/mocks.ts b/packages/fast-usdc/test/mocks.ts index 16db2a5d125..ad82a2665cf 100644 --- a/packages/fast-usdc/test/mocks.ts +++ b/packages/fast-usdc/test/mocks.ts @@ -69,6 +69,6 @@ export const makeTestFeeConfig = (usdc: Omit): FeeConfig => harden({ flat: usdc.make(1n), variableRate: makeRatio(2n, usdc.brand), - maxVariable: usdc.make(100n), + maxVariable: usdc.units(5), contractRate: makeRatio(20n, usdc.brand), }); diff --git a/packages/fast-usdc/test/utils/fees.test.ts b/packages/fast-usdc/test/utils/fees.test.ts index 5da214a8e26..07004b3b125 100644 --- a/packages/fast-usdc/test/utils/fees.test.ts +++ b/packages/fast-usdc/test/utils/fees.test.ts @@ -1,8 +1,12 @@ import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import { makeIssuerKit, AmountMath } from '@agoric/ertp'; import { makeRatioFromAmounts } from '@agoric/zoe/src/contractSupport/ratio.js'; +import { withAmountUtils } from '@agoric/zoe/tools/test-utils.js'; +import { q } from '@endo/errors'; import { makeFeeTools } from '../../src/utils/fees.js'; import type { FeeConfig } from '../../src/types.js'; +import { makeTestFeeConfig } from '../mocks.js'; +import { MockCctpTxEvidences } from '../fixtures.js'; const { add, isEqual } = AmountMath; @@ -44,17 +48,18 @@ const feeToolsScenario = test.macro({ { config = aFeeConfig, requested, expected }: FeelToolsScenario, ) => { const { totalFee, advance, split } = expected; + const feeTools = makeFeeTools(harden(config)); + const debugString = `expected:\n${q(feeTools.calculateSplit(requested)).toString()}`; t.true( isEqual(totalFee, add(split.ContractFee, split.PoolFee)), - 'sanity check: total fee equals sum of splits', + `sanity check: total fee equals sum of splits. ${debugString}`, ); t.true( isEqual(requested, add(totalFee, advance)), - 'sanity check: requested equals advance plus fee', + `sanity check: requested equals advance plus fee. ${debugString}`, ); - const feeTools = makeFeeTools(harden(config)); t.deepEqual(feeTools.calculateAdvanceFee(requested), totalFee); t.deepEqual(feeTools.calculateAdvance(requested), advance); t.deepEqual(feeTools.calculateSplit(requested), { @@ -161,7 +166,39 @@ test(feeToolsScenario, { }, }); -test.only('request must exceed fees', t => { +test(feeToolsScenario, { + name: 'AGORIC_PLUS_OSMO with commonPrivateArgs.feeConfig', + // 150_000_000n + requested: USDC(MockCctpTxEvidences.AGORIC_PLUS_OSMO().tx.amount), + // same as commonPrivateArgs.feeConfig from `CommonSetup` + config: makeTestFeeConfig(withAmountUtils(issuerKits.USDC)), + expected: { + totalFee: USDC(3000001n), // 1n + min(2% of 150USDC, 5USDC) + advance: USDC(146999999n), + split: { + ContractFee: USDC(600000n), // 20% of fee + PoolFee: USDC(2400001n), + }, + }, +}); + +test(feeToolsScenario, { + name: 'AGORIC_PLUS_DYDX with commonPrivateArgs.feeConfig', + // 300_000_000n + requested: USDC(MockCctpTxEvidences.AGORIC_PLUS_DYDX().tx.amount), + // same as commonPrivateArgs.feeConfig from `CommonSetup` + config: makeTestFeeConfig(withAmountUtils(issuerKits.USDC)), + expected: { + totalFee: USDC(5000001n), // 1n + min(2% of 300USDC, 5USDC) + advance: USDC(294999999n), + split: { + ContractFee: USDC(1000000n), // 20% of fee + PoolFee: USDC(4000001n), + }, + }, +}); + +test('request must exceed fees', t => { const feeTools = makeFeeTools(aFeeConfig); const expectedError = { message: 'Request must exceed fees.' }; From 35eb7ad48377f11dab8c717c442653f99587a816 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Tue, 19 Nov 2024 22:09:36 -0500 Subject: [PATCH 2/4] feat: `Advancer` uses `borrower` facet --- packages/fast-usdc/src/exos/advancer.js | 114 ++++++++++-------- packages/fast-usdc/src/fast-usdc.contract.js | 4 + packages/fast-usdc/test/exos/advancer.test.ts | 77 +++++++++--- 3 files changed, 126 insertions(+), 69 deletions(-) diff --git a/packages/fast-usdc/src/exos/advancer.js b/packages/fast-usdc/src/exos/advancer.js index e772f2ecb4a..9c5ffa536fe 100644 --- a/packages/fast-usdc/src/exos/advancer.js +++ b/packages/fast-usdc/src/exos/advancer.js @@ -1,4 +1,4 @@ -import { AmountMath, AmountShape, PaymentShape } from '@agoric/ertp'; +import { AmountMath, AmountShape } from '@agoric/ertp'; import { assertAllDefined } from '@agoric/internal'; import { ChainAddressShape } from '@agoric/orchestration'; import { pickFacet } from '@agoric/vat-data'; @@ -15,31 +15,25 @@ const { isGTE } = AmountMath; /** * @import {HostInterface} from '@agoric/async-flow'; * @import {NatAmount} from '@agoric/ertp'; - * @import {ChainAddress, ChainHub, Denom, DenomAmount, OrchestrationAccount} from '@agoric/orchestration'; + * @import {ChainAddress, ChainHub, Denom, OrchestrationAccount} from '@agoric/orchestration'; + * @import {ZoeTools} from '@agoric/orchestration/src/utils/zoe-tools.js'; * @import {VowTools} from '@agoric/vow'; * @import {Zone} from '@agoric/zone'; * @import {CctpTxEvidence, FeeConfig, LogFn} from '../types.js'; * @import {StatusManager} from './status-manager.js'; - */ - -/** - * Expected interface from LiquidityPool - * - * @typedef {{ - * lookupBalance(): NatAmount; - * borrow(amount: Amount<"nat">): Promise>; - * repay(payments: PaymentKeywordRecord): Promise - * }} AssetManagerFacet + * @import {LiquidityPoolKit} from './liquidity-pool.js'; */ /** * @typedef {{ * chainHub: ChainHub; * feeConfig: FeeConfig; + * localTransfer: ZoeTools['localTransfer']; * log: LogFn; * statusManager: StatusManager; * usdc: { brand: Brand<'nat'>; denom: Denom; }; * vowTools: VowTools; + * zcf: ZCF; * }} AdvancerKitPowers */ @@ -49,13 +43,15 @@ const AdvancerKitI = harden({ handleTransactionEvent: M.callWhen(CctpTxEvidenceShape).returns(), }), depositHandler: M.interface('DepositHandlerI', { - onFulfilled: M.call(AmountShape, { + onFulfilled: M.call(M.undefined(), { + amount: AmountShape, destination: ChainAddressShape, - payment: PaymentShape, + tmpSeat: M.remotable(), }).returns(VowShape), onRejected: M.call(M.error(), { + amount: AmountShape, destination: ChainAddressShape, - payment: PaymentShape, + tmpSeat: M.remotable(), }).returns(), }), transferHandler: M.interface('TransferHandlerI', { @@ -77,7 +73,16 @@ const AdvancerKitI = harden({ */ export const prepareAdvancerKit = ( zone, - { chainHub, feeConfig, log, statusManager, usdc, vowTools: { watch, when } }, + { + chainHub, + feeConfig, + localTransfer, + log, + statusManager, + usdc, + vowTools: { watch, when }, + zcf, + }, ) => { assertAllDefined({ chainHub, @@ -95,7 +100,7 @@ export const prepareAdvancerKit = ( AdvancerKitI, /** * @param {{ - * assetManagerFacet: AssetManagerFacet; + * borrowerFacet: LiquidityPoolKit['borrower']; * poolAccount: ERef>>; * }} config */ @@ -115,8 +120,7 @@ export const prepareAdvancerKit = ( async handleTransactionEvent(evidence) { await null; try { - // TODO poolAccount might be a vow we need to unwrap - const { assetManagerFacet, poolAccount } = this.state; + const { borrowerFacet, poolAccount } = this.state; const { recipientAddress } = evidence.aux; const { EUD } = addressTools.getQueryParams( recipientAddress, @@ -129,14 +133,12 @@ export const prepareAdvancerKit = ( const advanceAmount = feeTools.calculateAdvance(requestedAmount); // TODO: consider skipping and using `borrow()`s internal balance check - const poolBalance = assetManagerFacet.lookupBalance(); + const poolBalance = borrowerFacet.getBalance(); if (!isGTE(poolBalance, requestedAmount)) { log( `Insufficient pool funds`, `Requested ${q(advanceAmount)} but only have ${q(poolBalance)}`, ); - // report `requestedAmount`, not `advancedAmount`... do we need to - // communicate net to `StatusManger` in case fees change in between? statusManager.observe(evidence); return; } @@ -152,19 +154,29 @@ export const prepareAdvancerKit = ( return; } + const { zcfSeat: tmpSeat } = zcf.makeEmptySeatKit(); + const amountKWR = harden({ USDC: advanceAmount }); try { - const payment = await assetManagerFacet.borrow(advanceAmount); - const depositV = E(poolAccount).deposit(payment); - void watch(depositV, this.facets.depositHandler, { - destination, - payment, - }); + borrowerFacet.borrow(tmpSeat, amountKWR); } catch (e) { - // `.borrow()` might fail if the balance changes since we - // requested it. TODO - how to handle this? change ADVANCED -> OBSERVED? - // Note: `depositHandler` handles the `.deposit()` failure + // We do not expect this to fail since there are no turn boundaries + // between .getBalance() and .borrow(). + // We catch to report outside of the normal error flow since this is + // not expected. log('🚨 advance borrow failed', q(e).toString()); } + + const depositV = localTransfer( + tmpSeat, + // @ts-expect-error LocalAccountMethods vs OrchestrationAccount + poolAccount, + amountKWR, + ); + void watch(depositV, this.facets.depositHandler, { + amount: advanceAmount, + destination, + tmpSeat, + }); } catch (e) { log('Advancer error:', q(e).toString()); statusManager.observe(evidence); @@ -173,18 +185,16 @@ export const prepareAdvancerKit = ( }, depositHandler: { /** - * @param {NatAmount} amount amount returned from deposit - * @param {{ destination: ChainAddress; payment: Payment<'nat'> }} ctx + * @param {undefined} result + * @param {{ amount: Amount<'nat'>; destination: ChainAddress; tmpSeat: ZCFSeat }} ctx */ - onFulfilled(amount, { destination }) { + onFulfilled(result, { amount, destination }) { + // TODO do we need to ensure this isn't Vow for an LOA? const { poolAccount } = this.state; - const transferV = E(poolAccount).transfer( - destination, - /** @type {DenomAmount} */ ({ - denom: usdc.denom, - value: amount.value, - }), - ); + const transferV = E(poolAccount).transfer(destination, { + denom: usdc.denom, + value: amount.value, + }); return watch(transferV, this.facets.transferHandler, { destination, amount, @@ -192,12 +202,17 @@ export const prepareAdvancerKit = ( }, /** * @param {Error} error - * @param {{ destination: ChainAddress; payment: Payment<'nat'> }} ctx + * @param {{ amount: Amount<'nat'>; destination: ChainAddress; tmpSeat: ZCFSeat }} ctx */ - onRejected(error, { payment }) { - // TODO return live payment from ctx to LP + onRejected(error, { tmpSeat }) { + // TODO return seat allocation from ctx to LP? log('🚨 advance deposit failed', q(error).toString()); - log('TODO live payment to return to LP', q(payment).toString()); + // TODO #10510 (comprehensive error testing) determine + // course of action here + log( + 'TODO live payment on seat to return to LP', + q(tmpSeat).toString(), + ); }, }, transferHandler: { @@ -206,22 +221,23 @@ export const prepareAdvancerKit = ( * @param {{ destination: ChainAddress; amount: NatAmount; }} ctx */ onFulfilled(result, { destination, amount }) { - // TODO vstorage update? + // TODO vstorage update? We don't currently have a status for + // Advanced + transferV settled log( 'Advance transfer fulfilled', q({ amount, destination, result }).toString(), ); }, onRejected(error) { - // XXX retry logic? - // What do we do if we fail, should we keep a Status? + // TODO #10510 (comprehensive error testing) determine + // course of action here. This might fail due to timeout. log('Advance transfer rejected', q(error).toString()); }, }, }, { stateShape: harden({ - assetManagerFacet: M.remotable(), + borrowerFacet: M.remotable(), poolAccount: M.or(VowShape, M.remotable()), }), }, diff --git a/packages/fast-usdc/src/fast-usdc.contract.js b/packages/fast-usdc/src/fast-usdc.contract.js index 80b6602e35c..179cbf82c2b 100644 --- a/packages/fast-usdc/src/fast-usdc.contract.js +++ b/packages/fast-usdc/src/fast-usdc.contract.js @@ -14,6 +14,7 @@ import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/record import { E } from '@endo/far'; import { M, objectMap } from '@endo/patterns'; import { depositToSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js'; +import { makeZoeTools } from '@agoric/orchestration/src/utils/zoe-tools.js'; import { prepareAdvancer } from './exos/advancer.js'; import { prepareLiquidityPoolKit } from './exos/liquidity-pool.js'; import { prepareSettler } from './exos/settler.js'; @@ -74,6 +75,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => { const statusManager = prepareStatusManager(zone); const makeSettler = prepareSettler(zone, { statusManager }); const { chainHub, vowTools } = tools; + const zoeTools = makeZoeTools(zcf, vowTools); const makeAdvancer = prepareAdvancer(zone, { chainHub, feeConfig, @@ -84,6 +86,8 @@ export const contract = async (zcf, privateArgs, zone, tools) => { }), statusManager, vowTools, + zcf, + zoeTools, }); const makeFeedKit = prepareTransactionFeedKit(zone, zcf); assertAllDefined({ makeFeedKit, makeAdvancer, makeSettler, statusManager }); diff --git a/packages/fast-usdc/test/exos/advancer.test.ts b/packages/fast-usdc/test/exos/advancer.test.ts index b3ca8f0e050..fb21deabf86 100644 --- a/packages/fast-usdc/test/exos/advancer.test.ts +++ b/packages/fast-usdc/test/exos/advancer.test.ts @@ -7,6 +7,7 @@ import fetchedChainInfo from '@agoric/orchestration/src/fetched-chain-info.js'; import { Far } from '@endo/pass-style'; import { makePromiseKit } from '@endo/promise-kit'; import type { NatAmount } from '@agoric/ertp'; +import { type ZoeTools } from '@agoric/orchestration/src/utils/zoe-tools.js'; import { PendingTxStatus } from '../../src/constants.js'; import { prepareAdvancer } from '../../src/exos/advancer.js'; import { prepareStatusManager } from '../../src/exos/status-manager.js'; @@ -50,10 +51,27 @@ const createTestExtensions = (t, common: CommonSetup) => { usdc, }); + const mockZCF = Far('MockZCF', { + makeEmptySeatKit: () => ({ zcfSeat: Far('MockZCFSeat', {}) }), + }); + + const localTransferVK = vowTools.makeVowKit(); + const resolveLocalTransferV = () => { + // pretend funds move from tmpSeat to poolAccount + localTransferVK.resolver.resolve(); + }; + const mockZoeTools = Far('MockZoeTools', { + localTransfer(...args: Parameters) { + console.log('ZoeTools.localTransfer called with', args); + return localTransferVK.vow; + }, + }); + const feeConfig = makeTestFeeConfig(usdc); const makeAdvancer = prepareAdvancer(rootZone.subZone('advancer'), { chainHub, feeConfig, + localTransfer: mockZoeTools.localTransfer, log, statusManager, usdc: harden({ @@ -61,6 +79,8 @@ const createTestExtensions = (t, common: CommonSetup) => { denom: LOCAL_DENOM, }), vowTools, + // @ts-expect-error mocked zcf + zcf: mockZCF, }); /** pretend we have 1M USDC in pool deposits */ @@ -73,19 +93,19 @@ const createTestExtensions = (t, common: CommonSetup) => { mockPoolBalance = usdc.make(value); }; - const borrowUnderlyingPK = makePromiseKit>(); - const resolveBorrowUnderlyingP = async (amount: Amount<'nat'>) => { - const pmt = await pourPayment(amount); - return borrowUnderlyingPK.resolve(pmt); + const borrowUnderlyingPK = makePromiseKit(); + const resolveBorrowUnderlyingP = () => { + // pretend funds are allocated to tmpSeat provided to borrow + return borrowUnderlyingPK.resolve(); }; const rejectBorrowUnderlyingP = () => borrowUnderlyingPK.reject('Mock unable to borrow.'); const advancer = makeAdvancer({ - assetManagerFacet: Far('AssetManager', { - lookupBalance: () => mockPoolBalance, - borrow: (amount: NatAmount) => { - t.log('borrowUnderlying called with', amount); + borrowerFacet: Far('LiquidityPool Borrow Facet', { + getBalance: () => mockPoolBalance, + borrow: (seat: ZCFSeat, amounts: { USDC: NatAmount }) => { + t.log('borrowUnderlying called with', amounts); return borrowUnderlyingPK.promise; }, repay: () => Promise.resolve(), @@ -106,6 +126,7 @@ const createTestExtensions = (t, common: CommonSetup) => { setMockPoolBalance, resolveBorrowUnderlyingP, rejectBorrowUnderlyingP, + resolveLocalTransferV, }, services: { advancer, @@ -134,7 +155,11 @@ test('updates status to ADVANCED in happy path', async t => { extensions: { services: { advancer, statusManager }, helpers: { inspectLogs }, - mocks: { mockPoolAccount, resolveBorrowUnderlyingP }, + mocks: { + mockPoolAccount, + resolveBorrowUnderlyingP, + resolveLocalTransferV, + }, }, brands: { usdc }, } = t.context; @@ -142,7 +167,8 @@ test('updates status to ADVANCED in happy path', async t => { const mockEvidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); const handleTxP = advancer.handleTransactionEvent(mockEvidence); - await resolveBorrowUnderlyingP(usdc.make(mockEvidence.tx.amount)); + resolveBorrowUnderlyingP(); + resolveLocalTransferV(); await eventLoopIteration(); mockPoolAccount.transferVResolver.resolve(); @@ -162,7 +188,7 @@ test('updates status to ADVANCED in happy path', async t => { t.deepEqual(inspectLogs(0), [ 'Advance transfer fulfilled', - '{"amount":{"brand":"[Alleged: USDC brand]","value":"[150000000n]"},"destination":{"chainId":"osmosis-1","encoding":"bech32","value":"osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men"},"result":"[undefined]"}', + '{"amount":{"brand":"[Alleged: USDC brand]","value":"[146999999n]"},"destination":{"chainId":"osmosis-1","encoding":"bech32","value":"osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men"},"result":"[undefined]"}', ]); }); @@ -211,8 +237,8 @@ test('updates status to OBSERVED if balance query fails', async t => { // make a new advancer that intentionally throws const advancer = makeAdvancer({ // @ts-expect-error mock - assetManagerFacet: Far('AssetManager', { - lookupBalance: () => { + borrowerFacet: Far('LiquidityPool Borrow Facet', { + getBalance: () => { throw new Error('lookupBalance failed'); }, }), @@ -267,13 +293,17 @@ test('updates status to OBSERVED if makeChainAddress fails', async t => { ]); }); -// TODO, this failure should be handled differently +// TODO #10510 this failure should be handled differently test('does not update status on failed transfer', async t => { const { extensions: { services: { advancer, statusManager }, helpers: { inspectLogs }, - mocks: { mockPoolAccount, resolveBorrowUnderlyingP }, + mocks: { + mockPoolAccount, + resolveBorrowUnderlyingP, + resolveLocalTransferV, + }, }, brands: { usdc }, } = t.context; @@ -281,7 +311,8 @@ test('does not update status on failed transfer', async t => { const mockEvidence = MockCctpTxEvidences.AGORIC_PLUS_DYDX(); const handleTxP = advancer.handleTransactionEvent(mockEvidence); - await resolveBorrowUnderlyingP(usdc.make(mockEvidence.tx.amount)); + resolveBorrowUnderlyingP(); + resolveLocalTransferV(); mockPoolAccount.transferVResolver.reject(new Error('simulated error')); await handleTxP; @@ -340,23 +371,27 @@ test('will not advance same txHash:chainId evidence twice', async t => { extensions: { services: { advancer }, helpers: { inspectLogs }, - mocks: { mockPoolAccount, resolveBorrowUnderlyingP }, + mocks: { + mockPoolAccount, + resolveBorrowUnderlyingP, + resolveLocalTransferV, + }, }, - brands: { usdc }, } = t.context; const mockEvidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); // First attempt const handleTxP = advancer.handleTransactionEvent(mockEvidence); - await resolveBorrowUnderlyingP(usdc.make(mockEvidence.tx.amount)); + resolveBorrowUnderlyingP(); + resolveLocalTransferV(); mockPoolAccount.transferVResolver.resolve(); await handleTxP; await eventLoopIteration(); t.deepEqual(inspectLogs(0), [ 'Advance transfer fulfilled', - '{"amount":{"brand":"[Alleged: USDC brand]","value":"[150000000n]"},"destination":{"chainId":"osmosis-1","encoding":"bech32","value":"osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men"},"result":"[undefined]"}', + '{"amount":{"brand":"[Alleged: USDC brand]","value":"[146999999n]"},"destination":{"chainId":"osmosis-1","encoding":"bech32","value":"osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men"},"result":"[undefined]"}', ]); // Second attempt @@ -367,3 +402,5 @@ test('will not advance same txHash:chainId evidence twice', async t => { '"[Error: Transaction already seen: \\"seenTx:[\\\\\\"0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702\\\\\\",1]\\"]"', ]); }); + +test.todo('zoeTools.localTransfer fails to deposit borrowed USDC to LOA'); From c5d67af2b04808e6928b4c8c1e9fdda79c8ac847 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Tue, 19 Nov 2024 19:16:31 -0500 Subject: [PATCH 3/4] feat: integrate `Advancer` with contract --- packages/fast-usdc/src/exos/advancer.js | 13 ++-- packages/fast-usdc/src/fast-usdc.contract.js | 62 ++++++++++++------ packages/fast-usdc/src/fast-usdc.flows.js | 13 ++++ .../snapshots/fast-usdc.contract.test.ts.md | 37 +++++++++-- .../snapshots/fast-usdc.contract.test.ts.snap | Bin 1657 -> 2028 bytes 5 files changed, 93 insertions(+), 32 deletions(-) create mode 100644 packages/fast-usdc/src/fast-usdc.flows.js diff --git a/packages/fast-usdc/src/exos/advancer.js b/packages/fast-usdc/src/exos/advancer.js index 9c5ffa536fe..ceb4bc09ef8 100644 --- a/packages/fast-usdc/src/exos/advancer.js +++ b/packages/fast-usdc/src/exos/advancer.js @@ -1,5 +1,5 @@ import { AmountMath, AmountShape } from '@agoric/ertp'; -import { assertAllDefined } from '@agoric/internal'; +import { assertAllDefined, makeTracer } from '@agoric/internal'; import { ChainAddressShape } from '@agoric/orchestration'; import { pickFacet } from '@agoric/vat-data'; import { VowShape } from '@agoric/vow'; @@ -29,7 +29,7 @@ const { isGTE } = AmountMath; * chainHub: ChainHub; * feeConfig: FeeConfig; * localTransfer: ZoeTools['localTransfer']; - * log: LogFn; + * log?: LogFn; * statusManager: StatusManager; * usdc: { brand: Brand<'nat'>; denom: Denom; }; * vowTools: VowTools; @@ -77,12 +77,12 @@ export const prepareAdvancerKit = ( chainHub, feeConfig, localTransfer, - log, + log = makeTracer('Advancer', true), statusManager, usdc, vowTools: { watch, when }, zcf, - }, + } = /** @type {AdvancerKitPowers} */ ({}), ) => { assertAllDefined({ chainHub, @@ -101,7 +101,7 @@ export const prepareAdvancerKit = ( /** * @param {{ * borrowerFacet: LiquidityPoolKit['borrower']; - * poolAccount: ERef>>; + * poolAccount: HostInterface>; * }} config */ config => harden(config), @@ -189,7 +189,6 @@ export const prepareAdvancerKit = ( * @param {{ amount: Amount<'nat'>; destination: ChainAddress; tmpSeat: ZCFSeat }} ctx */ onFulfilled(result, { amount, destination }) { - // TODO do we need to ensure this isn't Vow for an LOA? const { poolAccount } = this.state; const transferV = E(poolAccount).transfer(destination, { denom: usdc.denom, @@ -238,7 +237,7 @@ export const prepareAdvancerKit = ( { stateShape: harden({ borrowerFacet: M.remotable(), - poolAccount: M.or(VowShape, M.remotable()), + poolAccount: M.remotable(), }), }, ); diff --git a/packages/fast-usdc/src/fast-usdc.contract.js b/packages/fast-usdc/src/fast-usdc.contract.js index 179cbf82c2b..7009585678d 100644 --- a/packages/fast-usdc/src/fast-usdc.contract.js +++ b/packages/fast-usdc/src/fast-usdc.contract.js @@ -11,10 +11,10 @@ import { } from '@agoric/orchestration'; 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 { depositToSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js'; -import { makeZoeTools } from '@agoric/orchestration/src/utils/zoe-tools.js'; import { prepareAdvancer } from './exos/advancer.js'; import { prepareLiquidityPoolKit } from './exos/liquidity-pool.js'; import { prepareSettler } from './exos/settler.js'; @@ -22,12 +22,16 @@ 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'; const trace = makeTracer('FastUsdc'); /** * @import {Denom} from '@agoric/orchestration'; + * @import {HostInterface} from '@agoric/async-flow'; + * @import {OrchestrationAccount} from '@agoric/orchestration'; * @import {OrchestrationPowers, OrchestrationTools} from '@agoric/orchestration/src/utils/start-helper.js'; + * @import {Vow} from '@agoric/vow'; * @import {Zone} from '@agoric/zone'; * @import {OperatorKit} from './exos/operator-kit.js'; * @import {CctpTxEvidence, FeeConfig} from './types.js'; @@ -74,12 +78,12 @@ export const contract = async (zcf, privateArgs, zone, tools) => { ); const statusManager = prepareStatusManager(zone); const makeSettler = prepareSettler(zone, { statusManager }); - const { chainHub, vowTools } = tools; - const zoeTools = makeZoeTools(zcf, vowTools); + const { chainHub, orchestrateAll, vowTools } = tools; + const { localTransfer } = makeZoeTools(zcf, vowTools); const makeAdvancer = prepareAdvancer(zone, { chainHub, feeConfig, - log: trace, + localTransfer, usdc: harden({ brand: terms.brands.USDC, denom: terms.usdcDenom, @@ -87,25 +91,9 @@ export const contract = async (zcf, privateArgs, zone, tools) => { statusManager, vowTools, zcf, - zoeTools, }); const makeFeedKit = prepareTransactionFeedKit(zone, zcf); assertAllDefined({ makeFeedKit, makeAdvancer, makeSettler, statusManager }); - const feedKit = makeFeedKit(); - const advancer = makeAdvancer( - // @ts-expect-error FIXME - {}, - ); - // Connect evidence stream to advancer - void observeIteration(subscribeEach(feedKit.public.getEvidenceSubscriber()), { - updateState(evidence) { - try { - void advancer.handleTransactionEvent(evidence); - } catch (err) { - trace('🚨 Error handling transaction event', err); - } - }, - }); const makeLiquidityPoolKit = prepareLiquidityPoolKit( zone, zcf, @@ -118,9 +106,12 @@ export const contract = async (zcf, privateArgs, zone, tools) => { 'test of forcing evidence', ); + const { makeLocalAccount } = orchestrateAll(flows, {}); + const creatorFacet = zone.exo('Fast USDC Creator', undefined, { /** @type {(operatorId: string) => Promise>} */ async makeOperatorInvitation(operatorId) { + // eslint-disable-next-line no-use-before-define return feedKit.creator.makeOperatorInvitation(operatorId); }, /** @@ -168,6 +159,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => { * @param {CctpTxEvidence} evidence */ makeTestPushInvitation(evidence) { + // eslint-disable-next-line no-use-before-define void advancer.handleTransactionEvent(evidence); return makeTestInvitation(); }, @@ -211,6 +203,34 @@ export const contract = async (zcf, privateArgs, zone, tools) => { makeLiquidityPoolKit(shareMint, privateArgs.storageNode), ); + const feedKit = zone.makeOnce('Feed Kit', () => makeFeedKit()); + + const poolAccountV = + // cast to HostInterface + /** @type { Vow>>} */ ( + /** @type {unknown}*/ ( + zone.makeOnce('Pool Local Orch Account', () => makeLocalAccount()) + ) + ); + const poolAccount = await vowTools.when(poolAccountV); + + const advancer = zone.makeOnce('Advancer', () => + makeAdvancer({ + borrowerFacet: poolKit.borrower, + poolAccount, + }), + ); + // Connect evidence stream to advancer + void observeIteration(subscribeEach(feedKit.public.getEvidenceSubscriber()), { + updateState(evidence) { + try { + void advancer.handleTransactionEvent(evidence); + } catch (err) { + trace('🚨 Error handling transaction event', err); + } + }, + }); + return harden({ creatorFacet, publicFacet }); }; harden(contract); diff --git a/packages/fast-usdc/src/fast-usdc.flows.js b/packages/fast-usdc/src/fast-usdc.flows.js new file mode 100644 index 00000000000..9f330a4c905 --- /dev/null +++ b/packages/fast-usdc/src/fast-usdc.flows.js @@ -0,0 +1,13 @@ +/** + * @import {Orchestrator, OrchestrationFlow} from '@agoric/orchestration'; + */ + +/** + * @satisfies {OrchestrationFlow} + * @param {Orchestrator} orch + */ +export const makeLocalAccount = async orch => { + const agoricChain = await orch.getChain('agoric'); + return agoricChain.makeAccount(); +}; +harden(makeLocalAccount); diff --git a/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.md b/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.md index 8421aa9cc3c..382c6399d37 100644 --- a/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.md +++ b/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.md @@ -30,9 +30,22 @@ Generated by [AVA](https://avajs.dev). chainHub: { ChainHub_kindHandle: 'Alleged: kind', ChainHub_singleton: 'Alleged: ChainHub', - bech32PrefixToChainName: {}, + bech32PrefixToChainName: { + agoric: 'agoric', + }, brandDenom: {}, - chainInfos: {}, + chainInfos: { + agoric: { + bech32Prefix: 'agoric', + chainId: 'agoric-3', + icqEnabled: false, + stakingTokens: [ + { + denom: 'ubld', + }, + ], + }, + }, connectionInfos: {}, denom: {}, lookupChainInfo_kindHandle: 'Alleged: kind', @@ -40,6 +53,7 @@ Generated by [AVA](https://avajs.dev). lookupConnectionInfo_kindHandle: 'Alleged: kind', }, contract: { + Advancer: 'Alleged: Fast USDC Advancer advancer', 'Fast USDC Advancer_kindHandle': 'Alleged: kind', 'Fast USDC Creator_kindHandle': 'Alleged: kind', 'Fast USDC Creator_singleton': 'Alleged: Fast USDC Creator', @@ -49,6 +63,11 @@ Generated by [AVA](https://avajs.dev). 'Fast USDC Settler_kindHandle': 'Alleged: kind', 'Fast USDC Status Manager_kindHandle': 'Alleged: kind', 'Fast USDC Status Manager_singleton': 'Alleged: Fast USDC Status Manager', + 'Feed Kit': { + creator: Object @Alleged: Fast USDC Feed creator {}, + operatorPowers: Object @Alleged: Fast USDC Feed operatorPowers {}, + public: Object @Alleged: Fast USDC Feed public {}, + }, Kinds: { 'Transaction Feed_kindHandle': 'Alleged: kind', }, @@ -63,12 +82,17 @@ Generated by [AVA](https://avajs.dev). 'Liquidity Pool_kindHandle': 'Alleged: kind', 'Operator Kit_kindHandle': 'Alleged: kind', PendingTxs: {}, + 'Pool Local Orch Account': 'Vow', SeenTxs: [], mint: { PoolShare: 'Alleged: zcfMint', }, operators: {}, - orchestration: {}, + orchestration: { + makeLocalAccount: { + asyncFlow_kindHandle: 'Alleged: kind', + }, + }, pending: {}, vstorage: { 'Durable Publish Kit_kindHandle': 'Alleged: kind', @@ -81,7 +105,12 @@ Generated by [AVA](https://avajs.dev). LocalChainFacade_kindHandle: 'Alleged: kind', Orchestrator_kindHandle: 'Alleged: kind', RemoteChainFacade_kindHandle: 'Alleged: kind', - chainName: {}, + chainName: { + agoric: { + pending: false, + value: Object @Alleged: LocalChainFacade public {}, + }, + }, ibcTools: { IBCTransferSenderKit_kindHandle: 'Alleged: kind', ibcResultWatcher_kindHandle: 'Alleged: kind', diff --git a/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.snap b/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.snap index 7658bda5e411311dc5ed7b0e841f69ef7dbd6102..de372333f822012eba0a396755e4eff2ea8585db 100644 GIT binary patch literal 2028 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 D;WZc8 From 7ec6fcff2fb4d0c4492dc327f59a6beebb51ad70 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Tue, 19 Nov 2024 18:53:43 -0500 Subject: [PATCH 4/4] test: `Advancer` contract test --- packages/fast-usdc/src/fast-usdc.contract.js | 4 +- .../fast-usdc/test/fast-usdc.contract.test.ts | 111 ++++++++++++++---- packages/fast-usdc/test/supports.ts | 8 +- 3 files changed, 97 insertions(+), 26 deletions(-) diff --git a/packages/fast-usdc/src/fast-usdc.contract.js b/packages/fast-usdc/src/fast-usdc.contract.js index 7009585678d..5e5a0466f5a 100644 --- a/packages/fast-usdc/src/fast-usdc.contract.js +++ b/packages/fast-usdc/src/fast-usdc.contract.js @@ -118,7 +118,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => { * @param {{ USDC: Amount<'nat'>}} amounts */ testBorrow(amounts) { - console.log('🚧🚧 UNTIL: borrow is integrated 🚧🚧', amounts); + console.log('🚧🚧 UNTIL: borrow is integrated (#10388) 🚧🚧', amounts); const { zcfSeat: tmpAssetManagerSeat } = zcf.makeEmptySeatKit(); // eslint-disable-next-line no-use-before-define poolKit.borrower.borrow(tmpAssetManagerSeat, amounts); @@ -131,7 +131,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => { * @returns {Promise} */ async testRepay(amounts, payments) { - console.log('🚧🚧 UNTIL: repay is integrated 🚧🚧', amounts); + console.log('🚧🚧 UNTIL: repay is integrated (#10388) 🚧🚧', amounts); const { zcfSeat: tmpAssetManagerSeat } = zcf.makeEmptySeatKit(); await depositToSeat( zcf, diff --git a/packages/fast-usdc/test/fast-usdc.contract.test.ts b/packages/fast-usdc/test/fast-usdc.contract.test.ts index 427ca2cf9d2..e697b9118da 100644 --- a/packages/fast-usdc/test/fast-usdc.contract.test.ts +++ b/packages/fast-usdc/test/fast-usdc.contract.test.ts @@ -23,6 +23,7 @@ import { commonSetup } from './supports.js'; import type { FastUsdcTerms } from '../src/fast-usdc.contract.js'; import { makeFeeTools } from '../src/utils/fees.js'; import type { PoolMetrics } from '../src/types.js'; +import { addressTools } from '../src/utils/address.js'; const dirname = path.dirname(new URL(import.meta.url).pathname); @@ -52,6 +53,14 @@ const startContract = async ( const { zoe, bundleAndInstall } = await setUpZoeForTest({ setJig: jig => { jig.chainHub.registerChain('osmosis', fetchedChainInfo.osmosis); + jig.chainHub.registerChain('agoric', fetchedChainInfo.agoric); + // TODO #10445 register noble<>agoric and noble<>osmosis instead + // for PFM routing. also will need to call `registerAsset` + jig.chainHub.registerConnection( + fetchedChainInfo.agoric.chainId, + fetchedChainInfo.osmosis.chainId, + fetchedChainInfo.agoric.connections['osmosis-1'], + ); }, }); const installation: Installation = @@ -69,26 +78,6 @@ const startContract = async ( return { ...startKit, zoe }; }; -// FIXME this makeTestPushInvitation forces evidence, which triggers advancing, -// which doesn't yet work -test.skip('advancing', async t => { - const common = await commonSetup(t); - - const { publicFacet, zoe } = await startContract(common); - - const e1 = await E(MockCctpTxEvidences.AGORIC_PLUS_DYDX)(); - - const inv = await E(publicFacet).makeTestPushInvitation(e1); - // the invitation maker itself pushes the evidence - - // the offer is still safe to make - const seat = await E(zoe).offer(inv); - t.is( - await E(seat).getOfferResult(), - 'inert; nothing should be expected from this offer', - ); -}); - test('oracle operators have closely-held rights to submit evidence of CCTP transactions', async t => { const common = await commonSetup(t); const { creatorFacet, zoe } = await startContract(common); @@ -551,3 +540,85 @@ test('baggage', async t => { const tree = inspectMapStore(contractBaggage); t.snapshot(tree, 'contract baggage after start'); }); + +test('advancing happy path', async t => { + const common = await commonSetup(t); + const { + brands: { usdc }, + commonPrivateArgs, + utils: { inspectLocalBridge, inspectBankBridge, transmitTransferAck }, + } = common; + + const { instance, publicFacet, zoe } = await startContract(common); + const terms = await E(zoe).getTerms(instance); + const { subscriber } = E.get( + E.get(E(publicFacet).getPublicTopics()).poolMetrics, + ); + const feeTools = makeFeeTools(commonPrivateArgs.feeConfig); + const { makeLP, purseOf } = makeLpTools(t, common, { + publicFacet, + subscriber, + terms, + zoe, + }); + + const evidence = await E(MockCctpTxEvidences.AGORIC_PLUS_OSMO)(); + + // seed pool with funds + const alice = makeLP('Alice', purseOf(evidence.tx.amount)); + await alice.deposit(evidence.tx.amount); + + // the invitation maker itself pushes the evidence + const inv = await E(publicFacet).makeTestPushInvitation(evidence); + const seat = await E(zoe).offer(inv); + t.is( + await E(seat).getOfferResult(), + 'inert; nothing should be expected from this offer', + ); + + // calculate advance net of fees + const expectedAdvance = feeTools.calculateAdvance( + usdc.make(evidence.tx.amount), + ); + t.log('Expecting to observe advance of', expectedAdvance); + + await eventLoopIteration(); // let Advancer do work + + // advance sent from PoolSeat to PoolAccount + t.deepEqual(inspectBankBridge().at(-1), { + amount: String(expectedAdvance.value), + denom: 'ibc/usdconagoric', + recipient: 'agoric1fakeLCAAddress', + type: 'VBANK_GIVE', + }); + + // ibc transfer sent over localChain bridge + const localBridgeMsg = inspectLocalBridge().at(-1); + const ibcTransferMsg = localBridgeMsg.messages[0]; + t.is(ibcTransferMsg['@type'], '/ibc.applications.transfer.v1.MsgTransfer'); + + const expectedReceiver = addressTools.getQueryParams( + evidence.aux.recipientAddress, + ).EUD; + t.is(ibcTransferMsg.receiver, expectedReceiver, 'sent to correct address'); + t.deepEqual(ibcTransferMsg.token, { + amount: String(expectedAdvance.value), + denom: 'ibc/usdconagoric', + }); + + // TODO #10445 expect PFM memo + t.is(ibcTransferMsg.memo, '', 'TODO expecting PFM memo'); + + // TODO #10445 expect routing through noble, not osmosis + t.is( + ibcTransferMsg.sourceChannel, + fetchedChainInfo.agoric.connections['osmosis-1'].transferChannel.channelId, + 'TODO expecting routing through Noble', + ); + + await transmitTransferAck(); + // Nothing we can check here, unless we want to inspect calls to `trace`. + // `test/exos/advancer.test.ts` covers calls to `log: LogFn` with mocks. + // This is still helpful to call, so we can observe "Advance transfer + // fulfilled" in the test output. +}); diff --git a/packages/fast-usdc/test/supports.ts b/packages/fast-usdc/test/supports.ts index 82e8955c4bc..8ce9d7b12ea 100644 --- a/packages/fast-usdc/test/supports.ts +++ b/packages/fast-usdc/test/supports.ts @@ -52,7 +52,7 @@ export const commonSetup = async (t: ExecutionContext) => { onToBridge: obj => bankBridgeMessages.push(obj), }); await E(bankManager).addAsset( - 'uusdc', + 'ibc/usdconagoric', 'USDC', 'USD Circle Stablecoin', usdc.issuerKit, @@ -64,13 +64,13 @@ export const commonSetup = async (t: ExecutionContext) => { // TODO https://github.com/Agoric/agoric-sdk/issues/9966 await makeWellKnownSpaces(agoricNamesAdmin, t.log, ['vbankAsset']); await E(E(agoricNamesAdmin).lookupAdmin('vbankAsset')).update( - 'uusdc', + 'ibc/usdconagoric', /** @type {AssetInfo} */ harden({ brand: usdc.brand, issuer: usdc.issuer, - issuerName: 'IST', + issuerName: 'USDC', denom: 'uusdc', - proposedName: 'IST', + proposedName: 'USDC', displayInfo: { IOU: true }, }), );