From 2fcb21f1e7f15b0fae3e187979b09feab5c5886e Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Fri, 10 Jan 2025 10:12:46 -0800 Subject: [PATCH 1/5] fix: omit 'admin' from operator offer result --- .../fast-usdc/src/exos/transaction-feed.js | 26 ++++++++++++++----- packages/fast-usdc/src/fast-usdc.contract.js | 4 +-- .../fast-usdc/test/fast-usdc.contract.test.ts | 17 +++++------- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/packages/fast-usdc/src/exos/transaction-feed.js b/packages/fast-usdc/src/exos/transaction-feed.js index 3adb69bb0f1..5b4ee237092 100644 --- a/packages/fast-usdc/src/exos/transaction-feed.js +++ b/packages/fast-usdc/src/exos/transaction-feed.js @@ -15,6 +15,10 @@ import { prepareOperatorKit } from './operator-kit.js'; const trace = makeTracer('TxFeed', true); +/** + * @typedef {Pick} OperatorOfferResult + */ + /** Name in the invitation purse (keyed also by this contract instance) */ export const INVITATION_MAKERS_DESC = 'oracle operator invitation'; @@ -27,8 +31,10 @@ const TransactionFeedKitI = harden({ ).returns(), }), creator: M.interface('Transaction Feed Creator', { - // TODO narrow the return shape to OperatorKit - initOperator: M.call(M.string()).returns(M.record()), + initOperator: M.call(M.string()).returns({ + invitationMakers: M.remotable(), + operator: M.remotable(), + }), makeOperatorInvitation: M.call(M.string()).returns(M.promise()), removeOperator: M.call(M.string()).returns(), }), @@ -92,14 +98,14 @@ export const prepareTransactionFeedKit = (zone, zcf) => { * CCTP transactions. * * @param {string} operatorId unique per contract instance - * @returns {Promise>} + * @returns {Promise>} */ makeOperatorInvitation(operatorId) { const { creator } = this.facets; trace('makeOperatorInvitation', operatorId); return zcf.makeInvitation( - /** @type {OfferHandler} */ + /** @type {OfferHandler} */ seat => { seat.exit(); return creator.initOperator(operatorId); @@ -107,7 +113,10 @@ export const prepareTransactionFeedKit = (zone, zcf) => { INVITATION_MAKERS_DESC, ); }, - /** @param {string} operatorId */ + /** + * @param {string} operatorId + * @returns {OperatorOfferResult} + */ initOperator(operatorId) { const { operators, pending, risks } = this.state; trace('initOperator', operatorId); @@ -123,7 +132,12 @@ export const prepareTransactionFeedKit = (zone, zcf) => { ); risks.init(operatorId, zone.detached().mapStore('risk assessments')); - return operatorKit; + // Subset facets to all the off-chain operator needs + const { invitationMakers, operator } = operatorKit; + return { + invitationMakers, + operator, + }; }, /** @param {string} operatorId */ diff --git a/packages/fast-usdc/src/fast-usdc.contract.js b/packages/fast-usdc/src/fast-usdc.contract.js index cbac54c5acc..0f0b494e170 100644 --- a/packages/fast-usdc/src/fast-usdc.contract.js +++ b/packages/fast-usdc/src/fast-usdc.contract.js @@ -37,7 +37,7 @@ const ADDRESSES_BAGGAGE_KEY = 'addresses'; * @import {Remote} from '@agoric/internal'; * @import {Marshaller, StorageNode} from '@agoric/internal/src/lib-chainStorage.js' * @import {Zone} from '@agoric/zone'; - * @import {OperatorKit} from './exos/operator-kit.js'; + * @import {OperatorOfferResult} from './exos/transaction-feed.js'; * @import {CctpTxEvidence, FeeConfig, RiskAssessment} from './types.js'; */ @@ -159,7 +159,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => { const { makeLocalAccount, makeNobleAccount } = orchestrateAll(flows, {}); const creatorFacet = zone.exo('Fast USDC Creator', undefined, { - /** @type {(operatorId: string) => Promise>} */ + /** @type {(operatorId: string) => Promise>} */ async makeOperatorInvitation(operatorId) { return feedKit.creator.makeOperatorInvitation(operatorId); }, diff --git a/packages/fast-usdc/test/fast-usdc.contract.test.ts b/packages/fast-usdc/test/fast-usdc.contract.test.ts index 202aef71192..ebd1bd5caea 100644 --- a/packages/fast-usdc/test/fast-usdc.contract.test.ts +++ b/packages/fast-usdc/test/fast-usdc.contract.test.ts @@ -39,6 +39,7 @@ import type { CctpTxEvidence, FeeConfig, PoolMetrics } from '../src/types.js'; import { makeFeeTools } from '../src/utils/fees.js'; import { MockCctpTxEvidences } from './fixtures.js'; import { commonSetup, uusdcOnAgoric } from './supports.js'; +import type { OperatorOfferResult } from '../src/exos/transaction-feed.js'; const dirname = path.dirname(new URL(import.meta.url).pathname); @@ -223,7 +224,7 @@ const purseOf = }; const makeOracleOperator = async ( - opInv: Invitation, + opInv: Invitation, txSubscriber: Subscriber, zoe: ZoeService, t: ExecutionContext, @@ -234,15 +235,9 @@ const makeOracleOperator = async ( description: 'oracle operator invitation', }); - // operator only gets `.invitationMakers` - // but for testing, we need `.admin` too. UNTIL #????? - const operatorKit = await E(E(zoe).offer(opInv)).getOfferResult(); - t.deepEqual(Object.keys(operatorKit), [ - 'admin', - 'invitationMakers', - 'operator', - ]); - const { invitationMakers } = operatorKit; + const offerResult = await E(E(zoe).offer(opInv)).getOfferResult(); + t.deepEqual(Object.keys(offerResult), ['invitationMakers', 'operator']); + const { invitationMakers } = offerResult; let active = true; @@ -274,7 +269,7 @@ const makeOracleOperator = async ( getDone: () => done, getFailures: () => harden([...failures]), // operator only gets .invitationMakers - getKit: () => operatorKit, + getKit: () => offerResult, setActive: flag => { active = flag; }, From b620c78318f0ead9f91137f9b96047f37d3b4878 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Fri, 20 Dec 2024 08:50:11 -0800 Subject: [PATCH 2/5] fix: removeOperator interface --- packages/fast-usdc/src/exos/transaction-feed.js | 2 +- packages/fast-usdc/test/exos/transaction-feed.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/fast-usdc/src/exos/transaction-feed.js b/packages/fast-usdc/src/exos/transaction-feed.js index 5b4ee237092..a57de4aba50 100644 --- a/packages/fast-usdc/src/exos/transaction-feed.js +++ b/packages/fast-usdc/src/exos/transaction-feed.js @@ -141,7 +141,7 @@ export const prepareTransactionFeedKit = (zone, zcf) => { }, /** @param {string} operatorId */ - async removeOperator(operatorId) { + removeOperator(operatorId) { const { operators } = this.state; trace('removeOperator', operatorId); const operatorKit = operators.get(operatorId); diff --git a/packages/fast-usdc/test/exos/transaction-feed.test.ts b/packages/fast-usdc/test/exos/transaction-feed.test.ts index e3cb8df299b..f05a78af9a3 100644 --- a/packages/fast-usdc/test/exos/transaction-feed.test.ts +++ b/packages/fast-usdc/test/exos/transaction-feed.test.ts @@ -189,7 +189,7 @@ test('disagreement after publishing', async t => { }); }); -test('disabled operator', async t => { +test('remove operator', async t => { const feedKit = makeFeedKit(); const { op1 } = await makeOperators(feedKit); const evidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); @@ -197,7 +197,7 @@ test('disabled operator', async t => { // works before disabling op1.operator.submitEvidence(evidence); - op1.admin.disable(); + await feedKit.creator.removeOperator('op1'); t.throws(() => op1.operator.submitEvidence(evidence), { message: 'submitEvidence for disabled operator', From 72cb717cea4fc6fdaec5c4ed0cfb7d0cdcf998b1 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Fri, 10 Jan 2025 16:24:28 -0800 Subject: [PATCH 3/5] chore: remove makeTestPushInvitation --- packages/fast-usdc/src/fast-usdc.contract.js | 21 -------------------- 1 file changed, 21 deletions(-) diff --git a/packages/fast-usdc/src/fast-usdc.contract.js b/packages/fast-usdc/src/fast-usdc.contract.js index 0f0b494e170..2d9b9844096 100644 --- a/packages/fast-usdc/src/fast-usdc.contract.js +++ b/packages/fast-usdc/src/fast-usdc.contract.js @@ -22,7 +22,6 @@ import { prepareStatusManager } from './exos/status-manager.js'; import { prepareTransactionFeedKit } from './exos/transaction-feed.js'; import * as flows from './fast-usdc.flows.js'; import { FastUSDCTermsShape, FeeConfigShape } from './type-guards.js'; -import { defineInertInvitation } from './utils/zoe.js'; const trace = makeTracer('FastUsdc'); @@ -151,11 +150,6 @@ export const contract = async (zcf, privateArgs, zone, tools) => { { makeRecorderKit }, ); - const makeTestInvitation = defineInertInvitation( - zcf, - 'test of forcing evidence', - ); - const { makeLocalAccount, makeNobleAccount } = orchestrateAll(flows, {}); const creatorFacet = zone.exo('Fast USDC Creator', undefined, { @@ -193,21 +187,6 @@ export const contract = async (zcf, privateArgs, zone, tools) => { }); 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 - * force handling of evidence. - * - * Provide an API call in the form of an invitation maker, so that the - * capability is available in the smart-wallet bridge during UI testing. - * - * @param {CctpTxEvidence} evidence - * @param {RiskAssessment} [risk] - */ - makeTestPushInvitation(evidence, risk = {}) { - void advancer.handleTransactionEvent({ evidence, risk }); - return makeTestInvitation(); - }, makeDepositInvitation() { return poolKit.public.makeDepositInvitation(); }, From 3ad8050d4576a434a47e1b2f43b041752e33788b Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Fri, 10 Jan 2025 11:13:01 -0800 Subject: [PATCH 4/5] refactor: wfd for walletFactoryDriver --- .../boot/test/fast-usdc/fast-usdc.test.ts | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/boot/test/fast-usdc/fast-usdc.test.ts b/packages/boot/test/fast-usdc/fast-usdc.test.ts index 015dded9ea5..c5b2543179a 100644 --- a/packages/boot/test/fast-usdc/fast-usdc.test.ts +++ b/packages/boot/test/fast-usdc/fast-usdc.test.ts @@ -51,8 +51,8 @@ test.before('bootstrap', async t => { test.after.always(t => t.context.shutdown?.()); test.serial('oracles provision before contract deployment', async t => { - const { walletFactoryDriver: wd } = t.context; - const watcherWallet = await wd.provideSmartWallet('agoric1watcher1'); + const { walletFactoryDriver: wfd } = t.context; + const watcherWallet = await wfd.provideSmartWallet('agoric1watcher1'); t.truthy(watcherWallet); }); @@ -66,12 +66,12 @@ test.serial( evalProposal, refreshAgoricNamesRemotes, storage, - walletFactoryDriver: wd, + walletFactoryDriver: wfd, } = t.context; const { oracles } = configurations.MAINNET; const [watcherWallet] = await Promise.all( - Object.values(oracles).map(addr => wd.provideSmartWallet(addr)), + Object.values(oracles).map(addr => wfd.provideSmartWallet(addr)), ); // inbound `startChannelOpenInit` responses immediately. @@ -201,8 +201,8 @@ test.serial('writes account addresses to vstorage', async t => { }); test.serial('LP deposits', async t => { - const { walletFactoryDriver: wd, agoricNamesRemotes } = t.context; - const lp = await wd.provideSmartWallet( + const { walletFactoryDriver: wfd, agoricNamesRemotes } = t.context; + const lp = await wfd.provideSmartWallet( 'agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8', ); @@ -270,15 +270,15 @@ test.serial('LP deposits', async t => { test.serial('makes usdc advance', async t => { const { - walletFactoryDriver: wd, + walletFactoryDriver: wfd, storage, agoricNamesRemotes, harness, } = t.context; const oracles = await Promise.all([ - wd.provideSmartWallet('agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8'), - wd.provideSmartWallet('agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr'), - wd.provideSmartWallet('agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj'), + wfd.provideSmartWallet('agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8'), + wfd.provideSmartWallet('agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr'), + wfd.provideSmartWallet('agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj'), ]); await Promise.all( oracles.map(wallet => @@ -298,7 +298,7 @@ test.serial('makes usdc advance', async t => { const lastNodeValue = storage.getValues('published.fastUsdc').at(-1); const { settlementAccount } = JSON.parse(NonNullish(lastNodeValue)); const evidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO( - // mock with the read settlementAccount address + // mock with the real settlementAccount address encodeAddressHook(settlementAccount, { EUD }), ); @@ -344,18 +344,18 @@ test.serial('makes usdc advance', async t => { }); test.serial('skips usdc advance when risks identified', async t => { - const { walletFactoryDriver: wd, storage } = t.context; + const { walletFactoryDriver: wfd, storage } = t.context; const oracles = await Promise.all([ - wd.provideSmartWallet('agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8'), - wd.provideSmartWallet('agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr'), - wd.provideSmartWallet('agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj'), + wfd.provideSmartWallet('agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8'), + wfd.provideSmartWallet('agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr'), + wfd.provideSmartWallet('agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj'), ]); const EUD = 'dydx1riskyeud'; const lastNodeValue = storage.getValues('published.fastUsdc').at(-1); const { settlementAccount } = JSON.parse(NonNullish(lastNodeValue)); const evidence = MockCctpTxEvidences.AGORIC_PLUS_DYDX( - // mock with the read settlementAccount address + // mock with the real settlementAccount address encodeAddressHook(settlementAccount, { EUD }), ); @@ -394,8 +394,8 @@ test.serial('skips usdc advance when risks identified', async t => { }); test.serial('LP withdraws', async t => { - const { walletFactoryDriver: wd, agoricNamesRemotes } = t.context; - const lp = await wd.provideSmartWallet( + const { walletFactoryDriver: wfd, agoricNamesRemotes } = t.context; + const lp = await wfd.provideSmartWallet( 'agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8', ); From 7cc7a5ed4e894180f1f6cd8933d76f3927d6d30a Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Fri, 10 Jan 2025 10:22:24 -0800 Subject: [PATCH 5/5] feat: remove operator --- .../boot/test/fast-usdc/fast-usdc.test.ts | 85 ++++++++++++++++--- packages/fast-usdc/src/fast-usdc.contract.js | 4 + packages/fast-usdc/src/fast-usdc.start.js | 1 + 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/packages/boot/test/fast-usdc/fast-usdc.test.ts b/packages/boot/test/fast-usdc/fast-usdc.test.ts index c5b2543179a..4d5eb541799 100644 --- a/packages/boot/test/fast-usdc/fast-usdc.test.ts +++ b/packages/boot/test/fast-usdc/fast-usdc.test.ts @@ -1,28 +1,30 @@ import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; - import type { TestFn } from 'ava'; + import { encodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js'; import { configurations } from '@agoric/fast-usdc/src/utils/deploy-config.js'; import { MockCctpTxEvidences } from '@agoric/fast-usdc/test/fixtures.js'; import { documentStorageSchema } from '@agoric/governance/tools/storageDoc.js'; -import { Fail } from '@endo/errors'; +import { + BridgeId, + deeplyFulfilledObject, + NonNullish, + objectMap, +} from '@agoric/internal'; import { unmarshalFromVstorage } from '@agoric/internal/src/marshal.js'; +import { defaultSerializer } from '@agoric/internal/src/storage-test-utils.js'; +import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; +import { Fail } from '@endo/errors'; import { makeMarshal } from '@endo/marshal'; import { - defaultMarshaller, - defaultSerializer, -} from '@agoric/internal/src/storage-test-utils.js'; -import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; -import { BridgeId, NonNullish } from '@agoric/internal'; + AckBehavior, + insistManagerType, + makeSwingsetHarness, +} from '../../tools/supports.js'; import { makeWalletFactoryContext, type WalletFactoryTestContext, } from '../bootstrapTests/walletFactory.js'; -import { - makeSwingsetHarness, - insistManagerType, - AckBehavior, -} from '../../tools/supports.js'; const test: TestFn< WalletFactoryTestContext & { @@ -463,3 +465,62 @@ test.serial('restart contract', async t => { const actual = await EV(kit.adminFacet).restartContract(kit.privateArgs); t.deepEqual(actual, { incarnationNumber: 1 }); }); + +test.serial('replace operators', async t => { + const { + agoricNamesRemotes, + storage, + runUtils: { EV }, + walletFactoryDriver: wfd, + } = t.context; + const { creatorFacet } = await EV.vat('bootstrap').consumeItem('fastUsdcKit'); + + const EUD = 'dydx1anything'; + const lastNodeValue = storage.getValues('published.fastUsdc').at(-1); + const { settlementAccount } = JSON.parse(NonNullish(lastNodeValue)); + const evidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO( + // mock with the real settlementAccount address + encodeAddressHook(settlementAccount, { EUD }), + ); + + // Remove old oracle operators (nested in block to isolate bindings) + { + // old oracles, which were started with MAINNET config + const { oracles } = configurations.MAINNET; + + for (const [name, address] of Object.entries(oracles)) { + t.log('Removing operator', name, 'at', address); + await EV(creatorFacet).removeOperator(address); + } + + const wallets = await Promise.all( + Object.values(oracles).map(addr => wfd.provideSmartWallet(addr)), + ); + + await Promise.all( + wallets.map(wallet => + wallet.sendOffer({ + id: 'submit-while-disabled', + invitationSpec: { + source: 'continuing', + previousOffer: 'claim-oracle-invitation', + invitationMakerName: 'SubmitEvidence', + invitationArgs: [evidence], + }, + proposal: {}, + }), + ), + ); + for (const wd of wallets) { + t.like(wd.getLatestUpdateRecord(), { + status: { + id: 'submit-while-disabled', + error: 'Error: submitEvidence for disabled operator', + }, + }); + } + } + + // TODO test adding new operators + // The naive approach is failing under XS. A new CoreEval may be necessary. +}); diff --git a/packages/fast-usdc/src/fast-usdc.contract.js b/packages/fast-usdc/src/fast-usdc.contract.js index 2d9b9844096..8970b8a8117 100644 --- a/packages/fast-usdc/src/fast-usdc.contract.js +++ b/packages/fast-usdc/src/fast-usdc.contract.js @@ -157,6 +157,10 @@ export const contract = async (zcf, privateArgs, zone, tools) => { async makeOperatorInvitation(operatorId) { return feedKit.creator.makeOperatorInvitation(operatorId); }, + /** @type {(operatorId: string) => void} */ + removeOperator(operatorId) { + return feedKit.creator.removeOperator(operatorId); + }, async connectToNoble() { return vowTools.when(nobleAccountV, nobleAccount => { trace('nobleAccount', nobleAccount); diff --git a/packages/fast-usdc/src/fast-usdc.start.js b/packages/fast-usdc/src/fast-usdc.start.js index 41042587aea..2be12397286 100644 --- a/packages/fast-usdc/src/fast-usdc.start.js +++ b/packages/fast-usdc/src/fast-usdc.start.js @@ -109,6 +109,7 @@ const publishFeedPolicy = async (node, policy) => { * }} FastUSDCCorePowers * * @typedef {StartedInstanceKitWithLabel & { + * creatorFacet: Awaited>['creatorFacet']; * privateArgs: StartParams['privateArgs']; * }} FastUSDCKit */