From 8ea6c3962f6464608ad5e78f24046345f4ba436b Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Wed, 8 Jan 2025 21:45:49 -0500 Subject: [PATCH 1/2] chore: use `.send()` for advances to local chain --- packages/fast-usdc/src/exos/advancer.js | 32 +++--- packages/fast-usdc/test/exos/advancer.test.ts | 105 +++++++++++++++++- packages/fast-usdc/test/fixtures.ts | 31 ++++++ packages/fast-usdc/test/mocks.ts | 6 + 4 files changed, 154 insertions(+), 20 deletions(-) diff --git a/packages/fast-usdc/src/exos/advancer.js b/packages/fast-usdc/src/exos/advancer.js index 650ea08a302..621c588e823 100644 --- a/packages/fast-usdc/src/exos/advancer.js +++ b/packages/fast-usdc/src/exos/advancer.js @@ -23,7 +23,7 @@ import { makeFeeTools } from '../utils/fees.js'; * @import {ZoeTools} from '@agoric/orchestration/src/utils/zoe-tools.js'; * @import {VowTools} from '@agoric/vow'; * @import {Zone} from '@agoric/zone'; - * @import {CctpTxEvidence, AddressHook, EvmHash, FeeConfig, LogFn, NobleAddress, EvidenceWithRisk} from '../types.js'; + * @import {AddressHook, EvmHash, FeeConfig, LogFn, NobleAddress, EvidenceWithRisk} from '../types.js'; * @import {StatusManager} from './status-manager.js'; * @import {LiquidityPoolKit} from './liquidity-pool.js'; */ @@ -213,14 +213,20 @@ export const prepareAdvancerKit = ( * @param {AdvancerVowCtx & { tmpSeat: ZCFSeat }} ctx */ onFulfilled(result, ctx) { - const { poolAccount, intermediateRecipient } = this.state; + const { poolAccount, intermediateRecipient, settlementAddress } = + this.state; const { destination, advanceAmount, tmpSeat: _, ...detail } = ctx; - const transferV = E(poolAccount).transfer( - destination, - { denom: usdc.denom, value: advanceAmount.value }, - { forwardOpts: { intermediateRecipient } }, - ); - return watch(transferV, this.facets.transferHandler, { + const amount = harden({ + denom: usdc.denom, + value: advanceAmount.value, + }); + const transferOrSendV = + destination.chainId === settlementAddress.chainId + ? E(poolAccount).send(destination, amount) + : E(poolAccount).transfer(destination, amount, { + forwardOpts: { intermediateRecipient }, + }); + return watch(transferOrSendV, this.facets.transferHandler, { destination, advanceAmount, ...detail, @@ -253,17 +259,13 @@ export const prepareAdvancerKit = ( }, transferHandler: { /** - * @param {unknown} result TODO confirm this is not a bigint (sequence) + * @param {undefined} result * @param {AdvancerVowCtx} ctx */ onFulfilled(result, ctx) { const { notifyFacet } = this.state; const { advanceAmount, destination, ...detail } = ctx; - log('Advance transfer fulfilled', { - advanceAmount, - destination, - result, - }); + log('Advance succeeded', { advanceAmount, destination }); // During development, due to a bug, this call threw. // The failure was silent (no diagnostics) due to: // - #10576 Vows do not report unhandled rejections @@ -278,7 +280,7 @@ export const prepareAdvancerKit = ( */ onRejected(error, ctx) { const { notifyFacet } = this.state; - log('Advance transfer rejected', error); + log('Advance failed', error); const { advanceAmount: _, ...restCtx } = ctx; notifyFacet.notifyAdvancingResult(restCtx, false); }, diff --git a/packages/fast-usdc/test/exos/advancer.test.ts b/packages/fast-usdc/test/exos/advancer.test.ts index c5e53c0b70a..b86ca9e6a13 100644 --- a/packages/fast-usdc/test/exos/advancer.test.ts +++ b/packages/fast-usdc/test/exos/advancer.test.ts @@ -56,6 +56,7 @@ const createTestExtensions = (t, common: CommonSetup) => { const { log, inspectLogs } = makeTestLogger(t.log); + chainHub.registerChain('agoric', fetchedChainInfo.agoric); chainHub.registerChain('dydx', fetchedChainInfo.dydx); chainHub.registerChain('osmosis', fetchedChainInfo.osmosis); @@ -221,7 +222,7 @@ test('updates status to ADVANCING in happy path', async t => { t.deepEqual(inspectLogs(), [ ['decoded EUD: osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men'], [ - 'Advance transfer fulfilled', + 'Advance succeeded', { advanceAmount: { brand: usdc.brand, @@ -232,7 +233,6 @@ test('updates status to ADVANCING in happy path', async t => { encoding: 'bech32', value: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men', }, - result: undefined, }, ], ]); @@ -364,7 +364,7 @@ test('calls notifyAdvancingResult (AdvancedFailed) on failed transfer', async t t.deepEqual(inspectLogs(), [ ['decoded EUD: dydx183dejcnmkka5dzcu9xw6mywq0p2m5peks28men'], - ['Advance transfer rejected', Error('simulated error')], + ['Advance failed', Error('simulated error')], ]); // We expect to see an `AdvancedFailed` update, but that is now Settler's job. @@ -488,7 +488,7 @@ test('will not advance same txHash:chainId evidence twice', async t => { t.deepEqual(inspectLogs(), [ ['decoded EUD: osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men'], [ - 'Advance transfer fulfilled', + 'Advance succeeded', { advanceAmount: { brand: usdc.brand, value: 146999999n }, destination: { @@ -496,7 +496,6 @@ test('will not advance same txHash:chainId evidence twice', async t => { encoding: 'bech32', value: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men', }, - result: undefined, }, ], ]); @@ -714,3 +713,99 @@ test('no status update if `checkMintedEarly` returns true', async t => { // no add'l logs as we return early ]); }); + +test('uses bank send for agoric1 EUD', async t => { + const { + extensions: { + services: { advancer }, + helpers: { inspectLogs, inspectNotifyCalls }, + mocks: { mockPoolAccount, resolveLocalTransferV }, + }, + brands: { usdc }, + bootstrap: { storage }, + } = t.context; + + const evidence = MockCctpTxEvidences.AGORIC_PLUS_AGORIC(); + void advancer.handleTransactionEvent({ evidence, risk: {} }); + + // pretend borrow succeeded and funds were depositing to the LCA + resolveLocalTransferV(); + // pretend the Bank Send settled + mockPoolAccount.sendVResolver.resolve(); + // wait for handleTransactionEvent to do work + await eventLoopIteration(); + + t.deepEqual(inspectLogs(), [ + ['decoded EUD: agoric13rj0cc0hm5ac2nt0sdup2l7gvkx4v9tyvgq3h2'], + [ + 'Advance succeeded', + { + advanceAmount: { + brand: usdc.brand, + value: 244999999n, + }, + destination: { + chainId: 'agoric-3', + encoding: 'bech32', + value: 'agoric13rj0cc0hm5ac2nt0sdup2l7gvkx4v9tyvgq3h2', + }, + }, + ], + ]); + + // ensure Settler is notified of successful advance + t.like(inspectNotifyCalls(), [ + [ + { + txHash: evidence.txHash, + forwardingAddress: evidence.tx.forwardingAddress, + fullAmount: usdc.make(evidence.tx.amount), + destination: { + value: decodeAddressHook(evidence.aux.recipientAddress).query.EUD, + }, + }, + true, // indicates send succeeded + ], + ]); +}); + +test('notifies of advance failure if bank send fails', async t => { + const { + extensions: { + services: { advancer }, + helpers: { inspectLogs, inspectNotifyCalls }, + mocks: { mockPoolAccount, resolveLocalTransferV }, + }, + brands: { usdc }, + } = t.context; + + const evidence = MockCctpTxEvidences.AGORIC_PLUS_AGORIC(); + void advancer.handleTransactionEvent({ evidence, risk: {} }); + + // pretend borrow succeeded and funds were depositing to the LCA + resolveLocalTransferV(); + // pretend the Bank Send failed + mockPoolAccount.sendVResolver.reject(new Error('simulated error')); + // wait for handleTransactionEvent to do work + await eventLoopIteration(); + + t.deepEqual(inspectLogs(), [ + ['decoded EUD: agoric13rj0cc0hm5ac2nt0sdup2l7gvkx4v9tyvgq3h2'], + ['Advance failed', Error('simulated error')], + ]); + + // ensure Settler is notified of failed advance + t.like(inspectNotifyCalls(), [ + [ + { + txHash: evidence.txHash, + forwardingAddress: evidence.tx.forwardingAddress, + fullAmount: usdc.make(evidence.tx.amount), + destination: { + value: decodeAddressHook(evidence.aux.recipientAddress).query.EUD, + }, + }, + false, // indicates send failed + ], + ]); +}); diff --git a/packages/fast-usdc/test/fixtures.ts b/packages/fast-usdc/test/fixtures.ts index 8496051125d..b903036660e 100644 --- a/packages/fast-usdc/test/fixtures.ts +++ b/packages/fast-usdc/test/fixtures.ts @@ -8,6 +8,7 @@ import type { CctpTxEvidence, EvmAddress } from '../src/types.js'; const mockScenarios = [ 'AGORIC_PLUS_OSMO', 'AGORIC_PLUS_DYDX', + 'AGORIC_PLUS_AGORIC', 'AGORIC_NO_PARAMS', 'AGORIC_UNKNOWN_EUD', ] as const; @@ -64,6 +65,27 @@ export const MockCctpTxEvidences: Record< }, chainId: 1, }), + AGORIC_PLUS_AGORIC: (receiverAddress?: string) => ({ + blockHash: + '0x80d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee6z9', + blockNumber: 21037600n, + txHash: + '0xd81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff3875527617z9', + tx: { + amount: 250000000n, + forwardingAddress: 'noble17ww3rfusv895d92c0ncgj0fl9trntn70jz7hd5', + sender: Senders.default, + }, + aux: { + forwardingChannel: 'channel-21', + recipientAddress: + receiverAddress || + encodeAddressHook(settlementAddress.value, { + EUD: 'agoric13rj0cc0hm5ac2nt0sdup2l7gvkx4v9tyvgq3h2', + }), + }, + chainId: 1, + }), AGORIC_NO_PARAMS: (receiverAddress?: string) => ({ blockHash: '0x70d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee699', @@ -136,6 +158,15 @@ export const MockVTransferEvents: Record< recieverAddress || MockCctpTxEvidences.AGORIC_PLUS_DYDX().aux.recipientAddress, }), + AGORIC_PLUS_AGORIC: (recieverAddress?: string) => + buildVTransferEvent({ + ...nobleDefaultVTransferParams, + amount: MockCctpTxEvidences.AGORIC_PLUS_AGORIC().tx.amount, + sender: MockCctpTxEvidences.AGORIC_PLUS_AGORIC().tx.forwardingAddress, + receiver: + recieverAddress || + MockCctpTxEvidences.AGORIC_PLUS_AGORIC().aux.recipientAddress, + }), AGORIC_NO_PARAMS: (recieverAddress?: string) => buildVTransferEvent({ ...nobleDefaultVTransferParams, diff --git a/packages/fast-usdc/test/mocks.ts b/packages/fast-usdc/test/mocks.ts index dcd1cfc3a42..05af9ab6273 100644 --- a/packages/fast-usdc/test/mocks.ts +++ b/packages/fast-usdc/test/mocks.ts @@ -24,6 +24,7 @@ export const prepareMockOrchAccounts = ( }, ) => { // each can only be resolved/rejected once per test + const poolAccountSendVK = makeVowKit(); const poolAccountTransferVK = makeVowKit(); const settleAccountTransferVK = makeVowKit(); @@ -37,6 +38,10 @@ export const prepareMockOrchAccounts = ( // XXX consider a mock for deposit failure return asVow(async () => usdc.issuer.getAmountOf(payment)); }, + send(destination: ChainAddress, amount: DenomAmount) { + log('PoolAccount.send() called with', destination, amount); + return poolAccountSendVK.vow; + }, }); const poolAccount = mockedPoolAccount as unknown as HostInterface< @@ -57,6 +62,7 @@ export const prepareMockOrchAccounts = ( mockPoolAccount: { account: poolAccount, transferVResolver: poolAccountTransferVK.resolver, + sendVResolver: poolAccountSendVK.resolver, }, settlement: { account: settlementAccount, From 891d363a1197f7e9096a7a1fd804d813e113a31e Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Wed, 8 Jan 2025 21:46:09 -0500 Subject: [PATCH 2/2] chore: `amounts` -> `amount` typo --- packages/orchestration/src/orchestration-api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/orchestration/src/orchestration-api.ts b/packages/orchestration/src/orchestration-api.ts index 500fcf46f51..d258b56aa62 100644 --- a/packages/orchestration/src/orchestration-api.ts +++ b/packages/orchestration/src/orchestration-api.ts @@ -179,7 +179,7 @@ export interface OrchestrationAccountCommon { * @param amount - the amount to send * @returns void */ - send: (toAccount: ChainAddress, amounts: AmountArg) => Promise; + send: (toAccount: ChainAddress, amount: AmountArg) => Promise; /** * Transfer multiple amounts to another account on the same chain. The promise settles when the transfer is complete.