diff --git a/a3p-integration/proposals/p:upgrade-19/.gitignore b/a3p-integration/proposals/p:upgrade-19/.gitignore index 17bb7d663e2..80f57c98cb2 100644 --- a/a3p-integration/proposals/p:upgrade-19/.gitignore +++ b/a3p-integration/proposals/p:upgrade-19/.gitignore @@ -1,2 +1,4 @@ replaceFeeDistributor/ testUpgradedBoard/ +addUsdLemons/ +upgradeProvisionPool/ diff --git a/a3p-integration/proposals/p:upgrade-19/depositUSD-LEMONS/deposit-usd-lemons-permit.json b/a3p-integration/proposals/p:upgrade-19/depositUSD-LEMONS/deposit-usd-lemons-permit.json new file mode 100644 index 00000000000..0a580519276 --- /dev/null +++ b/a3p-integration/proposals/p:upgrade-19/depositUSD-LEMONS/deposit-usd-lemons-permit.json @@ -0,0 +1,8 @@ +{ + "consume": { + "contractKits": true, + "namesByAddressAdmin": true, + "agoricNames": true + } +} + 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 new file mode 100644 index 00000000000..c7ee0db948f --- /dev/null +++ b/a3p-integration/proposals/p:upgrade-19/depositUSD-LEMONS/deposit-usd-lemons.js @@ -0,0 +1,52 @@ +// @ts-nocheck +/* eslint-disable no-undef */ +const PROVISIONING_POOL_ADDR = 'agoric1megzytg65cyrgzs6fvzxgrcqvwwl7ugpt62346'; + +const depositUsdLemons = async powers => { + const { + consume: { + contractKits: contractKitsP, + namesByAddressAdmin: namesByAddressAdminP, + agoricNames, + }, + } = powers; + + const namesByAddressAdmin = await namesByAddressAdminP; + + const getDepositFacet = async address => { + const hub = E(E(namesByAddressAdmin).lookupAdmin(address)).readonly(); + return E(hub).lookup('depositFacet'); + }; + + const [contractKits, usdLemonsIssuer, usdLemonsBrand, ppDepositFacet] = + await Promise.all([ + contractKitsP, + E(agoricNames).lookup('issuer', 'USD_LEMONS'), + E(agoricNames).lookup('brand', 'USD_LEMONS'), + getDepositFacet(PROVISIONING_POOL_ADDR), + ]); + + console.log('[CONTRACT_KITS]', contractKits); + console.log('[ISSUER]', usdLemonsIssuer); + + let usdLemonsMint; + for (const { publicFacet, creatorFacet: mint } of contractKits.values()) { + if (publicFacet === usdLemonsIssuer) { + usdLemonsMint = mint; + console.log('BINGO', mint); + break; + } + } + + console.log('Minting USD_LEMONS'); + const helloPayment = await E(usdLemonsMint).mintPayment( + harden({ brand: usdLemonsBrand, value: 500000n }), + ); + + console.log('Funding provision pool...'); + await E(ppDepositFacet).receive(helloPayment); + + console.log('Done.'); +}; + +depositUsdLemons; 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 f3b062f3c76..6848af6d060 100644 --- a/a3p-integration/proposals/p:upgrade-19/package.json +++ b/a3p-integration/proposals/p:upgrade-19/package.json @@ -3,6 +3,8 @@ "type": "/agoric.swingset.CoreEvalProposal", "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" @@ -13,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 new file mode 100644 index 00000000000..12934603921 --- /dev/null +++ b/a3p-integration/proposals/p:upgrade-19/provisionPool.test.js @@ -0,0 +1,186 @@ +/* eslint-env node */ +/** + * @file The goal of this file is to make sure v28-provisionPool and v14-bank can be successfully + * upgraded. These vats are related because of the issues below; + * - https://github.com/Agoric/agoric-sdk/issues/8722 + * - https://github.com/Agoric/agoric-sdk/issues/8724 + * + * The test scenario is as follows; + * 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 { + addUser, + evalBundles, + agd as agdAmbient, + agoric, + getISTBalance, + getDetailsMatchingVats, + GOV1ADDR, + openVault, + ATOM_DENOM, +} from '@agoric/synthetic-chain'; +import { + makeVstorageKit, + waitUntilAccountFunded, + waitUntilContractDeployed, +} 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 ambientAuthority = { + query: agdAmbient.query, + follow: agoric.follow, + setTimeout, + log: console.log, +}; + +test.before(async t => { + const vstorageKit = await makeVstorageKit( + { fetch }, + { rpcAddrs: ['http://localhost:26657'], chainName: 'agoriclocal' }, + ); + + t.context = { + vstorageKit, + }; +}); + +test.serial('upgrade provisionPool', async t => { + await evalBundles(UPGRADE_PP_DIR); + + const vatDetailsAfter = await getDetailsMatchingVats('provisionPool'); + const { incarnation } = vatDetailsAfter.find(vat => + vat.vatName.endsWith('provisionPool'), + ); + + t.log(vatDetailsAfter); + t.is(incarnation, 1, 'incorrect incarnation'); + t.pass(); +}); + +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.serial('auto provision', async t => { + // @ts-expect-error casting + const { vstorageKit } = t.context; + + const address = await addUser('automaticallyProvisioned'); + console.log('ADDR', 'automaticallyProvisioned', address); + + await bankSend(address, `50000000${ATOM_DENOM}`); + // some ist is needed for opening a new vault + await bankSend(address, `10000000uist`, GOV1ADDR); + await waitUntilAccountFunded( + 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}` }, + ); + + await openVault(address, '10.0', '20.0'); + await checkUserProvisioned(address, vstorageKit); + t.pass(); +}); + +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/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.sh b/a3p-integration/proposals/p:upgrade-19/test.sh index 056fe6836ac..055727518f8 100644 --- a/a3p-integration/proposals/p:upgrade-19/test.sh +++ b/a3p-integration/proposals/p:upgrade-19/test.sh @@ -2,3 +2,5 @@ yarn ava replaceFeeDistributor.test.js yarn ava upgradedBoard.test.js + +yarn ava provisionPool.test.js 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/golang/cosmos/app/upgrade.go b/golang/cosmos/app/upgrade.go index 8770675d792..2858370cf05 100644 --- a/golang/cosmos/app/upgrade.go +++ b/golang/cosmos/app/upgrade.go @@ -214,19 +214,25 @@ func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Conte ), ) - // // CoreProposals for Upgrade 19. These should not be introduced - // // before upgrade 18 is done because they would be run in n:upgrade-next - // CoreProposalSteps = append(CoreProposalSteps, - // vm.CoreProposalStepForModules( - // "@agoric/builders/scripts/inter-protocol/replace-feeDistributor.js", - // ), - // vm.CoreProposalStepForModules( - // "@agoric/builders/scripts/vats/upgrade-paRegistry.js", - // ), - // vm.CoreProposalStepForModules( - // "@agoric/builders/scripts/vats/upgrade-board.js", - // ), - // ) + // CoreProposals for Upgrade 19. These should not be introduced + // before upgrade 18 is done because they would be run in n:upgrade-next + // CoreProposalSteps = append(CoreProposalSteps, + // vm.CoreProposalStepForModules( + // "@agoric/builders/scripts/inter-protocol/replace-feeDistributor.js", + // ), + // vm.CoreProposalStepForModules( + // "@agoric/builders/scripts/vats/upgrade-paRegistry.js", + // ), + // vm.CoreProposalStepForModules( + // "@agoric/builders/scripts/vats/upgrade-board.js", + // ), + // vm.CoreProposalStepForModules( + // "@agoric/builders/scripts/vats/upgrade-provisionPool.js", + // ), + // vm.CoreProposalStepForModules( + // "@agoric/builders/scripts/vats/upgrade-bank.js", + // ), + // ) } app.upgradeDetails = &upgradeDetails{ diff --git a/packages/builders/scripts/testing/add-USD-LEMONS.js b/packages/builders/scripts/testing/add-USD-LEMONS.js new file mode 100644 index 00000000000..ed548b5561c --- /dev/null +++ b/packages/builders/scripts/testing/add-USD-LEMONS.js @@ -0,0 +1,19 @@ +import { makeHelpers } from '@agoric/deploy-script-support'; +import { psmProposalBuilder } from '../inter-protocol/add-collateral-core.js'; + +const addUsdLemonsProposalBuilder = async powers => { + return psmProposalBuilder(powers, { + anchorOptions: { + denom: 'ibc/000C0AAAEECAFE000', + keyword: 'USD_LEMONS', + decimalPlaces: 6, + proposedName: 'USD_LEMONS', + }, + }); +}; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ +export default async (homeP, endowments) => { + const { writeCoreEval } = await makeHelpers(homeP, endowments); + await writeCoreEval('add-LEMONS-PSM', addUsdLemonsProposalBuilder); +}; 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 9d31c752607..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 & { @@ -16,6 +19,10 @@ export const upgradeProvisionPool = async ( economicCommitteeCreatorFacet: electorateCreatorFacet, instancePrivateArgs: instancePrivateArgsP, provisionPoolStartResult: provisionPoolStartResultP, + bankManager, + namesByAddressAdmin: namesByAddressAdminP, + walletFactoryStartResult: walletFactoryStartResultP, + provisionWalletBridgeManager: provisionWalletBridgeManagerP, }, }, options, @@ -23,13 +30,27 @@ 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, instancePrivateArgs] = await Promise.all([ + const [ + provisionPoolStartResult, + instancePrivateArgs, + namesByAddressAdmin, + walletFactoryStartResult, + provisionWalletBridgeManager, + ] = await Promise.all([ provisionPoolStartResultP, instancePrivateArgsP, + namesByAddressAdminP, + walletFactoryStartResultP, + provisionWalletBridgeManagerP, ]); - const { adminFacet, instance } = provisionPoolStartResult; + const { + adminFacet, + instance, + creatorFacet: ppCreatorFacet, + } = provisionPoolStartResult; + const { creatorFacet: wfCreatorFacet } = walletFactoryStartResult; const [originalPrivateArgs, poserInvitation] = await Promise.all([ // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -48,7 +69,25 @@ export const upgradeProvisionPool = async ( newPrivateArgs, ); - console.log('ProvisionPool upgraded: ', upgradeResult); + tracer('ProvisionPool upgraded: ', upgradeResult); + + const references = { + bankManager, + namesByAddressAdmin, + walletFactory: wfCreatorFacet, + }; + + tracer('Calling setReferences with: ', references); + await E(ppCreatorFacet).setReferences(references); + + tracer('Creating bridgeHandler...'); + const bridgeHandler = await E(ppCreatorFacet).makeHandler(); + + tracer('Setting new bridgeHandler...'); + // @ts-expect-error casting + await E(provisionWalletBridgeManager).setHandler(bridgeHandler); + + tracer('Done.'); }; export const getManifestForUpgradingProvisionPool = ( @@ -61,6 +100,10 @@ export const getManifestForUpgradingProvisionPool = ( economicCommitteeCreatorFacet: true, instancePrivateArgs: true, provisionPoolStartResult: true, + bankManager: true, + namesByAddressAdmin: true, + walletFactoryStartResult: true, + provisionWalletBridgeManager: true, }, produce: {}, },