From 164151ecf34ae08b217817e970da1c8d64f763cb Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Mon, 25 Nov 2024 23:31:46 +0300 Subject: [PATCH] chore: make provisionBridgeHandler durable Make sure the handler returned from E(creatorFacet).makeHandler() is durable and provisionPool is upgradable multiple times. Refs: #10425 Refs: #10564 fix: remove unnecessary comment chore: get rid of ephemera, address change requests Refs: #10395 Refs: #10425 fix: drop trace chore: address change requests fix: rebase fixes fix: yarn.lock fix fix: bring back missing upgrade-paRegistry proposal --- .../proposals/p:upgrade-19/.gitignore | 1 + .../depositUSD-LEMONS/deposit-usd-lemons.js | 1 + .../nullUpgradePP/null-upgrade-pp-permit.json | 7 + .../nullUpgradePP/null-upgrade-pp.js | 38 +++ .../proposals/p:upgrade-19/package.json | 2 + .../p:upgrade-19/provisionPool.test.js | 215 +++++++++----- .../proposals/p:upgrade-19/test-lib/errors.js | 20 -- .../test-lib/provision-helpers.js | 56 ++++ .../p:upgrade-19/test-lib/sync-tools.js | 272 ------------------ .../proposals/p:upgrade-19/tsconfig.json | 7 +- .../proposals/p:upgrade-19/yarn.lock | 53 +++- .../proposals/z:acceptance/yarn.lock | 1 + packages/inter-protocol/package.json | 2 +- packages/inter-protocol/src/provisionPool.js | 12 +- .../inter-protocol/src/provisionPoolKit.js | 134 +++++---- .../inter-protocol/test/provisionPool.test.js | 16 +- .../upgrade-provisionPool-proposal.js | 15 +- 17 files changed, 398 insertions(+), 454 deletions(-) create mode 100644 a3p-integration/proposals/p:upgrade-19/nullUpgradePP/null-upgrade-pp-permit.json create mode 100644 a3p-integration/proposals/p:upgrade-19/nullUpgradePP/null-upgrade-pp.js delete mode 100644 a3p-integration/proposals/p:upgrade-19/test-lib/errors.js create mode 100644 a3p-integration/proposals/p:upgrade-19/test-lib/provision-helpers.js delete mode 100644 a3p-integration/proposals/p:upgrade-19/test-lib/sync-tools.js diff --git a/a3p-integration/proposals/p:upgrade-19/.gitignore b/a3p-integration/proposals/p:upgrade-19/.gitignore index d348dadf7d9..80f57c98cb2 100644 --- a/a3p-integration/proposals/p:upgrade-19/.gitignore +++ b/a3p-integration/proposals/p:upgrade-19/.gitignore @@ -1,3 +1,4 @@ replaceFeeDistributor/ testUpgradedBoard/ addUsdLemons/ +upgradeProvisionPool/ diff --git a/a3p-integration/proposals/p:upgrade-19/depositUSD-LEMONS/deposit-usd-lemons.js b/a3p-integration/proposals/p:upgrade-19/depositUSD-LEMONS/deposit-usd-lemons.js index 4a4a3c0313c..c7ee0db948f 100644 --- a/a3p-integration/proposals/p:upgrade-19/depositUSD-LEMONS/deposit-usd-lemons.js +++ b/a3p-integration/proposals/p:upgrade-19/depositUSD-LEMONS/deposit-usd-lemons.js @@ -1,3 +1,4 @@ +// @ts-nocheck /* eslint-disable no-undef */ const PROVISIONING_POOL_ADDR = 'agoric1megzytg65cyrgzs6fvzxgrcqvwwl7ugpt62346'; diff --git a/a3p-integration/proposals/p:upgrade-19/nullUpgradePP/null-upgrade-pp-permit.json b/a3p-integration/proposals/p:upgrade-19/nullUpgradePP/null-upgrade-pp-permit.json new file mode 100644 index 00000000000..668a9c7f0d5 --- /dev/null +++ b/a3p-integration/proposals/p:upgrade-19/nullUpgradePP/null-upgrade-pp-permit.json @@ -0,0 +1,7 @@ +{ + "consume": { + "provisionPoolStartResult": true, + "instancePrivateArgs": true, + "economicCommitteeCreatorFacet": true + } +} diff --git a/a3p-integration/proposals/p:upgrade-19/nullUpgradePP/null-upgrade-pp.js b/a3p-integration/proposals/p:upgrade-19/nullUpgradePP/null-upgrade-pp.js new file mode 100644 index 00000000000..be3fdc96b3d --- /dev/null +++ b/a3p-integration/proposals/p:upgrade-19/nullUpgradePP/null-upgrade-pp.js @@ -0,0 +1,38 @@ +// @ts-nocheck +/* eslint-disable no-undef */ +const nullUpgradePP = async powers => { + const { + consume: { + provisionPoolStartResult: provisionPoolStartResultP, + instancePrivateArgs: instancePrivateArgsP, + economicCommitteeCreatorFacet, + }, + } = powers; + + console.log('awaiting powers'); + const { adminFacet, instance } = await provisionPoolStartResultP; + const instancePrivateArgs = await instancePrivateArgsP; + + console.log('get privateArgs'); + const privateArgs = instancePrivateArgs.get(instance); + const [poolBank, poserInvitation] = await Promise.all([ + privateArgs.poolBank, + E(economicCommitteeCreatorFacet).getPoserInvitation(), + ]); + + console.log('DEBUG', { + adminFacet, + instance, + privateArgs, + poserInvitation, + }); + + await E(adminFacet).restartContract({ + ...privateArgs, + poolBank, + initialPoserInvitation: poserInvitation, + }); + console.log('Done'); +}; + +nullUpgradePP; diff --git a/a3p-integration/proposals/p:upgrade-19/package.json b/a3p-integration/proposals/p:upgrade-19/package.json index 9a18e4f658c..6848af6d060 100644 --- a/a3p-integration/proposals/p:upgrade-19/package.json +++ b/a3p-integration/proposals/p:upgrade-19/package.json @@ -4,6 +4,7 @@ "sdk-generate": [ "testing/replace-feeDistributor-short.js replaceFeeDistributor", "testing/add-USD-LEMONS.js addUsdLemons", + "vats/upgrade-provisionPool.js upgradeProvisionPool", "vats/upgrade-paRegistry.js", "vats/upgrade-board.js", "testing/test-upgraded-board.js testUpgradedBoard" @@ -14,6 +15,7 @@ "dependencies": { "@agoric/client-utils": "0.1.1-dev-02c06c4.0", "@agoric/ertp": "dev", + "@agoric/internal": "dev", "@agoric/synthetic-chain": "^0.4.3", "@agoric/zoe": "dev", "@endo/errors": "1.2.7", diff --git a/a3p-integration/proposals/p:upgrade-19/provisionPool.test.js b/a3p-integration/proposals/p:upgrade-19/provisionPool.test.js index df8511305d9..12934603921 100644 --- a/a3p-integration/proposals/p:upgrade-19/provisionPool.test.js +++ b/a3p-integration/proposals/p:upgrade-19/provisionPool.test.js @@ -6,124 +6,181 @@ * - https://github.com/Agoric/agoric-sdk/issues/8724 * * The test scenario is as follows; - * - Prerequisite: provisionPool and bank are already upgraded. See `upgrade.go` - * 1. Add a new account and successfully provision it - * - Observe new account's address under `published.wallet.${address}` - * 2. Send some USDC_axl to provisionPoolAddress and observe its IST balances increases accordingly - * 3. Introduce a new asset to the chain and start a PSM instance for the new asset - * 3a. Deposit some of that asset to provisionPoolAddress - * 3b. Observe provisionPoolAddress' IST balance increase by the amount deposited in step 3a + * 1. Upgrade provisionPool. This upgrade overrides provisionWalletBridgerManager with a durable one + * 2. Add a new account and successfully provision it + * - Observe new account's address under `published.wallet.${address}` + * 3. Send some USDC_axl to provisionPoolAddress and observe its IST balances increases accordingly + * 4. Introduce a new asset to the chain and start a PSM instance for the new asset + * 4a. Deposit some of that asset to provisionPoolAddress + * 4b. Observe provisionPoolAddress' IST balance increase by the amount deposited in step 4a + * 5. Perform a null upgrade for provisionPool. This upgrade does NOT override provisionWalletBridgerManager + * - The goal here is to allow testing the bridgeHandler from the first upgrade is in fact durable + * 6. Auto provision + * 6a. Introduce a new account + * 6b. Fund it with IST and ATOM to be able to open a vault + * 6c. Try to open a vault WITHOUT provisioning the newly introduced account + * 6d. Observe the new account's address under `published.wallet` + * 7. Same as step 2. Checks manual provision works after null upgrade */ import '@endo/init'; import test from 'ava'; -import { execFileSync } from 'node:child_process'; import { addUser, - makeAgd, evalBundles, agd as agdAmbient, agoric, - bankSend, getISTBalance, + getDetailsMatchingVats, + GOV1ADDR, + openVault, + ATOM_DENOM, } from '@agoric/synthetic-chain'; -import { NonNullish } from './test-lib/errors.js'; import { - retryUntilCondition, + makeVstorageKit, waitUntilAccountFunded, waitUntilContractDeployed, -} from './test-lib/sync-tools.js'; +} from '@agoric/client-utils'; +import { NonNullish } from '@agoric/internal'; +import { + bankSend, + checkUserProvisioned, + introduceAndProvision, + provision, +} from './test-lib/provision-helpers.js'; const PROVISIONING_POOL_ADDR = 'agoric1megzytg65cyrgzs6fvzxgrcqvwwl7ugpt62346'; const ADD_PSM_DIR = 'addUsdLemons'; const DEPOSIT_USD_LEMONS_DIR = 'depositUSD-LEMONS'; +const UPGRADE_PP_DIR = 'upgradeProvisionPool'; +const NULL_UPGRADE_PP_DIR = 'nullUpgradePP'; const USDC_DENOM = NonNullish(process.env.USDC_DENOM); -const agd = makeAgd({ execFileSync }).withOpts({ keyringBackend: 'test' }); - const ambientAuthority = { query: agdAmbient.query, follow: agoric.follow, setTimeout, + log: console.log, }; -const provision = (name, address) => - agd.tx(['swingset', 'provision-one', name, address, 'SMART_WALLET'], { - chainId: 'agoriclocal', - from: 'validator', - yes: true, - }); +test.before(async t => { + const vstorageKit = await makeVstorageKit( + { fetch }, + { rpcAddrs: ['http://localhost:26657'], chainName: 'agoriclocal' }, + ); -const introduceAndProvision = async name => { - const address = await addUser(name); - console.log('ADDR', name, address); + t.context = { + vstorageKit, + }; +}); - const provisionP = provision(name, address); +test.serial('upgrade provisionPool', async t => { + await evalBundles(UPGRADE_PP_DIR); - return { provisionP, address }; -}; + const vatDetailsAfter = await getDetailsMatchingVats('provisionPool'); + const { incarnation } = vatDetailsAfter.find(vat => + vat.vatName.endsWith('provisionPool'), + ); -const getProvisionedAddresses = async () => { - const { children } = await agd.query([ - 'vstorage', - 'children', - 'published.wallet', - ]); - return children; -}; + t.log(vatDetailsAfter); + t.is(incarnation, 1, 'incorrect incarnation'); + t.pass(); +}); -const checkUserProvisioned = addr => - retryUntilCondition( - getProvisionedAddresses, - children => children.includes(addr), - 'Account not provisioned', - { maxRetries: 5, retryIntervalMs: 1000, log: console.log, setTimeout }, - ); +test.serial( + `check provisionPool can recover purse and asset subscribers after upgrade`, + async t => { + // @ts-expect-error casting + const { vstorageKit } = t.context; + + // Introduce new user then provision + const { address } = await introduceAndProvision('provisionTester'); + await checkUserProvisioned(address, vstorageKit); + + // Send USDC_axl to pp + const istBalanceBefore = await getISTBalance(PROVISIONING_POOL_ADDR); + await bankSend(PROVISIONING_POOL_ADDR, `500000${USDC_DENOM}`); + + // Check IST balance + await waitUntilAccountFunded( + PROVISIONING_POOL_ADDR, + ambientAuthority, + { denom: 'uist', value: istBalanceBefore + 500000 }, + { errorMessage: 'Provision pool not able to swap USDC_axl for IST.' }, + ); + + // Introduce USD_LEMONS + await evalBundles(ADD_PSM_DIR); + await waitUntilContractDeployed('psm-IST-USD_LEMONS', ambientAuthority, { + errorMessage: 'psm-IST-USD_LEMONS instance not observed.', + }); + + // Provision the provisionPoolAddress. This is a workaround of provisionPoolAddress + // not having a depositFacet published to namesByAddress. Shouldn't be a problem since + // vat-bank keeps track of virtual purses per address basis. We need there to be + // depositFacet for provisionPoolAddress since we'll fund it with USD_LEMONS + await provision('provisionPoolAddress', PROVISIONING_POOL_ADDR); + await checkUserProvisioned(PROVISIONING_POOL_ADDR, vstorageKit); + + // Send USD_LEMONS to provisionPoolAddress + const istBalanceBeforeLemonsSent = await getISTBalance( + PROVISIONING_POOL_ADDR, + ); + await evalBundles(DEPOSIT_USD_LEMONS_DIR); + + // Check balance again + await waitUntilAccountFunded( + PROVISIONING_POOL_ADDR, + ambientAuthority, + { denom: 'uist', value: istBalanceBeforeLemonsSent + 500000 }, + { errorMessage: 'Provision pool not able to swap USDC_axl for IST.' }, + ); + t.pass(); + }, +); + +test.serial('null upgrade', async t => { + await evalBundles(NULL_UPGRADE_PP_DIR); + + const vatDetailsAfter = await getDetailsMatchingVats('provisionPool'); + const { incarnation } = vatDetailsAfter.find(vat => vat.vatID === 'v28'); // provisionPool is v28 + + t.log(vatDetailsAfter); + t.is(incarnation, 2, 'incorrect incarnation'); + t.pass(); +}); -test(`upgrade provision pool`, async t => { - // Introduce new user then provision - const { address } = await introduceAndProvision('provisionTester'); - await checkUserProvisioned(address); +test.serial('auto provision', async t => { + // @ts-expect-error casting + const { vstorageKit } = t.context; - // Send USDC_axl to pp - const istBalanceBefore = await getISTBalance(PROVISIONING_POOL_ADDR); - await bankSend(PROVISIONING_POOL_ADDR, `500000${USDC_DENOM}`); + const address = await addUser('automaticallyProvisioned'); + console.log('ADDR', 'automaticallyProvisioned', address); - // Check IST balance + await bankSend(address, `50000000${ATOM_DENOM}`); + // some ist is needed for opening a new vault + await bankSend(address, `10000000uist`, GOV1ADDR); await waitUntilAccountFunded( - PROVISIONING_POOL_ADDR, - ambientAuthority, - { denom: 'uist', value: istBalanceBefore + 500000 }, - { errorMessage: 'Provision pool not able to swap USDC_axl for IST.' }, + address, + // TODO: drop agd.query and switch to vstorgeKit + { log: console.log, setTimeout, query: agdAmbient.query }, + { denom: ATOM_DENOM, value: 50_000_000 }, + { errorMessage: `not able to fund ${address}` }, ); - // Introduce USD_LEMONS - await evalBundles(ADD_PSM_DIR); - await waitUntilContractDeployed('psm-IST-USD_LEMONS', ambientAuthority, { - errorMessage: 'psm-IST-USD_LEMONS instance not observed.', - }); - - // Provision the provisionPoolAddress. This is a workaround of provisionPoolAddress - // not having a depositFacet published to namesByAddress. Shouldn't be a problem since - // vat-bank keeps track of virtual purses per address basis. We need there to be - // depositFacet for provisionPoolAddress since we'll fund it with USD_LEMONS - await provision('provisionPoolAddress', PROVISIONING_POOL_ADDR); - await checkUserProvisioned(PROVISIONING_POOL_ADDR); - - // Send USD_LEMONS to provisionPoolAddress - const istBalanceBeforeLemonsSent = await getISTBalance( - PROVISIONING_POOL_ADDR, - ); - await evalBundles(DEPOSIT_USD_LEMONS_DIR); + await openVault(address, '10.0', '20.0'); + await checkUserProvisioned(address, vstorageKit); + t.pass(); +}); - // Check balance again - await waitUntilAccountFunded( - PROVISIONING_POOL_ADDR, - ambientAuthority, - { denom: 'uist', value: istBalanceBeforeLemonsSent + 500000 }, - { errorMessage: 'Provision pool not bale swap USDC_axl for IST.' }, - ); +test.serial('manual provision', async t => { + // @ts-expect-error casting + const { vstorageKit } = t.context; + + const { address } = await introduceAndProvision('manuallyProvisioned'); + await checkUserProvisioned(address, vstorageKit); + t.log('manuallyProvisioned address:', address); t.pass(); }); diff --git a/a3p-integration/proposals/p:upgrade-19/test-lib/errors.js b/a3p-integration/proposals/p:upgrade-19/test-lib/errors.js deleted file mode 100644 index 57dc771e6a5..00000000000 --- a/a3p-integration/proposals/p:upgrade-19/test-lib/errors.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @file Copied from "@agoric/internal" - */ - -import { q } from '@endo/errors'; - -/** - * @template T - * @param {T | null | undefined} val - * @param {string} [optDetails] - * @returns {T} - */ -export const NonNullish = (val, optDetails = `unexpected ${q(val)}`) => { - if (val != null) { - // This `!= null` idiom checks that `val` is neither `null` nor `undefined`. - return val; - } - assert.fail(optDetails); -}; -harden(NonNullish); diff --git a/a3p-integration/proposals/p:upgrade-19/test-lib/provision-helpers.js b/a3p-integration/proposals/p:upgrade-19/test-lib/provision-helpers.js new file mode 100644 index 00000000000..1a8eabee12c --- /dev/null +++ b/a3p-integration/proposals/p:upgrade-19/test-lib/provision-helpers.js @@ -0,0 +1,56 @@ +/* eslint-env node */ +import { retryUntilCondition } from '@agoric/client-utils'; +import { + addUser, + CHAINID, + makeAgd, + VALIDATORADDR, +} from '@agoric/synthetic-chain'; +import { execFileSync } from 'node:child_process'; + +const agd = makeAgd({ execFileSync }).withOpts({ keyringBackend: 'test' }); + +/** + * @param {string} addr + * @param {string} wanted + * @param {string} [from] + */ +export const bankSend = (addr, wanted, from = VALIDATORADDR) => { + return agd.tx(['bank', 'send', from, addr, wanted], { + chainId: CHAINID, + from, + yes: true, + }); +}; + +export const provision = (name, address) => + agd.tx(['swingset', 'provision-one', name, address, 'SMART_WALLET'], { + chainId: 'agoriclocal', + from: 'validator', + yes: true, + }); + +export const introduceAndProvision = async name => { + const address = await addUser(name); + console.log('ADDR', name, address); + + const provisionP = provision(name, address); + + return { provisionP, address }; +}; + +/** + * @param {import('@agoric/client-utils').VstorageKit} vstorageKit + */ +export const getProvisionedAddresses = async vstorageKit => { + const children = await vstorageKit.vstorage.keys('published.wallet'); + return children; +}; + +export const checkUserProvisioned = (addr, vstorageKit) => + retryUntilCondition( + () => getProvisionedAddresses(vstorageKit), + children => children.includes(addr), + 'Account not provisioned', + { maxRetries: 5, retryIntervalMs: 1000, log: console.log, setTimeout }, + ); diff --git a/a3p-integration/proposals/p:upgrade-19/test-lib/sync-tools.js b/a3p-integration/proposals/p:upgrade-19/test-lib/sync-tools.js deleted file mode 100644 index dac2ba7e04f..00000000000 --- a/a3p-integration/proposals/p:upgrade-19/test-lib/sync-tools.js +++ /dev/null @@ -1,272 +0,0 @@ -/* eslint-env node */ - -/** - * @file The purpose of this file is to bring together a set of tools that - * developers can use to synchronize operations they carry out in their tests. - * - * These operations include; - * - Making sure a core-eval resulted in successfully deploying a contract - * - Making sure a core-eval successfully sent zoe invitations to committee members for governance - * - Making sure an account is successfully funded with vbank assets like IST, BLD etc. - * - operation: query dest account's balance - * - condition: dest account has a balance >= sent token - * - Making sure an offer resulted successfully - * - */ - -/** - * @typedef {object} RetryOptions - * @property {number} [maxRetries] - * @property {number} [retryIntervalMs] - * @property {(...arg0: string[]) => void} [log] - * @property {(callback: Function, delay: number) => void} [setTimeout] - * - * @typedef {RetryOptions & {errorMessage: string}} WaitUntilOptions - * - * @typedef {object} CosmosBalanceThreshold - * @property {string} denom - * @property {number} value - */ - -const ambientSetTimeout = global.setTimeout; - -/** - * From https://github.com/Agoric/agoric-sdk/blob/442f07c8f0af03281b52b90e90c27131eef6f331/multichain-testing/tools/sleep.ts#L10 - * - * @param {number} ms - * @param {*} sleepOptions - */ -export const sleep = (ms, { log = () => {}, setTimeout = ambientSetTimeout }) => - new Promise(resolve => { - log(`Sleeping for ${ms}ms...`); - setTimeout(resolve, ms); - }); - -/** - * From https://github.com/Agoric/agoric-sdk/blob/442f07c8f0af03281b52b90e90c27131eef6f331/multichain-testing/tools/sleep.ts#L24 - * - * @param {() => Promise} operation - * @param {(result: any) => boolean} condition - * @param {string} message - * @param {RetryOptions} options - */ -export const retryUntilCondition = async ( - operation, - condition, - message, - { maxRetries = 6, retryIntervalMs = 3500, log = console.log, setTimeout }, -) => { - console.log({ maxRetries, retryIntervalMs, message }); - let retries = 0; - - while (retries < maxRetries) { - try { - const result = await operation(); - log('RESULT', result); - if (condition(result)) { - return result; - } - } catch (error) { - if (error instanceof Error) { - log(`Error: ${error.message}`); - } else { - log(`Unknown error: ${String(error)}`); - } - } - - retries += 1; - console.log( - `Retry ${retries}/${maxRetries} - Waiting for ${retryIntervalMs}ms for ${message}...`, - ); - await sleep(retryIntervalMs, { log, setTimeout }); - } - - throw Error(`${message} condition failed after ${maxRetries} retries.`); -}; - -/** - * @param {WaitUntilOptions} options - */ -const overrideDefaultOptions = options => { - const defaultValues = { - maxRetries: 6, - retryIntervalMs: 3500, - log: console.log, - errorMessage: 'Error', - }; - - return { ...defaultValues, ...options }; -}; - -/// ////////// Making sure a core-eval resulted successfully deploying a contract ///////////// - -const makeGetInstances = follow => async () => { - const instanceEntries = await follow( - '-lF', - `:published.agoricNames.instance`, - ); - - return Object.fromEntries(instanceEntries); -}; - -/** - * - * @param {string} contractName - * @param {{follow: () => object, setTimeout: (object) => void}} ambientAuthority - * @param {WaitUntilOptions} options - */ -export const waitUntilContractDeployed = ( - contractName, - ambientAuthority, - options, -) => { - const { follow, setTimeout } = ambientAuthority; - const getInstances = makeGetInstances(follow); - const { maxRetries, retryIntervalMs, errorMessage, log } = - overrideDefaultOptions(options); - - return retryUntilCondition( - getInstances, - instanceObject => Object.keys(instanceObject).includes(contractName), - errorMessage, - { maxRetries, retryIntervalMs, log, setTimeout }, - ); -}; - -/// ////////// Making sure an account is successfully funded with vbank assets like IST, BLD etc. /////////////// - -const makeQueryCosmosBalance = queryCb => async dest => { - const coins = await queryCb('bank', 'balances', dest); - return coins.balances; -}; - -/** - * - * @param {Array} balances - * @param {CosmosBalanceThreshold} threshold - * @returns {boolean} - */ -const checkCosmosBalance = (balances, threshold) => { - const balance = [...balances].find(({ denom }) => denom === threshold.denom); - return Number(balance.amount) >= threshold.value; -}; - -/** - * @param {string} destAcct - * @param {{query: () => Promise, setTimeout: (object) => void}} ambientAuthority - * @param {{denom: string, value: number}} threshold - * @param {WaitUntilOptions} options - */ -export const waitUntilAccountFunded = ( - destAcct, - ambientAuthority, - threshold, - options, -) => { - const { query, setTimeout } = ambientAuthority; - const queryCosmosBalance = makeQueryCosmosBalance(query); - const { maxRetries, retryIntervalMs, errorMessage, log } = - overrideDefaultOptions(options); - - return retryUntilCondition( - async () => queryCosmosBalance(destAcct), - balances => checkCosmosBalance(balances, threshold), - errorMessage, - { maxRetries, retryIntervalMs, log, setTimeout }, - ); -}; - -/// ////////// Making sure an offers get results ///////////// - -const makeQueryWallet = follow => async (/** @type {string} */ addr) => { - const update = await follow('-lF', `:published.wallet.${addr}`); - - return update; -}; - -/** - * - * @param {object} offerStatus - * @param {boolean} waitForPayouts - * @param {string} offerId - */ -const checkOfferState = (offerStatus, waitForPayouts, offerId) => { - const { updated, status } = offerStatus; - - if (updated !== 'offerStatus') return false; - if (!status) return false; - if (status.id !== offerId) return false; - if (!status.numWantsSatisfied || status.numWantsSatisfied !== 1) return false; - if (waitForPayouts && status.payouts) return true; - if (!waitForPayouts && status.result) return true; - - return false; -}; - -/** - * - * @param {string} addr - * @param {string} offerId - * @param {boolean} waitForPayouts - * @param {{follow: () => object, setTimeout: (callback: Function, delay: number) => void}} ambientAuthority - * @param {WaitUntilOptions} options - */ -export const waitUntilOfferResult = ( - addr, - offerId, - waitForPayouts, - ambientAuthority, - options, -) => { - const { follow, setTimeout } = ambientAuthority; - const queryWallet = makeQueryWallet(follow); - const { maxRetries, retryIntervalMs, errorMessage, log } = - overrideDefaultOptions(options); - - return retryUntilCondition( - async () => queryWallet(addr), - status => checkOfferState(status, waitForPayouts, offerId), - errorMessage, - { maxRetries, retryIntervalMs, log, setTimeout }, - ); -}; - -/// ////////// Making sure a core-eval successfully sent zoe invitations to committee members for governance ///////////// - -/** - * - * @param {{ updated: string, currentAmount: any }} update - * @returns {boolean} - */ -const checkForInvitation = update => { - const { updated, currentAmount } = update; - - if (updated !== 'balance') return false; - if (!currentAmount || !currentAmount.brand) return false; - - return currentAmount.brand.includes('Invitation'); -}; - -/** - * - * @param {string} addr - * @param {{follow: () => object, setTimeout: (object) => void}} ambientAuthority - * @param {WaitUntilOptions} options - */ -export const waitUntilInvitationReceived = ( - addr, - ambientAuthority, - options, -) => { - const { follow, setTimeout } = ambientAuthority; - const queryWallet = makeQueryWallet(follow); - const { maxRetries, retryIntervalMs, errorMessage, log } = - overrideDefaultOptions(options); - - return retryUntilCondition( - async () => queryWallet(addr), - checkForInvitation, - errorMessage, - { maxRetries, retryIntervalMs, log, setTimeout }, - ); -}; diff --git a/a3p-integration/proposals/p:upgrade-19/tsconfig.json b/a3p-integration/proposals/p:upgrade-19/tsconfig.json index 960c1f4587a..23a6b14091b 100644 --- a/a3p-integration/proposals/p:upgrade-19/tsconfig.json +++ b/a3p-integration/proposals/p:upgrade-19/tsconfig.json @@ -11,5 +11,10 @@ "noImplicitThis": true, // XXX synthetic-chain has some errors "skipLibCheck": true - } + }, + "exclude": [ + "addUsdLemons/", + "replaceFeeDistributor/", + "upgradeProvisionPool/" + ] } diff --git a/a3p-integration/proposals/p:upgrade-19/yarn.lock b/a3p-integration/proposals/p:upgrade-19/yarn.lock index f74d4368e0e..e795ff2f376 100644 --- a/a3p-integration/proposals/p:upgrade-19/yarn.lock +++ b/a3p-integration/proposals/p:upgrade-19/yarn.lock @@ -31,6 +31,21 @@ __metadata: languageName: node linkType: hard +"@agoric/base-zone@npm:0.1.1-dev-1dd4589.0+1dd4589": + version: 0.1.1-dev-1dd4589.0 + resolution: "@agoric/base-zone@npm:0.1.1-dev-1dd4589.0" + dependencies: + "@agoric/store": "npm:0.9.3-dev-1dd4589.0+1dd4589" + "@endo/common": "npm:^1.2.8" + "@endo/errors": "npm:^1.2.8" + "@endo/exo": "npm:^1.5.7" + "@endo/far": "npm:^1.1.9" + "@endo/pass-style": "npm:^1.4.7" + "@endo/patterns": "npm:^1.4.7" + checksum: 10c0/d0c214a7e69b427df1632d1ac10bc97f977823d723e4696ad8cf92a0eb9dd6297142ce4b98ae211c994547c43380c9a6487959a1719f7ee90fb128a00e5ff669 + languageName: node + linkType: hard + "@agoric/base-zone@npm:0.1.1-dev-3b799b8.0+3b799b8": version: 0.1.1-dev-3b799b8.0 resolution: "@agoric/base-zone@npm:0.1.1-dev-3b799b8.0" @@ -200,6 +215,26 @@ __metadata: languageName: node linkType: hard +"@agoric/internal@npm:dev": + version: 0.3.3-dev-1dd4589.0 + resolution: "@agoric/internal@npm:0.3.3-dev-1dd4589.0" + dependencies: + "@agoric/base-zone": "npm:0.1.1-dev-1dd4589.0+1dd4589" + "@endo/common": "npm:^1.2.8" + "@endo/errors": "npm:^1.2.8" + "@endo/far": "npm:^1.1.9" + "@endo/init": "npm:^1.1.7" + "@endo/marshal": "npm:^1.6.2" + "@endo/pass-style": "npm:^1.4.7" + "@endo/patterns": "npm:^1.4.7" + "@endo/promise-kit": "npm:^1.1.8" + "@endo/stream": "npm:^1.2.8" + anylogger: "npm:^0.21.0" + jessie.js: "npm:^0.3.4" + checksum: 10c0/eb261f7bde265f168698c9da04af7458e2747fd54e945cdef82b822af5749e8a9443bb827bbace959269a04ae58d33c4ea465c31b580bf8d07b15fa87cb80ca3 + languageName: node + linkType: hard + "@agoric/kmarshal@npm:0.1.1-dev-02c06c4.0+02c06c4": version: 0.1.1-dev-02c06c4.0 resolution: "@agoric/kmarshal@npm:0.1.1-dev-02c06c4.0" @@ -305,6 +340,19 @@ __metadata: languageName: node linkType: hard +"@agoric/store@npm:0.9.3-dev-1dd4589.0+1dd4589": + version: 0.9.3-dev-1dd4589.0 + resolution: "@agoric/store@npm:0.9.3-dev-1dd4589.0" + dependencies: + "@endo/errors": "npm:^1.2.8" + "@endo/exo": "npm:^1.5.7" + "@endo/marshal": "npm:^1.6.2" + "@endo/pass-style": "npm:^1.4.7" + "@endo/patterns": "npm:^1.4.7" + checksum: 10c0/72d2c3ff3cb0d8b48f06146e4deb0967273e10a86ec7c01e7bc176c5e1ab31d016b909bde1230c8f031ba60552c31ee7d0eccb5791985fc12330fdb6dcda5edb + languageName: node + linkType: hard + "@agoric/store@npm:0.9.3-dev-3b799b8.0+3b799b8": version: 0.9.3-dev-3b799b8.0 resolution: "@agoric/store@npm:0.9.3-dev-3b799b8.0" @@ -1096,7 +1144,7 @@ __metadata: languageName: node linkType: hard -"@endo/exo@npm:^1.5.6": +"@endo/exo@npm:^1.5.6, @endo/exo@npm:^1.5.7": version: 1.5.7 resolution: "@endo/exo@npm:1.5.7" dependencies: @@ -1238,7 +1286,7 @@ __metadata: languageName: node linkType: hard -"@endo/stream@npm:^1.2.7": +"@endo/stream@npm:^1.2.7, @endo/stream@npm:^1.2.8": version: 1.2.8 resolution: "@endo/stream@npm:1.2.8" dependencies: @@ -5203,6 +5251,7 @@ __metadata: dependencies: "@agoric/client-utils": "npm:0.1.1-dev-02c06c4.0" "@agoric/ertp": "npm:dev" + "@agoric/internal": "npm:dev" "@agoric/synthetic-chain": "npm:^0.4.3" "@agoric/zoe": "npm:dev" "@endo/errors": "npm:1.2.7" diff --git a/a3p-integration/proposals/z:acceptance/yarn.lock b/a3p-integration/proposals/z:acceptance/yarn.lock index 92dc8a54bfe..d76e6d95485 100644 --- a/a3p-integration/proposals/z:acceptance/yarn.lock +++ b/a3p-integration/proposals/z:acceptance/yarn.lock @@ -156,6 +156,7 @@ __metadata: "@agoric/vat-data": "npm:^0.5.2" "@agoric/vats": "npm:^0.15.1" "@agoric/zoe": "npm:^0.26.2" + "@agoric/zone": "npm:^0.2.2" "@endo/captp": "npm:^4.4.3" "@endo/errors": "npm:^1.2.8" "@endo/eventual-send": "npm:^1.2.8" diff --git a/packages/inter-protocol/package.json b/packages/inter-protocol/package.json index 76f545f1e35..46f5336f45a 100644 --- a/packages/inter-protocol/package.json +++ b/packages/inter-protocol/package.json @@ -41,6 +41,7 @@ "@agoric/vat-data": "^0.5.2", "@agoric/vats": "^0.15.1", "@agoric/zoe": "^0.26.2", + "@agoric/zone": "^0.2.2", "@endo/captp": "^4.4.3", "@endo/eventual-send": "^1.2.8", "@endo/far": "^1.1.9", @@ -53,7 +54,6 @@ "@agoric/smart-wallet": "^0.5.3", "@agoric/swingset-liveslots": "^0.10.2", "@agoric/swingset-vat": "^0.32.2", - "@agoric/zone": "^0.2.2", "@endo/bundle-source": "^3.5.0", "@endo/init": "^1.1.7", "@endo/promise-kit": "^1.1.8", diff --git a/packages/inter-protocol/src/provisionPool.js b/packages/inter-protocol/src/provisionPool.js index 7eb37c61dd1..de96cfabaab 100644 --- a/packages/inter-protocol/src/provisionPool.js +++ b/packages/inter-protocol/src/provisionPool.js @@ -12,7 +12,11 @@ import { prepareExo } from '@agoric/vat-data'; import { provideSingleton } from '@agoric/zoe/src/contractSupport/durability.js'; import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js'; import { TopicsRecordShape } from '@agoric/zoe/src/contractSupport/topics.js'; -import { prepareProvisionPoolKit } from './provisionPoolKit.js'; +import { makeDurableZone } from '@agoric/zone/durable.js'; +import { + prepareBridgeProvisionTool, + prepareProvisionPoolKit, +} from './provisionPoolKit.js'; /** @import {Marshal} from '@endo/marshal'; */ @@ -72,11 +76,15 @@ export const start = async (zcf, privateArgs, baggage) => { privateArgs.marshaller, ); - const makeProvisionPoolKit = prepareProvisionPoolKit(baggage, { + const zone = makeDurableZone(baggage); + + const makeBridgeProvisionTool = prepareBridgeProvisionTool(zone); + const makeProvisionPoolKit = prepareProvisionPoolKit(zone, { makeRecorderKit, params, poolBank, zcf, + makeBridgeProvisionTool, }); const provisionPoolKit = await provideSingleton( diff --git a/packages/inter-protocol/src/provisionPoolKit.js b/packages/inter-protocol/src/provisionPoolKit.js index ee05ea5cc06..6c3694e6097 100644 --- a/packages/inter-protocol/src/provisionPoolKit.js +++ b/packages/inter-protocol/src/provisionPoolKit.js @@ -1,7 +1,6 @@ // @ts-check import { X, q, Fail } from '@endo/errors'; import { E } from '@endo/far'; -import { Far } from '@endo/marshal'; import { AmountMath, BrandShape } from '@agoric/ertp'; import { deeplyFulfilledObject, makeTracer } from '@agoric/internal'; @@ -15,7 +14,6 @@ import { M, makeScalarBigMapStore, makeScalarBigSetStore, - prepareExoClassKit, } from '@agoric/vat-data'; import { PowerFlags } from '@agoric/vats/src/walletFlags.js'; import { @@ -75,18 +73,22 @@ const FIRST_LOWER_NEAR_KEYWORD = /^[a-z][a-zA-Z0-9_$]*$/; * Given attenuated access to the funding purse, handle requests to provision * smart wallets. * - * @param {(depositBank: ERef) => Promise} sendInitialPayment - * @param {() => void} onProvisioned + * @param {import('@agoric/zone').Zone} zone */ -export const makeBridgeProvisionTool = (sendInitialPayment, onProvisioned) => { - /** @param {ProvisionPoolKitReferences} refs */ - const makeBridgeHandler = ({ - bankManager, - namesByAddressAdmin, - walletFactory, - }) => - Far('provisioningHandler', { - fromBridge: async obj => { +export const prepareBridgeProvisionTool = zone => + zone.exoClass( + 'smartWalletProvisioningHandler', + M.interface('ProvisionBridgeHandlerMaker', { + fromBridge: M.callWhen(M.record()).returns(), + }), + (bankManager, walletFactory, namesByAddressAdmin, forHandler) => ({ + bankManager, + walletFactory, + namesByAddressAdmin, + forHandler, + }), + { + async fromBridge(obj) { obj.type === 'PLEASE_PROVISION' || Fail`Unrecognized request ${obj.type}`; trace('PLEASE_PROVISION', obj); @@ -94,9 +96,12 @@ export const makeBridgeProvisionTool = (sendInitialPayment, onProvisioned) => { powerFlags.includes(PowerFlags.SMART_WALLET) || Fail`missing SMART_WALLET in powerFlags`; + const { bankManager, walletFactory, namesByAddressAdmin, forHandler } = + this.state; + const bank = E(bankManager).getBankForAddress(address); // only proceed if we can provide funds - await sendInitialPayment(bank); + await forHandler.sendInitialPayment(bank); const [_, created] = await E(walletFactory).provideSmartWallet( address, @@ -104,31 +109,30 @@ export const makeBridgeProvisionTool = (sendInitialPayment, onProvisioned) => { namesByAddressAdmin, ); if (created) { - onProvisioned(); + forHandler.onProvisioned(); } trace(created ? 'provisioned' : 're-provisioned', address); }, - }); - return makeBridgeHandler; -}; + }, + ); /** - * @param {import('@agoric/vat-data').Baggage} baggage + * @param {import('@agoric/zone').Zone} zone * @param {{ * makeRecorderKit: import('@agoric/zoe/src/contractSupport/recorder.js').MakeRecorderKit; * params: any; * poolBank: import('@endo/far').ERef; * zcf: ZCF; + * makeBridgeProvisionTool: ReturnType; * }} powers */ export const prepareProvisionPoolKit = ( - baggage, - { makeRecorderKit, params, poolBank, zcf }, + zone, + { makeRecorderKit, params, poolBank, zcf, makeBridgeProvisionTool }, ) => { const zoe = zcf.getZoeService(); - const makeProvisionPoolKitInternal = prepareExoClassKit( - baggage, + const makeProvisionPoolKitInternal = zone.exoClassKit( 'ProvisionPoolKit', { machine: M.interface('ProvisionPoolKit machine', { @@ -151,6 +155,7 @@ export const prepareProvisionPoolKit = ( ackWallet: M.call(M.string()).returns(M.boolean()), }), helper: UnguardedHelperI, + forHandler: UnguardedHelperI, public: M.interface('ProvisionPoolKit public', { getPublicTopics: M.call().returns({ metrics: PublicTopicShape }), }), @@ -217,23 +222,24 @@ export const prepareProvisionPoolKit = ( const refs = await deeplyFulfilledObject(obj); Object.assign(this.state, refs); }, + /** @returns {import('@agoric/vats').BridgeHandler} */ makeHandler() { const { bankManager, namesByAddressAdmin, walletFactory } = this.state; if (!bankManager || !namesByAddressAdmin || !walletFactory) { throw Fail`must set references before handling requests`; } - const { helper } = this.facets; - // a bit obtuse but leave for backwards compatibility with tests - const innerMaker = makeBridgeProvisionTool( - bank => helper.sendInitialPayment(bank), - () => helper.onProvisioned(), - ); - return innerMaker({ + + const { forHandler } = this.facets; + + const provisionHandler = makeBridgeProvisionTool( bankManager, - namesByAddressAdmin, walletFactory, - }); + namesByAddressAdmin, + forHandler, + ); + + return provisionHandler; }, /** * @param {Brand} brand @@ -311,37 +317,6 @@ export const prepareProvisionPoolKit = ( ); facets.helper.publishMetrics(); }, - onProvisioned() { - const { state, facets } = this; - state.walletsProvisioned += 1n; - facets.helper.publishMetrics(); - }, - /** @param {ERef} destBank */ - async sendInitialPayment(destBank) { - const { - facets: { helper }, - state: { fundPurse, poolBrand }, - } = this; - const perAccountInitialAmount = /** @type {Amount<'nat'>} */ ( - params.getPerAccountInitialAmount() - ); - const initialPmt = await E(fundPurse).withdraw( - perAccountInitialAmount, - ); - - const destPurse = E(destBank).getPurse(poolBrand); - return E(destPurse) - .deposit(initialPmt) - .then(amt => { - helper.onSendFunds(perAccountInitialAmount); - trace('provisionPool sent', amt); - }) - .catch(reason => { - console.error(X`initial deposit failed: ${q(reason)}`); - void E(fundPurse).deposit(initialPmt); - throw reason; - }); - }, /** * @param {ERef} exchangePurse * @param {ERef} brand @@ -491,6 +466,39 @@ export const prepareProvisionPoolKit = ( return rxd; }, }, + forHandler: { + onProvisioned() { + const { state, facets } = this; + state.walletsProvisioned += 1n; + facets.helper.publishMetrics(); + }, + /** @param {ERef} destBank */ + async sendInitialPayment(destBank) { + const { + facets: { helper }, + state: { fundPurse, poolBrand }, + } = this; + const perAccountInitialAmount = /** @type {Amount<'nat'>} */ ( + params.getPerAccountInitialAmount() + ); + const initialPmt = await E(fundPurse).withdraw( + perAccountInitialAmount, + ); + + const destPurse = E(destBank).getPurse(poolBrand); + return E(destPurse) + .deposit(initialPmt) + .then(amt => { + helper.onSendFunds(perAccountInitialAmount); + trace('provisionPool sent', amt); + }) + .catch(reason => { + console.error(X`initial deposit failed: ${q(reason)}`); + void E(fundPurse).deposit(initialPmt); + throw reason; + }); + }, + }, public: { getPublicTopics() { return { diff --git a/packages/inter-protocol/test/provisionPool.test.js b/packages/inter-protocol/test/provisionPool.test.js index 48ea08f7312..e591643a927 100644 --- a/packages/inter-protocol/test/provisionPool.test.js +++ b/packages/inter-protocol/test/provisionPool.test.js @@ -19,7 +19,8 @@ import { makeFakeBoard } from '@agoric/vats/tools/board-utils.js'; import { makeRatio } from '@agoric/zoe/src/contractSupport/ratio.js'; import { E, Far } from '@endo/far'; import path from 'path'; -import { makeBridgeProvisionTool } from '../src/provisionPoolKit.js'; +import { makeHeapZone } from '@agoric/zone'; +import { prepareBridgeProvisionTool } from '../src/provisionPoolKit.js'; import { makeMockChainStorageRoot, setUpZoeForTest, @@ -364,15 +365,14 @@ test('makeBridgeProvisionTool handles duplicate requests', async t => { const { nameHub: namesByAddress, nameAdmin: namesByAddressAdmin } = makeNameHubKit(); const publishMetrics = () => {}; - const makeHandler = makeBridgeProvisionTool( - sendInitialPayment, - publishMetrics, - ); - const handler = makeHandler({ + const zone = makeHeapZone(); + const makeBridgeProvisionTool = prepareBridgeProvisionTool(zone); + const handler = makeBridgeProvisionTool( bankManager, - namesByAddressAdmin, walletFactory, - }); + namesByAddressAdmin, + Far('helpers', { sendInitialPayment, onProvisioned: publishMetrics }), + ); t.log('1st request to provision a SMART_WALLET for', address); await handler.fromBridge({ diff --git a/packages/vats/src/proposals/upgrade-provisionPool-proposal.js b/packages/vats/src/proposals/upgrade-provisionPool-proposal.js index 4dd74af9c69..a94fd6ef418 100644 --- a/packages/vats/src/proposals/upgrade-provisionPool-proposal.js +++ b/packages/vats/src/proposals/upgrade-provisionPool-proposal.js @@ -1,5 +1,8 @@ import { E } from '@endo/far'; import { deeplyFulfilled } from '@endo/marshal'; +import { makeTracer } from '@agoric/internal'; + +const tracer = makeTracer('UpgradeProvisionPool'); /** * @param {BootstrapPowers & { @@ -27,7 +30,7 @@ export const upgradeProvisionPool = async ( const { provisionPoolRef } = options.options; assert(provisionPoolRef.bundleID); - console.log(`PROVISION POOL BUNDLE ID: `, provisionPoolRef.bundleID); + tracer(`PROVISION POOL BUNDLE ID: `, provisionPoolRef); const [ provisionPoolStartResult, @@ -66,7 +69,7 @@ export const upgradeProvisionPool = async ( newPrivateArgs, ); - console.log('ProvisionPool upgraded: ', upgradeResult); + tracer('ProvisionPool upgraded: ', upgradeResult); const references = { bankManager, @@ -74,17 +77,17 @@ export const upgradeProvisionPool = async ( walletFactory: wfCreatorFacet, }; - console.log('Calling setReferences with: ', references); + tracer('Calling setReferences with: ', references); await E(ppCreatorFacet).setReferences(references); - console.log('Creating bridgeHandler...'); + tracer('Creating bridgeHandler...'); const bridgeHandler = await E(ppCreatorFacet).makeHandler(); - console.log('Setting new bridgeHandler...'); + tracer('Setting new bridgeHandler...'); // @ts-expect-error casting await E(provisionWalletBridgeManager).setHandler(bridgeHandler); - console.log('Done.'); + tracer('Done.'); }; export const getManifestForUpgradingProvisionPool = (