From 8eb7dee8f70facf8fb0b36c36d630c4153f4c722 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Mon, 11 Nov 2024 13:01:02 -0800 Subject: [PATCH] feat: TransactionFeedKit --- packages/fast-usdc/package.json | 1 + packages/fast-usdc/src/exos/advancer.js | 7 ++- .../fast-usdc/src/exos/transaction-feed.js | 48 +++++++++++++++-- packages/fast-usdc/src/fast-usdc.contract.js | 53 ++++++++++++++++--- packages/fast-usdc/test/exos/advancer.test.ts | 4 +- .../test/exos/transaction-feed.test.ts | 11 ++++ .../fast-usdc/test/fast-usdc.contract.test.ts | 25 +++++++-- 7 files changed, 127 insertions(+), 22 deletions(-) create mode 100644 packages/fast-usdc/test/exos/transaction-feed.test.ts diff --git a/packages/fast-usdc/package.json b/packages/fast-usdc/package.json index e887503bf07..7a21cb1511d 100644 --- a/packages/fast-usdc/package.json +++ b/packages/fast-usdc/package.json @@ -33,6 +33,7 @@ "dependencies": { "@agoric/ertp": "^0.16.2", "@agoric/internal": "^0.3.2", + "@agoric/notifier": "^0.6.2", "@agoric/orchestration": "^0.1.0", "@agoric/store": "^0.9.2", "@agoric/vow": "^0.1.0", diff --git a/packages/fast-usdc/src/exos/advancer.js b/packages/fast-usdc/src/exos/advancer.js index ab5443066d3..13a73cf7f9e 100644 --- a/packages/fast-usdc/src/exos/advancer.js +++ b/packages/fast-usdc/src/exos/advancer.js @@ -14,23 +14,22 @@ import { addressTools } from '../utils/address.js'; * @import {Zone} from '@agoric/zone'; * @import {CctpTxEvidence, LogFn} from '../types.js'; * @import {StatusManager} from './status-manager.js'; - * @import {TransactionFeed} from './transaction-feed.js'; + * @import {TransactionFeedKit} from './transaction-feed.js'; */ /** * @param {Zone} zone * @param {object} caps * @param {ChainHub} caps.chainHub - * @param {TransactionFeed} caps.feed * @param {LogFn} caps.log * @param {StatusManager} caps.statusManager * @param {VowTools} caps.vowTools */ export const prepareAdvancer = ( zone, - { chainHub, feed, log, statusManager, vowTools: { watch } }, + { chainHub, log, statusManager, vowTools: { watch } }, ) => { - assertAllDefined({ feed, statusManager, watch }); + assertAllDefined({ statusManager, watch }); const transferHandler = zone.exo( 'Fast USDC Advance Transfer Handler', diff --git a/packages/fast-usdc/src/exos/transaction-feed.js b/packages/fast-usdc/src/exos/transaction-feed.js index 481f33a1eb0..d39a3eebc00 100644 --- a/packages/fast-usdc/src/exos/transaction-feed.js +++ b/packages/fast-usdc/src/exos/transaction-feed.js @@ -1,13 +1,53 @@ +import { makeTracer } from '@agoric/internal'; +import { prepareDurablePublishKit } from '@agoric/notifier'; +import { M } from '@endo/patterns'; +import { CctpTxEvidenceShape } from '../typeGuards.js'; + /** * @import {Zone} from '@agoric/zone'; + * @import {CctpTxEvidence} from '../types.js'; */ +const trace = makeTracer('TxFeed', true); + +export const INVITATION_MAKERS_DESC = 'transaction oracle invitation'; + +const TransactionFeedKitI = harden({ + admin: M.interface('Transaction Feed Admin', { + submitEvidence: M.call(CctpTxEvidenceShape).returns(), + }), + public: M.interface('Transaction Feed Public', { + getEvidenceStream: M.call().returns(M.remotable()), + }), +}); + /** * @param {Zone} zone */ -export const prepareTransactionFeed = zone => { - return zone.exo('Fast USDC Feed', undefined, {}); +export const prepareTransactionFeedKit = zone => { + const kinds = zone.mapStore('Kinds'); + const makeDurablePublishKit = prepareDurablePublishKit( + kinds, + 'Transaction Feed', + ); + /** @type {PublishKit} */ + const { publisher, subscriber } = makeDurablePublishKit(); + + return zone.exoClassKit('Fast USDC Feed', TransactionFeedKitI, () => ({}), { + admin: { + /** @param {CctpTxEvidence } evidence */ + submitEvidence: evidence => { + trace('TEMPORARY: Add evidence:', evidence); + // TODO decentralize + // TODO validate that it's valid to publish + publisher.publish(evidence); + }, + }, + public: { + getEvidenceStream: () => subscriber, + }, + }); }; -harden(prepareTransactionFeed); +harden(prepareTransactionFeedKit); -/** @typedef {ReturnType} TransactionFeed */ +/** @typedef {ReturnType} TransactionFeedKit */ diff --git a/packages/fast-usdc/src/fast-usdc.contract.js b/packages/fast-usdc/src/fast-usdc.contract.js index 445073dcfb5..4dc100c40cc 100644 --- a/packages/fast-usdc/src/fast-usdc.contract.js +++ b/packages/fast-usdc/src/fast-usdc.contract.js @@ -1,17 +1,19 @@ import { BrandShape } from '@agoric/ertp/src/typeGuards.js'; +import { assertAllDefined, makeTracer } from '@agoric/internal'; +import { observeIteration, subscribeEach } from '@agoric/notifier'; import { withOrchestration } from '@agoric/orchestration'; import { M } from '@endo/patterns'; -import { assertAllDefined, makeTracer } from '@agoric/internal'; -import { prepareTransactionFeed } from './exos/transaction-feed.js'; -import { prepareSettler } from './exos/settler.js'; import { prepareAdvancer } from './exos/advancer.js'; +import { prepareSettler } from './exos/settler.js'; import { prepareStatusManager } from './exos/status-manager.js'; +import { prepareTransactionFeedKit } from './exos/transaction-feed.js'; const trace = makeTracer('FastUsdc'); /** * @import {OrchestrationPowers, OrchestrationTools} from '@agoric/orchestration/src/utils/start-helper.js'; * @import {Zone} from '@agoric/zone'; + * @import {CctpTxEvidence} from './types.js'; */ /** @@ -44,21 +46,58 @@ export const contract = async (zcf, privateArgs, zone, tools) => { assert('PoolShares' in terms.brands, 'no PoolShares brand'); const statusManager = prepareStatusManager(zone); - const feed = prepareTransactionFeed(zone); const makeSettler = prepareSettler(zone, { statusManager }); const { chainHub, vowTools } = tools; const makeAdvancer = prepareAdvancer(zone, { chainHub, - feed, log: trace, statusManager, vowTools, }); - assertAllDefined({ feed, makeAdvancer, makeSettler, statusManager }); + const makeFeedKit = prepareTransactionFeedKit(zone); + 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.getEvidenceStream()), { + updateState(evidence) { + try { + advancer.handleTransactionEvent(evidence); + } catch (err) { + trace('🚨 Error handling transaction event', err); + } + }, + }); const creatorFacet = zone.exo('Fast USDC Creator', undefined, {}); - return harden({ creatorFacet }); + const publicFacet = zone.exo('Fast USDC Public', undefined, { + // XXX to be removed before production + /** + * NB: Any caller with access to this invitation maker has the ability to + * add evidence. + * + * Provide an API call in the form of an invitation maker, so that the + * capability is available in the smart-wallet bridge. + * + * @param {CctpTxEvidence} evidence + */ + makeTestPushInvitation(evidence) { + // TODO(bootstrap integration): force this to throw and confirm that it + // shows up in the the smart-wallet UpdateRecord `error` property + feedKit.admin.submitEvidence(evidence); + return zcf.makeInvitation(async cSeat => { + trace('Offer made on noop invitation'); + cSeat.exit(); + return 'noop; evidence was pushed in the invitation maker call'; + }, 'noop invitation'); + }, + }); + + return harden({ creatorFacet, publicFacet }); }; harden(contract); diff --git a/packages/fast-usdc/test/exos/advancer.test.ts b/packages/fast-usdc/test/exos/advancer.test.ts index c285a46c6d4..4597253b657 100644 --- a/packages/fast-usdc/test/exos/advancer.test.ts +++ b/packages/fast-usdc/test/exos/advancer.test.ts @@ -8,7 +8,7 @@ import type { Zone } from '@agoric/zone'; import type { VowTools } from '@agoric/vow'; import { prepareAdvancer } from '../../src/exos/advancer.js'; import { prepareStatusManager } from '../../src/exos/status-manager.js'; -import { prepareTransactionFeed } from '../../src/exos/transaction-feed.js'; +import { prepareTransactionFeedKit } from '../../src/exos/transaction-feed.js'; import { commonSetup } from '../supports.js'; import { MockCctpTxEvidences } from '../fixtures.js'; @@ -43,10 +43,8 @@ test.beforeEach(async t => { const statusManager = prepareStatusManager( rootZone.subZone('status-manager'), ); - const feed = prepareTransactionFeed(rootZone.subZone('feed')); const makeAdvancer = prepareAdvancer(rootZone.subZone('advancer'), { chainHub, - feed, statusManager, vowTools, log, diff --git a/packages/fast-usdc/test/exos/transaction-feed.test.ts b/packages/fast-usdc/test/exos/transaction-feed.test.ts new file mode 100644 index 00000000000..5688b9db73c --- /dev/null +++ b/packages/fast-usdc/test/exos/transaction-feed.test.ts @@ -0,0 +1,11 @@ +// Must be first to set up globals +import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; + +import { makeHeapZone } from '@agoric/zone'; +import { prepareTransactionFeedKit } from '../../src/exos/transaction-feed.js'; + +test('basics', t => { + const zone = makeHeapZone(); + const kit = prepareTransactionFeedKit(zone); + t.deepEqual(Object.values(kit), []); +}); diff --git a/packages/fast-usdc/test/fast-usdc.contract.test.ts b/packages/fast-usdc/test/fast-usdc.contract.test.ts index 8d4d3db0ec6..6e24bae2133 100644 --- a/packages/fast-usdc/test/fast-usdc.contract.test.ts +++ b/packages/fast-usdc/test/fast-usdc.contract.test.ts @@ -3,6 +3,8 @@ import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js'; import { E } from '@endo/far'; import path from 'path'; +import fetchedChainInfo from '@agoric/orchestration/src/fetched-chain-info.js'; +import { MockCctpTxEvidences } from './fixtures.js'; import { commonSetup } from './supports.js'; const dirname = path.dirname(new URL(import.meta.url).pathname); @@ -12,17 +14,20 @@ type StartFn = typeof import('../src/fast-usdc.contract.js').start; test('start', async t => { const { - bootstrap, brands: { poolShares, usdc }, commonPrivateArgs, - utils, } = await commonSetup(t); - const { zoe, bundleAndInstall } = await setUpZoeForTest(); + const { zoe, bundleAndInstall } = await setUpZoeForTest({ + setJig: jig => { + jig.chainHub.registerChain('osmosis', fetchedChainInfo.osmosis); + }, + }); + const installation: Installation = await bundleAndInstall(contractFile); - const { creatorFacet } = await E(zoe).startInstance( + const { creatorFacet, publicFacet } = await E(zoe).startInstance( installation, { PoolShares: poolShares.issuer, USDC: usdc.issuer }, { @@ -32,4 +37,16 @@ test('start', async t => { commonPrivateArgs, ); t.truthy(creatorFacet); + + const e1 = await E(MockCctpTxEvidences.AGORIC_NO_PARAMS)(); + + 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(), + 'noop; evidence was pushed in the invitation maker call', + ); });