Skip to content

Commit

Permalink
10387 simple transaction feed for Fast USDC (#10452)
Browse files Browse the repository at this point in the history
closes: #10387

## Description

A basic transaction feed component. It has a public invitation maker that isn't for production but will work for contract tests as Advancer and Settle are built.

### Security Considerations


### Scaling Considerations


### Documentation Considerations


### Testing Considerations


### Upgrade Considerations
  • Loading branch information
mergify[bot] authored Nov 13, 2024
2 parents eb1d655 + 8eb7dee commit 596ebab
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 27 deletions.
1 change: 1 addition & 0 deletions packages/fast-usdc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 3 additions & 4 deletions packages/fast-usdc/src/exos/advancer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
48 changes: 44 additions & 4 deletions packages/fast-usdc/src/exos/transaction-feed.js
Original file line number Diff line number Diff line change
@@ -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<CctpTxEvidence>} */
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<typeof prepareTransactionFeed>} TransactionFeed */
/** @typedef {ReturnType<typeof prepareTransactionFeedKit>} TransactionFeedKit */
53 changes: 46 additions & 7 deletions packages/fast-usdc/src/fast-usdc.contract.js
Original file line number Diff line number Diff line change
@@ -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';
*/

/**
Expand Down Expand Up @@ -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);

Expand Down
4 changes: 1 addition & 3 deletions packages/fast-usdc/test/exos/advancer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down
11 changes: 11 additions & 0 deletions packages/fast-usdc/test/exos/transaction-feed.test.ts
Original file line number Diff line number Diff line change
@@ -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), []);
});
25 changes: 21 additions & 4 deletions packages/fast-usdc/test/fast-usdc.contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<StartFn> =
await bundleAndInstall(contractFile);

const { creatorFacet } = await E(zoe).startInstance(
const { creatorFacet, publicFacet } = await E(zoe).startInstance(
installation,
{ PoolShares: poolShares.issuer, USDC: usdc.issuer },
{
Expand All @@ -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',
);
});
1 change: 1 addition & 0 deletions packages/fast-usdc/test/supports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ export const commonSetup = async (t: ExecutionContext<any>) => {
},
facadeServices: {
agoricNames,
/** A chainHub for Exo tests, distinct from the one a contract makes within `withOrchestration` */
chainHub,
localchain,
orchestrationService: cosmosInterchainService,
Expand Down
9 changes: 5 additions & 4 deletions packages/orchestration/src/utils/start-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,6 @@ export const provideOrchestration = (
remotePowers,
marshaller,
) => {
zcf.setTestJig(() => ({
baggage,
}));

// separate zones
const zones = (() => {
const zone = makeDurableZone(baggage);
Expand Down Expand Up @@ -160,6 +156,11 @@ export const provideOrchestration = (
zones.contract.subZone('orchestration'),
);

zcf.setTestJig(() => ({
baggage,
chainHub,
}));

return {
...defaultOrchestrateKit,
makeOrchestrateKit,
Expand Down
2 changes: 1 addition & 1 deletion packages/zoe/tools/setup-zoe.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const makeZoeForTest = vatAdminSvc =>
* Returns promises for `zoe` and the `feeMintAccess`.
* Provide testing versions of capabilities for Zoe contracts.
*
* @template {object} [T=unknown]
* @template {object} [T=any]
* @param {object} options
* @param {(jig: T) => void} [options.setJig]
* @param {FeeIssuerConfig} [options.feeIssuerConfig]
Expand Down

0 comments on commit 596ebab

Please sign in to comment.