From ac8cf069f5cdfa0a25cafa845add048db2d93e3a Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Fri, 6 Dec 2024 19:43:25 -0600 Subject: [PATCH] chore(fast-usdc): limited operation before connecting to noble Don't require connecting to noble until we want to advance to chains other than agoric. --- packages/fast-usdc/src/exos/advancer.js | 40 +++++++++++++---- packages/fast-usdc/src/exos/settler.js | 31 +++++++++---- packages/fast-usdc/src/fast-usdc.contract.js | 41 ++++++++++-------- packages/fast-usdc/src/fast-usdc.start.js | 5 +++ .../fast-usdc/test/fast-usdc.contract.test.ts | 1 + .../snapshots/fast-usdc.contract.test.ts.md | 15 +++++-- .../snapshots/fast-usdc.contract.test.ts.snap | Bin 5732 -> 5883 bytes 7 files changed, 96 insertions(+), 37 deletions(-) diff --git a/packages/fast-usdc/src/exos/advancer.js b/packages/fast-usdc/src/exos/advancer.js index f244ced3884..948ca80660a 100644 --- a/packages/fast-usdc/src/exos/advancer.js +++ b/packages/fast-usdc/src/exos/advancer.js @@ -18,7 +18,7 @@ import { makeFeeTools } from '../utils/fees.js'; * @import {HostInterface} from '@agoric/async-flow'; * @import {TypedPattern} from '@agoric/internal' * @import {NatAmount} from '@agoric/ertp'; - * @import {ChainAddress, ChainHub, Denom, OrchestrationAccount} from '@agoric/orchestration'; + * @import {ChainAddress, ChainHub, Denom, OrchestrationAccount, IBCMsgTransferOptions} from '@agoric/orchestration'; * @import {ZoeTools} from '@agoric/orchestration/src/utils/zoe-tools.js'; * @import {VowTools} from '@agoric/vow'; * @import {Zone} from '@agoric/zone'; @@ -54,6 +54,9 @@ const AdvancerVowCtxShape = M.splitRecord( /** type guards internal to the AdvancerKit */ const AdvancerKitI = harden({ + admin: M.interface('AdminI', { + setIntermediateRecipient: M.call(ChainAddressShape).returns(), + }), advancer: M.interface('AdvancerI', { handleTransactionEvent: M.callWhen(CctpTxEvidenceShape).returns(), }), @@ -70,6 +73,19 @@ const AdvancerKitI = harden({ }), }); +/** + * @param {ChainAddress|undefined} intermediateRecipient + * @returns {IBCMsgTransferOptions | undefined} + */ +const ibcOpts = intermediateRecipient => + intermediateRecipient + ? { + forwardOpts: { + intermediateRecipient, + }, + } + : undefined; + /** * @typedef {{ * fullAmount: NatAmount; @@ -116,11 +132,21 @@ export const prepareAdvancerKit = ( * notifyFacet: import('./settler.js').SettlerKit['notify']; * borrowerFacet: LiquidityPoolKit['borrower']; * poolAccount: HostInterface>; - * intermediateRecipient: ChainAddress; + * intermediateRecipient?: ChainAddress; * }} config */ - config => harden(config), + config => + harden({ + ...config, + intermediateRecipient: config.intermediateRecipient, + }), { + admin: { + /** @param {ChainAddress} intermediateRecipient */ + setIntermediateRecipient(intermediateRecipient) { + this.state.intermediateRecipient = intermediateRecipient; + }, + }, advancer: { /** * Must perform a status update for every observed transaction. @@ -196,11 +222,7 @@ export const prepareAdvancerKit = ( denom: usdc.denom, value: advanceAmount.value, }, - { - forwardOpts: { - intermediateRecipient, - }, - }, + ibcOpts(intermediateRecipient), ); return watch(transferV, this.facets.transferHandler, { destination, @@ -259,7 +281,7 @@ export const prepareAdvancerKit = ( notifyFacet: M.remotable(), borrowerFacet: M.remotable(), poolAccount: M.remotable(), - intermediateRecipient: ChainAddressShape, + intermediateRecipient: M.opt(ChainAddressShape), }), }, ); diff --git a/packages/fast-usdc/src/exos/settler.js b/packages/fast-usdc/src/exos/settler.js index 035c288cabd..f2ea902ffe7 100644 --- a/packages/fast-usdc/src/exos/settler.js +++ b/packages/fast-usdc/src/exos/settler.js @@ -12,7 +12,7 @@ import { EvmHashShape } from '../type-guards.js'; /** * @import {FungibleTokenPacketData} from '@agoric/cosmic-proto/ibc/applications/transfer/v2/packet.js'; - * @import {Denom, OrchestrationAccount, ChainHub, ChainAddress} from '@agoric/orchestration'; + * @import {Denom, OrchestrationAccount, ChainHub, ChainAddress, IBCMsgTransferOptions} from '@agoric/orchestration'; * @import {WithdrawToSeat} from '@agoric/orchestration/src/utils/zoe-tools' * @import {IBCChannelID, VTransferIBCEvent} from '@agoric/vats'; * @import {Zone} from '@agoric/zone'; @@ -31,6 +31,19 @@ import { EvmHashShape } from '../type-guards.js'; const makeMintedEarlyKey = (addr, amount) => `pendingTx:${JSON.stringify([addr, String(amount)])}`; +/** + * @param {ChainAddress|undefined} intermediateRecipient + * @returns {IBCMsgTransferOptions | undefined} + */ +const ibcOpts = intermediateRecipient => + intermediateRecipient + ? { + forwardOpts: { + intermediateRecipient, + }, + } + : undefined; + /** * @param {Zone} zone * @param {object} caps @@ -62,6 +75,7 @@ export const prepareSettler = ( { creator: M.interface('SettlerCreatorI', { monitorMintingDeposits: M.callWhen().returns(M.any()), + setIntermediateRecipient: M.call(ChainAddressShape).returns(), }), tap: M.interface('SettlerTapI', { receiveUpcall: M.call(M.record()).returns(M.promise()), @@ -94,13 +108,14 @@ export const prepareSettler = ( * remoteDenom: Denom; * repayer: LiquidityPoolKit['repayer']; * settlementAccount: HostInterface> - * intermediateRecipient: ChainAddress; + * intermediateRecipient?: ChainAddress; * }} config */ config => { log('config', config); return { ...config, + intermediateRecipient: config.intermediateRecipient, /** @type {HostInterface|undefined} */ registration: undefined, /** @type {SetStore>} */ @@ -117,6 +132,10 @@ export const prepareSettler = ( assert.typeof(registration, 'object'); this.state.registration = registration; }, + /** @param {ChainAddress} intermediateRecipient */ + setIntermediateRecipient(intermediateRecipient) { + this.state.intermediateRecipient = intermediateRecipient; + }, }, tap: { /** @param {VTransferIBCEvent} event */ @@ -265,11 +284,7 @@ export const prepareSettler = ( const txfrV = E(settlementAccount).transfer( dest, AmountMath.make(USDC, fullValue), - { - forwardOpts: { - intermediateRecipient, - }, - }, + ibcOpts(intermediateRecipient), ); void vowTools.watch(txfrV, this.facets.transferHandler, { txHash, @@ -312,7 +327,7 @@ export const prepareSettler = ( sourceChannel: M.string(), remoteDenom: M.string(), mintedEarly: M.remotable('mintedEarly'), - intermediateRecipient: ChainAddressShape, + intermediateRecipient: M.opt(ChainAddressShape), }), }, ); diff --git a/packages/fast-usdc/src/fast-usdc.contract.js b/packages/fast-usdc/src/fast-usdc.contract.js index 1bcdf2b4a4e..78a1eb5d0b2 100644 --- a/packages/fast-usdc/src/fast-usdc.contract.js +++ b/packages/fast-usdc/src/fast-usdc.contract.js @@ -14,7 +14,7 @@ import { provideSingleton } from '@agoric/zoe/src/contractSupport/durability.js' import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js'; import { E } from '@endo/far'; import { M } from '@endo/patterns'; -import { prepareAdvancer } from './exos/advancer.js'; +import { prepareAdvancer, prepareAdvancerKit } from './exos/advancer.js'; import { prepareLiquidityPoolKit } from './exos/liquidity-pool.js'; import { prepareSettler } from './exos/settler.js'; import { prepareStatusManager } from './exos/status-manager.js'; @@ -112,7 +112,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => { }); const { localTransfer } = makeZoeTools(zcf, vowTools); - const makeAdvancer = prepareAdvancer(zone, { + const makeAdvancerKit = prepareAdvancerKit(zone, { chainHub, feeConfig, localTransfer, @@ -126,7 +126,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => { }); const makeFeedKit = prepareTransactionFeedKit(zone, zcf); - assertAllDefined({ makeFeedKit, makeAdvancer, makeSettler, statusManager }); + assertAllDefined({ makeFeedKit, makeSettler, statusManager }); const makeLiquidityPoolKit = prepareLiquidityPoolKit( zone, @@ -142,11 +142,27 @@ export const contract = async (zcf, privateArgs, zone, tools) => { const { makeLocalAccount, makeNobleAccount } = orchestrateAll(flows, {}); + const nobleAccountV = zone.makeOnce('NobleAccount', makeNobleAccount); + const creatorFacet = zone.exo('Fast USDC Creator', undefined, { /** @type {(operatorId: string) => Promise>} */ async makeOperatorInvitation(operatorId) { return feedKit.creator.makeOperatorInvitation(operatorId); }, + async connectToNoble() { + return vowTools.when(nobleAccountV, nobleAccount => { + trace('nobleAccount', nobleAccount); + return vowTools.when( + E(nobleAccount).getAddress(), + intermediateRecipient => { + trace('intermediateRecipient', intermediateRecipient); + advancerAdmin.setIntermediateRecipient(intermediateRecipient); + settlerKit.creator.setIntermediateRecipient(intermediateRecipient); + return intermediateRecipient; + }, + ); + }); + }, }); const publicFacet = zone.exo('Fast USDC Public', undefined, { @@ -214,7 +230,6 @@ 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,18 +237,12 @@ export const contract = async (zcf, privateArgs, zone, tools) => { makeLocalAccount(), ); // when() is OK here since this clearly resolves promptly. - /** @type {[HostInterface>, HostInterface>, HostInterface>]} */ - const [nobleAccount, poolAccount, settlementAccount] = await vowTools.when( - vowTools.all([nobleAccountV, poolAccountV, settleAccountV]), + /** @type {[HostInterface>, HostInterface>]} */ + const [poolAccount, settlementAccount] = await vowTools.when( + vowTools.all([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'), @@ -243,15 +252,13 @@ export const contract = async (zcf, privateArgs, zone, tools) => { sourceChannel: agToNoble.transferChannel.counterPartyChannelId, remoteDenom: 'uusdc', settlementAccount, - intermediateRecipient, }); - const advancer = zone.makeOnce('Advancer', () => - makeAdvancer({ + const { advancer, admin: advancerAdmin } = zone.makeOnce('AdvancerKit', () => + makeAdvancerKit({ borrowerFacet: poolKit.borrower, notifyFacet: settlerKit.notify, poolAccount, - intermediateRecipient, }), ); // Connect evidence stream to advancer diff --git a/packages/fast-usdc/src/fast-usdc.start.js b/packages/fast-usdc/src/fast-usdc.start.js index 237af828f30..0a5808d2e4b 100644 --- a/packages/fast-usdc/src/fast-usdc.start.js +++ b/packages/fast-usdc/src/fast-usdc.start.js @@ -236,6 +236,11 @@ export const startFastUSDC = async ( produceInstance.reset(); produceInstance.resolve(instance); + + if ('uusdc' in assetInfo) { + const addr = await E(kit.creatorFacet).connectToNoble(); + trace('noble intermediate recipient', addr); + } trace('startFastUSDC done', instance); }; harden(startFastUSDC); diff --git a/packages/fast-usdc/test/fast-usdc.contract.test.ts b/packages/fast-usdc/test/fast-usdc.contract.test.ts index c622acd50a7..665ed382320 100644 --- a/packages/fast-usdc/test/fast-usdc.contract.test.ts +++ b/packages/fast-usdc/test/fast-usdc.contract.test.ts @@ -85,6 +85,7 @@ const startContract = async ( E(startKit.creatorFacet).makeOperatorInvitation(`operator-${opIx}`), ), ); + await E(startKit.creatorFacet).connectToNoble(); return { ...startKit, 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 1c6fe4d0433..36771ab38d5 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 @@ -22,9 +22,13 @@ Generated by [AVA](https://avajs.dev). }, LogStore_kindHandle: 'Alleged: kind', StateUnwrapper_kindHandle: 'Alleged: kind', - asyncFuncEagerWakers: [], + asyncFuncEagerWakers: [ + Object @Alleged: asyncFlow flow {}, + ], asyncFuncFailures: {}, - flowForOutcomeVow: {}, + flowForOutcomeVow: { + 'Alleged: VowInternalsKit vowV0': 'Alleged: asyncFlow flow', + }, unwrapMap: 'Alleged: weakMapStore', }, chainHub: { @@ -561,7 +565,12 @@ Generated by [AVA](https://avajs.dev). lookupConnectionInfo_kindHandle: 'Alleged: kind', }, contract: { - Advancer: 'Alleged: Fast USDC Advancer advancer', + AdvancerKit: { + admin: Object @Alleged: Fast USDC Advancer admin {}, + advancer: Object @Alleged: Fast USDC Advancer advancer {}, + depositHandler: Object @Alleged: Fast USDC Advancer depositHandler {}, + transferHandler: Object @Alleged: Fast USDC Advancer transferHandler {}, + }, 'Fast USDC Advancer_kindHandle': 'Alleged: kind', 'Fast USDC Creator_kindHandle': 'Alleged: kind', 'Fast USDC Creator_singleton': 'Alleged: Fast USDC Creator', 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 09943c2591deaefbf4b8da2940903a15a2764757..38ef797e25059bcb09114f400b67e7124aab55fd 100644 GIT binary patch literal 5883 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 R{12f={|Cad?D7eA002i#Ti*Zx literal 5732 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