From 96d1681232b35b0eaf7977ed98110ecca2a10c35 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 21 Feb 2024 17:22:20 -0600 Subject: [PATCH 01/20] chore(postalSvc): copy 3d899ff --- contract/src/postalSvc.js | 64 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 contract/src/postalSvc.js diff --git a/contract/src/postalSvc.js b/contract/src/postalSvc.js new file mode 100644 index 00000000..045d8374 --- /dev/null +++ b/contract/src/postalSvc.js @@ -0,0 +1,64 @@ +// @ts-check +import { E, Far } from '@endo/far'; +import { M, mustMatch } from '@endo/patterns'; +import { withdrawFromSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js'; + +const { keys, values } = Object; + +/** + * @typedef {object} PostalSvcTerms + * @property {import('@agoric/vats').NameHub} namesByAddress + */ + +/** @param {ZCF} zcf */ +export const start = zcf => { + const { namesByAddress, issuers } = zcf.getTerms(); + mustMatch(namesByAddress, M.remotable('namesByAddress')); + console.log('postalSvc issuers', Object.keys(issuers)); + + /** + * @param {string} addr + * @returns {ERef} + */ + const getDepositFacet = addr => { + assert.typeof(addr, 'string'); + return E(namesByAddress).lookup(addr, 'depositFacet'); + }; + + /** + * @param {string} addr + * @param {Payment} pmt + */ + const sendTo = (addr, pmt) => E(getDepositFacet(addr)).receive(pmt); + + /** @param {string} recipient */ + const makeSendInvitation = recipient => { + assert.typeof(recipient, 'string'); + + /** @type {OfferHandler} */ + const handleSend = async seat => { + const { give } = seat.getProposal(); + const depositFacet = await getDepositFacet(recipient); + const payouts = await withdrawFromSeat(zcf, seat, give); + + // XXX partial failure? return payments? + await Promise.all( + values(payouts).map(pmtP => + Promise.resolve(pmtP).then(pmt => E(depositFacet).receive(pmt)), + ), + ); + seat.exit(); + return `sent ${keys(payouts).join(', ')}`; + }; + + return zcf.makeInvitation(handleSend, 'send'); + }; + + const publicFacet = Far('postalSvc', { + lookup: (...path) => E(namesByAddress).lookup(...path), + getDepositFacet, + sendTo, + makeSendInvitation, + }); + return { publicFacet }; +}; From 6fe5ee460c2fb91543ea88c27d97e2feda8c15bf Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 21 Feb 2024 17:16:17 -0600 Subject: [PATCH 02/20] chore(start-postalSvc.js): copy b8be868 --- contract/src/start-postalSvc.js | 99 +++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 contract/src/start-postalSvc.js diff --git a/contract/src/start-postalSvc.js b/contract/src/start-postalSvc.js new file mode 100644 index 00000000..4da0b4b4 --- /dev/null +++ b/contract/src/start-postalSvc.js @@ -0,0 +1,99 @@ +/** + * @file core eval script* to start the postalSvc contract. + * + * * see test-gimix-proposal.js to make a script from this file. + * + * The `permit` export specifies the corresponding permit. + */ +// @ts-check + +import { E } from '@endo/far'; +import { fixHub } from './fixHub.js'; + +const trace = (...args) => console.log('start-postalSvc', ...args); + +const fail = msg => { + throw Error(msg); +}; + +/** + * @typedef { typeof import('../src/postalSvc.js').start } PostalSvcFn + * + * @typedef {{ + * produce: { postalSvcKit: Producer }, + * installation: { + * consume: { postalSvc: Promise> }, + * produce: { postalSvc: Producer> }, + * } + * instance: { + * consume: { postalSvc: Promise['instance']> }, + * produce: { postalSvc: Producer['instance']> }, + * } + * }} PostalSvcPowers + */ +/** + * @deprecated use contractStarter + * a la starterSam in ../test/market-actors.js + * + * @param {BootstrapPowers & PostalSvcPowers} powers + * @param {{ options?: { postalSvc: { + * bundleID: string; + * issuerNames?: string[]; + * }}}} config + */ +export const startPostalSvc = async (powers, config) => { + console.warn('DEPRECATED. Use contractStarter. See starterSam example.'); + + const { + consume: { zoe, namesByAddressAdmin }, + installation: { + produce: { postalSvc: produceInstallation }, + }, + instance: { + produce: { postalSvc: produceInstance }, + }, + issuer: { consume: consumeIssuer }, + } = powers; + const { + bundleID = fail(`no bundleID`), + issuerNames = ['IST', 'Invitation'], + } = config.options?.postalSvc ?? {}; + + /** @type {Installation} */ + const installation = await E(zoe).installBundleID(bundleID); + produceInstallation.resolve(installation); + + const namesByAddress = await fixHub(namesByAddressAdmin); + + const issuers = Object.fromEntries( + issuerNames.map(n => [n, consumeIssuer[n]]), + ); + const { instance } = await E(zoe).startInstance(installation, issuers, { + namesByAddress, + }); + produceInstance.resolve(instance); + + trace('postalSvc started'); +}; + +export const manifest = /** @type {const} */ ({ + [startPostalSvc.name]: { + consume: { + agoricNames: true, + namesByAddress: true, + namesByAddressAdmin: true, + zoe: true, + }, + installation: { + produce: { postalSvc: true }, + }, + instance: { + produce: { postalSvc: true }, + }, + }, +}); + +export const permit = JSON.stringify(Object.values(manifest)[0]); + +// script completion value +startPostalSvc; From ba133ec15dc46aad40fbf0a1daa1f02d8a72168c Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Thu, 22 Feb 2024 14:07:19 -0600 Subject: [PATCH 03/20] chore(start-postalSvc): match builder patterns - not deprecated - permit tweak, handle undefined config - ATOM not available from consume.issuer - move type burden to callee --- contract/src/start-postalSvc.js | 38 ++++++++++++++------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/contract/src/start-postalSvc.js b/contract/src/start-postalSvc.js index 4da0b4b4..f603795f 100644 --- a/contract/src/start-postalSvc.js +++ b/contract/src/start-postalSvc.js @@ -1,8 +1,6 @@ /** * @file core eval script* to start the postalSvc contract. * - * * see test-gimix-proposal.js to make a script from this file. - * * The `permit` export specifies the corresponding permit. */ // @ts-check @@ -12,9 +10,7 @@ import { fixHub } from './fixHub.js'; const trace = (...args) => console.log('start-postalSvc', ...args); -const fail = msg => { - throw Error(msg); -}; +const { Fail } = assert; /** * @typedef { typeof import('../src/postalSvc.js').start } PostalSvcFn @@ -31,33 +27,31 @@ const fail = msg => { * } * }} PostalSvcPowers */ + /** - * @deprecated use contractStarter - * a la starterSam in ../test/market-actors.js - * - * @param {BootstrapPowers & PostalSvcPowers} powers + * @param {BootstrapPowers} powers * @param {{ options?: { postalSvc: { * bundleID: string; * issuerNames?: string[]; - * }}}} config + * }}}} [config] */ export const startPostalSvc = async (powers, config) => { - console.warn('DEPRECATED. Use contractStarter. See starterSam example.'); - + /** @type { BootstrapPowers & PostalSvcPowers} */ + // @ts-expect-error bootstrap powers evolve with BLD staker governance + const postalPowers = powers; const { - consume: { zoe, namesByAddressAdmin }, + consume: { zoe, namesByAddressAdmin, agoricNames }, installation: { produce: { postalSvc: produceInstallation }, }, instance: { produce: { postalSvc: produceInstance }, }, - issuer: { consume: consumeIssuer }, - } = powers; + } = postalPowers; const { - bundleID = fail(`no bundleID`), - issuerNames = ['IST', 'Invitation'], - } = config.options?.postalSvc ?? {}; + bundleID = Fail`no bundleID`, + issuerNames = ['IST', 'Invitation', 'BLD', 'ATOM'], + } = config?.options?.postalSvc ?? {}; /** @type {Installation} */ const installation = await E(zoe).installBundleID(bundleID); @@ -65,8 +59,9 @@ export const startPostalSvc = async (powers, config) => { const namesByAddress = await fixHub(namesByAddressAdmin); + // XXX ATOM isn't available via consume.issuer.ATOM. Odd. const issuers = Object.fromEntries( - issuerNames.map(n => [n, consumeIssuer[n]]), + issuerNames.map(n => [n, E(agoricNames).lookup('issuer', n)]), ); const { instance } = await E(zoe).startInstance(installation, issuers, { namesByAddress, @@ -93,7 +88,6 @@ export const manifest = /** @type {const} */ ({ }, }); -export const permit = JSON.stringify(Object.values(manifest)[0]); +export const permit = Object.values(manifest)[0]; -// script completion value -startPostalSvc; +export const main = startPostalSvc; From f49c04e68b496a314f9c52b62c683c1a85d35be7 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Thu, 22 Feb 2024 02:13:33 -0600 Subject: [PATCH 04/20] test(nameProxy): works in 1 case --- contract/test/ui-kit-goals/test-nameProxy.js | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 contract/test/ui-kit-goals/test-nameProxy.js diff --git a/contract/test/ui-kit-goals/test-nameProxy.js b/contract/test/ui-kit-goals/test-nameProxy.js new file mode 100644 index 00000000..aa6561c1 --- /dev/null +++ b/contract/test/ui-kit-goals/test-nameProxy.js @@ -0,0 +1,26 @@ +import '@endo/init/debug.js'; +import test from 'ava'; + +import { Far } from '@endo/far'; +import { makeNameHubKit } from '@agoric/vats'; +import { makeNameProxy } from './name-service-client.js'; + +test('makeNameProxy makes NameHub lookup convenient', async t => { + const k0 = makeNameHubKit(); + const kb = makeNameHubKit(); + k0.nameAdmin.update('brand', kb.nameHub, kb.nameAdmin); + const atomBrand = Far('Atom Brand', {}); + kb.nameAdmin.update('Atom', atomBrand); + + const agoricNames = k0.nameHub; + + const A = makeNameProxy(agoricNames); + + const ab = await A.brand.Atom; + t.log('brand', ab); + t.is(ab, atomBrand); + + const b = await A.brand; + t.log('hub', b); + t.is(b, kb.nameHub); +}); From 0e5393065f8f79d89ab6940f5833b4957d0507ab Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 5 Mar 2024 14:54:59 -0600 Subject: [PATCH 05/20] feat(name-service-client): makeAgoricNames, makeNameProxy --- .../test/ui-kit-goals/name-service-client.js | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 contract/test/ui-kit-goals/name-service-client.js diff --git a/contract/test/ui-kit-goals/name-service-client.js b/contract/test/ui-kit-goals/name-service-client.js new file mode 100644 index 00000000..2ccb6647 --- /dev/null +++ b/contract/test/ui-kit-goals/name-service-client.js @@ -0,0 +1,55 @@ +// @ts-check +import { E, Far } from '@endo/far'; + +/** @param {{ queryData: (path: string) => any }} qt */ +export const makeAgoricNames = async qt => { + assert(qt); + const nameHubCache = new Map(); + + /** @param {string} kind */ + const lookupKind = async kind => { + assert.typeof(kind, 'string'); + if (nameHubCache.has(kind)) { + return nameHubCache.get(kind); + } + const entries = await qt.queryData(`published.agoricNames.${kind}`); + const record = Object.fromEntries(entries); + const hub = Far('NameHub', { + lookup: name => record[name], + keys: () => entries.map(e => e[0]), + entries: () => entries, + }); + nameHubCache.set(kind, hub); + return hub; + }; + + const invalidate = () => { + nameHubCache.clear(); + }; + + const hub0 = Far('Hub', { + lookup: async (kind, ...more) => { + const hub2 = lookupKind(kind); + if (more.length > 0) { + return E(hub2).lookup(...more); + } + return hub2; + }, + }); + + return { lookup: hub0.lookup, invalidate }; +}; +const pmethods = harden(['then', 'catch', 'finally']); +// See also: https://github.com/endojs/endo/tree/mfig-o/packages/o +/** @param {ERef>} nodeP */ + +export const makeNameProxy = nodeP => + new Proxy(nodeP, { + get(target, prop, _rx) { + assert.typeof(prop, 'string'); + if (pmethods.includes(prop)) { + return target[prop].bind(target); + } + return makeNameProxy(E(target).lookup(prop)); + }, + }); From 09e06222249d5465a9e71424c9859505e8de173d Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 21 Feb 2024 17:19:11 -0600 Subject: [PATCH 06/20] chore(market-actors): copy b39f531 --- contract/test/market-actors.js | 467 +++++++++++++++++++++++++++++++++ 1 file changed, 467 insertions(+) create mode 100644 contract/test/market-actors.js diff --git a/contract/test/market-actors.js b/contract/test/market-actors.js new file mode 100644 index 00000000..d618b18d --- /dev/null +++ b/contract/test/market-actors.js @@ -0,0 +1,467 @@ +// @ts-check +import { E, getInterfaceOf } from '@endo/far'; +import { makePromiseKit } from '@endo/promise-kit'; +import { AmountMath } from '@agoric/ertp/src/amountMath.js'; +import { allValues, mapValues, seatLike } from './wallet-tools.js'; + +const { entries, fromEntries, keys } = Object; +const { Fail } = assert; + +/** + * @typedef {{ + * brand: Record> & { timer: unknown } + * issuer: Record> + * instance: Record> + * installation: Record> + * }} WellKnown + */ + +/** + * @typedef {{ + * assetKind: Map + * }} WellKnownKinds + */ + +/** + * @param {import('ava').ExecutionContext} t + * @param {{ wallet: import('./wallet-tools.js').MockWallet }} mine + * @param { WellKnown & WellKnownKinds } wellKnown + * @param {{ + * rxAddr: string, + * toSend: AmountKeywordRecord, + * svcInstance?: Instance, + * }} shared + */ +export const payerPete = async ( + t, + { wallet }, + wellKnown, + { rxAddr, toSend, svcInstance }, +) => { + const instance = await (svcInstance || wellKnown.instance.postalSvc); + + t.log('Pete offers to send to', rxAddr, 'via contract', instance); + const updates = await E(wallet.offers).executeOffer({ + id: 'peteSend1', + invitationSpec: { + source: 'contract', + instance, + publicInvitationMaker: 'makeSendInvitation', + invitationArgs: [rxAddr], + }, + proposal: { give: toSend }, + }); + + for await (const update of updates) { + // t.log('pete update', update); + if (update?.status?.payouts) { + for (const [kwd, amt] of Object.entries(update?.status?.payouts)) { + const { brand } = amt; + const kind = + wellKnown.assetKind.get(brand) || Fail`no kind for brand ${kwd}`; + t.log('Pete payout should be empty', amt); + t.deepEqual(amt, AmountMath.makeEmpty(brand, kind)); + } + t.is(update?.status?.numWantsSatisfied, 1); + break; + } + } +}; + +/** + * @param {import('ava').ExecutionContext} t + * @param {{ wallet: import('./wallet-tools.js').MockWallet, }} mine + * @param {WellKnown & WellKnownKinds} wellKnown + * @param {{ toSend: AmountKeywordRecord }} shared + */ +export const receiverRose = async (t, { wallet }, wellKnown, { toSend }) => { + const { makeEmpty } = AmountMath; + const purseNotifier = await allValues( + mapValues(toSend, amt => E(wallet.peek).purseNotifier(amt.brand)), + ); + + const initial = await allValues( + mapValues(purseNotifier, pn => E(pn).getUpdateSince()), + ); + for (const [name, update] of Object.entries(initial)) { + t.log('Rose', name, 'purse starts emtpy', update.value); + const brand = toSend[name].brand; + const kind = wellKnown.assetKind.get(brand) || Fail`${brand}`; + t.deepEqual(update.value, makeEmpty(brand, kind)); + } + + const done = await allValues( + Object.fromEntries( + Object.entries(initial).map(([name, update]) => { + const amtP = E(purseNotifier[name]) + .getUpdateSince(update.updateCount) + .then(u => { + t.log('Rose rxd', u.value); + t.deepEqual(u.value, toSend[name]); + return u.value; + }); + return [name, amtP]; + }), + ), + ); + t.log('Rose got balance updates', Object.keys(done)); + t.deepEqual(Object.keys(done), Object.keys(toSend)); +}; + +/** + * @param {import('ava').ExecutionContext} t + * @param {{ wallet: import('./wallet-tools.js').MockWallet, }} mine + * @param {{ toSend: AmountKeywordRecord }} shared + */ +export const receiverRex = async (t, { wallet }, { toSend }) => { + const purseNotifier = await allValues( + mapValues(toSend, amt => E(wallet.peek).purseNotifier(amt.brand)), + ); + + const initial = await allValues( + mapValues(purseNotifier, pn => E(pn).getUpdateSince()), + ); + + const done = await allValues( + fromEntries( + entries(initial).map(([name, update]) => { + const amtP = E(purseNotifier[name]) + .getUpdateSince(update.updateCount) + .then(u => { + t.log('Rex rxd', u.value); + t.deepEqual(u.value, toSend[name]); + return u.value; + }); + return [name, amtP]; + }), + ), + ); + t.log('Rex got balance updates', keys(done)); + t.deepEqual(keys(done), keys(toSend)); +}; + +export const senderContract = async ( + t, + { zoe, terms: { postalSvc: instance, destAddr: addr1 } }, +) => { + const iIssuer = await E(zoe).getInvitationIssuer(); + const iBrand = await E(iIssuer).getBrand(); + const postalSvc = E(zoe).getPublicFacet(instance); + const purse = await E(iIssuer).makeEmptyPurse(); + + const noInvitations = AmountMath.make(iBrand, harden([])); + const pmt1 = await E(purse).withdraw(noInvitations); + + t.log( + 'senderContract: E(', + getInterfaceOf(await postalSvc), + ').sendTo(', + addr1, + ',', + noInvitations, + ')', + ); + const sent = await E(postalSvc).sendTo(addr1, pmt1); + t.deepEqual(sent, noInvitations); +}; + +/** + * Auxiliary data + * @typedef {{ + * boardAux: (obj: unknown) => Promise + * }} BoardAux + */ + +/** + * @param {import('ava').ExecutionContext} t + * @param {{ + * wallet: import('./wallet-tools.js').MockWallet, + * }} mine + * @param { WellKnown & BoardAux} wellKnown + */ +export const starterSam = async (t, mine, wellKnown) => { + const { wallet } = mine; + + const checkKeys = (label, actual, expected) => { + label && t.log(label, actual); + t.deepEqual(keys(actual), keys(expected)); + }; + const first = array => { + t.true(Array.isArray(array)); + t.is(array.length, 1); + const [it] = array; + return it; + }; + const brand = { + Invitation: await wellKnown.brand.Invitation, + }; + const instance = { + contractStarter: await wellKnown.instance.contractStarter, + }; + + const expected = { + result: 'UNPUBLISHED', + payouts: { + Fee: { brand: brand.IST, value: 0n }, + Handles: { + brand: brand.Invitation, + value: [ + { + customDetails: { + installation: true, + instance: true, + }, + description: true, + handle: true, + installation: true, + instance: instance.contractStarter, + }, + ], + }, + }, + }; + + let offerSeq = 0; + /** @param {{ bundleID: string, label?: string}} opts */ + const install = async opts => { + const starterAux = await wellKnown.boardAux(instance.contractStarter); + const { prices } = starterAux.terms; + const { installBundleID: Fee } = prices; + t.log('sam gives', Fee, 'to install', opts.label); + const updates = await E(wallet.offers).executeOffer({ + id: `install-${(offerSeq += 1)}`, + invitationSpec: { + source: 'contract', + instance: instance.contractStarter, + publicInvitationMaker: 'makeInstallInvitation', + }, + proposal: { give: { Fee } }, + offerArgs: opts, + }); + const payouts = await E(seatLike(updates)).getPayouts(); + t.log('sam install paid', payouts); + const { + value: [ + { + customDetails: { installation }, + }, + ], + } = payouts.Handles; + return installation; + }; + + const getPostalSvcTerms = async () => { + const { + terms: { namesByAddress }, + } = await wellKnown.boardAux(instance.contractStarter); + t.log('Sam got namesByAddress from contractStarter terms', namesByAddress); + return { namesByAddress }; + }; + + /** + * @template SF + * @param {import('../src/contractStarter.js').StartOptions} opts + */ + const installAndStart = async opts => { + t.log( + 'Sam starts', + opts.instanceLabel, + 'from', + (opts.bundleID || '').slice(0, 8), + ); + const starterAux = await wellKnown.boardAux(instance.contractStarter); + const { prices } = starterAux.terms; + const { add } = AmountMath; + const Fee = add(prices.installBundleID, prices.startInstance); + t.log('sam gives', Fee, 'to start', opts.instanceLabel); + + const updates = await E(wallet.offers).executeOffer({ + id: `samStart-${(offerSeq += 1)}`, + invitationSpec: { + source: 'contract', + instance: instance.contractStarter, + publicInvitationMaker: 'makeStartInvitation', + invitationArgs: [opts], + }, + proposal: { give: { Fee } }, + }); + + const seat = seatLike(updates); + + const result = await E(seat).getOfferResult(); + t.log('Sam gets result', result); + t.is(result, expected.result); + + const payouts = await E(seat).getPayouts(); + checkKeys(undefined, payouts, expected.payouts); + const { Handles } = payouts; + t.is(Handles.brand, expected.payouts.Handles.brand); + const details = first(Handles.value); + const [details0] = expected.payouts.Handles.value; + checkKeys(undefined, details, details0); + t.is(details.instance, details0.instance); + checkKeys( + 'Sam gets instance etc.', + details.customDetails, + details0.customDetails, + ); + + return details.customDetails; + }; + + return { install, getPostalSvcTerms, installAndStart }; +}; + +/** + * @param {import('ava').ExecutionContext} t + * @param {{ + * wallet: import('./wallet-tools.js').MockWallet + * }} mine + * @param {*} wellKnown + */ +export const launcherLarry = async (t, { wallet }, wellKnown) => { + const { timerService: timer } = wellKnown; + const timerBrand = await wellKnown.brand.timer; + const MNY = { + brand: await wellKnown.brand.MNY, + issuer: await wellKnown.issuer.MNY, + }; + + const instance = { + contractStarter: await wellKnown.instance.contractStarter, + }; + + let offerSeq = 0; + let launchOfferId; + const deadline = harden({ timerBrand, absValue: 10n }); + + const launch = async ( + installation, + customTerms = { name: 'BRD', supplyQty: 1_000_000n }, + ) => { + !launchOfferId || Fail`already launched`; + + t.log('Larry prepares to launch', customTerms, 'at', deadline); + const { name } = customTerms; + const starterAux = await wellKnown.boardAux(instance.contractStarter); + const { startInstance } = starterAux.terms.prices; + + const startOpts = { + instanceLabel: `${name}-launch`, + installation, + issuerKeywordRecord: { Deposit: MNY.issuer }, + customTerms: { ...customTerms, deadline }, + }; + /** @type {import('@agoric/smart-wallet').OfferSpec} */ + + const id = `launch-${(offerSeq += 1)}`; + t.log('Larry pays', startInstance, 'to start', startOpts.label); + const updates = await E(wallet.offers).executeOffer({ + id, + invitationSpec: { + source: 'contract', + instance: instance.contractStarter, + publicInvitationMaker: 'makeStartInvitation', + invitationArgs: [startOpts], + }, + proposal: { + give: { Fee: startInstance }, + }, + }); + + const seat = seatLike(updates); + const result = await E(seat).getOfferResult(); + t.log('larry launch result', result); + const { Handles } = await E(seat).getPayouts(); + const { instance: launched } = Handles.value[0].customDetails; + t.log('larry launch instance', launched); + launchOfferId = id; + + return launched; + }; + + const collect = async () => { + t.log('Larry collects at', deadline); + const id = `collect-${(offerSeq += 1)}`; + const up2 = await E(wallet.offers).executeOffer({ + id, + invitationSpec: { + source: 'continuing', + previousOffer: launchOfferId, + invitationMakerName: 'Collect', + }, + proposal: { + want: { Deposit: AmountMath.make(MNY.brand, 0n) }, + exit: { afterDeadline: { timer, deadline } }, + }, + }); + const seat = seatLike(up2); + const result = await E(seat).getOfferResult(); + t.log('result', result); + const proceeds = await E(seat).getPayouts(); + t.log('Larry collected', proceeds); + }; + return { launch, collect }; +}; + +/** + * @param {import('ava').ExecutionContext} t + * @param {{ + * wallet: import('./wallet-tools.js').MockWallet, + * ix: number, + * qty: bigint, + * }} mine + * @param {*} wk + */ +export const makeFan = (t, { wallet, ix, qty }, wk) => { + let offerSeq = 0; + const deposit = async instance => { + const { terms } = await wk.boardAux(instance); + const { brands, issuers } = terms; + // t.log(ix, 'fan found brands', brands); + + await E(wallet.offers).addIssuer(issuers.Share); + await E(wallet.offers).addIssuer(issuers.BRD); + + const updates = await E(wallet.offers).executeOffer({ + id: `deposit-${(offerSeq += 1)}`, + invitationSpec: { + source: 'contract', + instance, + publicInvitationMaker: 'makeDepositInvitation', + }, + proposal: { + give: { Deposit: AmountMath.make(brands.Deposit, qty) }, + want: { Shares: AmountMath.make(brands.Share, qty) }, + }, + }); + const seat = seatLike(updates); + const payouts = await E(seat).getPayouts(); + t.log(ix, 'fan got', payouts.Shares); + return payouts.Shares; + }; + + const redeem = async instance => { + const { terms } = await wk.boardAux(instance); + const { brands } = terms; + + const updates = await E(wallet.offers).executeOffer({ + id: `redeem-${(offerSeq += 1)}`, + invitationSpec: { + source: 'contract', + instance, + publicInvitationMaker: 'makeRedeemInvitation', + }, + proposal: { + give: { Shares: AmountMath.make(brands.Share, qty) }, + want: { Minted: AmountMath.make(brands.BRD, 1n) }, + }, + }); + const seat = seatLike(updates); + const payouts = await E(seat).getPayouts(); + t.log(ix, 'fan got', payouts.Minted); + return payouts.Minted; + }; + + return { deposit, redeem }; +}; From f6f1a77e9a9a76509a320adec31120a682dfda41 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Thu, 22 Feb 2024 02:11:00 -0600 Subject: [PATCH 07/20] fix(market-actors): don't cheat on assetKind - chore: purseNotifer -> purseUpdates - chore: prune starter, launcher actors - getPayoutAmounts --- contract/test/market-actors.js | 409 +++++---------------------------- 1 file changed, 52 insertions(+), 357 deletions(-) diff --git a/contract/test/market-actors.js b/contract/test/market-actors.js index d618b18d..59a71d2a 100644 --- a/contract/test/market-actors.js +++ b/contract/test/market-actors.js @@ -1,11 +1,14 @@ // @ts-check import { E, getInterfaceOf } from '@endo/far'; -import { makePromiseKit } from '@endo/promise-kit'; -import { AmountMath } from '@agoric/ertp/src/amountMath.js'; -import { allValues, mapValues, seatLike } from './wallet-tools.js'; +import { AmountMath, AssetKind } from '@agoric/ertp/src/amountMath.js'; +import { allValues, mapValues } from '../src/objectTools.js'; +import { seatLike } from './wallet-tools.js'; +import { + makeNameProxy, + makeAgoricNames, +} from './ui-kit-goals/name-service-client.js'; const { entries, fromEntries, keys } = Object; -const { Fail } = assert; /** * @typedef {{ @@ -24,21 +27,25 @@ const { Fail } = assert; /** * @param {import('ava').ExecutionContext} t - * @param {{ wallet: import('./wallet-tools.js').MockWallet }} mine - * @param { WellKnown & WellKnownKinds } wellKnown + * @param {{ + * wallet: import('./wallet-tools.js').MockWallet; + * queryTool: Pick; + * }} mine * @param {{ * rxAddr: string, - * toSend: AmountKeywordRecord, - * svcInstance?: Instance, + * toSend: AmountKeywordRecord; * }} shared */ export const payerPete = async ( t, - { wallet }, - wellKnown, - { rxAddr, toSend, svcInstance }, + { wallet, queryTool }, + { rxAddr, toSend }, ) => { - const instance = await (svcInstance || wellKnown.instance.postalSvc); + const hub = await makeAgoricNames(queryTool); + /** @type {WellKnown} */ + const agoricNames = makeNameProxy(hub); + + const instance = await agoricNames.instance.postalSvc; t.log('Pete offers to send to', rxAddr, 'via contract', instance); const updates = await E(wallet.offers).executeOffer({ @@ -52,60 +59,51 @@ export const payerPete = async ( proposal: { give: toSend }, }); - for await (const update of updates) { - // t.log('pete update', update); - if (update?.status?.payouts) { - for (const [kwd, amt] of Object.entries(update?.status?.payouts)) { - const { brand } = amt; - const kind = - wellKnown.assetKind.get(brand) || Fail`no kind for brand ${kwd}`; - t.log('Pete payout should be empty', amt); - t.deepEqual(amt, AmountMath.makeEmpty(brand, kind)); - } - t.is(update?.status?.numWantsSatisfied, 1); - break; - } + const seat = seatLike(updates); + const payouts = await E(seat).getPayoutAmounts(); + for (const [kwd, amt] of entries(payouts)) { + const { brand } = amt; + const kind = AssetKind.NAT; // TODO: handle non-fungible amounts + t.log('Pete payout should be empty', kwd, amt); + t.deepEqual(amt, AmountMath.makeEmpty(brand, kind)); } }; /** * @param {import('ava').ExecutionContext} t * @param {{ wallet: import('./wallet-tools.js').MockWallet, }} mine - * @param {WellKnown & WellKnownKinds} wellKnown * @param {{ toSend: AmountKeywordRecord }} shared */ -export const receiverRose = async (t, { wallet }, wellKnown, { toSend }) => { - const { makeEmpty } = AmountMath; - const purseNotifier = await allValues( - mapValues(toSend, amt => E(wallet.peek).purseNotifier(amt.brand)), +export const receiverRose = async (t, { wallet }, { toSend }) => { + console.time('rose'); + console.timeLog('rose', 'before notifiers'); + const purseNotifier = mapValues(toSend, amt => + wallet.peek.purseUpdates(amt.brand), ); + console.timeLog('rose', 'after notifiers; before initial'); const initial = await allValues( - mapValues(purseNotifier, pn => E(pn).getUpdateSince()), + mapValues(purseNotifier, pn => pn.next().then(u => u.value)), ); - for (const [name, update] of Object.entries(initial)) { - t.log('Rose', name, 'purse starts emtpy', update.value); - const brand = toSend[name].brand; - const kind = wellKnown.assetKind.get(brand) || Fail`${brand}`; - t.deepEqual(update.value, makeEmpty(brand, kind)); - } + console.timeLog('rose', 'got initial', initial); + t.log('Rose initial', initial); + t.deepEqual(keys(initial), keys(toSend)); const done = await allValues( - Object.fromEntries( - Object.entries(initial).map(([name, update]) => { - const amtP = E(purseNotifier[name]) - .getUpdateSince(update.updateCount) - .then(u => { - t.log('Rose rxd', u.value); - t.deepEqual(u.value, toSend[name]); - return u.value; - }); + fromEntries( + entries(initial).map(([name, _update]) => { + const amtP = purseNotifier[name].next().then(u => { + const expected = AmountMath.add(initial[name], toSend[name]); + t.log('Rose updated balance', name, u.value); + t.deepEqual(u.value, expected); + return u.value; + }); return [name, amtP]; }), ), ); - t.log('Rose got balance updates', Object.keys(done)); - t.deepEqual(Object.keys(done), Object.keys(toSend)); + t.log('Rose got balance updates', keys(done)); + t.deepEqual(keys(done), keys(toSend)); }; /** @@ -114,19 +112,17 @@ export const receiverRose = async (t, { wallet }, wellKnown, { toSend }) => { * @param {{ toSend: AmountKeywordRecord }} shared */ export const receiverRex = async (t, { wallet }, { toSend }) => { - const purseNotifier = await allValues( - mapValues(toSend, amt => E(wallet.peek).purseNotifier(amt.brand)), + const purseUpdates = await allValues( + mapValues(toSend, amt => E(wallet.peek).purseUpdates(amt.brand)), ); - const initial = await allValues( - mapValues(purseNotifier, pn => E(pn).getUpdateSince()), - ); + const initial = await allValues(mapValues(purseUpdates, pn => E(pn).next())); const done = await allValues( fromEntries( - entries(initial).map(([name, update]) => { - const amtP = E(purseNotifier[name]) - .getUpdateSince(update.updateCount) + keys(initial).map(name => { + const amtP = E(purseUpdates[name]) + .next() .then(u => { t.log('Rex rxd', u.value); t.deepEqual(u.value, toSend[name]); @@ -164,304 +160,3 @@ export const senderContract = async ( const sent = await E(postalSvc).sendTo(addr1, pmt1); t.deepEqual(sent, noInvitations); }; - -/** - * Auxiliary data - * @typedef {{ - * boardAux: (obj: unknown) => Promise - * }} BoardAux - */ - -/** - * @param {import('ava').ExecutionContext} t - * @param {{ - * wallet: import('./wallet-tools.js').MockWallet, - * }} mine - * @param { WellKnown & BoardAux} wellKnown - */ -export const starterSam = async (t, mine, wellKnown) => { - const { wallet } = mine; - - const checkKeys = (label, actual, expected) => { - label && t.log(label, actual); - t.deepEqual(keys(actual), keys(expected)); - }; - const first = array => { - t.true(Array.isArray(array)); - t.is(array.length, 1); - const [it] = array; - return it; - }; - const brand = { - Invitation: await wellKnown.brand.Invitation, - }; - const instance = { - contractStarter: await wellKnown.instance.contractStarter, - }; - - const expected = { - result: 'UNPUBLISHED', - payouts: { - Fee: { brand: brand.IST, value: 0n }, - Handles: { - brand: brand.Invitation, - value: [ - { - customDetails: { - installation: true, - instance: true, - }, - description: true, - handle: true, - installation: true, - instance: instance.contractStarter, - }, - ], - }, - }, - }; - - let offerSeq = 0; - /** @param {{ bundleID: string, label?: string}} opts */ - const install = async opts => { - const starterAux = await wellKnown.boardAux(instance.contractStarter); - const { prices } = starterAux.terms; - const { installBundleID: Fee } = prices; - t.log('sam gives', Fee, 'to install', opts.label); - const updates = await E(wallet.offers).executeOffer({ - id: `install-${(offerSeq += 1)}`, - invitationSpec: { - source: 'contract', - instance: instance.contractStarter, - publicInvitationMaker: 'makeInstallInvitation', - }, - proposal: { give: { Fee } }, - offerArgs: opts, - }); - const payouts = await E(seatLike(updates)).getPayouts(); - t.log('sam install paid', payouts); - const { - value: [ - { - customDetails: { installation }, - }, - ], - } = payouts.Handles; - return installation; - }; - - const getPostalSvcTerms = async () => { - const { - terms: { namesByAddress }, - } = await wellKnown.boardAux(instance.contractStarter); - t.log('Sam got namesByAddress from contractStarter terms', namesByAddress); - return { namesByAddress }; - }; - - /** - * @template SF - * @param {import('../src/contractStarter.js').StartOptions} opts - */ - const installAndStart = async opts => { - t.log( - 'Sam starts', - opts.instanceLabel, - 'from', - (opts.bundleID || '').slice(0, 8), - ); - const starterAux = await wellKnown.boardAux(instance.contractStarter); - const { prices } = starterAux.terms; - const { add } = AmountMath; - const Fee = add(prices.installBundleID, prices.startInstance); - t.log('sam gives', Fee, 'to start', opts.instanceLabel); - - const updates = await E(wallet.offers).executeOffer({ - id: `samStart-${(offerSeq += 1)}`, - invitationSpec: { - source: 'contract', - instance: instance.contractStarter, - publicInvitationMaker: 'makeStartInvitation', - invitationArgs: [opts], - }, - proposal: { give: { Fee } }, - }); - - const seat = seatLike(updates); - - const result = await E(seat).getOfferResult(); - t.log('Sam gets result', result); - t.is(result, expected.result); - - const payouts = await E(seat).getPayouts(); - checkKeys(undefined, payouts, expected.payouts); - const { Handles } = payouts; - t.is(Handles.brand, expected.payouts.Handles.brand); - const details = first(Handles.value); - const [details0] = expected.payouts.Handles.value; - checkKeys(undefined, details, details0); - t.is(details.instance, details0.instance); - checkKeys( - 'Sam gets instance etc.', - details.customDetails, - details0.customDetails, - ); - - return details.customDetails; - }; - - return { install, getPostalSvcTerms, installAndStart }; -}; - -/** - * @param {import('ava').ExecutionContext} t - * @param {{ - * wallet: import('./wallet-tools.js').MockWallet - * }} mine - * @param {*} wellKnown - */ -export const launcherLarry = async (t, { wallet }, wellKnown) => { - const { timerService: timer } = wellKnown; - const timerBrand = await wellKnown.brand.timer; - const MNY = { - brand: await wellKnown.brand.MNY, - issuer: await wellKnown.issuer.MNY, - }; - - const instance = { - contractStarter: await wellKnown.instance.contractStarter, - }; - - let offerSeq = 0; - let launchOfferId; - const deadline = harden({ timerBrand, absValue: 10n }); - - const launch = async ( - installation, - customTerms = { name: 'BRD', supplyQty: 1_000_000n }, - ) => { - !launchOfferId || Fail`already launched`; - - t.log('Larry prepares to launch', customTerms, 'at', deadline); - const { name } = customTerms; - const starterAux = await wellKnown.boardAux(instance.contractStarter); - const { startInstance } = starterAux.terms.prices; - - const startOpts = { - instanceLabel: `${name}-launch`, - installation, - issuerKeywordRecord: { Deposit: MNY.issuer }, - customTerms: { ...customTerms, deadline }, - }; - /** @type {import('@agoric/smart-wallet').OfferSpec} */ - - const id = `launch-${(offerSeq += 1)}`; - t.log('Larry pays', startInstance, 'to start', startOpts.label); - const updates = await E(wallet.offers).executeOffer({ - id, - invitationSpec: { - source: 'contract', - instance: instance.contractStarter, - publicInvitationMaker: 'makeStartInvitation', - invitationArgs: [startOpts], - }, - proposal: { - give: { Fee: startInstance }, - }, - }); - - const seat = seatLike(updates); - const result = await E(seat).getOfferResult(); - t.log('larry launch result', result); - const { Handles } = await E(seat).getPayouts(); - const { instance: launched } = Handles.value[0].customDetails; - t.log('larry launch instance', launched); - launchOfferId = id; - - return launched; - }; - - const collect = async () => { - t.log('Larry collects at', deadline); - const id = `collect-${(offerSeq += 1)}`; - const up2 = await E(wallet.offers).executeOffer({ - id, - invitationSpec: { - source: 'continuing', - previousOffer: launchOfferId, - invitationMakerName: 'Collect', - }, - proposal: { - want: { Deposit: AmountMath.make(MNY.brand, 0n) }, - exit: { afterDeadline: { timer, deadline } }, - }, - }); - const seat = seatLike(up2); - const result = await E(seat).getOfferResult(); - t.log('result', result); - const proceeds = await E(seat).getPayouts(); - t.log('Larry collected', proceeds); - }; - return { launch, collect }; -}; - -/** - * @param {import('ava').ExecutionContext} t - * @param {{ - * wallet: import('./wallet-tools.js').MockWallet, - * ix: number, - * qty: bigint, - * }} mine - * @param {*} wk - */ -export const makeFan = (t, { wallet, ix, qty }, wk) => { - let offerSeq = 0; - const deposit = async instance => { - const { terms } = await wk.boardAux(instance); - const { brands, issuers } = terms; - // t.log(ix, 'fan found brands', brands); - - await E(wallet.offers).addIssuer(issuers.Share); - await E(wallet.offers).addIssuer(issuers.BRD); - - const updates = await E(wallet.offers).executeOffer({ - id: `deposit-${(offerSeq += 1)}`, - invitationSpec: { - source: 'contract', - instance, - publicInvitationMaker: 'makeDepositInvitation', - }, - proposal: { - give: { Deposit: AmountMath.make(brands.Deposit, qty) }, - want: { Shares: AmountMath.make(brands.Share, qty) }, - }, - }); - const seat = seatLike(updates); - const payouts = await E(seat).getPayouts(); - t.log(ix, 'fan got', payouts.Shares); - return payouts.Shares; - }; - - const redeem = async instance => { - const { terms } = await wk.boardAux(instance); - const { brands } = terms; - - const updates = await E(wallet.offers).executeOffer({ - id: `redeem-${(offerSeq += 1)}`, - invitationSpec: { - source: 'contract', - instance, - publicInvitationMaker: 'makeRedeemInvitation', - }, - proposal: { - give: { Shares: AmountMath.make(brands.Share, qty) }, - want: { Minted: AmountMath.make(brands.BRD, 1n) }, - }, - }); - const seat = seatLike(updates); - const payouts = await E(seat).getPayouts(); - t.log(ix, 'fan got', payouts.Minted); - return payouts.Minted; - }; - - return { deposit, redeem }; -}; From e29dfa25dd5727a64a818e1334bdaa7679fd2a44 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 21 Feb 2024 17:14:35 -0600 Subject: [PATCH 08/20] chore(test-postalSvc): copy from ag-power-tools c75c0dd --- contract/test/test-postalSvc.js | 152 ++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 contract/test/test-postalSvc.js diff --git a/contract/test/test-postalSvc.js b/contract/test/test-postalSvc.js new file mode 100644 index 00000000..fb4c6d6f --- /dev/null +++ b/contract/test/test-postalSvc.js @@ -0,0 +1,152 @@ +// @ts-check +// XXX what's the state-of-the-art in ava setup? +// eslint-disable-next-line import/order +import { test as anyTest } from './prepare-test-env-ava.js'; + +import { createRequire } from 'module'; + +import { E } from '@endo/far'; +import { AmountMath, AssetKind } from '@agoric/ertp/src/amountMath.js'; +import { makeIssuerKit } from '@agoric/ertp'; +import { startPostalSvc } from '../src/start-postalSvc.js'; +import { + bootAndInstallBundles, + getBundleId, + makeBundleCacheContext, +} from './boot-tools.js'; +import { allValues, mapValues, mockWalletFactory } from './wallet-tools.js'; +import { + payerPete, + receiverRex, + receiverRose, + senderContract, +} from './market-actors.js'; + +const { entries, fromEntries, keys } = Object; + +/** @type {import('ava').TestFn>>} */ +const test = anyTest; + +const nodeRequire = createRequire(import.meta.url); + +const bundleRoots = { + postalSvc: nodeRequire.resolve('../src/postalSvc.js'), +}; + +test.before(async t => (t.context = await makeBundleCacheContext(t))); + +test('deliver payment using offer', async t => { + const { powers: p0, bundles } = await bootAndInstallBundles(t, bundleRoots); + /** @type { typeof p0 & import('../src/start-postalSvc.js').PostalSvcPowers} */ + // @ts-expect-error bootstrap powers evolve with BLD staker governance + const powers = p0; + + const iKit = { + MNY: makeIssuerKit('MNY'), + Item: makeIssuerKit('Item', AssetKind.SET), + }; + const { MNY, Item } = iKit; + entries(iKit).forEach(([name, kit]) => { + powers.issuer.produce[name].resolve(kit.issuer); + powers.brand.produce[name].resolve(kit.brand); + }); + + const bundleID = getBundleId(bundles.postalSvc); + await startPostalSvc(powers, { + options: { postalSvc: { bundleID, issuerNames: ['MNY', 'Item'] } }, + }); + + const { zoe, namesByAddressAdmin, chainStorage } = powers.consume; + + const smartWalletIssuers = { + Invitation: await E(zoe).getInvitationIssuer(), + IST: await E(zoe).getFeeIssuer(), + MNY: MNY.issuer, + Item: Item.issuer, + }; + + const walletFactory = mockWalletFactory( + { zoe, namesByAddressAdmin, chainStorage }, + smartWalletIssuers, + ); + + const wellKnown = { + installation: {}, + // TODO: have pete check installation before making an offer? + // hm. don't think walletFactory supports that. + instance: powers.instance.consume, + issuer: {}, + brand: powers.brand.consume, + assetKind: new Map( + /** @type {[Brand, AssetKind][]} */ ([ + [MNY.brand, AssetKind.NAT], + [Item.brand, AssetKind.SET], + ]), + ), + }; + const { make: amt } = AmountMath; + const shared = { + rxAddr: 'agoric1receiverRose', + toSend: { + Pmt: amt(MNY.brand, 3n), + Inventory: amt(Item.brand, harden(['map'])), + }, + }; + + const wallet = { + pete: await walletFactory.makeSmartWallet('agoric1payerPete'), + rose: await walletFactory.makeSmartWallet(shared.rxAddr), + }; + await E(wallet.pete.deposit).receive( + MNY.mint.mintPayment(amt(MNY.brand, 10n)), + ); + await E(wallet.pete.deposit).receive( + Item.mint.mintPayment(amt(Item.brand, harden(['potion', 'map']))), + ); + + await Promise.all([ + payerPete(t, { wallet: wallet.pete }, wellKnown, shared), + receiverRose(t, { wallet: wallet.rose }, wellKnown, shared), + ]); +}); + +test('send invitation* from contract using publicFacet of postalSvc', async t => { + const { powers: p0, bundles } = await bootAndInstallBundles(t, bundleRoots); + /** @type { typeof p0 & import('../src/start-postalSvc.js').PostalSvcPowers} */ + // @ts-expect-error bootstrap powers evolve with BLD staker governance + const powers = p0; + + const bundleID = getBundleId(bundles.postalSvc); + await startPostalSvc(powers, { options: { postalSvc: { bundleID } } }); + + const { zoe, namesByAddressAdmin, chainStorage } = powers.consume; + const smartWalletIssuers = { + Invitation: await E(zoe).getInvitationIssuer(), + IST: await E(zoe).getFeeIssuer(), + }; + + const walletFactory = mockWalletFactory( + { zoe, namesByAddressAdmin, chainStorage }, + smartWalletIssuers, + ); + const instance = await powers.instance.consume.postalSvc; + + const shared = { + rxAddr: 'agoric1receiverRex', + toSend: { + ToDoNothing: AmountMath.make( + await powers.brand.consume.Invitation, + harden([]), + ), + }, + }; + + const wallet = await walletFactory.makeSmartWallet(shared.rxAddr); + const terms = { postalSvc: instance, destAddr: shared.rxAddr }; + await Promise.all([ + senderContract(t, { zoe, terms }), + receiverRex(t, { wallet }, shared), + ]); +}); + +test.todo('partial failure: send N+1 payments where >= 1 delivery fails'); From 576a18fbb4d03767b54fd1bdc42b6d179fb8ebd8 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 21 Feb 2024 18:07:59 -0600 Subject: [PATCH 09/20] style(test-postalSvc): lint --- contract/test/test-postalSvc.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contract/test/test-postalSvc.js b/contract/test/test-postalSvc.js index fb4c6d6f..912705e5 100644 --- a/contract/test/test-postalSvc.js +++ b/contract/test/test-postalSvc.js @@ -14,7 +14,7 @@ import { getBundleId, makeBundleCacheContext, } from './boot-tools.js'; -import { allValues, mapValues, mockWalletFactory } from './wallet-tools.js'; +import { mockWalletFactory } from './wallet-tools.js'; import { payerPete, receiverRex, @@ -22,7 +22,7 @@ import { senderContract, } from './market-actors.js'; -const { entries, fromEntries, keys } = Object; +const { entries } = Object; /** @type {import('ava').TestFn>>} */ const test = anyTest; @@ -46,10 +46,10 @@ test('deliver payment using offer', async t => { Item: makeIssuerKit('Item', AssetKind.SET), }; const { MNY, Item } = iKit; - entries(iKit).forEach(([name, kit]) => { + for (const [name, kit] of entries(iKit)) { powers.issuer.produce[name].resolve(kit.issuer); powers.brand.produce[name].resolve(kit.brand); - }); + } const bundleID = getBundleId(bundles.postalSvc); await startPostalSvc(powers, { From e90ef59c490b4715b0ea751881daad46449e0269 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 4 Mar 2024 18:26:01 -0600 Subject: [PATCH 10/20] chore(boot-tools): refine bundle logging --- contract/test/boot-tools.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/contract/test/boot-tools.js b/contract/test/boot-tools.js index de0cc6d3..5ee947bd 100644 --- a/contract/test/boot-tools.js +++ b/contract/test/boot-tools.js @@ -108,20 +108,20 @@ export const mockBootstrapPowers = async ( }; /** - * @param {import('ava').ExecutionContext} t * @param {BundleCache} bundleCache * @param {Record} bundleRoots * @param {InstallBundle} installBundle + * @param {(...args: unknown[]) => void} log * * @typedef {(id: string, bundle: CachedBundle, name: string) => Promise} InstallBundle * @typedef {Awaited>} BundleCache * @typedef {{ moduleFormat: 'endoZipBase64', endoZipBase64Sha512: string }} CachedBundle */ export const installBundles = async ( - t, bundleCache, bundleRoots, installBundle, + log = console.log, ) => { /** @type {Record} */ const bundles = {}; @@ -129,7 +129,7 @@ export const installBundles = async ( for (const [name, rootModulePath] of Object.entries(bundleRoots)) { const bundle = await bundleCache.load(rootModulePath, name); const bundleID = getBundleId(bundle); - t.log('publish bundle', name, bundleID.slice(0, 8)); + log('publish bundle', name, bundleID.slice(0, 8)); await installBundle(bundleID, bundle, name); bundles[name] = bundle; } @@ -143,10 +143,10 @@ export const bootAndInstallBundles = async (t, bundleRoots) => { const { vatAdminState } = powersKit; const bundles = await installBundles( - t, t.context.bundleCache, bundleRoots, (bundleID, bundle, _name) => vatAdminState.installBundle(bundleID, bundle), + t.log, ); return { ...powersKit, bundles }; }; @@ -199,7 +199,12 @@ export const makeMockTools = async (t, bundleCache) => { ); let pid = 0; - const runCoreEval = async ({ behavior, config }) => { + const runCoreEval = async ({ + behavior, + config, + entryFile: _e, + name: _todo, + }) => { if (!behavior) throw Error('TODO: run core eval without live behavior'); await behavior(powers, config); pid += 1; @@ -226,8 +231,8 @@ export const makeMockTools = async (t, bundleCache) => { return { makeQueryTool, - installBundles: bundleRoots => - installBundles(t, bundleCache, bundleRoots, installBundle), + installBundles: (bundleRoots, log) => + installBundles(bundleCache, bundleRoots, installBundle, log), runCoreEval, provisionSmartWallet: async (addr, balances) => { const it = await walletFactory.makeSmartWallet(addr); From d0bc156ac9f44b197607df4997cd4c075d98e1a5 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 21 Feb 2024 22:06:57 -0600 Subject: [PATCH 11/20] test(postalSvc): toward e2e testing with runCoreEval - test: contract instance is available - separate tests for runCoreEval etc. - start using CapData across vats - makeNameProxy for A.brand.ATOM - refactor agoricNames - real addresses - move initial balances into provisionSmartWallet, since we can't access ERTP mints in the e2e case - concise dockerExec - t.log for bundles, core eval - WIP: leave CapData to another level WIP: revert e2e postalSvc testing --- contract/test/test-postalSvc.js | 191 ++++++++++++++++++++------------ 1 file changed, 118 insertions(+), 73 deletions(-) diff --git a/contract/test/test-postalSvc.js b/contract/test/test-postalSvc.js index 912705e5..43a2aabf 100644 --- a/contract/test/test-postalSvc.js +++ b/contract/test/test-postalSvc.js @@ -5,15 +5,11 @@ import { test as anyTest } from './prepare-test-env-ava.js'; import { createRequire } from 'module'; -import { E } from '@endo/far'; -import { AmountMath, AssetKind } from '@agoric/ertp/src/amountMath.js'; -import { makeIssuerKit } from '@agoric/ertp'; +import { E, passStyleOf } from '@endo/far'; +import { AmountMath } from '@agoric/ertp/src/amountMath.js'; import { startPostalSvc } from '../src/start-postalSvc.js'; -import { - bootAndInstallBundles, - getBundleId, - makeBundleCacheContext, -} from './boot-tools.js'; +import { bootAndInstallBundles, makeMockTools } from './boot-tools.js'; +import { makeBundleCacheContext, getBundleId } from './bundle-tools.js'; import { mockWalletFactory } from './wallet-tools.js'; import { payerPete, @@ -21,10 +17,9 @@ import { receiverRose, senderContract, } from './market-actors.js'; +import { makeAgoricNames, makeNameProxy } from './ui-kit-goals/queryKit.js'; -const { entries } = Object; - -/** @type {import('ava').TestFn>>} */ +/** @type {import('ava').TestFn>>} */ const test = anyTest; const nodeRequire = createRequire(import.meta.url); @@ -33,103 +28,153 @@ const bundleRoots = { postalSvc: nodeRequire.resolve('../src/postalSvc.js'), }; -test.before(async t => (t.context = await makeBundleCacheContext(t))); +const scriptRoots = { + postalSvc: nodeRequire.resolve('../src/start-postalSvc.js'), +}; + +/** @param {import('ava').ExecutionContext} t */ +const makeTestContext = async t => { + const bc = await makeBundleCacheContext(t); + + console.time('makeTestTools'); + console.timeLog('makeTestTools', 'start'); + const tools = await makeMockTools(t, bc.bundleCache); + console.timeEnd('makeTestTools'); -test('deliver payment using offer', async t => { - const { powers: p0, bundles } = await bootAndInstallBundles(t, bundleRoots); - /** @type { typeof p0 & import('../src/start-postalSvc.js').PostalSvcPowers} */ - // @ts-expect-error bootstrap powers evolve with BLD staker governance - const powers = p0; + return { ...tools, ...bc }; +}; + +test.before(async t => (t.context = await makeTestContext(t))); - const iKit = { - MNY: makeIssuerKit('MNY'), - Item: makeIssuerKit('Item', AssetKind.SET), +test.serial('well-known brand (ATOM) is available', async t => { + const { makeQueryTool } = t.context; + const hub0 = makeAgoricNames(makeQueryTool()); + const agoricNames = makeNameProxy(hub0); + await null; + const brand = { + ATOM: await agoricNames.brand.ATOM, }; - const { MNY, Item } = iKit; - for (const [name, kit] of entries(iKit)) { - powers.issuer.produce[name].resolve(kit.issuer); - powers.brand.produce[name].resolve(kit.brand); - } + t.log(brand); + t.is(passStyleOf(brand.ATOM), 'remotable'); +}); +test.serial('install bundle: postalSvc / send', async t => { + const { installBundles } = t.context; + console.time('installBundles'); + console.timeLog('installBundles', Object.keys(bundleRoots).length, 'todo'); + const bundles = await installBundles(bundleRoots, (...args) => + console.timeLog('installBundles', ...args), + ); + console.timeEnd('installBundles'); + + const id = getBundleId(bundles.postalSvc); + const shortId = id.slice(0, 8); + t.log('postalSvc', shortId); + t.is(id.length, 3 + 128, 'bundleID length'); + t.regex(id, /^b1-.../); + + Object.assign(t.context.shared, { bundles }); +}); + +test.serial('deploy contract with core eval: postalSvc / send', async t => { + const { runCoreEval } = t.context; + const { bundles } = t.context.shared; const bundleID = getBundleId(bundles.postalSvc); - await startPostalSvc(powers, { - options: { postalSvc: { bundleID, issuerNames: ['MNY', 'Item'] } }, + + const name = 'send'; + const result = await runCoreEval({ + name, + behavior: startPostalSvc, + entryFile: scriptRoots.postalSvc, + config: { + options: { postalSvc: { bundleID, issuerNames: ['ATOM', 'Item'] } }, + }, }); - const { zoe, namesByAddressAdmin, chainStorage } = powers.consume; + t.log(result.voting_end_time, '#', result.proposal_id, name); + t.like(result, { + content: { + '@type': '/agoric.swingset.CoreEvalProposal', + }, + status: 'PROPOSAL_STATUS_PASSED', + }); +}); - const smartWalletIssuers = { - Invitation: await E(zoe).getInvitationIssuer(), - IST: await E(zoe).getFeeIssuer(), - MNY: MNY.issuer, - Item: Item.issuer, - }; +test.serial('agoricNames.instances has contract: postalSvc', async t => { + const { makeQueryTool } = t.context; + const hub0 = makeAgoricNames(makeQueryTool()); + const agoricNames = makeNameProxy(hub0); + await null; + const instance = await agoricNames.instance.postalSvc; + t.log(instance); + t.is(passStyleOf(instance), 'remotable'); +}); - const walletFactory = mockWalletFactory( - { zoe, namesByAddressAdmin, chainStorage }, - smartWalletIssuers, - ); +test.serial('deliver payment using offer', async t => { + const { provisionSmartWallet, makeQueryTool } = t.context; + const qt = makeQueryTool(); + const hub0 = makeAgoricNames(qt); + /** @type {import('./market-actors.js').WellKnown} */ + const agoricNames = makeNameProxy(hub0); - const wellKnown = { - installation: {}, - // TODO: have pete check installation before making an offer? - // hm. don't think walletFactory supports that. - instance: powers.instance.consume, - issuer: {}, - brand: powers.brand.consume, - assetKind: new Map( - /** @type {[Brand, AssetKind][]} */ ([ - [MNY.brand, AssetKind.NAT], - [Item.brand, AssetKind.SET], - ]), - ), - }; + await null; const { make: amt } = AmountMath; const shared = { - rxAddr: 'agoric1receiverRose', + rxAddr: 'agoric1aap7m84dt0rwhhfw49d4kv2gqetzl56vn8aaxj', toSend: { - Pmt: amt(MNY.brand, 3n), - Inventory: amt(Item.brand, harden(['map'])), + Pmt: amt(await agoricNames.brand.ATOM, 3n), + // TODO non-fungible: Inventory: amt(Item.brand, harden(['map'])), }, }; const wallet = { - pete: await walletFactory.makeSmartWallet('agoric1payerPete'), - rose: await walletFactory.makeSmartWallet(shared.rxAddr), + pete: await provisionSmartWallet( + 'agoric1xe269y3fhye8nrlduf826wgn499y6wmnv32tw5', + { ATOM: 10n, BLD: 75n }, + ), + rose: await provisionSmartWallet(shared.rxAddr, { + BLD: 20n, + // TODO non-fungibles: Item: amt(Item.brand, harden(['potion', 'map'])), + }), }; - await E(wallet.pete.deposit).receive( - MNY.mint.mintPayment(amt(MNY.brand, 10n)), - ); - await E(wallet.pete.deposit).receive( - Item.mint.mintPayment(amt(Item.brand, harden(['potion', 'map']))), - ); + const pqt = makeQueryTool(); + for (const kind of ['instance', 'brand']) { + const entries = await E(E(hub0).lookup(kind)).entries(); + pqt.fromCapData(qt.toCapData(entries)); + } await Promise.all([ - payerPete(t, { wallet: wallet.pete }, wellKnown, shared), - receiverRose(t, { wallet: wallet.rose }, wellKnown, shared), + payerPete(t, { wallet: wallet.pete, queryTool: pqt }, shared), + receiverRose(t, { wallet: wallet.rose }, shared), ]); }); +test.todo('E2E: send using publicFacet using contract'); + test('send invitation* from contract using publicFacet of postalSvc', async t => { - const { powers: p0, bundles } = await bootAndInstallBundles(t, bundleRoots); - /** @type { typeof p0 & import('../src/start-postalSvc.js').PostalSvcPowers} */ - // @ts-expect-error bootstrap powers evolve with BLD staker governance - const powers = p0; + const { powers, bundles } = await bootAndInstallBundles(t, bundleRoots); const bundleID = getBundleId(bundles.postalSvc); - await startPostalSvc(powers, { options: { postalSvc: { bundleID } } }); + await startPostalSvc(powers, { + options: { postalSvc: { bundleID, issuerNames: ['IST', 'Invitation'] } }, + }); - const { zoe, namesByAddressAdmin, chainStorage } = powers.consume; + const { zoe, namesByAddressAdmin } = powers.consume; const smartWalletIssuers = { Invitation: await E(zoe).getInvitationIssuer(), IST: await E(zoe).getFeeIssuer(), }; + // TODO: use CapData across vats + // const boardMarshaller = await E(board).getPublishingMarshaller(); const walletFactory = mockWalletFactory( - { zoe, namesByAddressAdmin, chainStorage }, + { zoe, namesByAddressAdmin }, smartWalletIssuers, ); - const instance = await powers.instance.consume.postalSvc; + /** @type {import('../src/start-postalSvc.js').PostalSvcPowers} */ + // @ts-expect-error cast + const postalSpace = powers; + const instance = await postalSpace.instance.consume.postalSvc; const shared = { rxAddr: 'agoric1receiverRex', From f6935ec97a5c4b105187436a71c1c7cdd5964c3c Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 4 Mar 2024 22:39:54 -0600 Subject: [PATCH 12/20] test(postalSvc): snapshot offer sent by client/front-end --- contract/test/market-actors.js | 7 +++++-- contract/test/test-postalSvc.js | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/contract/test/market-actors.js b/contract/test/market-actors.js index 59a71d2a..b4f53438 100644 --- a/contract/test/market-actors.js +++ b/contract/test/market-actors.js @@ -48,7 +48,8 @@ export const payerPete = async ( const instance = await agoricNames.instance.postalSvc; t.log('Pete offers to send to', rxAddr, 'via contract', instance); - const updates = await E(wallet.offers).executeOffer({ + /** @type {import('@agoric/smart-wallet/src/offers.js').OfferSpec} */ + const sendOffer = { id: 'peteSend1', invitationSpec: { source: 'contract', @@ -57,7 +58,9 @@ export const payerPete = async ( invitationArgs: [rxAddr], }, proposal: { give: toSend }, - }); + }; + t.snapshot(sendOffer, 'client sends offer'); + const updates = await E(wallet.offers).executeOffer(sendOffer); const seat = seatLike(updates); const payouts = await E(seat).getPayoutAmounts(); diff --git a/contract/test/test-postalSvc.js b/contract/test/test-postalSvc.js index 43a2aabf..62dfac5f 100644 --- a/contract/test/test-postalSvc.js +++ b/contract/test/test-postalSvc.js @@ -17,7 +17,10 @@ import { receiverRose, senderContract, } from './market-actors.js'; -import { makeAgoricNames, makeNameProxy } from './ui-kit-goals/queryKit.js'; +import { + makeNameProxy, + makeAgoricNames, +} from './ui-kit-goals/name-service-client.js'; /** @type {import('ava').TestFn>>} */ const test = anyTest; From f25ccc3064fcf3ef7a91fe861cc76d9d2961b426 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 4 Mar 2024 22:40:12 -0600 Subject: [PATCH 13/20] chore: generated snapshots --- contract/test/snapshots/test-postalSvc.js.md | 29 ++++++++++++++++++ .../test/snapshots/test-postalSvc.js.snap | Bin 0 -> 543 bytes 2 files changed, 29 insertions(+) create mode 100644 contract/test/snapshots/test-postalSvc.js.md create mode 100644 contract/test/snapshots/test-postalSvc.js.snap diff --git a/contract/test/snapshots/test-postalSvc.js.md b/contract/test/snapshots/test-postalSvc.js.md new file mode 100644 index 00000000..ee2f600e --- /dev/null +++ b/contract/test/snapshots/test-postalSvc.js.md @@ -0,0 +1,29 @@ +# Snapshot report for `test/test-postalSvc.js` + +The actual snapshot is saved in `test-postalSvc.js.snap`. + +Generated by [AVA](https://avajs.dev). + +## deliver payment using offer + +> client sends offer + + { + id: 'peteSend1', + invitationSpec: { + instance: Object @Alleged: InstanceHandle {}, + invitationArgs: [ + 'agoric1aap7m84dt0rwhhfw49d4kv2gqetzl56vn8aaxj', + ], + publicInvitationMaker: 'makeSendInvitation', + source: 'contract', + }, + proposal: { + give: { + Pmt: { + brand: Object @Alleged: ATOM brand {}, + value: 3n, + }, + }, + }, + } diff --git a/contract/test/snapshots/test-postalSvc.js.snap b/contract/test/snapshots/test-postalSvc.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..674ff436c94cb9786c8897212af61b84a8370791 GIT binary patch literal 543 zcmV+)0^t2YRzVKoHeRmSZ5qHi4m_(F`>+B$!|r8h50~gqhILaXPK# zi_a(J?yMwCitq`vP=%kMp`zyl_yN9vf}vt{i4IQyqnw@2P}yj1gi51qJVq^yV!4@Qmgq^sEPER~47@e3e4qoZ9eTf{dPj9m zwF&J$wD;pf=0v=n}`jTXta|e34Y`jT|i=ph~tp*Z}xM!zyS)s6tp~ zMWzmNC9DO(C&%%^2*5tbpa#%1qzswlc0iLV`*y+P&9pLEA h{$Aw9&^9Ppj8bzc%KK#KD9z=4`UkozhT(k!004H^18@KU literal 0 HcmV?d00001 From 8359e2317548c9f53b2332207ec88503c125e37b Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 4 Mar 2024 23:00:22 -0600 Subject: [PATCH 14/20] docs: distinguish Rose from Rex with comments --- contract/test/market-actors.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/contract/test/market-actors.js b/contract/test/market-actors.js index b4f53438..fa5808d7 100644 --- a/contract/test/market-actors.js +++ b/contract/test/market-actors.js @@ -73,6 +73,10 @@ export const payerPete = async ( }; /** + * Rose expects to receive `shared.toSend` amounts. + * She expects initial balances to be empty; + * and relies on `wellKnown.assetKind` to make an empty amount from a brand. + * * @param {import('ava').ExecutionContext} t * @param {{ wallet: import('./wallet-tools.js').MockWallet, }} mine * @param {{ toSend: AmountKeywordRecord }} shared @@ -110,6 +114,9 @@ export const receiverRose = async (t, { wallet }, { toSend }) => { }; /** + * Rex expects to receive `shared.toSend` amounts. + * Rex doesn't check his initial balances + * * @param {import('ava').ExecutionContext} t * @param {{ wallet: import('./wallet-tools.js').MockWallet, }} mine * @param {{ toSend: AmountKeywordRecord }} shared From 836e0bab30da64f75cbc26a6db2f629990562473 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 4 Mar 2024 23:04:29 -0600 Subject: [PATCH 15/20] refactor: factor trackDeposits out of Rose, Rex --- contract/test/market-actors.js | 51 ++++++++++++++++------------------ 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/contract/test/market-actors.js b/contract/test/market-actors.js index fa5808d7..b63d918e 100644 --- a/contract/test/market-actors.js +++ b/contract/test/market-actors.js @@ -72,6 +72,21 @@ export const payerPete = async ( } }; +const trackDeposits = async (t, initial, purseUpdates, toSend) => + allValues( + fromEntries( + entries(initial).map(([name, _update]) => { + const amtP = purseUpdates[name].next().then(u => { + const expected = AmountMath.add(initial[name], toSend[name]); + t.log('updated balance', name, u.value); + t.deepEqual(u.value, expected); + return u.value; + }); + return [name, amtP]; + }), + ), + ); + /** * Rose expects to receive `shared.toSend` amounts. * She expects initial balances to be empty; @@ -96,19 +111,7 @@ export const receiverRose = async (t, { wallet }, { toSend }) => { t.log('Rose initial', initial); t.deepEqual(keys(initial), keys(toSend)); - const done = await allValues( - fromEntries( - entries(initial).map(([name, _update]) => { - const amtP = purseNotifier[name].next().then(u => { - const expected = AmountMath.add(initial[name], toSend[name]); - t.log('Rose updated balance', name, u.value); - t.deepEqual(u.value, expected); - return u.value; - }); - return [name, amtP]; - }), - ), - ); + const done = await trackDeposits(t, initial, purseNotifier, toSend); t.log('Rose got balance updates', keys(done)); t.deepEqual(keys(done), keys(toSend)); }; @@ -126,22 +129,16 @@ export const receiverRex = async (t, { wallet }, { toSend }) => { mapValues(toSend, amt => E(wallet.peek).purseUpdates(amt.brand)), ); - const initial = await allValues(mapValues(purseUpdates, pn => E(pn).next())); - - const done = await allValues( - fromEntries( - keys(initial).map(name => { - const amtP = E(purseUpdates[name]) - .next() - .then(u => { - t.log('Rex rxd', u.value); - t.deepEqual(u.value, toSend[name]); - return u.value; - }); - return [name, amtP]; - }), + const initial = await allValues( + mapValues(purseUpdates, up => + E(up) + .next() + .then(u => u.value), ), ); + + const done = await trackDeposits(t, initial, purseUpdates, toSend); + t.log('Rex got balance updates', keys(done)); t.deepEqual(keys(done), keys(toSend)); }; From f1d684faed4b76430f64ab83f99816486383675e Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 4 Mar 2024 23:27:46 -0600 Subject: [PATCH 16/20] refactor: follow X.contract.js pattern; spell out postalService --- ...ostalSvc.js => postal-service.contract.js} | 2 +- contract/src/start-postalSvc.js | 42 +++++++++---------- contract/test/market-actors.js | 10 ++--- contract/test/test-postalSvc.js | 38 +++++++++-------- 4 files changed, 47 insertions(+), 45 deletions(-) rename contract/src/{postalSvc.js => postal-service.contract.js} (96%) diff --git a/contract/src/postalSvc.js b/contract/src/postal-service.contract.js similarity index 96% rename from contract/src/postalSvc.js rename to contract/src/postal-service.contract.js index 045d8374..2fdd4880 100644 --- a/contract/src/postalSvc.js +++ b/contract/src/postal-service.contract.js @@ -14,7 +14,7 @@ const { keys, values } = Object; export const start = zcf => { const { namesByAddress, issuers } = zcf.getTerms(); mustMatch(namesByAddress, M.remotable('namesByAddress')); - console.log('postalSvc issuers', Object.keys(issuers)); + console.log('postal-service issuers', Object.keys(issuers)); /** * @param {string} addr diff --git a/contract/src/start-postalSvc.js b/contract/src/start-postalSvc.js index f603795f..afd48b2a 100644 --- a/contract/src/start-postalSvc.js +++ b/contract/src/start-postalSvc.js @@ -1,5 +1,5 @@ /** - * @file core eval script* to start the postalSvc contract. + * @file core eval script* to start the postalService contract. * * The `permit` export specifies the corresponding permit. */ @@ -8,52 +8,52 @@ import { E } from '@endo/far'; import { fixHub } from './fixHub.js'; -const trace = (...args) => console.log('start-postalSvc', ...args); +const trace = (...args) => console.log('start-postalService', ...args); const { Fail } = assert; /** - * @typedef { typeof import('../src/postalSvc.js').start } PostalSvcFn + * @typedef { typeof import('./postal-service.contract.js').start } PostalServiceFn * * @typedef {{ - * produce: { postalSvcKit: Producer }, + * produce: { postalServiceKit: Producer }, * installation: { - * consume: { postalSvc: Promise> }, - * produce: { postalSvc: Producer> }, + * consume: { postalService: Promise> }, + * produce: { postalService: Producer> }, * } * instance: { - * consume: { postalSvc: Promise['instance']> }, - * produce: { postalSvc: Producer['instance']> }, + * consume: { postalService: Promise['instance']> }, + * produce: { postalService: Producer['instance']> }, * } - * }} PostalSvcPowers + * }} PostalServicePowers */ /** * @param {BootstrapPowers} powers - * @param {{ options?: { postalSvc: { + * @param {{ options?: { postalService: { * bundleID: string; * issuerNames?: string[]; * }}}} [config] */ -export const startPostalSvc = async (powers, config) => { - /** @type { BootstrapPowers & PostalSvcPowers} */ +export const startPostalService = async (powers, config) => { + /** @type { BootstrapPowers & PostalServicePowers} */ // @ts-expect-error bootstrap powers evolve with BLD staker governance const postalPowers = powers; const { consume: { zoe, namesByAddressAdmin, agoricNames }, installation: { - produce: { postalSvc: produceInstallation }, + produce: { postalService: produceInstallation }, }, instance: { - produce: { postalSvc: produceInstance }, + produce: { postalService: produceInstance }, }, } = postalPowers; const { bundleID = Fail`no bundleID`, issuerNames = ['IST', 'Invitation', 'BLD', 'ATOM'], - } = config?.options?.postalSvc ?? {}; + } = config?.options?.postalService ?? {}; - /** @type {Installation} */ + /** @type {Installation} */ const installation = await E(zoe).installBundleID(bundleID); produceInstallation.resolve(installation); @@ -68,11 +68,11 @@ export const startPostalSvc = async (powers, config) => { }); produceInstance.resolve(instance); - trace('postalSvc started'); + trace('postalService started'); }; export const manifest = /** @type {const} */ ({ - [startPostalSvc.name]: { + [startPostalService.name]: { consume: { agoricNames: true, namesByAddress: true, @@ -80,14 +80,14 @@ export const manifest = /** @type {const} */ ({ zoe: true, }, installation: { - produce: { postalSvc: true }, + produce: { postalService: true }, }, instance: { - produce: { postalSvc: true }, + produce: { postalService: true }, }, }, }); export const permit = Object.values(manifest)[0]; -export const main = startPostalSvc; +export const main = startPostalService; diff --git a/contract/test/market-actors.js b/contract/test/market-actors.js index b63d918e..4668fcc8 100644 --- a/contract/test/market-actors.js +++ b/contract/test/market-actors.js @@ -45,7 +45,7 @@ export const payerPete = async ( /** @type {WellKnown} */ const agoricNames = makeNameProxy(hub); - const instance = await agoricNames.instance.postalSvc; + const instance = await agoricNames.instance.postalService; t.log('Pete offers to send to', rxAddr, 'via contract', instance); /** @type {import('@agoric/smart-wallet/src/offers.js').OfferSpec} */ @@ -145,11 +145,11 @@ export const receiverRex = async (t, { wallet }, { toSend }) => { export const senderContract = async ( t, - { zoe, terms: { postalSvc: instance, destAddr: addr1 } }, + { zoe, terms: { postalService: instance, destAddr: addr1 } }, ) => { const iIssuer = await E(zoe).getInvitationIssuer(); const iBrand = await E(iIssuer).getBrand(); - const postalSvc = E(zoe).getPublicFacet(instance); + const postalService = E(zoe).getPublicFacet(instance); const purse = await E(iIssuer).makeEmptyPurse(); const noInvitations = AmountMath.make(iBrand, harden([])); @@ -157,13 +157,13 @@ export const senderContract = async ( t.log( 'senderContract: E(', - getInterfaceOf(await postalSvc), + getInterfaceOf(await postalService), ').sendTo(', addr1, ',', noInvitations, ')', ); - const sent = await E(postalSvc).sendTo(addr1, pmt1); + const sent = await E(postalService).sendTo(addr1, pmt1); t.deepEqual(sent, noInvitations); }; diff --git a/contract/test/test-postalSvc.js b/contract/test/test-postalSvc.js index 62dfac5f..534ac3d3 100644 --- a/contract/test/test-postalSvc.js +++ b/contract/test/test-postalSvc.js @@ -7,7 +7,7 @@ import { createRequire } from 'module'; import { E, passStyleOf } from '@endo/far'; import { AmountMath } from '@agoric/ertp/src/amountMath.js'; -import { startPostalSvc } from '../src/start-postalSvc.js'; +import { startPostalService } from '../src/start-postalSvc.js'; import { bootAndInstallBundles, makeMockTools } from './boot-tools.js'; import { makeBundleCacheContext, getBundleId } from './bundle-tools.js'; import { mockWalletFactory } from './wallet-tools.js'; @@ -61,7 +61,7 @@ test.serial('well-known brand (ATOM) is available', async t => { t.is(passStyleOf(brand.ATOM), 'remotable'); }); -test.serial('install bundle: postalSvc / send', async t => { +test.serial('install bundle: postalService / send', async t => { const { installBundles } = t.context; console.time('installBundles'); console.timeLog('installBundles', Object.keys(bundleRoots).length, 'todo'); @@ -70,27 +70,27 @@ test.serial('install bundle: postalSvc / send', async t => { ); console.timeEnd('installBundles'); - const id = getBundleId(bundles.postalSvc); + const id = getBundleId(bundles.postalService); const shortId = id.slice(0, 8); - t.log('postalSvc', shortId); + t.log('postalService', shortId); t.is(id.length, 3 + 128, 'bundleID length'); t.regex(id, /^b1-.../); Object.assign(t.context.shared, { bundles }); }); -test.serial('deploy contract with core eval: postalSvc / send', async t => { +test.serial('deploy contract with core eval: postalService / send', async t => { const { runCoreEval } = t.context; const { bundles } = t.context.shared; - const bundleID = getBundleId(bundles.postalSvc); + const bundleID = getBundleId(bundles.postalService); const name = 'send'; const result = await runCoreEval({ name, - behavior: startPostalSvc, - entryFile: scriptRoots.postalSvc, + behavior: startPostalService, + entryFile: scriptRoots.postalService, config: { - options: { postalSvc: { bundleID, issuerNames: ['ATOM', 'Item'] } }, + options: { postalService: { bundleID, issuerNames: ['ATOM', 'Item'] } }, }, }); @@ -103,12 +103,12 @@ test.serial('deploy contract with core eval: postalSvc / send', async t => { }); }); -test.serial('agoricNames.instances has contract: postalSvc', async t => { +test.serial('agoricNames.instances has contract: postalService', async t => { const { makeQueryTool } = t.context; const hub0 = makeAgoricNames(makeQueryTool()); const agoricNames = makeNameProxy(hub0); await null; - const instance = await agoricNames.instance.postalSvc; + const instance = await agoricNames.instance.postalService; t.log(instance); t.is(passStyleOf(instance), 'remotable'); }); @@ -154,12 +154,14 @@ test.serial('deliver payment using offer', async t => { test.todo('E2E: send using publicFacet using contract'); -test('send invitation* from contract using publicFacet of postalSvc', async t => { +test('send invitation* from contract using publicFacet of postalService', async t => { const { powers, bundles } = await bootAndInstallBundles(t, bundleRoots); - const bundleID = getBundleId(bundles.postalSvc); - await startPostalSvc(powers, { - options: { postalSvc: { bundleID, issuerNames: ['IST', 'Invitation'] } }, + const bundleID = getBundleId(bundles.postalService); + await startPostalService(powers, { + options: { + postalService: { bundleID, issuerNames: ['IST', 'Invitation'] }, + }, }); const { zoe, namesByAddressAdmin } = powers.consume; @@ -174,10 +176,10 @@ test('send invitation* from contract using publicFacet of postalSvc', async t => { zoe, namesByAddressAdmin }, smartWalletIssuers, ); - /** @type {import('../src/start-postalSvc.js').PostalSvcPowers} */ + /** @type {import('../src/start-postalSvc.js').PostalServicePowers} */ // @ts-expect-error cast const postalSpace = powers; - const instance = await postalSpace.instance.consume.postalSvc; + const instance = await postalSpace.instance.consume.postalService; const shared = { rxAddr: 'agoric1receiverRex', @@ -190,7 +192,7 @@ test('send invitation* from contract using publicFacet of postalSvc', async t => }; const wallet = await walletFactory.makeSmartWallet(shared.rxAddr); - const terms = { postalSvc: instance, destAddr: shared.rxAddr }; + const terms = { postalService: instance, destAddr: shared.rxAddr }; await Promise.all([ senderContract(t, { zoe, terms }), receiverRex(t, { wallet }, shared), From b7dc97a2196ab64c80a9727e5176c1ecd7fb0c4a Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 5 Mar 2024 00:05:51 -0600 Subject: [PATCH 17/20] refactor: follow X.proposal.js pattern --- .../{start-postalSvc.js => postal-service.proposal.js} | 0 contract/test/test-postalSvc.js | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) rename contract/src/{start-postalSvc.js => postal-service.proposal.js} (100%) diff --git a/contract/src/start-postalSvc.js b/contract/src/postal-service.proposal.js similarity index 100% rename from contract/src/start-postalSvc.js rename to contract/src/postal-service.proposal.js diff --git a/contract/test/test-postalSvc.js b/contract/test/test-postalSvc.js index 534ac3d3..34012a90 100644 --- a/contract/test/test-postalSvc.js +++ b/contract/test/test-postalSvc.js @@ -7,7 +7,7 @@ import { createRequire } from 'module'; import { E, passStyleOf } from '@endo/far'; import { AmountMath } from '@agoric/ertp/src/amountMath.js'; -import { startPostalService } from '../src/start-postalSvc.js'; +import { startPostalService } from '../src/postal-service.proposal.js'; import { bootAndInstallBundles, makeMockTools } from './boot-tools.js'; import { makeBundleCacheContext, getBundleId } from './bundle-tools.js'; import { mockWalletFactory } from './wallet-tools.js'; @@ -28,11 +28,11 @@ const test = anyTest; const nodeRequire = createRequire(import.meta.url); const bundleRoots = { - postalSvc: nodeRequire.resolve('../src/postalSvc.js'), + postalService: nodeRequire.resolve('../src/postal-service.contract.js'), }; const scriptRoots = { - postalSvc: nodeRequire.resolve('../src/start-postalSvc.js'), + postalService: nodeRequire.resolve('../src/postal-service.proposal.js'), }; /** @param {import('ava').ExecutionContext} t */ @@ -176,7 +176,7 @@ test('send invitation* from contract using publicFacet of postalService', async { zoe, namesByAddressAdmin }, smartWalletIssuers, ); - /** @type {import('../src/start-postalSvc.js').PostalServicePowers} */ + /** @type {import('../src/postal-service.proposal.js').PostalServicePowers} */ // @ts-expect-error cast const postalSpace = powers; const instance = await postalSpace.instance.consume.postalService; From f356af715e2db032af6b03ed5f08d6fb57edd07d Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 5 Mar 2024 00:17:44 -0600 Subject: [PATCH 18/20] chore(postal-service.proposal): punt trace() --- contract/src/postal-service.proposal.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contract/src/postal-service.proposal.js b/contract/src/postal-service.proposal.js index afd48b2a..abf42fe0 100644 --- a/contract/src/postal-service.proposal.js +++ b/contract/src/postal-service.proposal.js @@ -8,8 +8,6 @@ import { E } from '@endo/far'; import { fixHub } from './fixHub.js'; -const trace = (...args) => console.log('start-postalService', ...args); - const { Fail } = assert; /** @@ -68,7 +66,7 @@ export const startPostalService = async (powers, config) => { }); produceInstance.resolve(instance); - trace('postalService started'); + console.log('postalService started'); }; export const manifest = /** @type {const} */ ({ From ffd4cfdd30d4abb760a0be4c9acd0bc7a3e18ef1 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 5 Mar 2024 16:19:00 -0600 Subject: [PATCH 19/20] refactor: use E.when() Co-authored-by: Chris Hibbert --- contract/src/postal-service.contract.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contract/src/postal-service.contract.js b/contract/src/postal-service.contract.js index 2fdd4880..8c2efd6a 100644 --- a/contract/src/postal-service.contract.js +++ b/contract/src/postal-service.contract.js @@ -44,7 +44,7 @@ export const start = zcf => { // XXX partial failure? return payments? await Promise.all( values(payouts).map(pmtP => - Promise.resolve(pmtP).then(pmt => E(depositFacet).receive(pmt)), + E.when(pmtP, pmt => E(depositFacet).receive(pmt)), ), ); seat.exit(); From 5e0fa9a098384ca7078cdb92aff87f7ee629b98b Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 5 Mar 2024 16:23:04 -0600 Subject: [PATCH 20/20] test(postalSvc): TODO -> test.todo() --- contract/test/test-postalSvc.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contract/test/test-postalSvc.js b/contract/test/test-postalSvc.js index 34012a90..03653492 100644 --- a/contract/test/test-postalSvc.js +++ b/contract/test/test-postalSvc.js @@ -113,6 +113,8 @@ test.serial('agoricNames.instances has contract: postalService', async t => { t.is(passStyleOf(instance), 'remotable'); }); +test.todo('deliver payment using offer with non-fungible'); + test.serial('deliver payment using offer', async t => { const { provisionSmartWallet, makeQueryTool } = t.context; const qt = makeQueryTool(); @@ -126,7 +128,6 @@ test.serial('deliver payment using offer', async t => { rxAddr: 'agoric1aap7m84dt0rwhhfw49d4kv2gqetzl56vn8aaxj', toSend: { Pmt: amt(await agoricNames.brand.ATOM, 3n), - // TODO non-fungible: Inventory: amt(Item.brand, harden(['map'])), }, }; @@ -137,7 +138,6 @@ test.serial('deliver payment using offer', async t => { ), rose: await provisionSmartWallet(shared.rxAddr, { BLD: 20n, - // TODO non-fungibles: Item: amt(Item.brand, harden(['potion', 'map'])), }), }; const pqt = makeQueryTool();