From 26145098da39e1a475d77be540c41479414b5775 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Wed, 4 Dec 2024 17:24:13 -0500 Subject: [PATCH] chore: provide noble ica for `intermediateRecipient` - noble only accepts bech32 addresses for the recipient field in ibc transfers - this change creates an ICA noble to facilitate the requirement - if an IBC transfer fails, we expect it to return the origin (agoric `localOrchAccount`), not the `intermediateRecipient` --- packages/fast-usdc/src/exos/advancer.js | 20 +++++++++++++----- packages/fast-usdc/src/exos/settler.js | 10 ++++++++- packages/fast-usdc/src/fast-usdc.contract.js | 20 +++++++++++++----- packages/fast-usdc/src/fast-usdc.flows.js | 10 +++++++++ packages/fast-usdc/test/exos/advancer.test.ts | 4 +++- packages/fast-usdc/test/exos/settler.test.ts | 16 +++++++++++++- .../fast-usdc/test/fast-usdc.contract.test.ts | 3 ++- packages/fast-usdc/test/fixtures.ts | 7 ++++++ .../snapshots/fast-usdc.contract.test.ts.md | 8 +++++++ .../snapshots/fast-usdc.contract.test.ts.snap | Bin 5661 -> 5732 bytes 10 files changed, 84 insertions(+), 14 deletions(-) diff --git a/packages/fast-usdc/src/exos/advancer.js b/packages/fast-usdc/src/exos/advancer.js index f5a9f8afd09..f244ced3884 100644 --- a/packages/fast-usdc/src/exos/advancer.js +++ b/packages/fast-usdc/src/exos/advancer.js @@ -116,6 +116,7 @@ export const prepareAdvancerKit = ( * notifyFacet: import('./settler.js').SettlerKit['notify']; * borrowerFacet: LiquidityPoolKit['borrower']; * poolAccount: HostInterface>; + * intermediateRecipient: ChainAddress; * }} config */ config => harden(config), @@ -187,12 +188,20 @@ export const prepareAdvancerKit = ( * @param {AdvancerVowCtx & { tmpSeat: ZCFSeat }} ctx */ onFulfilled(result, ctx) { - const { poolAccount } = this.state; + const { poolAccount, intermediateRecipient } = this.state; const { destination, advanceAmount, ...detail } = ctx; - const transferV = E(poolAccount).transfer(destination, { - denom: usdc.denom, - value: advanceAmount.value, - }); + const transferV = E(poolAccount).transfer( + destination, + { + denom: usdc.denom, + value: advanceAmount.value, + }, + { + forwardOpts: { + intermediateRecipient, + }, + }, + ); return watch(transferV, this.facets.transferHandler, { destination, advanceAmount, @@ -250,6 +259,7 @@ export const prepareAdvancerKit = ( notifyFacet: M.remotable(), borrowerFacet: M.remotable(), poolAccount: M.remotable(), + intermediateRecipient: ChainAddressShape, }), }, ); diff --git a/packages/fast-usdc/src/exos/settler.js b/packages/fast-usdc/src/exos/settler.js index 72f1e1f9626..035c288cabd 100644 --- a/packages/fast-usdc/src/exos/settler.js +++ b/packages/fast-usdc/src/exos/settler.js @@ -1,5 +1,6 @@ import { AmountMath } from '@agoric/ertp'; import { assertAllDefined, makeTracer } from '@agoric/internal'; +import { ChainAddressShape } from '@agoric/orchestration'; import { atob } from '@endo/base64'; import { E } from '@endo/far'; import { M } from '@endo/patterns'; @@ -93,6 +94,7 @@ export const prepareSettler = ( * remoteDenom: Denom; * repayer: LiquidityPoolKit['repayer']; * settlementAccount: HostInterface> + * intermediateRecipient: ChainAddress; * }} config */ config => { @@ -255,7 +257,7 @@ export const prepareSettler = ( * @param {string} EUD */ forward(txHash, sender, fullValue, EUD) { - const { settlementAccount } = this.state; + const { settlementAccount, intermediateRecipient } = this.state; const dest = chainHub.makeChainAddress(EUD); @@ -263,6 +265,11 @@ export const prepareSettler = ( const txfrV = E(settlementAccount).transfer( dest, AmountMath.make(USDC, fullValue), + { + forwardOpts: { + intermediateRecipient, + }, + }, ); void vowTools.watch(txfrV, this.facets.transferHandler, { txHash, @@ -305,6 +312,7 @@ export const prepareSettler = ( sourceChannel: M.string(), remoteDenom: M.string(), mintedEarly: M.remotable('mintedEarly'), + intermediateRecipient: ChainAddressShape, }), }, ); diff --git a/packages/fast-usdc/src/fast-usdc.contract.js b/packages/fast-usdc/src/fast-usdc.contract.js index aa799e101d2..eb91487bbc4 100644 --- a/packages/fast-usdc/src/fast-usdc.contract.js +++ b/packages/fast-usdc/src/fast-usdc.contract.js @@ -140,7 +140,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => { 'test of forcing evidence', ); - const { makeLocalAccount } = orchestrateAll(flows, {}); + const { makeLocalAccount, makeNobleAccount } = orchestrateAll(flows, {}); const creatorFacet = zone.exo('Fast USDC Creator', undefined, { /** @type {(operatorId: string) => Promise>} */ @@ -214,7 +214,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => { privateArgs.assetInfo, ); } - + const nobleAccountV = zone.makeOnce('NobleAccount', () => makeNobleAccount()); const feedKit = zone.makeOnce('Feed Kit', () => makeFeedKit()); const poolAccountV = zone.makeOnce('PoolAccount', () => makeLocalAccount()); @@ -222,10 +222,18 @@ export const contract = async (zcf, privateArgs, zone, tools) => { makeLocalAccount(), ); // when() is OK here since this clearly resolves promptly. - /** @type {HostInterface>[]} */ - const [poolAccount, settlementAccount] = await vowTools.when( - vowTools.all([poolAccountV, settleAccountV]), + /** @type {[HostInterface>, HostInterface>, HostInterface>]} */ + const [nobleAccount, poolAccount, settlementAccount] = await vowTools.when( + vowTools.all([nobleAccountV, poolAccountV, settleAccountV]), + ); + trace('settlementAccount', settlementAccount); + trace('poolAccount', poolAccount); + trace('nobleAccount', nobleAccount); + + const intermediateRecipient = await vowTools.when( + E(nobleAccount).getAddress(), ); + trace('intermediateRecipient', intermediateRecipient); const [_agoric, _noble, agToNoble] = await vowTools.when( chainHub.getChainsAndConnection('agoric', 'noble'), @@ -235,6 +243,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => { sourceChannel: agToNoble.transferChannel.counterPartyChannelId, remoteDenom: 'uusdc', settlementAccount, + intermediateRecipient, }); const advancer = zone.makeOnce('Advancer', () => @@ -242,6 +251,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => { borrowerFacet: poolKit.borrower, notifyFacet: settlerKit.notify, poolAccount, + intermediateRecipient, }), ); // Connect evidence stream to advancer diff --git a/packages/fast-usdc/src/fast-usdc.flows.js b/packages/fast-usdc/src/fast-usdc.flows.js index 9f330a4c905..a9aedd6228e 100644 --- a/packages/fast-usdc/src/fast-usdc.flows.js +++ b/packages/fast-usdc/src/fast-usdc.flows.js @@ -11,3 +11,13 @@ export const makeLocalAccount = async orch => { return agoricChain.makeAccount(); }; harden(makeLocalAccount); + +/** + * @satisfies {OrchestrationFlow} + * @param {Orchestrator} orch + */ +export const makeNobleAccount = async orch => { + const nobleChain = await orch.getChain('noble'); + return nobleChain.makeAccount(); +}; +harden(makeNobleAccount); diff --git a/packages/fast-usdc/test/exos/advancer.test.ts b/packages/fast-usdc/test/exos/advancer.test.ts index 0921fb4838f..818554dacca 100644 --- a/packages/fast-usdc/test/exos/advancer.test.ts +++ b/packages/fast-usdc/test/exos/advancer.test.ts @@ -14,7 +14,7 @@ import { prepareStatusManager } from '../../src/exos/status-manager.js'; import { makeFeeTools } from '../../src/utils/fees.js'; import { addressTools } from '../../src/utils/address.js'; import { commonSetup } from '../supports.js'; -import { MockCctpTxEvidences } from '../fixtures.js'; +import { MockCctpTxEvidences, intermediateRecipient } from '../fixtures.js'; import { makeTestFeeConfig, makeTestLogger, @@ -113,6 +113,7 @@ const createTestExtensions = (t, common: CommonSetup) => { borrowerFacet: mockBorrowerF, notifyFacet: mockNotifyF, poolAccount: mockAccounts.mockPoolAccount.account, + intermediateRecipient, }); return { @@ -221,6 +222,7 @@ test('updates status to OBSERVED on insufficient pool funds', async t => { borrowerFacet: mockBorrowerErrorF, notifyFacet: mockNotifyF, poolAccount: mockPoolAccount.account, + intermediateRecipient, }); const mockEvidence = MockCctpTxEvidences.AGORIC_PLUS_DYDX(); diff --git a/packages/fast-usdc/test/exos/settler.test.ts b/packages/fast-usdc/test/exos/settler.test.ts index c970f55eaed..f900c7cd191 100644 --- a/packages/fast-usdc/test/exos/settler.test.ts +++ b/packages/fast-usdc/test/exos/settler.test.ts @@ -9,7 +9,11 @@ import { prepareSettler } from '../../src/exos/settler.js'; import { prepareStatusManager } from '../../src/exos/status-manager.js'; import type { CctpTxEvidence } from '../../src/types.js'; import { makeFeeTools } from '../../src/utils/fees.js'; -import { MockCctpTxEvidences, MockVTransferEvents } from '../fixtures.js'; +import { + MockCctpTxEvidences, + MockVTransferEvents, + intermediateRecipient, +} from '../fixtures.js'; import { makeTestLogger, prepareMockOrchAccounts } from '../mocks.js'; import { commonSetup } from '../supports.js'; @@ -87,6 +91,7 @@ const makeTestContext = async t => { fetchedChainInfo.agoric.connections['noble-1'].transferChannel .counterPartyChannelId, remoteDenom: 'uusdc', + intermediateRecipient, }); const simulate = harden({ @@ -280,6 +285,15 @@ test('slow path: forward to EUD; remove pending tx', async t => { value: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men', }, usdc.units(150), + { + forwardOpts: { + intermediateRecipient: { + chainId: 'noble-1', + encoding: 'bech32', + value: 'noble1test', + }, + }, + }, ], ]); diff --git a/packages/fast-usdc/test/fast-usdc.contract.test.ts b/packages/fast-usdc/test/fast-usdc.contract.test.ts index a2fb779be97..c622acd50a7 100644 --- a/packages/fast-usdc/test/fast-usdc.contract.test.ts +++ b/packages/fast-usdc/test/fast-usdc.contract.test.ts @@ -97,6 +97,7 @@ const startContract = async ( const makeTestContext = async (t: ExecutionContext) => { const common = await commonSetup(t); + await E(common.mocks.ibcBridge).setAddressPrefix('noble'); const startKit = await startContract(common, 2); @@ -405,7 +406,7 @@ const makeCustomer = ( const [ibcTransferMsg] = lm.messages; // support advances to noble + other chains const receiver = - ibcTransferMsg.receiver === 'pfm' + ibcTransferMsg.receiver === 'noble1test' // intermediateRecipient value ? JSON.parse(ibcTransferMsg.memo).forward.receiver : ibcTransferMsg.receiver; return ( diff --git a/packages/fast-usdc/test/fixtures.ts b/packages/fast-usdc/test/fixtures.ts index 32af6aa2126..cbd83ccb657 100644 --- a/packages/fast-usdc/test/fixtures.ts +++ b/packages/fast-usdc/test/fixtures.ts @@ -1,6 +1,7 @@ import type { VTransferIBCEvent } from '@agoric/vats'; import { buildVTransferEvent } from '@agoric/orchestration/tools/ibc-mocks.js'; import fetchedChainInfo from '@agoric/orchestration/src/fetched-chain-info.js'; +import type { ChainAddress } from '@agoric/orchestration'; import type { CctpTxEvidence } from '../src/types.js'; const mockScenarios = [ @@ -141,3 +142,9 @@ export const MockVTransferEvents: Record< MockCctpTxEvidences.AGORIC_UNKNOWN_EUD().aux.recipientAddress, }), }; + +export const intermediateRecipient: ChainAddress = harden({ + chainId: 'noble-1', + value: 'noble1test', + encoding: 'bech32', +}); 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 72890a09ece..1c6fe4d0433 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 @@ -588,6 +588,7 @@ Generated by [AVA](https://avajs.dev). withdrawHandler: Object @Alleged: Liquidity Pool withdrawHandler {}, }, 'Liquidity Pool_kindHandle': 'Alleged: kind', + NobleAccount: 'Vow', 'Operator Kit_kindHandle': 'Alleged: kind', PendingTxs: {}, PoolAccount: 'Vow', @@ -601,6 +602,9 @@ Generated by [AVA](https://avajs.dev). makeLocalAccount: { asyncFlow_kindHandle: 'Alleged: kind', }, + makeNobleAccount: { + asyncFlow_kindHandle: 'Alleged: kind', + }, }, pending: {}, vstorage: { @@ -620,6 +624,10 @@ Generated by [AVA](https://avajs.dev). pending: false, value: Object @Alleged: LocalChainFacade public {}, }, + noble: { + pending: false, + value: Object @Alleged: RemoteChainFacade public {}, + }, }, ibcTools: { IBCTransferSenderKit_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 c289cbdd0bb7e35e0f224dfca6d4478ef07e313f..09943c2591deaefbf4b8da2940903a15a2764757 100644 GIT binary patch literal 5732 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