diff --git a/a3p-integration/proposals/p:upgrade-19/.gitignore b/a3p-integration/proposals/p:upgrade-19/.gitignore index 9c595f7f2c6..b1f2bfa095d 100644 --- a/a3p-integration/proposals/p:upgrade-19/.gitignore +++ b/a3p-integration/proposals/p:upgrade-19/.gitignore @@ -5,4 +5,5 @@ addUsdOlives/ upgradeProvisionPool/ upgradeAgoricNames/ publishTestInfo/ +upgrade-mintHolder/ upgradeAssetReserve/ diff --git a/a3p-integration/proposals/p:upgrade-19/mint-payment/send-script-permit.json b/a3p-integration/proposals/p:upgrade-19/mint-payment/send-script-permit.json new file mode 100644 index 00000000000..27ba77ddaf6 --- /dev/null +++ b/a3p-integration/proposals/p:upgrade-19/mint-payment/send-script-permit.json @@ -0,0 +1 @@ +true diff --git a/a3p-integration/proposals/p:upgrade-19/mint-payment/send-script.tjs b/a3p-integration/proposals/p:upgrade-19/mint-payment/send-script.tjs new file mode 100644 index 00000000000..eb1a4815470 --- /dev/null +++ b/a3p-integration/proposals/p:upgrade-19/mint-payment/send-script.tjs @@ -0,0 +1,56 @@ +/* global E */ + +/// +/// + +/** + * The primary purpose of this script is to mint a payment of a certain + * bankAsset and deposit in an user wallet. + * + * The receiverAddress and label placeholders should be replaced with + * the desired address and asset name during the execution of each test case. + * + * See z:acceptance/mintHolder.test.js + * + * @param {BootstrapPowers} powers + */ +const sendBankAsset = async powers => { + const { + consume: { namesByAddress, contractKits: contractKitsP }, + } = powers; + + const receiverAddress = '{{ADDRESS}}'; + const label = '{{LABEL}}'; + const valueStr = '{{VALUE}}'; + const value = BigInt(valueStr) + + console.log(`Start sendBankAsset for ${label}`); + + const contractKits = await contractKitsP; + const mintHolderKit = Array.from(contractKits.values()).filter( + kit => kit.label && kit.label === label, + ); + + const { creatorFacet: mint, publicFacet: issuer } = mintHolderKit[0]; + + /* + * Ensure that publicFacet holds an issuer by verifying that has + * the makeEmptyPurse method. + */ + await E(issuer).makeEmptyPurse() + + const brand = await E(issuer).getBrand(); + const amount = harden({ value, brand }); + const payment = await E(mint).mintPayment(amount); + + const receiverDepositFacet = E(namesByAddress).lookup( + receiverAddress, + 'depositFacet', + ); + + await E(receiverDepositFacet).receive(payment); + + console.log(`Finished sendBankAsset for ${label}`); +}; + +sendBankAsset; diff --git a/a3p-integration/proposals/p:upgrade-19/mintHolder.test.js b/a3p-integration/proposals/p:upgrade-19/mintHolder.test.js new file mode 100644 index 00000000000..32a187bcb25 --- /dev/null +++ b/a3p-integration/proposals/p:upgrade-19/mintHolder.test.js @@ -0,0 +1,28 @@ +/* eslint-env node */ + +import '@endo/init'; +import test from 'ava'; +import { addUser, provisionSmartWallet } from '@agoric/synthetic-chain'; +import { + mintPayment, + getAssetList, + swap, + getPSMChildren, + upgradeMintHolder, +} from './test-lib/mintHolder-helpers.js'; +import { networkConfig } from './test-lib/index.js'; + +test('mintHolder contract is upgraded', async t => { + const receiver = await addUser('receiver'); + await provisionSmartWallet(receiver, `20000000ubld`); + + let assetList = await getAssetList(); + t.log('List of mintHolder vats being upgraded: ', assetList); + await upgradeMintHolder(`upgrade-mintHolder`, assetList); + await mintPayment(t, receiver, assetList, 10); + + const psmLabelList = await getPSMChildren(fetch, networkConfig); + assetList = await getAssetList(psmLabelList); + t.log('List of assets being swapped with IST via PSM: ', assetList); + await swap(t, receiver, assetList, 5); +}); diff --git a/a3p-integration/proposals/p:upgrade-19/package.json b/a3p-integration/proposals/p:upgrade-19/package.json index d9e5edccb02..666230406a8 100644 --- a/a3p-integration/proposals/p:upgrade-19/package.json +++ b/a3p-integration/proposals/p:upgrade-19/package.json @@ -11,13 +11,14 @@ "testing/test-upgraded-board.js testUpgradedBoard", "vats/upgrade-agoricNames.js agoricNamesCoreEvals/upgradeAgoricNames", "testing/add-USD-OLIVES.js agoricNamesCoreEvals/addUsdOlives", - "testing/publish-test-info.js agoricNamesCoreEvals/publishTestInfo" + "testing/publish-test-info.js agoricNamesCoreEvals/publishTestInfo", + "vats/upgrade-mintHolder.js upgrade-mintHolder A3P_INTEGRATION" ] }, "type": "module", "license": "Apache-2.0", "dependencies": { - "@agoric/client-utils": "0.1.1-dev-02c06c4.0", + "@agoric/client-utils": "dev", "@agoric/ertp": "dev", "@agoric/internal": "dev", "@agoric/synthetic-chain": "^0.4.3", diff --git a/a3p-integration/proposals/p:upgrade-19/test-lib/mintHolder-helpers.js b/a3p-integration/proposals/p:upgrade-19/test-lib/mintHolder-helpers.js new file mode 100644 index 00000000000..9639fd98662 --- /dev/null +++ b/a3p-integration/proposals/p:upgrade-19/test-lib/mintHolder-helpers.js @@ -0,0 +1,162 @@ +/* eslint-env node */ + +import { + agoric, + evalBundles, + getDetailsMatchingVats, + getISTBalance, +} from '@agoric/synthetic-chain'; +import { makeVstorageKit, retryUntilCondition } from '@agoric/client-utils'; +import { readFile, writeFile } from 'node:fs/promises'; +import { psmSwap, snapshotAgoricNames } from './psm-lib.js'; + +/** + * @param {string} fileName base file name without .tjs extension + * @param {Record} replacements + */ +export const replaceTemplateValuesInFile = async (fileName, replacements) => { + let script = await readFile(`${fileName}.tjs`, 'utf-8'); + for (const [template, value] of Object.entries(replacements)) { + script = script.replaceAll(`{{${template}}}`, value); + } + await writeFile(`${fileName}.js`, script); +}; + +export const getPSMChildren = async (fetch, networkConfig) => { + const { + vstorage: { keys }, + } = await makeVstorageKit({ fetch }, networkConfig); + + const children = await keys('published.psm.IST'); + + return children; +}; + +export const getAssetList = async labelList => { + const assetList = []; + const { vbankAssets } = await snapshotAgoricNames(); + + // Determine the assets to consider based on labelList + const assetsToConsider = + labelList || Object.values(vbankAssets).map(asset => asset.issuerName); + + for (const label of assetsToConsider) { + if (label === 'IST') { + break; + } + + const vbankAsset = Object.values(vbankAssets).find( + asset => asset.issuerName === label, + ); + assert(vbankAsset, `vbankAsset not found for ${label}`); + + const { denom } = vbankAsset; + const mintHolderVat = `zcf-mintHolder-${label}`; + + assetList.push({ label, denom, mintHolderVat }); + } + + return assetList; +}; + +export const mintPayment = async (t, address, assetList, value) => { + const SUBMISSION_DIR = 'mint-payment'; + + for (const asset of assetList) { + const { label, denom } = asset; + const scaled = BigInt(parseInt(value, 10) * 1_000_000).toString(); + + await replaceTemplateValuesInFile(`${SUBMISSION_DIR}/send-script`, { + ADDRESS: address, + LABEL: label, + VALUE: scaled, + }); + + await evalBundles(SUBMISSION_DIR); + + const balance = await getISTBalance(address, denom); + + // Add to value the BLD provisioned to smart wallet + if (label === 'BLD') { + value += 10; + } + + t.is( + balance, + value, + `receiver ${denom} balance ${balance} is not ${value}`, + ); + } +}; + +export const swap = async (t, address, assetList, want) => { + for (const asset of assetList) { + const { label, denom } = asset; + + // TODO: remove condition after fixing issue #10655 + if (/^DAI/.test(label)) { + break; + } + + const pair = `IST.${label}`; + + const istBalanceBefore = await getISTBalance(address, 'uist'); + const anchorBalanceBefore = await getISTBalance(address, denom); + + const psmSwapIo = { + now: Date.now, + follow: agoric.follow, + setTimeout, + log: console.log, + }; + + await psmSwap( + address, + ['swap', '--pair', pair, '--wantMinted', want], + psmSwapIo, + ); + + const istBalanceAfter = await getISTBalance(address, 'uist'); + const anchorBalanceAfter = await getISTBalance(address, denom); + + t.is(istBalanceAfter, istBalanceBefore + want); + t.is(anchorBalanceAfter, anchorBalanceBefore - want); + } +}; + +const getIncarnationForAllVats = async assetList => { + const vatsIncarnation = {}; + + for (const asset of assetList) { + const { label, mintHolderVat } = asset; + const matchingVats = await getDetailsMatchingVats(label); + const expectedVat = matchingVats.find(vat => vat.vatName === mintHolderVat); + vatsIncarnation[label] = expectedVat.incarnation; + } + assert(Object.keys(vatsIncarnation).length === assetList.length); + + return vatsIncarnation; +}; + +const checkVatsUpgraded = (before, current) => { + for (const vatLabel in before) { + if (current[vatLabel] !== before[vatLabel] + 1) { + console.log(`${vatLabel} upgrade failed. `); + return false; + } + } + return true; +}; + +export const upgradeMintHolder = async (submissionPath, assetList) => { + const before = await getIncarnationForAllVats(assetList); + + await evalBundles(submissionPath); + + return retryUntilCondition( + async () => getIncarnationForAllVats(assetList), + current => checkVatsUpgraded(before, current), + `mintHolder upgrade not processed yet`, + { setTimeout, retryIntervalMs: 5000, maxRetries: 15 }, + ); +}; diff --git a/a3p-integration/proposals/p:upgrade-19/test-lib/psm-lib.js b/a3p-integration/proposals/p:upgrade-19/test-lib/psm-lib.js index 8f8d0abadc8..f98f5f6508c 100644 --- a/a3p-integration/proposals/p:upgrade-19/test-lib/psm-lib.js +++ b/a3p-integration/proposals/p:upgrade-19/test-lib/psm-lib.js @@ -3,10 +3,15 @@ import { execa } from 'execa'; import { getNetworkConfig } from 'agoric/src/helpers.js'; -import { waitUntilOfferResult } from '@agoric/client-utils'; +import { + waitUntilOfferResult, + makeFromBoard, + boardSlottingMarshaller, +} from '@agoric/client-utils'; import { deepMapObject } from '@agoric/internal'; import { agd, + agoric, agopsLocation, CHAINID, executeCommand, @@ -285,3 +290,23 @@ export const tryISTBalances = async (t, actualBalance, expectedBalance) => { const minFeeDebit = 200_000; t.is(actualBalance + minFeeDebit, expectedBalance); }; + +const fromBoard = makeFromBoard(); +const marshaller = boardSlottingMarshaller(fromBoard.convertSlotToVal); + +/** + * @param {string} path + */ +const objectFromVstorageEntries = async path => { + const rawEntries = await agoric.follow('-lF', `:${path}`, '-o', 'text'); + return Object.fromEntries(marshaller.fromCapData(JSON.parse(rawEntries))); +}; + +export const snapshotAgoricNames = async () => { + const [brands, instances, vbankAssets] = await Promise.all([ + objectFromVstorageEntries('published.agoricNames.brand'), + objectFromVstorageEntries('published.agoricNames.instance'), + objectFromVstorageEntries('published.agoricNames.vbankAsset'), + ]); + return { brands, instances, vbankAssets }; +}; diff --git a/a3p-integration/proposals/p:upgrade-19/test.sh b/a3p-integration/proposals/p:upgrade-19/test.sh index 9b998c5fa7e..193e27da231 100644 --- a/a3p-integration/proposals/p:upgrade-19/test.sh +++ b/a3p-integration/proposals/p:upgrade-19/test.sh @@ -2,7 +2,7 @@ yarn ava replaceFeeDistributor.test.js yarn ava upgradedBoard.test.js - +yarn ava mintHolder.test.js yarn ava provisionPool.test.js yarn ava agoricNames.test.js diff --git a/a3p-integration/proposals/p:upgrade-19/yarn.lock b/a3p-integration/proposals/p:upgrade-19/yarn.lock index a9c86ac69e3..29afd5a4438 100644 --- a/a3p-integration/proposals/p:upgrade-19/yarn.lock +++ b/a3p-integration/proposals/p:upgrade-19/yarn.lock @@ -27,21 +27,6 @@ __metadata: languageName: node linkType: hard -"@agoric/base-zone@npm:0.1.1-dev-02c06c4.0+02c06c4": - version: 0.1.1-dev-02c06c4.0 - resolution: "@agoric/base-zone@npm:0.1.1-dev-02c06c4.0" - dependencies: - "@agoric/store": "npm:0.9.3-dev-02c06c4.0+02c06c4" - "@endo/common": "npm:^1.2.7" - "@endo/errors": "npm:^1.2.7" - "@endo/exo": "npm:^1.5.6" - "@endo/far": "npm:^1.1.8" - "@endo/pass-style": "npm:^1.4.6" - "@endo/patterns": "npm:^1.4.6" - checksum: 10c0/54c4fc0855010809b09aa0558454639ecd87152af8c52024e07b1a46f38aeeef8d4642318eaf933b5219fc16e849a832d7a2c6d0e1827634dc6a64dd7530353b - 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" @@ -72,6 +57,21 @@ __metadata: languageName: node linkType: hard +"@agoric/base-zone@npm:0.1.1-dev-c1ae023.0+c1ae023": + version: 0.1.1-dev-c1ae023.0 + resolution: "@agoric/base-zone@npm:0.1.1-dev-c1ae023.0" + dependencies: + "@agoric/store": "npm:0.9.3-dev-c1ae023.0+c1ae023" + "@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/d7c75720d675c5f2fd524d0c597e83957fecee138392b3fc6dfa5610e8f301ccffecc8fbacb6c5a0700e4f5b9f95496029d0eb05320c92e23bbbeaa428ed9f87 + languageName: node + linkType: hard + "@agoric/base-zone@npm:0.1.1-dev-e596a01.0+e596a01": version: 0.1.1-dev-e596a01.0 resolution: "@agoric/base-zone@npm:0.1.1-dev-e596a01.0" @@ -101,24 +101,24 @@ __metadata: languageName: node linkType: hard -"@agoric/casting@npm:0.4.3-dev-02c06c4.0+02c06c4": - version: 0.4.3-dev-02c06c4.0 - resolution: "@agoric/casting@npm:0.4.3-dev-02c06c4.0" +"@agoric/casting@npm:0.4.3-dev-c1ae023.0+c1ae023": + version: 0.4.3-dev-c1ae023.0 + resolution: "@agoric/casting@npm:0.4.3-dev-c1ae023.0" dependencies: - "@agoric/internal": "npm:0.3.3-dev-02c06c4.0+02c06c4" - "@agoric/notifier": "npm:0.6.3-dev-02c06c4.0+02c06c4" - "@agoric/store": "npm:0.9.3-dev-02c06c4.0+02c06c4" + "@agoric/internal": "npm:0.3.3-dev-c1ae023.0+c1ae023" + "@agoric/notifier": "npm:0.6.3-dev-c1ae023.0+c1ae023" + "@agoric/store": "npm:0.9.3-dev-c1ae023.0+c1ae023" "@cosmjs/encoding": "npm:^0.32.3" "@cosmjs/proto-signing": "npm:^0.32.3" "@cosmjs/stargate": "npm:^0.32.3" "@cosmjs/tendermint-rpc": "npm:^0.32.3" - "@endo/errors": "npm:^1.2.7" - "@endo/far": "npm:^1.1.8" - "@endo/init": "npm:^1.1.6" - "@endo/lockdown": "npm:^1.0.12" - "@endo/marshal": "npm:^1.6.1" - "@endo/promise-kit": "npm:^1.1.7" - checksum: 10c0/e0fbef620ff0b358961f23d0545f962e2255dd0c490ee0c2635ba47fbe77f5b0f0c1b64a2bd638ca0030d9e81a099253feeef74b236652a8cb80dbc4932f4e08 + "@endo/errors": "npm:^1.2.8" + "@endo/far": "npm:^1.1.9" + "@endo/init": "npm:^1.1.7" + "@endo/lockdown": "npm:^1.0.13" + "@endo/marshal": "npm:^1.6.2" + "@endo/promise-kit": "npm:^1.1.8" + checksum: 10c0/e4a8985d94ad49b785102f9db08415aaf07052178974dd2007fa0e359c50f83b9fead0934e9f0e7e81090dddde2c0e1b42e6f9b149989ec29e6887d980153dd6 languageName: node linkType: hard @@ -143,27 +143,6 @@ __metadata: languageName: node linkType: hard -"@agoric/client-utils@npm:0.1.1-dev-02c06c4.0": - version: 0.1.1-dev-02c06c4.0 - resolution: "@agoric/client-utils@npm:0.1.1-dev-02c06c4.0" - dependencies: - "@agoric/casting": "npm:0.4.3-dev-02c06c4.0+02c06c4" - "@agoric/ertp": "npm:0.16.3-dev-02c06c4.0+02c06c4" - "@agoric/internal": "npm:0.3.3-dev-02c06c4.0+02c06c4" - "@agoric/smart-wallet": "npm:0.5.4-dev-02c06c4.0+02c06c4" - "@agoric/vats": "npm:0.15.2-dev-02c06c4.0+02c06c4" - "@cosmjs/stargate": "npm:^0.32.3" - "@cosmjs/tendermint-rpc": "npm:^0.32.3" - "@endo/common": "npm:^1.2.7" - "@endo/errors": "npm:^1.2.7" - "@endo/marshal": "npm:^1.6.1" - "@endo/pass-style": "npm:^1.4.6" - "@endo/patterns": "npm:^1.4.6" - "@endo/promise-kit": "npm:^1.1.7" - checksum: 10c0/75009c017319e9d641d2653ee582307185363bb54a091aad3d85cbf59e514e3243c05f06e4ef7dd96bd5de1a702bd037e15bf03331c41333ce089664de13966f - languageName: node - linkType: hard - "@agoric/client-utils@npm:0.1.1-dev-e596a01.0+e596a01": version: 0.1.1-dev-e596a01.0 resolution: "@agoric/client-utils@npm:0.1.1-dev-e596a01.0" @@ -185,42 +164,44 @@ __metadata: languageName: node linkType: hard -"@agoric/cosmic-proto@npm:0.4.1-dev-02c06c4.0+02c06c4": - version: 0.4.1-dev-02c06c4.0 - resolution: "@agoric/cosmic-proto@npm:0.4.1-dev-02c06c4.0" +"@agoric/client-utils@npm:dev": + version: 0.1.1-dev-c1ae023.0 + resolution: "@agoric/client-utils@npm:0.1.1-dev-c1ae023.0" dependencies: - "@endo/base64": "npm:^1.0.8" - "@endo/init": "npm:^1.1.6" - checksum: 10c0/a691d32d5aeb4152ee75ed1a9dd6fcaa49500da939fb3ca8a3b2949b5c3d67afe5ac27f824966850be42831e4bba7a0c83702fc525da800a5a1721ec2566548a + "@agoric/casting": "npm:0.4.3-dev-c1ae023.0+c1ae023" + "@agoric/ertp": "npm:0.16.3-dev-c1ae023.0+c1ae023" + "@agoric/internal": "npm:0.3.3-dev-c1ae023.0+c1ae023" + "@agoric/smart-wallet": "npm:0.5.4-dev-c1ae023.0+c1ae023" + "@agoric/vats": "npm:0.15.2-dev-c1ae023.0+c1ae023" + "@cosmjs/stargate": "npm:^0.32.3" + "@cosmjs/tendermint-rpc": "npm:^0.32.3" + "@endo/common": "npm:^1.2.8" + "@endo/errors": "npm:^1.2.8" + "@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" + checksum: 10c0/be944d730e4b3c5a0f811d999a9a707f90c447ef8a35f5fabf6db104671252c1c549d8c62c297508de40406d8a8965e8650b7cd827271fd9f6cbad2291a2e9ed languageName: node linkType: hard -"@agoric/cosmic-proto@npm:0.4.1-dev-e596a01.0+e596a01": - version: 0.4.1-dev-e596a01.0 - resolution: "@agoric/cosmic-proto@npm:0.4.1-dev-e596a01.0" +"@agoric/cosmic-proto@npm:0.4.1-dev-c1ae023.0+c1ae023": + version: 0.4.1-dev-c1ae023.0 + resolution: "@agoric/cosmic-proto@npm:0.4.1-dev-c1ae023.0" dependencies: "@endo/base64": "npm:^1.0.9" "@endo/init": "npm:^1.1.7" - checksum: 10c0/2048e794ec9a346fb3a618b1b64d54985241967930b8b34c9220316b206fca4d3ecdf738e23e56021d45c3818f4513842e6d4c4d917a537dad59c13651d0ae35 + checksum: 10c0/78571d7f2c64df92d7f186ffad8c1e4c31c428495344555dc38ce74fc66397a4ac44f8d121b0929e6bb64a919bd7ecac708d04b4050021d69c68e388a2ea2de7 languageName: node linkType: hard -"@agoric/ertp@npm:0.16.3-dev-02c06c4.0+02c06c4": - version: 0.16.3-dev-02c06c4.0 - resolution: "@agoric/ertp@npm:0.16.3-dev-02c06c4.0" +"@agoric/cosmic-proto@npm:0.4.1-dev-e596a01.0+e596a01": + version: 0.4.1-dev-e596a01.0 + resolution: "@agoric/cosmic-proto@npm:0.4.1-dev-e596a01.0" dependencies: - "@agoric/notifier": "npm:0.6.3-dev-02c06c4.0+02c06c4" - "@agoric/store": "npm:0.9.3-dev-02c06c4.0+02c06c4" - "@agoric/vat-data": "npm:0.5.3-dev-02c06c4.0+02c06c4" - "@agoric/zone": "npm:0.2.3-dev-02c06c4.0+02c06c4" - "@endo/errors": "npm:^1.2.7" - "@endo/eventual-send": "npm:^1.2.7" - "@endo/far": "npm:^1.1.8" - "@endo/marshal": "npm:^1.6.1" - "@endo/nat": "npm:^5.0.12" - "@endo/patterns": "npm:^1.4.6" - "@endo/promise-kit": "npm:^1.1.7" - checksum: 10c0/079356fee7cb840873effc2a78b3659d1979f57f3aecdbbaea5dc876f506671d8d6a4e8b169f739457e7fb1e96e5c1e3806d88fab2cec8cd2c4b370d7a70aeef + "@endo/base64": "npm:^1.0.9" + "@endo/init": "npm:^1.1.7" + checksum: 10c0/2048e794ec9a346fb3a618b1b64d54985241967930b8b34c9220316b206fca4d3ecdf738e23e56021d45c3818f4513842e6d4c4d917a537dad59c13651d0ae35 languageName: node linkType: hard @@ -243,6 +224,25 @@ __metadata: languageName: node linkType: hard +"@agoric/ertp@npm:0.16.3-dev-c1ae023.0+c1ae023": + version: 0.16.3-dev-c1ae023.0 + resolution: "@agoric/ertp@npm:0.16.3-dev-c1ae023.0" + dependencies: + "@agoric/notifier": "npm:0.6.3-dev-c1ae023.0+c1ae023" + "@agoric/store": "npm:0.9.3-dev-c1ae023.0+c1ae023" + "@agoric/vat-data": "npm:0.5.3-dev-c1ae023.0+c1ae023" + "@agoric/zone": "npm:0.2.3-dev-c1ae023.0+c1ae023" + "@endo/errors": "npm:^1.2.8" + "@endo/eventual-send": "npm:^1.2.8" + "@endo/far": "npm:^1.1.9" + "@endo/marshal": "npm:^1.6.2" + "@endo/nat": "npm:^5.0.13" + "@endo/patterns": "npm:^1.4.7" + "@endo/promise-kit": "npm:^1.1.8" + checksum: 10c0/5a437d6a2b6b418dd016407c2095d7d98415818c422c3c53db217d99b3bbb2e476f5b7e9decbc628b82c8e85d2da939cf3846248eeb5d688b4789b21a1118194 + languageName: node + linkType: hard + "@agoric/ertp@npm:0.16.3-dev-e596a01.0+e596a01": version: 0.16.3-dev-e596a01.0 resolution: "@agoric/ertp@npm:0.16.3-dev-e596a01.0" @@ -262,27 +262,27 @@ __metadata: languageName: node linkType: hard -"@agoric/governance@npm:0.10.4-dev-02c06c4.0+02c06c4": - version: 0.10.4-dev-02c06c4.0 - resolution: "@agoric/governance@npm:0.10.4-dev-02c06c4.0" +"@agoric/governance@npm:0.10.4-dev-c1ae023.0+c1ae023": + version: 0.10.4-dev-c1ae023.0 + resolution: "@agoric/governance@npm:0.10.4-dev-c1ae023.0" dependencies: - "@agoric/ertp": "npm:0.16.3-dev-02c06c4.0+02c06c4" - "@agoric/internal": "npm:0.3.3-dev-02c06c4.0+02c06c4" - "@agoric/notifier": "npm:0.6.3-dev-02c06c4.0+02c06c4" - "@agoric/store": "npm:0.9.3-dev-02c06c4.0+02c06c4" - "@agoric/time": "npm:0.3.3-dev-02c06c4.0+02c06c4" - "@agoric/vat-data": "npm:0.5.3-dev-02c06c4.0+02c06c4" - "@agoric/zoe": "npm:0.26.3-dev-02c06c4.0+02c06c4" - "@endo/bundle-source": "npm:^3.4.2" - "@endo/captp": "npm:^4.4.2" - "@endo/errors": "npm:^1.2.7" - "@endo/eventual-send": "npm:^1.2.7" - "@endo/far": "npm:^1.1.8" - "@endo/marshal": "npm:^1.6.1" - "@endo/nat": "npm:^5.0.12" - "@endo/promise-kit": "npm:^1.1.7" + "@agoric/ertp": "npm:0.16.3-dev-c1ae023.0+c1ae023" + "@agoric/internal": "npm:0.3.3-dev-c1ae023.0+c1ae023" + "@agoric/notifier": "npm:0.6.3-dev-c1ae023.0+c1ae023" + "@agoric/store": "npm:0.9.3-dev-c1ae023.0+c1ae023" + "@agoric/time": "npm:0.3.3-dev-c1ae023.0+c1ae023" + "@agoric/vat-data": "npm:0.5.3-dev-c1ae023.0+c1ae023" + "@agoric/zoe": "npm:0.26.3-dev-c1ae023.0+c1ae023" + "@endo/bundle-source": "npm:^3.5.0" + "@endo/captp": "npm:^4.4.3" + "@endo/errors": "npm:^1.2.8" + "@endo/eventual-send": "npm:^1.2.8" + "@endo/far": "npm:^1.1.9" + "@endo/marshal": "npm:^1.6.2" + "@endo/nat": "npm:^5.0.13" + "@endo/promise-kit": "npm:^1.1.8" import-meta-resolve: "npm:^2.2.1" - checksum: 10c0/888427200f47bc141ccc4dd07801d6b62c265edaae62abccf81d0bc0f4181fe544942d052a0f1f4a69e1c71fed287f60957b1667b1d951d4c61a1abfeec92c60 + checksum: 10c0/445a41d4d2f226bbb99ad274d94d9f90cd4b4277db0428cc62046d7fdc508bbac6745859dfc4d16159a7e83629f501aa22a0752b99481a99b76e7d31f628290c languageName: node linkType: hard @@ -336,11 +336,11 @@ __metadata: languageName: node linkType: hard -"@agoric/internal@npm:0.3.3-dev-02c06c4.0+02c06c4": - version: 0.3.3-dev-02c06c4.0 - resolution: "@agoric/internal@npm:0.3.3-dev-02c06c4.0" +"@agoric/internal@npm:0.3.3-dev-3b799b8.0+3b799b8": + version: 0.3.3-dev-3b799b8.0 + resolution: "@agoric/internal@npm:0.3.3-dev-3b799b8.0" dependencies: - "@agoric/base-zone": "npm:0.1.1-dev-02c06c4.0+02c06c4" + "@agoric/base-zone": "npm:0.1.1-dev-3b799b8.0+3b799b8" "@endo/common": "npm:^1.2.7" "@endo/errors": "npm:^1.2.7" "@endo/far": "npm:^1.1.8" @@ -352,27 +352,27 @@ __metadata: "@endo/stream": "npm:^1.2.7" anylogger: "npm:^0.21.0" jessie.js: "npm:^0.3.4" - checksum: 10c0/ec9ab609f0e55c777748e870f7f00e9e19c2f41b9ac0967d0f98ebf1559aa59b69cbfa4f2b95c698187bc1d6a578ff3b67e1c6e3fba7c501bcf4567c3a52e122 + checksum: 10c0/332369a9acb41e46a579c9e0d084a12e16a78ad71f794f10d631235694281580b06ea88e25bf31d9eadc353b9c8d62e561232a21bb4c45f17c41057d0fb4c171 languageName: node linkType: hard -"@agoric/internal@npm:0.3.3-dev-3b799b8.0+3b799b8": - version: 0.3.3-dev-3b799b8.0 - resolution: "@agoric/internal@npm:0.3.3-dev-3b799b8.0" +"@agoric/internal@npm:0.3.3-dev-c1ae023.0+c1ae023": + version: 0.3.3-dev-c1ae023.0 + resolution: "@agoric/internal@npm:0.3.3-dev-c1ae023.0" dependencies: - "@agoric/base-zone": "npm:0.1.1-dev-3b799b8.0+3b799b8" - "@endo/common": "npm:^1.2.7" - "@endo/errors": "npm:^1.2.7" - "@endo/far": "npm:^1.1.8" - "@endo/init": "npm:^1.1.6" - "@endo/marshal": "npm:^1.6.1" - "@endo/pass-style": "npm:^1.4.6" - "@endo/patterns": "npm:^1.4.6" - "@endo/promise-kit": "npm:^1.1.7" - "@endo/stream": "npm:^1.2.7" + "@agoric/base-zone": "npm:0.1.1-dev-c1ae023.0+c1ae023" + "@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/332369a9acb41e46a579c9e0d084a12e16a78ad71f794f10d631235694281580b06ea88e25bf31d9eadc353b9c8d62e561232a21bb4c45f17c41057d0fb4c171 + checksum: 10c0/6ef8c160be33be88adefa67a861fb0758d03933a4bcc6f225e3b4e41c592553555fc9d477c1731e9ce86d28e2db49c72053f53188a199ab0f8c81a06423adc2c languageName: node linkType: hard @@ -416,17 +416,6 @@ __metadata: 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" - dependencies: - "@endo/errors": "npm:^1.2.7" - "@endo/far": "npm:^1.1.8" - "@endo/marshal": "npm:^1.6.1" - checksum: 10c0/f7a124e2d9876edeb72fe8d66f090acb7fbbb7ad8d6bbcad3d60a25e7eee98e005a52222d3c570b1e9ecc504dd7bea4444232fe756824c97000bba439ea142ee - languageName: node - linkType: hard - "@agoric/kmarshal@npm:0.1.1-dev-3b799b8.0+3b799b8": version: 0.1.1-dev-3b799b8.0 resolution: "@agoric/kmarshal@npm:0.1.1-dev-3b799b8.0" @@ -438,6 +427,17 @@ __metadata: languageName: node linkType: hard +"@agoric/kmarshal@npm:0.1.1-dev-c1ae023.0+c1ae023": + version: 0.1.1-dev-c1ae023.0 + resolution: "@agoric/kmarshal@npm:0.1.1-dev-c1ae023.0" + dependencies: + "@endo/errors": "npm:^1.2.8" + "@endo/far": "npm:^1.1.9" + "@endo/marshal": "npm:^1.6.2" + checksum: 10c0/5f4c1784fa4fa6de50f288722794ac0c98d0719e3558bc9147b726014a72dc3222a68f4c7f40e42f170e4b59481579a4d318e3cffb665720dafc23c80915ec6b + languageName: node + linkType: hard + "@agoric/kmarshal@npm:0.1.1-dev-e596a01.0+e596a01": version: 0.1.1-dev-e596a01.0 resolution: "@agoric/kmarshal@npm:0.1.1-dev-e596a01.0" @@ -449,20 +449,20 @@ __metadata: languageName: node linkType: hard -"@agoric/network@npm:0.1.1-dev-02c06c4.0+02c06c4": - version: 0.1.1-dev-02c06c4.0 - resolution: "@agoric/network@npm:0.1.1-dev-02c06c4.0" +"@agoric/network@npm:0.1.1-dev-c1ae023.0+c1ae023": + version: 0.1.1-dev-c1ae023.0 + resolution: "@agoric/network@npm:0.1.1-dev-c1ae023.0" dependencies: - "@agoric/internal": "npm:0.3.3-dev-02c06c4.0+02c06c4" - "@agoric/store": "npm:0.9.3-dev-02c06c4.0+02c06c4" - "@agoric/vat-data": "npm:0.5.3-dev-02c06c4.0+02c06c4" - "@endo/base64": "npm:^1.0.8" - "@endo/errors": "npm:^1.2.7" - "@endo/far": "npm:^1.1.8" - "@endo/pass-style": "npm:^1.4.6" - "@endo/patterns": "npm:^1.4.6" - "@endo/promise-kit": "npm:^1.1.7" - checksum: 10c0/0e14d617b90a1bf63ebe5776182f6cab2b87c9f18e951740ce7cddcdd418d630835a21c88af3910c2d73b5b6dc28a455a8f32dc15f66f48089f414916f5c541e + "@agoric/internal": "npm:0.3.3-dev-c1ae023.0+c1ae023" + "@agoric/store": "npm:0.9.3-dev-c1ae023.0+c1ae023" + "@agoric/vat-data": "npm:0.5.3-dev-c1ae023.0+c1ae023" + "@endo/base64": "npm:^1.0.9" + "@endo/errors": "npm:^1.2.8" + "@endo/far": "npm:^1.1.9" + "@endo/pass-style": "npm:^1.4.7" + "@endo/patterns": "npm:^1.4.7" + "@endo/promise-kit": "npm:^1.1.8" + checksum: 10c0/93fd60ad6a0ce650427853677be076c77ce00d3201f29ec0922eb5fd38ce5ec2316fb9a564a76d3fae809299ef193e80109b89b79c4191ddb9525e5e4cb33302 languageName: node linkType: hard @@ -483,21 +483,6 @@ __metadata: languageName: node linkType: hard -"@agoric/notifier@npm:0.6.3-dev-02c06c4.0+02c06c4": - version: 0.6.3-dev-02c06c4.0 - resolution: "@agoric/notifier@npm:0.6.3-dev-02c06c4.0" - dependencies: - "@agoric/internal": "npm:0.3.3-dev-02c06c4.0+02c06c4" - "@agoric/vat-data": "npm:0.5.3-dev-02c06c4.0+02c06c4" - "@endo/errors": "npm:^1.2.7" - "@endo/far": "npm:^1.1.8" - "@endo/marshal": "npm:^1.6.1" - "@endo/patterns": "npm:^1.4.6" - "@endo/promise-kit": "npm:^1.1.7" - checksum: 10c0/6b4698c179c1483231124c6f6abc535bf86cef0bf61f3db05821031e06a3c63e7676fbba88170c2fb4716cd6dd96dc18b5c90d10b04aeee89e7be80c3b825e92 - languageName: node - linkType: hard - "@agoric/notifier@npm:0.6.3-dev-3b799b8.0+3b799b8": version: 0.6.3-dev-3b799b8.0 resolution: "@agoric/notifier@npm:0.6.3-dev-3b799b8.0" @@ -513,6 +498,21 @@ __metadata: languageName: node linkType: hard +"@agoric/notifier@npm:0.6.3-dev-c1ae023.0+c1ae023": + version: 0.6.3-dev-c1ae023.0 + resolution: "@agoric/notifier@npm:0.6.3-dev-c1ae023.0" + dependencies: + "@agoric/internal": "npm:0.3.3-dev-c1ae023.0+c1ae023" + "@agoric/vat-data": "npm:0.5.3-dev-c1ae023.0+c1ae023" + "@endo/errors": "npm:^1.2.8" + "@endo/far": "npm:^1.1.9" + "@endo/marshal": "npm:^1.6.2" + "@endo/patterns": "npm:^1.4.7" + "@endo/promise-kit": "npm:^1.1.8" + checksum: 10c0/b810e7c98305e3e398fc1b97fa611c48b6af9a0144a865d357c2e5ac10cb60fc55bd01d87f8165fbc09c009166c7bc3a0513e2b1bf35e0742c0607e0c6579060 + languageName: node + linkType: hard + "@agoric/notifier@npm:0.6.3-dev-e596a01.0+e596a01": version: 0.6.3-dev-e596a01.0 resolution: "@agoric/notifier@npm:0.6.3-dev-e596a01.0" @@ -528,26 +528,26 @@ __metadata: languageName: node linkType: hard -"@agoric/smart-wallet@npm:0.5.4-dev-02c06c4.0+02c06c4": - version: 0.5.4-dev-02c06c4.0 - resolution: "@agoric/smart-wallet@npm:0.5.4-dev-02c06c4.0" +"@agoric/smart-wallet@npm:0.5.4-dev-c1ae023.0+c1ae023": + version: 0.5.4-dev-c1ae023.0 + resolution: "@agoric/smart-wallet@npm:0.5.4-dev-c1ae023.0" dependencies: - "@agoric/ertp": "npm:0.16.3-dev-02c06c4.0+02c06c4" - "@agoric/internal": "npm:0.3.3-dev-02c06c4.0+02c06c4" - "@agoric/notifier": "npm:0.6.3-dev-02c06c4.0+02c06c4" - "@agoric/store": "npm:0.9.3-dev-02c06c4.0+02c06c4" - "@agoric/vat-data": "npm:0.5.3-dev-02c06c4.0+02c06c4" - "@agoric/vats": "npm:0.15.2-dev-02c06c4.0+02c06c4" - "@agoric/vow": "npm:0.1.1-dev-02c06c4.0+02c06c4" - "@agoric/zoe": "npm:0.26.3-dev-02c06c4.0+02c06c4" - "@agoric/zone": "npm:0.2.3-dev-02c06c4.0+02c06c4" - "@endo/errors": "npm:^1.2.7" - "@endo/eventual-send": "npm:^1.2.7" - "@endo/far": "npm:^1.1.8" - "@endo/marshal": "npm:^1.6.1" - "@endo/nat": "npm:^5.0.12" - "@endo/promise-kit": "npm:^1.1.7" - checksum: 10c0/a2bc7bf868bdc578f4c2251f04119b1e72eeae5ff14334eeffbd8bfd8706b4ded203e2f239d8a85283fd06dfa6910c9b01b9a7d55de0fe6767c0f729c3915034 + "@agoric/ertp": "npm:0.16.3-dev-c1ae023.0+c1ae023" + "@agoric/internal": "npm:0.3.3-dev-c1ae023.0+c1ae023" + "@agoric/notifier": "npm:0.6.3-dev-c1ae023.0+c1ae023" + "@agoric/store": "npm:0.9.3-dev-c1ae023.0+c1ae023" + "@agoric/vat-data": "npm:0.5.3-dev-c1ae023.0+c1ae023" + "@agoric/vats": "npm:0.15.2-dev-c1ae023.0+c1ae023" + "@agoric/vow": "npm:0.1.1-dev-c1ae023.0+c1ae023" + "@agoric/zoe": "npm:0.26.3-dev-c1ae023.0+c1ae023" + "@agoric/zone": "npm:0.2.3-dev-c1ae023.0+c1ae023" + "@endo/errors": "npm:^1.2.8" + "@endo/eventual-send": "npm:^1.2.8" + "@endo/far": "npm:^1.1.9" + "@endo/marshal": "npm:^1.6.2" + "@endo/nat": "npm:^5.0.13" + "@endo/promise-kit": "npm:^1.1.8" + checksum: 10c0/94096186933d72b4db40e965adcef8d2e36b4917e6f4e1ca5d103d949c201daeaa16ad4d48ce121aec7048defb6177f75270418f175ae7ba390f169e8e37c380 languageName: node linkType: hard @@ -574,19 +574,6 @@ __metadata: languageName: node linkType: hard -"@agoric/store@npm:0.9.3-dev-02c06c4.0+02c06c4": - version: 0.9.3-dev-02c06c4.0 - resolution: "@agoric/store@npm:0.9.3-dev-02c06c4.0" - dependencies: - "@endo/errors": "npm:^1.2.7" - "@endo/exo": "npm:^1.5.6" - "@endo/marshal": "npm:^1.6.1" - "@endo/pass-style": "npm:^1.4.6" - "@endo/patterns": "npm:^1.4.6" - checksum: 10c0/975d6d7f72bc3e0bb9087a1998c4c6092504cf084d1615a74d7bd7ea10d9032d5a0845e24399f787a7a9b1974e00a61a995f73d47bf8a8444ac1d9ed8aeb94a6 - 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" @@ -613,31 +600,29 @@ __metadata: languageName: node linkType: hard -"@agoric/store@npm:0.9.3-dev-e596a01.0+e596a01": - version: 0.9.3-dev-e596a01.0 - resolution: "@agoric/store@npm:0.9.3-dev-e596a01.0" +"@agoric/store@npm:0.9.3-dev-c1ae023.0+c1ae023": + version: 0.9.3-dev-c1ae023.0 + resolution: "@agoric/store@npm:0.9.3-dev-c1ae023.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/b931aa2566d2ef2fea087938c34a79a6682a15f0fc9a5084e73c671d970f22ab3a1040febab4f7f0ae793858597834d76840b1d9c20a89048b725e3b5443b84f + checksum: 10c0/9fd6d5464906144140a868d38e63d1ee2f8f06240a8ba2e71ed73eb7df5ce9c03a74a79290b4dc2b2e4c3c3d59ba07228f7019965cf234a4178b815f8861f002 languageName: node linkType: hard -"@agoric/swing-store@npm:0.9.2-dev-02c06c4.0+02c06c4": - version: 0.9.2-dev-02c06c4.0 - resolution: "@agoric/swing-store@npm:0.9.2-dev-02c06c4.0" +"@agoric/store@npm:0.9.3-dev-e596a01.0+e596a01": + version: 0.9.3-dev-e596a01.0 + resolution: "@agoric/store@npm:0.9.3-dev-e596a01.0" dependencies: - "@agoric/internal": "npm:0.3.3-dev-02c06c4.0+02c06c4" - "@endo/base64": "npm:^1.0.8" - "@endo/bundle-source": "npm:^3.4.2" - "@endo/check-bundle": "npm:^1.0.11" - "@endo/errors": "npm:^1.2.7" - "@endo/nat": "npm:^5.0.12" - better-sqlite3: "npm:^9.1.1" - checksum: 10c0/71fd32035b20398c2a28eb8c7ada724c601cde1408f2ffadcbf6b06c06899ce9d0e595f75a570e9cc466c864c5ea29bdddf2b4a4f07e9031496588ea5904fabe + "@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/b931aa2566d2ef2fea087938c34a79a6682a15f0fc9a5084e73c671d970f22ab3a1040febab4f7f0ae793858597834d76840b1d9c20a89048b725e3b5443b84f languageName: node linkType: hard @@ -656,6 +641,21 @@ __metadata: languageName: node linkType: hard +"@agoric/swing-store@npm:0.9.2-dev-c1ae023.0+c1ae023": + version: 0.9.2-dev-c1ae023.0 + resolution: "@agoric/swing-store@npm:0.9.2-dev-c1ae023.0" + dependencies: + "@agoric/internal": "npm:0.3.3-dev-c1ae023.0+c1ae023" + "@endo/base64": "npm:^1.0.9" + "@endo/bundle-source": "npm:^3.5.0" + "@endo/check-bundle": "npm:^1.0.12" + "@endo/errors": "npm:^1.2.8" + "@endo/nat": "npm:^5.0.13" + better-sqlite3: "npm:^9.1.1" + checksum: 10c0/56adf70976c1c7f6f2437045fdd2c10f128b0d79224f83aed842b8b42f2253328d24151817748e2d4bc8c1f70a840d254646423a69496ff25e049bc3909233d5 + languageName: node + linkType: hard + "@agoric/swing-store@npm:0.9.2-dev-e596a01.0+e596a01": version: 0.9.2-dev-e596a01.0 resolution: "@agoric/swing-store@npm:0.9.2-dev-e596a01.0" @@ -671,27 +671,6 @@ __metadata: languageName: node linkType: hard -"@agoric/swingset-liveslots@npm:0.10.3-dev-02c06c4.0+02c06c4": - version: 0.10.3-dev-02c06c4.0 - resolution: "@agoric/swingset-liveslots@npm:0.10.3-dev-02c06c4.0" - dependencies: - "@agoric/internal": "npm:0.3.3-dev-02c06c4.0+02c06c4" - "@agoric/store": "npm:0.9.3-dev-02c06c4.0+02c06c4" - "@endo/env-options": "npm:^1.1.7" - "@endo/errors": "npm:^1.2.7" - "@endo/eventual-send": "npm:^1.2.7" - "@endo/exo": "npm:^1.5.6" - "@endo/far": "npm:^1.1.8" - "@endo/init": "npm:^1.1.6" - "@endo/marshal": "npm:^1.6.1" - "@endo/nat": "npm:^5.0.12" - "@endo/pass-style": "npm:^1.4.6" - "@endo/patterns": "npm:^1.4.6" - "@endo/promise-kit": "npm:^1.1.7" - checksum: 10c0/0c0b7a9ff81b173b6610ef4b7af296ae1b1dbb753d4f2ef36e4b276aeb8e4a54a7a53e7293b051f3e196e8175e085db71728e986f7d226e90a08f9c7eb25b874 - languageName: node - linkType: hard - "@agoric/swingset-liveslots@npm:0.10.3-dev-3b799b8.0+3b799b8": version: 0.10.3-dev-3b799b8.0 resolution: "@agoric/swingset-liveslots@npm:0.10.3-dev-3b799b8.0" @@ -713,6 +692,27 @@ __metadata: languageName: node linkType: hard +"@agoric/swingset-liveslots@npm:0.10.3-dev-c1ae023.0+c1ae023": + version: 0.10.3-dev-c1ae023.0 + resolution: "@agoric/swingset-liveslots@npm:0.10.3-dev-c1ae023.0" + dependencies: + "@agoric/internal": "npm:0.3.3-dev-c1ae023.0+c1ae023" + "@agoric/store": "npm:0.9.3-dev-c1ae023.0+c1ae023" + "@endo/env-options": "npm:^1.1.8" + "@endo/errors": "npm:^1.2.8" + "@endo/eventual-send": "npm:^1.2.8" + "@endo/exo": "npm:^1.5.7" + "@endo/far": "npm:^1.1.9" + "@endo/init": "npm:^1.1.7" + "@endo/marshal": "npm:^1.6.2" + "@endo/nat": "npm:^5.0.13" + "@endo/pass-style": "npm:^1.4.7" + "@endo/patterns": "npm:^1.4.7" + "@endo/promise-kit": "npm:^1.1.8" + checksum: 10c0/a44a74766ce6be12d65f7328a544993a07a2b3b74f8f998c48c085a39b38746644005d285cb5d3e6282b1e327e7e809c9cd23e36667da2b7f11926808000b1f0 + languageName: node + linkType: hard + "@agoric/swingset-liveslots@npm:0.10.3-dev-e596a01.0+e596a01": version: 0.10.3-dev-e596a01.0 resolution: "@agoric/swingset-liveslots@npm:0.10.3-dev-e596a01.0" @@ -734,19 +734,19 @@ __metadata: languageName: node linkType: hard -"@agoric/swingset-vat@npm:0.32.3-dev-02c06c4.0+02c06c4": - version: 0.32.3-dev-02c06c4.0 - resolution: "@agoric/swingset-vat@npm:0.32.3-dev-02c06c4.0" +"@agoric/swingset-vat@npm:0.32.3-dev-3b799b8.0+3b799b8": + version: 0.32.3-dev-3b799b8.0 + resolution: "@agoric/swingset-vat@npm:0.32.3-dev-3b799b8.0" dependencies: - "@agoric/internal": "npm:0.3.3-dev-02c06c4.0+02c06c4" - "@agoric/kmarshal": "npm:0.1.1-dev-02c06c4.0+02c06c4" - "@agoric/store": "npm:0.9.3-dev-02c06c4.0+02c06c4" - "@agoric/swing-store": "npm:0.9.2-dev-02c06c4.0+02c06c4" - "@agoric/swingset-liveslots": "npm:0.10.3-dev-02c06c4.0+02c06c4" - "@agoric/swingset-xsnap-supervisor": "npm:0.10.3-dev-02c06c4.0+02c06c4" - "@agoric/time": "npm:0.3.3-dev-02c06c4.0+02c06c4" - "@agoric/vat-data": "npm:0.5.3-dev-02c06c4.0+02c06c4" - "@agoric/xsnap-lockdown": "npm:0.14.1-dev-02c06c4.0+02c06c4" + "@agoric/internal": "npm:0.3.3-dev-3b799b8.0+3b799b8" + "@agoric/kmarshal": "npm:0.1.1-dev-3b799b8.0+3b799b8" + "@agoric/store": "npm:0.9.3-dev-3b799b8.0+3b799b8" + "@agoric/swing-store": "npm:0.9.2-dev-3b799b8.0+3b799b8" + "@agoric/swingset-liveslots": "npm:0.10.3-dev-3b799b8.0+3b799b8" + "@agoric/swingset-xsnap-supervisor": "npm:0.10.3-dev-3b799b8.0+3b799b8" + "@agoric/time": "npm:0.3.3-dev-3b799b8.0+3b799b8" + "@agoric/vat-data": "npm:0.5.3-dev-3b799b8.0+3b799b8" + "@agoric/xsnap-lockdown": "npm:0.14.1-dev-3b799b8.0+3b799b8" "@endo/base64": "npm:^1.0.8" "@endo/bundle-source": "npm:^3.4.2" "@endo/captp": "npm:^4.4.2" @@ -778,41 +778,41 @@ __metadata: ava: ^5.3.0 bin: vat: bin/vat - checksum: 10c0/43e4f64c6b157c7f343cd1a9a710061d8d81926986b5aec43d7b79e3a7b4ca46ab309285104767b780c6877f64ae79d8ecbb1847b84541a0f26cc38999e94ce4 + checksum: 10c0/661426721b4106f9e51bc5f86858b166a24b3954429ec8cfe20f3a6650017807c4af12a78b9f64aa549b317356d1a52f44aee9137dde738f74d077d789dad482 languageName: node linkType: hard -"@agoric/swingset-vat@npm:0.32.3-dev-3b799b8.0+3b799b8": - version: 0.32.3-dev-3b799b8.0 - resolution: "@agoric/swingset-vat@npm:0.32.3-dev-3b799b8.0" +"@agoric/swingset-vat@npm:0.32.3-dev-c1ae023.0+c1ae023": + version: 0.32.3-dev-c1ae023.0 + resolution: "@agoric/swingset-vat@npm:0.32.3-dev-c1ae023.0" dependencies: - "@agoric/internal": "npm:0.3.3-dev-3b799b8.0+3b799b8" - "@agoric/kmarshal": "npm:0.1.1-dev-3b799b8.0+3b799b8" - "@agoric/store": "npm:0.9.3-dev-3b799b8.0+3b799b8" - "@agoric/swing-store": "npm:0.9.2-dev-3b799b8.0+3b799b8" - "@agoric/swingset-liveslots": "npm:0.10.3-dev-3b799b8.0+3b799b8" - "@agoric/swingset-xsnap-supervisor": "npm:0.10.3-dev-3b799b8.0+3b799b8" - "@agoric/time": "npm:0.3.3-dev-3b799b8.0+3b799b8" - "@agoric/vat-data": "npm:0.5.3-dev-3b799b8.0+3b799b8" - "@agoric/xsnap-lockdown": "npm:0.14.1-dev-3b799b8.0+3b799b8" - "@endo/base64": "npm:^1.0.8" - "@endo/bundle-source": "npm:^3.4.2" - "@endo/captp": "npm:^4.4.2" - "@endo/check-bundle": "npm:^1.0.11" - "@endo/compartment-mapper": "npm:^1.3.1" - "@endo/errors": "npm:^1.2.7" - "@endo/eventual-send": "npm:^1.2.7" - "@endo/far": "npm:^1.1.8" - "@endo/import-bundle": "npm:^1.3.1" - "@endo/init": "npm:^1.1.6" - "@endo/marshal": "npm:^1.6.1" - "@endo/nat": "npm:^5.0.12" - "@endo/pass-style": "npm:^1.4.6" - "@endo/patterns": "npm:^1.4.6" - "@endo/promise-kit": "npm:^1.1.7" - "@endo/ses-ava": "npm:^1.2.7" - "@endo/stream": "npm:^1.2.7" - "@endo/zip": "npm:^1.0.8" + "@agoric/internal": "npm:0.3.3-dev-c1ae023.0+c1ae023" + "@agoric/kmarshal": "npm:0.1.1-dev-c1ae023.0+c1ae023" + "@agoric/store": "npm:0.9.3-dev-c1ae023.0+c1ae023" + "@agoric/swing-store": "npm:0.9.2-dev-c1ae023.0+c1ae023" + "@agoric/swingset-liveslots": "npm:0.10.3-dev-c1ae023.0+c1ae023" + "@agoric/swingset-xsnap-supervisor": "npm:0.10.3-dev-c1ae023.0+c1ae023" + "@agoric/time": "npm:0.3.3-dev-c1ae023.0+c1ae023" + "@agoric/vat-data": "npm:0.5.3-dev-c1ae023.0+c1ae023" + "@agoric/xsnap-lockdown": "npm:0.14.1-dev-c1ae023.0+c1ae023" + "@endo/base64": "npm:^1.0.9" + "@endo/bundle-source": "npm:^3.5.0" + "@endo/captp": "npm:^4.4.3" + "@endo/check-bundle": "npm:^1.0.12" + "@endo/compartment-mapper": "npm:^1.4.0" + "@endo/errors": "npm:^1.2.8" + "@endo/eventual-send": "npm:^1.2.8" + "@endo/far": "npm:^1.1.9" + "@endo/import-bundle": "npm:^1.3.2" + "@endo/init": "npm:^1.1.7" + "@endo/marshal": "npm:^1.6.2" + "@endo/nat": "npm:^5.0.13" + "@endo/pass-style": "npm:^1.4.7" + "@endo/patterns": "npm:^1.4.7" + "@endo/promise-kit": "npm:^1.1.8" + "@endo/ses-ava": "npm:^1.2.8" + "@endo/stream": "npm:^1.2.8" + "@endo/zip": "npm:^1.0.9" ansi-styles: "npm:^6.2.1" anylogger: "npm:^0.21.0" better-sqlite3: "npm:^9.1.1" @@ -826,7 +826,7 @@ __metadata: ava: ^5.3.0 bin: vat: bin/vat - checksum: 10c0/661426721b4106f9e51bc5f86858b166a24b3954429ec8cfe20f3a6650017807c4af12a78b9f64aa549b317356d1a52f44aee9137dde738f74d077d789dad482 + checksum: 10c0/66482c449eeccd00167dec73899a5c510719620eba2ad457e77c04790b3573e8e82138e4d362f1b4ee859719278bdb49835df0007318d216ce6e54e67d08ee95 languageName: node linkType: hard @@ -878,13 +878,6 @@ __metadata: languageName: node linkType: hard -"@agoric/swingset-xsnap-supervisor@npm:0.10.3-dev-02c06c4.0+02c06c4": - version: 0.10.3-dev-02c06c4.0 - resolution: "@agoric/swingset-xsnap-supervisor@npm:0.10.3-dev-02c06c4.0" - checksum: 10c0/a269373b02fcfcb10a47d601ef13ee1c01c2a8666bed7119e0b6f47027ce5f8d2321d5aaf6d22cbd949db69f4403cfda4d439d84c27bb4d7dd0fe421173fcb58 - languageName: node - linkType: hard - "@agoric/swingset-xsnap-supervisor@npm:0.10.3-dev-3b799b8.0+3b799b8": version: 0.10.3-dev-3b799b8.0 resolution: "@agoric/swingset-xsnap-supervisor@npm:0.10.3-dev-3b799b8.0" @@ -892,6 +885,13 @@ __metadata: languageName: node linkType: hard +"@agoric/swingset-xsnap-supervisor@npm:0.10.3-dev-c1ae023.0+c1ae023": + version: 0.10.3-dev-c1ae023.0 + resolution: "@agoric/swingset-xsnap-supervisor@npm:0.10.3-dev-c1ae023.0" + checksum: 10c0/9f5b3bfe1f76f74f0ab605a67a8c822ba76fd80f7da9a13ee7933425effce76877240b5bd155a677c20740259bab1f0087131784035ef8a85e1278d718450589 + languageName: node + linkType: hard + "@agoric/swingset-xsnap-supervisor@npm:0.10.3-dev-e596a01.0+e596a01": version: 0.10.3-dev-e596a01.0 resolution: "@agoric/swingset-xsnap-supervisor@npm:0.10.3-dev-e596a01.0" @@ -914,18 +914,6 @@ __metadata: languageName: node linkType: hard -"@agoric/time@npm:0.3.3-dev-02c06c4.0+02c06c4": - version: 0.3.3-dev-02c06c4.0 - resolution: "@agoric/time@npm:0.3.3-dev-02c06c4.0" - dependencies: - "@agoric/store": "npm:0.9.3-dev-02c06c4.0+02c06c4" - "@endo/errors": "npm:^1.2.7" - "@endo/nat": "npm:^5.0.12" - "@endo/patterns": "npm:^1.4.6" - checksum: 10c0/ffe92b98f6f0f0d88aa1ecc2782775af94e77c4ba23f55005c5d201abb7ecd7b75dcb703f5b11e13588ad15e643fff2a42ebe74b0934872446e6647ff8caf3ef - languageName: node - linkType: hard - "@agoric/time@npm:0.3.3-dev-3b799b8.0+3b799b8": version: 0.3.3-dev-3b799b8.0 resolution: "@agoric/time@npm:0.3.3-dev-3b799b8.0" @@ -938,6 +926,18 @@ __metadata: languageName: node linkType: hard +"@agoric/time@npm:0.3.3-dev-c1ae023.0+c1ae023": + version: 0.3.3-dev-c1ae023.0 + resolution: "@agoric/time@npm:0.3.3-dev-c1ae023.0" + dependencies: + "@agoric/store": "npm:0.9.3-dev-c1ae023.0+c1ae023" + "@endo/errors": "npm:^1.2.8" + "@endo/nat": "npm:^5.0.13" + "@endo/patterns": "npm:^1.4.7" + checksum: 10c0/f4c6c900f25eda4bc32ee045f0b87ad1f3b38aa400047d51fbac534aa69bceaaf4f07130c539276a96c51a885394847ccd0a42da950722c58276998bdd7555ed + languageName: node + linkType: hard + "@agoric/time@npm:0.3.3-dev-e596a01.0+e596a01": version: 0.3.3-dev-e596a01.0 resolution: "@agoric/time@npm:0.3.3-dev-e596a01.0" @@ -950,20 +950,6 @@ __metadata: languageName: node linkType: hard -"@agoric/vat-data@npm:0.5.3-dev-02c06c4.0+02c06c4": - version: 0.5.3-dev-02c06c4.0 - resolution: "@agoric/vat-data@npm:0.5.3-dev-02c06c4.0" - dependencies: - "@agoric/base-zone": "npm:0.1.1-dev-02c06c4.0+02c06c4" - "@agoric/store": "npm:0.9.3-dev-02c06c4.0+02c06c4" - "@agoric/swingset-liveslots": "npm:0.10.3-dev-02c06c4.0+02c06c4" - "@endo/errors": "npm:^1.2.7" - "@endo/exo": "npm:^1.5.6" - "@endo/patterns": "npm:^1.4.6" - checksum: 10c0/b381d8e22d6d51b3dbad23820e61c97e269117c822679405b149882269e26366f3f15fff4520ca3d72ceb30bdad3fa713599573235335220689e0c37f040af5a - languageName: node - linkType: hard - "@agoric/vat-data@npm:0.5.3-dev-3b799b8.0+3b799b8": version: 0.5.3-dev-3b799b8.0 resolution: "@agoric/vat-data@npm:0.5.3-dev-3b799b8.0" @@ -978,6 +964,20 @@ __metadata: languageName: node linkType: hard +"@agoric/vat-data@npm:0.5.3-dev-c1ae023.0+c1ae023": + version: 0.5.3-dev-c1ae023.0 + resolution: "@agoric/vat-data@npm:0.5.3-dev-c1ae023.0" + dependencies: + "@agoric/base-zone": "npm:0.1.1-dev-c1ae023.0+c1ae023" + "@agoric/store": "npm:0.9.3-dev-c1ae023.0+c1ae023" + "@agoric/swingset-liveslots": "npm:0.10.3-dev-c1ae023.0+c1ae023" + "@endo/errors": "npm:^1.2.8" + "@endo/exo": "npm:^1.5.7" + "@endo/patterns": "npm:^1.4.7" + checksum: 10c0/009a8ca0f385770d4be37f8db2970ca03b035717193c18e43ce2f8ea6c212fba9a6f925dd376971e449416c6c3569178446318ecebdfc22016b03a50aed3fea4 + languageName: node + linkType: hard + "@agoric/vat-data@npm:0.5.3-dev-e596a01.0+e596a01": version: 0.5.3-dev-e596a01.0 resolution: "@agoric/vat-data@npm:0.5.3-dev-e596a01.0" @@ -992,34 +992,34 @@ __metadata: languageName: node linkType: hard -"@agoric/vats@npm:0.15.2-dev-02c06c4.0+02c06c4": - version: 0.15.2-dev-02c06c4.0 - resolution: "@agoric/vats@npm:0.15.2-dev-02c06c4.0" - dependencies: - "@agoric/cosmic-proto": "npm:0.4.1-dev-02c06c4.0+02c06c4" - "@agoric/ertp": "npm:0.16.3-dev-02c06c4.0+02c06c4" - "@agoric/governance": "npm:0.10.4-dev-02c06c4.0+02c06c4" - "@agoric/internal": "npm:0.3.3-dev-02c06c4.0+02c06c4" - "@agoric/network": "npm:0.1.1-dev-02c06c4.0+02c06c4" - "@agoric/notifier": "npm:0.6.3-dev-02c06c4.0+02c06c4" - "@agoric/store": "npm:0.9.3-dev-02c06c4.0+02c06c4" - "@agoric/swingset-vat": "npm:0.32.3-dev-02c06c4.0+02c06c4" - "@agoric/time": "npm:0.3.3-dev-02c06c4.0+02c06c4" - "@agoric/vat-data": "npm:0.5.3-dev-02c06c4.0+02c06c4" - "@agoric/vow": "npm:0.1.1-dev-02c06c4.0+02c06c4" - "@agoric/zoe": "npm:0.26.3-dev-02c06c4.0+02c06c4" - "@agoric/zone": "npm:0.2.3-dev-02c06c4.0+02c06c4" - "@endo/errors": "npm:^1.2.7" - "@endo/far": "npm:^1.1.8" - "@endo/import-bundle": "npm:^1.3.1" - "@endo/marshal": "npm:^1.6.1" - "@endo/nat": "npm:^5.0.12" - "@endo/pass-style": "npm:^1.4.6" - "@endo/patterns": "npm:^1.4.6" - "@endo/promise-kit": "npm:^1.1.7" +"@agoric/vats@npm:0.15.2-dev-c1ae023.0+c1ae023": + version: 0.15.2-dev-c1ae023.0 + resolution: "@agoric/vats@npm:0.15.2-dev-c1ae023.0" + dependencies: + "@agoric/cosmic-proto": "npm:0.4.1-dev-c1ae023.0+c1ae023" + "@agoric/ertp": "npm:0.16.3-dev-c1ae023.0+c1ae023" + "@agoric/governance": "npm:0.10.4-dev-c1ae023.0+c1ae023" + "@agoric/internal": "npm:0.3.3-dev-c1ae023.0+c1ae023" + "@agoric/network": "npm:0.1.1-dev-c1ae023.0+c1ae023" + "@agoric/notifier": "npm:0.6.3-dev-c1ae023.0+c1ae023" + "@agoric/store": "npm:0.9.3-dev-c1ae023.0+c1ae023" + "@agoric/swingset-vat": "npm:0.32.3-dev-c1ae023.0+c1ae023" + "@agoric/time": "npm:0.3.3-dev-c1ae023.0+c1ae023" + "@agoric/vat-data": "npm:0.5.3-dev-c1ae023.0+c1ae023" + "@agoric/vow": "npm:0.1.1-dev-c1ae023.0+c1ae023" + "@agoric/zoe": "npm:0.26.3-dev-c1ae023.0+c1ae023" + "@agoric/zone": "npm:0.2.3-dev-c1ae023.0+c1ae023" + "@endo/errors": "npm:^1.2.8" + "@endo/far": "npm:^1.1.9" + "@endo/import-bundle": "npm:^1.3.2" + "@endo/marshal": "npm:^1.6.2" + "@endo/nat": "npm:^5.0.13" + "@endo/pass-style": "npm:^1.4.7" + "@endo/patterns": "npm:^1.4.7" + "@endo/promise-kit": "npm:^1.1.8" import-meta-resolve: "npm:^2.2.1" jessie.js: "npm:^0.3.4" - checksum: 10c0/0b2b6b9a964f1194c68818e1b5b65fef15138b6b670e58fb35322b169d540a73fa453d7918290d7311caeef3cd4db5423bf0b8490a0b31087eb12230a429a9ce + checksum: 10c0/d8920290ce6cab7f7dccbc6890df6ee0dfb2d25a4b63b9e6d5794aec8379d17bc253196162668ab1c02c824af07bb2aa890053ef9c55206b62342d43079ef806 languageName: node linkType: hard @@ -1054,22 +1054,6 @@ __metadata: languageName: node linkType: hard -"@agoric/vow@npm:0.1.1-dev-02c06c4.0+02c06c4": - version: 0.1.1-dev-02c06c4.0 - resolution: "@agoric/vow@npm:0.1.1-dev-02c06c4.0" - dependencies: - "@agoric/base-zone": "npm:0.1.1-dev-02c06c4.0+02c06c4" - "@agoric/internal": "npm:0.3.3-dev-02c06c4.0+02c06c4" - "@endo/env-options": "npm:^1.1.7" - "@endo/errors": "npm:^1.2.7" - "@endo/eventual-send": "npm:^1.2.7" - "@endo/pass-style": "npm:^1.4.6" - "@endo/patterns": "npm:^1.4.6" - "@endo/promise-kit": "npm:^1.1.7" - checksum: 10c0/6da30cb8fe91333fa6d961df09ce293ee9ee24104c6e86ff87832f4975016b02e0c520e82dd9338a69059106417bdd86be0aaf2df59295f1424d8c86d9f0eb11 - languageName: node - linkType: hard - "@agoric/vow@npm:0.1.1-dev-3b799b8.0+3b799b8": version: 0.1.1-dev-3b799b8.0 resolution: "@agoric/vow@npm:0.1.1-dev-3b799b8.0" @@ -1086,6 +1070,22 @@ __metadata: languageName: node linkType: hard +"@agoric/vow@npm:0.1.1-dev-c1ae023.0+c1ae023": + version: 0.1.1-dev-c1ae023.0 + resolution: "@agoric/vow@npm:0.1.1-dev-c1ae023.0" + dependencies: + "@agoric/base-zone": "npm:0.1.1-dev-c1ae023.0+c1ae023" + "@agoric/internal": "npm:0.3.3-dev-c1ae023.0+c1ae023" + "@endo/env-options": "npm:^1.1.8" + "@endo/errors": "npm:^1.2.8" + "@endo/eventual-send": "npm:^1.2.8" + "@endo/pass-style": "npm:^1.4.7" + "@endo/patterns": "npm:^1.4.7" + "@endo/promise-kit": "npm:^1.1.8" + checksum: 10c0/094266768c8ff75032ce96faae9163e7dfa4e9ada55ce88354ca6de4b993d28252bcb6637900793f4e3f7b16afe62ac2f2bcc8825613b5cf95701ca46d4b4066 + languageName: node + linkType: hard + "@agoric/vow@npm:0.1.1-dev-e596a01.0+e596a01": version: 0.1.1-dev-e596a01.0 resolution: "@agoric/vow@npm:0.1.1-dev-e596a01.0" @@ -1102,13 +1102,6 @@ __metadata: languageName: node linkType: hard -"@agoric/xsnap-lockdown@npm:0.14.1-dev-02c06c4.0+02c06c4": - version: 0.14.1-dev-02c06c4.0 - resolution: "@agoric/xsnap-lockdown@npm:0.14.1-dev-02c06c4.0" - checksum: 10c0/7b883c30a1ec8bc4fb727c5e442acc6ddd4af98067ce86f2f85c333ce8bafe525049a8b09668b4fec7671c368205095949d096df5c45be827a40e550863398a9 - languageName: node - linkType: hard - "@agoric/xsnap-lockdown@npm:0.14.1-dev-3b799b8.0+3b799b8": version: 0.14.1-dev-3b799b8.0 resolution: "@agoric/xsnap-lockdown@npm:0.14.1-dev-3b799b8.0" @@ -1116,6 +1109,13 @@ __metadata: languageName: node linkType: hard +"@agoric/xsnap-lockdown@npm:0.14.1-dev-c1ae023.0+c1ae023": + version: 0.14.1-dev-c1ae023.0 + resolution: "@agoric/xsnap-lockdown@npm:0.14.1-dev-c1ae023.0" + checksum: 10c0/65aefeb29497f8780849677ee81ba6f46f358d9f29ad5790541d5626ad8b08e34e27fa9063e24a4ddef5e2bf616e7aec16b6fff505a5bd3bbd0c87ceac110f49 + languageName: node + linkType: hard + "@agoric/xsnap-lockdown@npm:0.14.1-dev-e596a01.0+e596a01": version: 0.14.1-dev-e596a01.0 resolution: "@agoric/xsnap-lockdown@npm:0.14.1-dev-e596a01.0" @@ -1123,36 +1123,36 @@ __metadata: languageName: node linkType: hard -"@agoric/zoe@npm:0.26.3-dev-02c06c4.0+02c06c4": - version: 0.26.3-dev-02c06c4.0 - resolution: "@agoric/zoe@npm:0.26.3-dev-02c06c4.0" +"@agoric/zoe@npm:0.26.3-dev-c1ae023.0+c1ae023": + version: 0.26.3-dev-c1ae023.0 + resolution: "@agoric/zoe@npm:0.26.3-dev-c1ae023.0" dependencies: - "@agoric/base-zone": "npm:0.1.1-dev-02c06c4.0+02c06c4" - "@agoric/ertp": "npm:0.16.3-dev-02c06c4.0+02c06c4" - "@agoric/internal": "npm:0.3.3-dev-02c06c4.0+02c06c4" - "@agoric/notifier": "npm:0.6.3-dev-02c06c4.0+02c06c4" - "@agoric/store": "npm:0.9.3-dev-02c06c4.0+02c06c4" - "@agoric/swingset-liveslots": "npm:0.10.3-dev-02c06c4.0+02c06c4" - "@agoric/swingset-vat": "npm:0.32.3-dev-02c06c4.0+02c06c4" - "@agoric/time": "npm:0.3.3-dev-02c06c4.0+02c06c4" - "@agoric/vat-data": "npm:0.5.3-dev-02c06c4.0+02c06c4" - "@agoric/vow": "npm:0.1.1-dev-02c06c4.0+02c06c4" - "@agoric/zone": "npm:0.2.3-dev-02c06c4.0+02c06c4" - "@endo/bundle-source": "npm:^3.4.2" - "@endo/captp": "npm:^4.4.2" - "@endo/common": "npm:^1.2.7" - "@endo/errors": "npm:^1.2.7" - "@endo/eventual-send": "npm:^1.2.7" - "@endo/exo": "npm:^1.5.6" - "@endo/far": "npm:^1.1.8" - "@endo/import-bundle": "npm:^1.3.1" - "@endo/marshal": "npm:^1.6.1" - "@endo/nat": "npm:^5.0.12" - "@endo/pass-style": "npm:^1.4.6" - "@endo/patterns": "npm:^1.4.6" - "@endo/promise-kit": "npm:^1.1.7" + "@agoric/base-zone": "npm:0.1.1-dev-c1ae023.0+c1ae023" + "@agoric/ertp": "npm:0.16.3-dev-c1ae023.0+c1ae023" + "@agoric/internal": "npm:0.3.3-dev-c1ae023.0+c1ae023" + "@agoric/notifier": "npm:0.6.3-dev-c1ae023.0+c1ae023" + "@agoric/store": "npm:0.9.3-dev-c1ae023.0+c1ae023" + "@agoric/swingset-liveslots": "npm:0.10.3-dev-c1ae023.0+c1ae023" + "@agoric/swingset-vat": "npm:0.32.3-dev-c1ae023.0+c1ae023" + "@agoric/time": "npm:0.3.3-dev-c1ae023.0+c1ae023" + "@agoric/vat-data": "npm:0.5.3-dev-c1ae023.0+c1ae023" + "@agoric/vow": "npm:0.1.1-dev-c1ae023.0+c1ae023" + "@agoric/zone": "npm:0.2.3-dev-c1ae023.0+c1ae023" + "@endo/bundle-source": "npm:^3.5.0" + "@endo/captp": "npm:^4.4.3" + "@endo/common": "npm:^1.2.8" + "@endo/errors": "npm:^1.2.8" + "@endo/eventual-send": "npm:^1.2.8" + "@endo/exo": "npm:^1.5.7" + "@endo/far": "npm:^1.1.9" + "@endo/import-bundle": "npm:^1.3.2" + "@endo/marshal": "npm:^1.6.2" + "@endo/nat": "npm:^5.0.13" + "@endo/pass-style": "npm:^1.4.7" + "@endo/patterns": "npm:^1.4.7" + "@endo/promise-kit": "npm:^1.1.8" yargs-parser: "npm:^21.1.1" - checksum: 10c0/9aa34ecf57ea7882241206da18e88235152c05be6bf267eda2b51156ca2c48730b70be3429b5881e7fa0a02dfd97334da94ece9c82537277b0fd0b2f125c6e48 + checksum: 10c0/3b54c53506206d7406c0ffb309cb59d90a5cae13d0697320055127be163379369114a032b2af7c272e5646b9d9b65f1131c88750544ba859d5e1a11b618210b0 languageName: node linkType: hard @@ -1222,19 +1222,6 @@ __metadata: languageName: node linkType: hard -"@agoric/zone@npm:0.2.3-dev-02c06c4.0+02c06c4": - version: 0.2.3-dev-02c06c4.0 - resolution: "@agoric/zone@npm:0.2.3-dev-02c06c4.0" - dependencies: - "@agoric/base-zone": "npm:0.1.1-dev-02c06c4.0+02c06c4" - "@agoric/vat-data": "npm:0.5.3-dev-02c06c4.0+02c06c4" - "@endo/errors": "npm:^1.2.7" - "@endo/far": "npm:^1.1.8" - "@endo/pass-style": "npm:^1.4.6" - checksum: 10c0/f4cf6df5a81cba46762a3ebfb63c3f13633974205eb3b7051c845d0ac2adb2cac2b9b6ab100c64c8dc63eec8ad9c051f2ddc04153f4f07f1046a763869f589fe - languageName: node - linkType: hard - "@agoric/zone@npm:0.2.3-dev-3b799b8.0+3b799b8": version: 0.2.3-dev-3b799b8.0 resolution: "@agoric/zone@npm:0.2.3-dev-3b799b8.0" @@ -1248,6 +1235,19 @@ __metadata: languageName: node linkType: hard +"@agoric/zone@npm:0.2.3-dev-c1ae023.0+c1ae023": + version: 0.2.3-dev-c1ae023.0 + resolution: "@agoric/zone@npm:0.2.3-dev-c1ae023.0" + dependencies: + "@agoric/base-zone": "npm:0.1.1-dev-c1ae023.0+c1ae023" + "@agoric/vat-data": "npm:0.5.3-dev-c1ae023.0+c1ae023" + "@endo/errors": "npm:^1.2.8" + "@endo/far": "npm:^1.1.9" + "@endo/pass-style": "npm:^1.4.7" + checksum: 10c0/292be1e4198ecdbc87a300efc1548190d26698c6c541dd8e0f6794dc0407b9e3bfc013f8dadd6788e995327044ce8f8e4181ff01b851b77562224aa273d066bf + languageName: node + linkType: hard + "@agoric/zone@npm:0.2.3-dev-e596a01.0+e596a01": version: 0.2.3-dev-e596a01.0 resolution: "@agoric/zone@npm:0.2.3-dev-e596a01.0" @@ -1749,7 +1749,7 @@ __metadata: languageName: node linkType: hard -"@endo/lockdown@npm:^1.0.12, @endo/lockdown@npm:^1.0.13": +"@endo/lockdown@npm:^1.0.13": version: 1.0.13 resolution: "@endo/lockdown@npm:1.0.13" dependencies: @@ -6349,7 +6349,7 @@ __metadata: version: 0.0.0-use.local resolution: "root-workspace-0b6124@workspace:." dependencies: - "@agoric/client-utils": "npm:0.1.1-dev-02c06c4.0" + "@agoric/client-utils": "npm:dev" "@agoric/ertp": "npm:dev" "@agoric/internal": "npm:dev" "@agoric/synthetic-chain": "npm:^0.4.3" diff --git a/a3p-integration/proposals/s:stake-bld/package.json b/a3p-integration/proposals/s:stake-bld/package.json index 2c0a5093c38..2432417370d 100644 --- a/a3p-integration/proposals/s:stake-bld/package.json +++ b/a3p-integration/proposals/s:stake-bld/package.json @@ -26,7 +26,8 @@ }, "packageManager": "yarn@4.5.3", "devDependencies": { - "@types/node": "^22.0.0" + "@types/node": "^22.0.0", + "typescript": "^5.7.2" }, "resolutions": { "@agoric/cosmos": "portal:../../agoric-sdk/golang/cosmos", diff --git a/a3p-integration/proposals/s:stake-bld/stakeBld.test.js b/a3p-integration/proposals/s:stake-bld/stakeBld.test.js index 549e4562545..13836f6cf3d 100644 --- a/a3p-integration/proposals/s:stake-bld/stakeBld.test.js +++ b/a3p-integration/proposals/s:stake-bld/stakeBld.test.js @@ -8,7 +8,7 @@ import { GOV1ADDR } from '@agoric/synthetic-chain'; import { Tendermint34Client } from '@cosmjs/tendermint-rpc'; import assert from 'node:assert'; import process from 'node:process'; -import { networkConfig, walletUtils } from './test-lib/index.js'; +import { networkConfig, agdWalletUtils } from './test-lib/index.js'; // XXX not the same as VALIDATOR_ADDRESS, which is actually the delegator const VALIDATOR_ADDRESS = process.env.VALIDATOR_ADDRESS; @@ -26,14 +26,15 @@ const currentDelegation = async () => { test('basic', async t => { assert(GOV1ADDR); - const { brand } = walletUtils.agoricNames; + const { brand } = agdWalletUtils.agoricNames; t.is((await currentDelegation()).length, 1, 'just the initial delegation'); /** @type {import('@agoric/ertp').Brand} */ + // @ts-expect-error actually a BoardRemote const BLDBrand = brand.BLD; - await walletUtils.broadcastBridgeAction(GOV1ADDR, { + await agdWalletUtils.broadcastBridgeAction(GOV1ADDR, { method: 'executeOffer', offer: { id: 'request-stake', @@ -50,7 +51,7 @@ test('basic', async t => { }, }); - await walletUtils.broadcastBridgeAction(GOV1ADDR, { + await agdWalletUtils.broadcastBridgeAction(GOV1ADDR, { method: 'executeOffer', offer: { id: 'request-delegate-6', @@ -75,7 +76,7 @@ test('basic', async t => { // omit 'delegation' because it has 'delegatorAddress' which is different every test run }); - await walletUtils.broadcastBridgeAction(GOV1ADDR, { + await agdWalletUtils.broadcastBridgeAction(GOV1ADDR, { method: 'executeOffer', offer: { id: 'request-undelegate', diff --git a/a3p-integration/proposals/s:stake-bld/test-lib/index.js b/a3p-integration/proposals/s:stake-bld/test-lib/index.js index 9c22b218e19..b56da4836cc 100644 --- a/a3p-integration/proposals/s:stake-bld/test-lib/index.js +++ b/a3p-integration/proposals/s:stake-bld/test-lib/index.js @@ -1,9 +1,9 @@ /* eslint-env node */ +import { makeSmartWalletKit, LOCAL_CONFIG } from '@agoric/client-utils'; import { execFileSync } from 'child_process'; -import { LOCAL_CONFIG as networkConfig } from '@agoric/client-utils'; -import { makeWalletUtils } from './wallet.js'; +import { makeAgdWalletKit } from './wallet.js'; -export { networkConfig }; +export const networkConfig = LOCAL_CONFIG; /** * Resolve after a delay in milliseconds. @@ -13,7 +13,12 @@ export { networkConfig }; */ const delay = ms => new Promise(resolve => setTimeout(() => resolve(), ms)); -export const walletUtils = await makeWalletUtils( - { execFileSync, delay, fetch }, +export const smartWalletKit = await makeSmartWalletKit( + { delay, fetch }, + networkConfig, +); + +export const agdWalletUtils = await makeAgdWalletKit( + { execFileSync, smartWalletKit, delay }, networkConfig, ); diff --git a/a3p-integration/proposals/s:stake-bld/test-lib/wallet.js b/a3p-integration/proposals/s:stake-bld/test-lib/wallet.js index e3ef23af929..fa8e8ca5112 100644 --- a/a3p-integration/proposals/s:stake-bld/test-lib/wallet.js +++ b/a3p-integration/proposals/s:stake-bld/test-lib/wallet.js @@ -1,16 +1,21 @@ // @ts-check -import { makeVstorageKit } from '@agoric/client-utils'; import { sendAction } from 'agoric/src/lib/index.js'; import { inspect } from 'util'; -export const makeWalletUtils = async ( - { delay, execFileSync, fetch }, +/** + * Stop-gap using execFileSync until we have a pure JS signing client. + * + * @param {object} root0 + * @param {import('child_process')['execFileSync']} root0.execFileSync + * @param {import('@agoric/client-utils').SmartWalletKit} root0.smartWalletKit + * @param {any} root0.delay + * @param {import('@agoric/client-utils').MinimalNetworkConfig} networkConfig + */ +export const makeAgdWalletKit = async ( + { execFileSync, smartWalletKit, delay }, networkConfig, ) => { - const { agoricNames, fromBoard, marshaller, readLatestHead, vstorage } = - await makeVstorageKit({ fetch }, networkConfig); - /** * * @param {string} from @@ -23,17 +28,12 @@ export const makeWalletUtils = async ( delay, execFileSync, from, - marshaller, keyring: { backend: 'test' }, }); }; return { - agoricNames, + ...smartWalletKit, broadcastBridgeAction, - fromBoard, - networkConfig, - readLatestHead, - vstorage, }; }; diff --git a/a3p-integration/proposals/s:stake-bld/yarn.lock b/a3p-integration/proposals/s:stake-bld/yarn.lock index a406cffd13a..cad8d9ce435 100644 --- a/a3p-integration/proposals/s:stake-bld/yarn.lock +++ b/a3p-integration/proposals/s:stake-bld/yarn.lock @@ -4617,6 +4617,7 @@ __metadata: agoric: "npm:dev" ava: "npm:^5.3.1" execa: "npm:^8.0.1" + typescript: "npm:^5.7.2" languageName: unknown linkType: soft @@ -5189,6 +5190,16 @@ __metadata: languageName: node linkType: hard +"typescript@npm:^5.7.2": + version: 5.7.2 + resolution: "typescript@npm:5.7.2" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/a873118b5201b2ef332127ef5c63fb9d9c155e6fdbe211cbd9d8e65877283797cca76546bad742eea36ed7efbe3424a30376818f79c7318512064e8625d61622 + languageName: node + linkType: hard + "typescript@patch:typescript@npm%3A5.1.6 - 5.6.x#optional!builtin": version: 5.6.3 resolution: "typescript@patch:typescript@npm%3A5.6.3#optional!builtin::version=5.6.3&hash=8c6c40" @@ -5199,6 +5210,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@npm%3A^5.7.2#optional!builtin": + version: 5.7.2 + resolution: "typescript@patch:typescript@npm%3A5.7.2#optional!builtin::version=5.7.2&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/f3b8082c9d1d1629a215245c9087df56cb784f9fb6f27b5d55577a20e68afe2a889c040aacff6d27e35be165ecf9dca66e694c42eb9a50b3b2c451b36b5675cb + languageName: node + linkType: hard + "undici-types@npm:~5.26.4": version: 5.26.5 resolution: "undici-types@npm:5.26.5" diff --git a/a3p-integration/proposals/z:acceptance/.gitignore b/a3p-integration/proposals/z:acceptance/.gitignore index 3d143254692..7f5da265d56 100644 --- a/a3p-integration/proposals/z:acceptance/.gitignore +++ b/a3p-integration/proposals/z:acceptance/.gitignore @@ -2,3 +2,4 @@ restart-valueVow start-valueVow localchaintest-submission +recorded-instances-submission diff --git a/a3p-integration/proposals/z:acceptance/package.json b/a3p-integration/proposals/z:acceptance/package.json index 8a906d701b8..fd4b89562ae 100644 --- a/a3p-integration/proposals/z:acceptance/package.json +++ b/a3p-integration/proposals/z:acceptance/package.json @@ -3,6 +3,7 @@ "type": "/agoric.swingset.CoreEvalProposal", "sdk-generate": [ "testing/start-valueVow.js start-valueVow", + "testing/recorded-retired-instances.js recorded-instances-submission", "vats/test-localchain.js localchaintest-submission", "testing/restart-valueVow.js restart-valueVow" ] diff --git a/a3p-integration/proposals/z:acceptance/psm.test.js b/a3p-integration/proposals/z:acceptance/psm.test.js index 8496cc1d3b3..ae176ec51c8 100644 --- a/a3p-integration/proposals/z:acceptance/psm.test.js +++ b/a3p-integration/proposals/z:acceptance/psm.test.js @@ -21,7 +21,7 @@ import { } from '@agoric/synthetic-chain'; import { waitUntilAccountFunded } from '@agoric/client-utils'; import test from 'ava'; -import { NonNullish } from './test-lib/errors.js'; +import { NonNullish } from '@agoric/internal/src/errors.js'; import { adjustBalancesIfNotProvisioned, bankSend, diff --git a/a3p-integration/proposals/z:acceptance/recorded-retired.test.js b/a3p-integration/proposals/z:acceptance/recorded-retired.test.js new file mode 100644 index 00000000000..a5a00d01107 --- /dev/null +++ b/a3p-integration/proposals/z:acceptance/recorded-retired.test.js @@ -0,0 +1,11 @@ +import test from 'ava'; + +import { evalBundles } from '@agoric/synthetic-chain'; + +const SUBMISSION_DIR = 'recorded-instances-submission'; + +test(`recorded instances in u18`, async t => { + const result = await evalBundles(SUBMISSION_DIR); + console.log('recorded retired instance result:', result); + t.pass('checked names'); +}); diff --git a/a3p-integration/proposals/z:acceptance/test-lib/chain.js b/a3p-integration/proposals/z:acceptance/test-lib/chain.js deleted file mode 100644 index 74cc0d3c14f..00000000000 --- a/a3p-integration/proposals/z:acceptance/test-lib/chain.js +++ /dev/null @@ -1,140 +0,0 @@ -/** @file copied from packages/agoric-cli */ -// TODO DRY in https://github.com/Agoric/agoric-sdk/issues/9109 -// @ts-check -/* global process */ - -const agdBinary = 'agd'; - -/** - * @param {ReadonlyArray} swingsetArgs - * @param {import('./rpc.js').MinimalNetworkConfig & { - * from: string, - * fees?: string, - * dryRun?: boolean, - * verbose?: boolean, - * keyring?: {home?: string, backend: string} - * stdout?: Pick - * execFileSync: typeof import('child_process').execFileSync - * }} opts - */ -export const execSwingsetTransaction = (swingsetArgs, opts) => { - const { - from, - fees, - dryRun = false, - verbose = true, - keyring = undefined, - chainName, - rpcAddrs, - stdout = process.stdout, - execFileSync, - } = opts; - const homeOpt = keyring?.home ? [`--home=${keyring.home}`] : []; - const backendOpt = keyring?.backend - ? [`--keyring-backend=${keyring.backend}`] - : []; - const feeOpt = fees ? ['--fees', fees] : []; - const cmd = [`--node=${rpcAddrs[0]}`, `--chain-id=${chainName}`].concat( - homeOpt, - backendOpt, - feeOpt, - [`--from=${from}`, 'tx', 'swingset'], - swingsetArgs, - ); - - if (dryRun) { - stdout.write(`Run this interactive command in shell:\n\n`); - stdout.write(`${agdBinary} `); - stdout.write(cmd.join(' ')); - stdout.write('\n'); - } else { - const yesCmd = cmd.concat(['--yes']); - if (verbose) console.log('Executing ', agdBinary, yesCmd); - const out = execFileSync(agdBinary, yesCmd, { encoding: 'utf-8' }); - - // agd puts this diagnostic on stdout rather than stderr :-/ - // "Default sign-mode 'direct' not supported by Ledger, using sign-mode 'amino-json'. - if (out.startsWith('Default sign-mode')) { - const stripDiagnostic = out.replace(/^Default[^\n]+\n/, ''); - return stripDiagnostic; - } - return out; - } -}; -harden(execSwingsetTransaction); - -/** - * @param {import('./rpc.js').MinimalNetworkConfig & { - * execFileSync: typeof import('child_process').execFileSync, - * delay: (ms: number) => Promise, - * period?: number, - * retryMessage?: string, - * }} opts - * @returns {(l: (b: { time: string, height: string }) => Promise) => Promise} - */ -export const pollBlocks = opts => async lookup => { - const { execFileSync, delay, rpcAddrs, period = 3 * 1000 } = opts; - assert(execFileSync, 'missing execFileSync'); - const { retryMessage } = opts; - - const nodeArgs = [`--node=${rpcAddrs[0]}`]; - - await null; // separate sync prologue - - for (;;) { - const sTxt = execFileSync(agdBinary, ['status', ...nodeArgs]); - const status = JSON.parse(sTxt.toString()); - const { - SyncInfo: { latest_block_time: time, latest_block_height: height }, - } = status; - try { - // see await null above - const result = await lookup({ time, height }); - return result; - } catch (_err) { - console.error( - time, - retryMessage || 'not in block', - height, - 'retrying...', - ); - await delay(period); - } - } -}; - -/** - * @param {string} txhash - * @param {import('./rpc.js').MinimalNetworkConfig & { - * execFileSync: typeof import('child_process').execFileSync, - * delay: (ms: number) => Promise, - * period?: number, - * }} opts - */ -export const pollTx = async (txhash, opts) => { - const { execFileSync, rpcAddrs, chainName } = opts; - assert(execFileSync, 'missing execFileSync in pollTx'); - - const nodeArgs = [`--node=${rpcAddrs[0]}`]; - const outJson = ['--output', 'json']; - - const lookup = async () => { - const out = execFileSync( - agdBinary, - [ - 'query', - 'tx', - txhash, - `--chain-id=${chainName}`, - ...nodeArgs, - ...outJson, - ], - { stdio: ['ignore', 'pipe', 'ignore'] }, - ); - // XXX this type is defined in a .proto file somewhere - /** @type {{ height: string, txhash: string, code: number, timestamp: string }} */ - const info = JSON.parse(out.toString()); - return info; - }; - return pollBlocks({ ...opts, retryMessage: 'tx not in block' })(lookup); -}; diff --git a/a3p-integration/proposals/z:acceptance/test-lib/errors.js b/a3p-integration/proposals/z:acceptance/test-lib/errors.js deleted file mode 100644 index 57dc771e6a5..00000000000 --- a/a3p-integration/proposals/z:acceptance/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/z:acceptance/test-lib/index.js b/a3p-integration/proposals/z:acceptance/test-lib/index.js index 479d81503fc..b56da4836cc 100644 --- a/a3p-integration/proposals/z:acceptance/test-lib/index.js +++ b/a3p-integration/proposals/z:acceptance/test-lib/index.js @@ -1,12 +1,9 @@ /* eslint-env node */ -import { makeWalletUtils } from '@agoric/client-utils'; +import { makeSmartWalletKit, LOCAL_CONFIG } from '@agoric/client-utils'; import { execFileSync } from 'child_process'; -import { makeAgdWalletUtils } from './wallet.js'; +import { makeAgdWalletKit } from './wallet.js'; -export const networkConfig = { - rpcAddrs: ['http://0.0.0.0:26657'], - chainName: 'agoriclocal', -}; +export const networkConfig = LOCAL_CONFIG; /** * Resolve after a delay in milliseconds. @@ -16,12 +13,12 @@ export const networkConfig = { */ const delay = ms => new Promise(resolve => setTimeout(() => resolve(), ms)); -export const walletUtils = await makeWalletUtils( +export const smartWalletKit = await makeSmartWalletKit( { delay, fetch }, networkConfig, ); -export const agdWalletUtils = await makeAgdWalletUtils( - { execFileSync, setTimeout, walletUtils }, +export const agdWalletUtils = await makeAgdWalletKit( + { execFileSync, smartWalletKit, delay }, networkConfig, ); diff --git a/a3p-integration/proposals/z:acceptance/test-lib/kread.js b/a3p-integration/proposals/z:acceptance/test-lib/kread.js index 01cd0d29b5f..1bb58b3eef1 100644 --- a/a3p-integration/proposals/z:acceptance/test-lib/kread.js +++ b/a3p-integration/proposals/z:acceptance/test-lib/kread.js @@ -1,4 +1,5 @@ // @ts-nocheck FIXME +// XXX uses agoric.follow to read data through spawned processes; replace with VstorageKit import assert from 'node:assert'; import { diff --git a/a3p-integration/proposals/z:acceptance/test-lib/psm-lib.js b/a3p-integration/proposals/z:acceptance/test-lib/psm-lib.js index 7b5beceb1d1..74763e6f401 100644 --- a/a3p-integration/proposals/z:acceptance/test-lib/psm-lib.js +++ b/a3p-integration/proposals/z:acceptance/test-lib/psm-lib.js @@ -26,7 +26,7 @@ import { VALIDATORADDR, } from '@agoric/synthetic-chain'; import fsp from 'node:fs/promises'; -import { NonNullish } from './errors.js'; +import { NonNullish } from '@agoric/internal/src/errors.js'; import { getBalances } from './utils.js'; /** @import {Result as ExecaResult, ExecaError} from 'execa'; */ diff --git a/a3p-integration/proposals/z:acceptance/test-lib/rpc.js b/a3p-integration/proposals/z:acceptance/test-lib/rpc.js deleted file mode 100644 index 2a25b534911..00000000000 --- a/a3p-integration/proposals/z:acceptance/test-lib/rpc.js +++ /dev/null @@ -1,272 +0,0 @@ -/** @file copied from packages/agoric-cli */ -// TODO DRY in https://github.com/Agoric/agoric-sdk/issues/9109 -// @ts-check -/* global Buffer */ - -import { - boardSlottingMarshaller, - makeBoardRemote, -} from '@agoric/internal/src/marshal.js'; -import { Fail } from '@endo/errors'; - -export { boardSlottingMarshaller }; - -/** @type {(val: any) => string} */ -export const boardValToSlot = val => { - if ('getBoardId' in val) { - return val.getBoardId(); - } - throw Fail`unknown obj in boardSlottingMarshaller.valToSlot ${val}`; -}; - -/** @param {string} agoricNetSubdomain */ -export const networkConfigUrl = agoricNetSubdomain => - `https://${agoricNetSubdomain}.agoric.net/network-config`; -/** @param {string} agoricNetSubdomain */ -export const rpcUrl = agoricNetSubdomain => - `https://${agoricNetSubdomain}.rpc.agoric.net:443`; - -/** - * @typedef {{ rpcAddrs: string[], chainName: string }} MinimalNetworkConfig - */ - -/** @type {MinimalNetworkConfig} */ -const networkConfig = { - rpcAddrs: ['http://0.0.0.0:26657'], - chainName: 'agoriclocal', -}; -export { networkConfig }; -// console.warn('networkConfig', networkConfig); - -/** - * @param {object} powers - * @param {typeof window.fetch} powers.fetch - * @param {MinimalNetworkConfig} config - */ -export const makeVStorage = ({ fetch }, config = networkConfig) => { - /** @param {string} path */ - const getJSON = path => { - const url = config.rpcAddrs[0] + path; - // console.warn('fetching', url); - return fetch(url, { keepalive: true }).then(res => res.json()); - }; - // height=0 is the same as omitting height and implies the highest block - const url = (path = 'published', { kind = 'children', height = 0 } = {}) => - `/abci_query?path=%22/custom/vstorage/${kind}/${path}%22&height=${height}`; - - const readStorage = (path = 'published', { kind = 'children', height = 0 }) => - getJSON(url(path, { kind, height })) - .catch(err => { - throw Error(`cannot read ${kind} of ${path}: ${err.message}`); - }) - .then(data => { - const { - result: { response }, - } = data; - if (response?.code !== 0) { - /** @type {any} */ - const err = Error( - `error code ${response?.code} reading ${kind} of ${path}: ${response.log}`, - ); - err.code = response?.code; - err.codespace = response?.codespace; - throw err; - } - return data; - }); - - return { - url, - /** @param {{ result: { response: { code: number, value: string } } }} rawResponse */ - decode({ result: { response } }) { - const { code } = response; - if (code !== 0) { - throw response; - } - const { value } = response; - return Buffer.from(value, 'base64').toString(); - }, - /** - * - * @param {string} path - * @returns {Promise} latest vstorage value at path - */ - async readLatest(path = 'published') { - const raw = await readStorage(path, { kind: 'data' }); - return this.decode(raw); - }, - async keys(path = 'published') { - const raw = await readStorage(path, { kind: 'children' }); - return JSON.parse(this.decode(raw)).children; - }, - /** - * @param {string} path - * @param {number} [height] default is highest - * @returns {Promise<{blockHeight: number, values: string[]}>} - */ - async readAt(path, height = undefined) { - const raw = await readStorage(path, { kind: 'data', height }); - const txt = this.decode(raw); - /** @type {{ value: string }} */ - const { value } = JSON.parse(txt); - return JSON.parse(value); - }, - /** - * Read values going back as far as available - * - * @param {string} path - * @param {number | string} [minHeight] - * @returns {Promise} - */ - async readFully(path, minHeight = undefined) { - const parts = []; - // undefined the first iteration, to query at the highest - let blockHeight; - await null; - do { - // console.debug('READING', { blockHeight }); - let values; - try { - ({ blockHeight, values } = await this.readAt( - path, - blockHeight && Number(blockHeight) - 1, - )); - // console.debug('readAt returned', { blockHeight }); - } catch (err) { - if ( - // CosmosSDK ErrNotFound; there is no data at the path - (err.codespace === 'sdk' && err.code === 38) || - // CosmosSDK ErrUnknownRequest; misrepresentation of the same until - // https://github.com/Agoric/agoric-sdk/commit/dafc7c1708977aaa55e245dc09a73859cf1df192 - // TODO remove after upgrade-12 - err.message.match(/unknown request/) - ) { - // console.error(err); - break; - } - throw err; - } - parts.push(values); - // console.debug('PUSHED', values); - // console.debug('NEW', { blockHeight, minHeight }); - if (minHeight && Number(blockHeight) <= Number(minHeight)) break; - } while (blockHeight > 0); - return parts.flat(); - }, - }; -}; -/** @typedef {ReturnType} VStorage */ - -export const makeFromBoard = () => { - const cache = new Map(); - /** @type {(boardId: string, iface?: string) => ReturnType} */ - const convertSlotToVal = (boardId, iface) => { - if (cache.has(boardId)) { - return cache.get(boardId); - } - const val = makeBoardRemote({ boardId, iface }); - cache.set(boardId, val); - return val; - }; - return harden({ convertSlotToVal }); -}; -/** @typedef {ReturnType} IdMap */ - -export const storageHelper = { - /** @param { string } txt */ - parseCapData: txt => { - assert(typeof txt === 'string', typeof txt); - /** @type {{ value: string }} */ - const { value } = JSON.parse(txt); - const specimen = JSON.parse(value); - const { blockHeight, values } = specimen; - assert(values, `empty values in specimen ${value}`); - const capDatas = storageHelper.parseMany(values); - return { blockHeight, capDatas }; - }, - /** - * @param {string} txt - * @param {IdMap} ctx - */ - unserializeTxt: (txt, ctx) => { - const { capDatas } = storageHelper.parseCapData(txt); - return capDatas.map(capData => - boardSlottingMarshaller(ctx.convertSlotToVal).fromCapData(capData), - ); - }, - /** @param {string[]} capDataStrings array of stringified capData */ - parseMany: capDataStrings => { - assert(capDataStrings && capDataStrings.length); - /** @type {{ body: string, slots: string[] }[]} */ - const capDatas = capDataStrings.map(s => JSON.parse(s)); - for (const capData of capDatas) { - assert(typeof capData === 'object' && capData !== null); - assert('body' in capData && 'slots' in capData); - assert(typeof capData.body === 'string'); - assert(Array.isArray(capData.slots)); - } - return capDatas; - }, -}; -harden(storageHelper); - -/** - * @param {IdMap} ctx - * @param {VStorage} vstorage - * @returns {Promise} - */ -export const makeAgoricNames = async (ctx, vstorage) => { - /** @type {Record} */ - const reverse = {}; - const entries = await Promise.all( - ['brand', 'instance', 'vbankAsset'].map(async kind => { - const content = await vstorage.readLatest( - `published.agoricNames.${kind}`, - ); - /** @type {Array<[string, import('@agoric/vats/tools/board-utils.js').BoardRemote]>} */ - const parts = storageHelper.unserializeTxt(content, ctx).at(-1); - for (const [name, remote] of parts) { - if ('getBoardId' in remote) { - reverse[/** @type {string} */ (remote.getBoardId())] = name; - } - } - return [kind, Object.fromEntries(parts)]; - }), - ); - return { ...Object.fromEntries(entries), reverse }; -}; - -/** - * @param {{ fetch: typeof window.fetch }} io - * @param {MinimalNetworkConfig} config - */ -export const makeVstorageKit = async ({ fetch }, config = networkConfig) => { - await null; - try { - const vstorage = makeVStorage({ fetch }, config); - const fromBoard = makeFromBoard(); - const agoricNames = await makeAgoricNames(fromBoard, vstorage); - - const marshaller = boardSlottingMarshaller(fromBoard.convertSlotToVal); - - /** @type {(txt: string) => unknown} */ - const unserializeHead = txt => - storageHelper.unserializeTxt(txt, fromBoard).at(-1); - - /** @type {(path: string) => Promise} */ - const readLatestHead = path => - vstorage.readLatest(path).then(unserializeHead); - - return { - agoricNames, - fromBoard, - marshaller, - readLatestHead, - unserializeHead, - vstorage, - }; - } catch (err) { - throw Error(`RPC failure (${config.rpcAddrs}): ${err.message}`); - } -}; -/** @typedef {Awaited>} RpcUtils */ diff --git a/a3p-integration/proposals/z:acceptance/test-lib/utils.js b/a3p-integration/proposals/z:acceptance/test-lib/utils.js index 330fb799c6d..0826daa3660 100644 --- a/a3p-integration/proposals/z:acceptance/test-lib/utils.js +++ b/a3p-integration/proposals/z:acceptance/test-lib/utils.js @@ -1,10 +1,13 @@ /* eslint-env node */ -import { makeStargateClient, makeVstorageKit } from '@agoric/client-utils'; +import { + LOCAL_CONFIG, + makeStargateClient, + makeVstorageKit, +} from '@agoric/client-utils'; import { readFile, writeFile } from 'node:fs/promises'; -import { networkConfig } from './rpc.js'; -export const stargateClientP = makeStargateClient(networkConfig, { fetch }); -export const vstorageKitP = makeVstorageKit({ fetch }, networkConfig); +export const stargateClientP = makeStargateClient(LOCAL_CONFIG, { fetch }); +export const vstorageKit = makeVstorageKit({ fetch }, LOCAL_CONFIG); /** * @import {WalletUtils} from '@agoric/client-utils'; diff --git a/a3p-integration/proposals/z:acceptance/test-lib/vaults.js b/a3p-integration/proposals/z:acceptance/test-lib/vaults.js index 88a07564ca9..1dabb741e95 100644 --- a/a3p-integration/proposals/z:acceptance/test-lib/vaults.js +++ b/a3p-integration/proposals/z:acceptance/test-lib/vaults.js @@ -1,10 +1,6 @@ /* eslint-env node */ -import { - boardSlottingMarshaller, - makeFromBoard, - retryUntilCondition, -} from '@agoric/client-utils'; +import { makeAgoricNames, retryUntilCondition } from '@agoric/client-utils'; import { AmountMath } from '@agoric/ertp'; import { agops, @@ -18,19 +14,15 @@ import { ceilMultiplyBy, makeRatio, } from '@agoric/zoe/src/contractSupport/ratio.js'; -import { E } from '@endo/far'; -import { walletUtils } from './index.js'; -import { listVaults, vstorageKitP } from './utils.js'; - -const fromBoard = makeFromBoard(); -const marshaller = boardSlottingMarshaller(fromBoard.convertSlotToVal); +import { smartWalletKit } from './index.js'; +import { listVaults, vstorageKit } from './utils.js'; /** * @param {string} address * @returns {Promise<{ vaultID: string, debt: bigint, collateral: bigint, state: string }>} */ export const getLastVaultFromAddress = async address => { - const activeVaults = await listVaults(address, walletUtils); + const activeVaults = await listVaults(address, smartWalletKit); const vaultPath = activeVaults[activeVaults.length - 1]; const vaultID = vaultPath.split('.').pop(); @@ -97,7 +89,10 @@ export const getMinInitialDebt = async () => { * @returns {Promise<{ mintFee: import('@agoric/ertp/src/types.js').NatAmount, adjustedToMintAmount: import('@agoric/ertp/src/types.js').NatAmount }>} */ export const calculateMintFee = async (toMintValue, vaultManager) => { - const { brand } = await E.get(vstorageKitP).agoricNames; + const { brand } = await makeAgoricNames( + vstorageKit.fromBoard, + vstorageKit.vstorage, + ); /** @type {import('@agoric/ertp').Brand} */ // @ts-expect-error let this BoardRemote masquerade as a Brand const ISTBrand = brand.IST; @@ -157,11 +152,16 @@ const paramChangeOfferGeneration = async ( ) => { const ISTunit = 1_000_000n; // aka displayInfo: { decimalPlaces: 6 } - const { brand } = await E.get(vstorageKitP).agoricNames; + const agoricNames = await makeAgoricNames( + vstorageKit.fromBoard, + vstorageKit.vstorage, + ); + + const { brand } = agoricNames; assert(brand.IST); assert(brand.ATOM); - const { instance } = await E.get(vstorageKitP).agoricNames; + const { instance } = agoricNames; assert(instance.VaultFactory); const voteDurSec = BigInt(voteDur); @@ -203,7 +203,7 @@ const paramChangeOfferGeneration = async ( }; // @ts-expect-error tolerate BoardRemote instances with getBoardId methods - return JSON.stringify(marshaller.toCapData(harden(body))); + return JSON.stringify(vstorageKit.marshaller.toCapData(harden(body))); }; /** diff --git a/a3p-integration/proposals/z:acceptance/test-lib/wallet.js b/a3p-integration/proposals/z:acceptance/test-lib/wallet.js index 11813654bbc..fa8e8ca5112 100644 --- a/a3p-integration/proposals/z:acceptance/test-lib/wallet.js +++ b/a3p-integration/proposals/z:acceptance/test-lib/wallet.js @@ -1,74 +1,21 @@ -// TODO DRY in https://github.com/Agoric/agoric-sdk/issues/9109 // @ts-check -/* global */ +import { sendAction } from 'agoric/src/lib/index.js'; import { inspect } from 'util'; -import { execSwingsetTransaction, pollTx } from './chain.js'; -import { makeTimerUtils } from './utils.js'; - -/** - * Sign and broadcast a wallet-action. - * - * @throws { Error & { code: number } } if transaction fails - * @param {import('@agoric/smart-wallet/src/smartWallet.js').BridgeAction} bridgeAction - * @param {import('./rpc.js').MinimalNetworkConfig & { - * from: string, - * marshaller: Pick, 'toCapData'>, - * fees?: string, - * verbose?: boolean, - * keyring?: {home?: string, backend: string}, - * stdout?: Pick, - * execFileSync: typeof import('child_process').execFileSync, - * delay: (ms: number) => Promise, - * dryRun?: boolean, - * }} opts - */ -export const sendAction = async (bridgeAction, opts) => { - const { marshaller } = opts; - // @ts-expect-error BridgeAction has methods disallowed by Passable - const offerBody = JSON.stringify(marshaller.toCapData(harden(bridgeAction))); - - // tryExit should not require --allow-spend - // https://github.com/Agoric/agoric-sdk/issues/7291 - const spendMethods = ['executeOffer', 'tryExitOffer']; - const spendArg = spendMethods.includes(bridgeAction.method) - ? ['--allow-spend'] - : []; - - const act = ['wallet-action', ...spendArg, offerBody]; - const out = execSwingsetTransaction([...act, '--output', 'json'], opts); - if (opts.dryRun) { - return; - } - - assert(out); // not dry run - const tx = JSON.parse(out); - if (tx.code !== 0) { - const err = Error(`failed to send tx: ${tx.raw_log} code: ${tx.code}`); - // @ts-expect-error XXX how to add properties to an error? - err.code = tx.code; - throw err; - } - - return pollTx(tx.txhash, opts); -}; /** * Stop-gap using execFileSync until we have a pure JS signing client. * * @param {object} root0 - * @param {import('@agoric/client-utils').WalletUtils} root0.walletUtils * @param {import('child_process')['execFileSync']} root0.execFileSync - * @param {typeof setTimeout} root0.setTimeout + * @param {import('@agoric/client-utils').SmartWalletKit} root0.smartWalletKit + * @param {any} root0.delay * @param {import('@agoric/client-utils').MinimalNetworkConfig} networkConfig */ -export const makeAgdWalletUtils = async ( - { execFileSync, walletUtils, setTimeout }, +export const makeAgdWalletKit = async ( + { execFileSync, smartWalletKit, delay }, networkConfig, ) => { - const { marshaller } = walletUtils; - - const { delay } = await makeTimerUtils({ setTimeout }); /** * * @param {string} from @@ -81,13 +28,12 @@ export const makeAgdWalletUtils = async ( delay, execFileSync, from, - // @ts-expect-error version skew in @endo/marshal and/or @endo/pass-style - marshaller, keyring: { backend: 'test' }, }); }; return { + ...smartWalletKit, broadcastBridgeAction, }; }; diff --git a/a3p-integration/proposals/z:acceptance/test.sh b/a3p-integration/proposals/z:acceptance/test.sh index a029b8d6e8a..e45cced45d1 100755 --- a/a3p-integration/proposals/z:acceptance/test.sh +++ b/a3p-integration/proposals/z:acceptance/test.sh @@ -12,6 +12,9 @@ yarn ava core-eval.test.js scripts/test-vaults.ts +echo ACCEPTANCE TESTING recorded instances +yarn ava recorded-retired.test.js + echo ACCEPTANCE TESTING kread yarn ava kread.test.js diff --git a/a3p-integration/proposals/z:acceptance/valueVow.test.js b/a3p-integration/proposals/z:acceptance/valueVow.test.js index 7e62e16420d..8490351eb58 100644 --- a/a3p-integration/proposals/z:acceptance/valueVow.test.js +++ b/a3p-integration/proposals/z:acceptance/valueVow.test.js @@ -10,7 +10,7 @@ import { GOV1ADDR as GETTER, // not particular to governance, just a handy wallet GOV2ADDR as SETTER, } from '@agoric/synthetic-chain'; -import { agdWalletUtils, walletUtils } from './test-lib/index.js'; +import { agdWalletUtils } from './test-lib/index.js'; const START_VALUEVOW_DIR = 'start-valueVow'; const RESTART_VALUEVOW_DIR = 'restart-valueVow'; @@ -37,7 +37,7 @@ test('vow survives restart', async t => { t.log('confirm the value is not in offer results'); let getterStatus = await retryUntilCondition( /** @type {() => Promise} */ - async () => walletUtils.readLatestHead(`published.wallet.${GETTER}`), + async () => agdWalletUtils.readLatestHead(`published.wallet.${GETTER}`), value => value.status.id === 'get-value' && value.updated === 'offerStatus', 'Offer get-value not succeeded', { @@ -79,7 +79,9 @@ test('vow survives restart', async t => { }); t.log('confirm the value is now in offer results'); - getterStatus = await walletUtils.readLatestHead(`published.wallet.${GETTER}`); + getterStatus = await agdWalletUtils.readLatestHead( + `published.wallet.${GETTER}`, + ); t.like(getterStatus, { status: { result: offerArgs.value } }); }); diff --git a/a3p-integration/proposals/z:acceptance/vaults.test.js b/a3p-integration/proposals/z:acceptance/vaults.test.js index 62086e7ad68..d99c8fe45e1 100644 --- a/a3p-integration/proposals/z:acceptance/vaults.test.js +++ b/a3p-integration/proposals/z:acceptance/vaults.test.js @@ -17,7 +17,7 @@ import { openVault, USER1ADDR, } from '@agoric/synthetic-chain'; -import { agdWalletUtils, walletUtils } from './test-lib/index.js'; +import { agdWalletUtils } from './test-lib/index.js'; import { getPriceFeedRoundId, verifyPushedPrice, @@ -55,7 +55,7 @@ const exec = { offerId = `openVault-${Date.now()}`, collateralBrandKey = 'ATOM', ) => { - const offer = Offers.vaults.OpenVault(walletUtils.agoricNames, { + const offer = Offers.vaults.OpenVault(agdWalletUtils.agoricNames, { giveCollateral, wantMinted, offerId, @@ -73,14 +73,14 @@ test.serial('open new vault', async t => { await bankSend(USER1ADDR, `20000000${ATOM_DENOM}`); const istBalanceBefore = await getISTBalance(USER1ADDR); - const activeVaultsBefore = await listVaults(USER1ADDR, walletUtils); + const activeVaultsBefore = await listVaults(USER1ADDR, agdWalletUtils); const mint = 5.0; const collateral = 10.0; await exec.vaults.OpenVault(USER1ADDR, mint, collateral); const istBalanceAfter = await getISTBalance(USER1ADDR); - const activeVaultsAfter = await listVaults(USER1ADDR, walletUtils); + const activeVaultsAfter = await listVaults(USER1ADDR, agdWalletUtils); await tryISTBalances( t, @@ -178,7 +178,7 @@ test.serial( 'user cannot open a vault under the minimum initial debt', async t => { await bankSend(GOV1ADDR, `200000000000000000${ATOM_DENOM}`); - const activeVaultsBefore = await listVaults(GOV1ADDR, walletUtils); + const activeVaultsBefore = await listVaults(GOV1ADDR, agdWalletUtils); const minInitialDebt = await getMinInitialDebt(); @@ -192,7 +192,7 @@ test.serial( }, ); - const activeVaultsAfter = await listVaults(GOV1ADDR, walletUtils); + const activeVaultsAfter = await listVaults(GOV1ADDR, agdWalletUtils); t.is( activeVaultsAfter.length, @@ -203,7 +203,7 @@ test.serial( ); test.serial('user cannot open a vault above debt limit', async t => { - const activeVaultsBefore = await listVaults(GOV1ADDR, walletUtils); + const activeVaultsBefore = await listVaults(GOV1ADDR, agdWalletUtils); const { availableDebtForMint } = await getAvailableDebtForMint(VAULT_MANAGER); @@ -217,7 +217,7 @@ test.serial('user cannot open a vault above debt limit', async t => { }, ); - const activeVaultsAfter = await listVaults(GOV1ADDR, walletUtils); + const activeVaultsAfter = await listVaults(GOV1ADDR, agdWalletUtils); t.is( activeVaultsAfter.length, @@ -228,7 +228,7 @@ test.serial('user cannot open a vault above debt limit', async t => { test.serial('user can open a vault under debt limit', async t => { const istBalanceBefore = await getISTBalance(GOV1ADDR); - const activeVaultsBefore = await listVaults(GOV1ADDR, walletUtils); + const activeVaultsBefore = await listVaults(GOV1ADDR, agdWalletUtils); const { availableDebtForMint } = await getAvailableDebtForMint(VAULT_MANAGER); @@ -238,7 +238,7 @@ test.serial('user can open a vault under debt limit', async t => { await openVault(GOV1ADDR, mint.toString(), collateral.toString()); const istBalanceAfter = await getISTBalance(GOV1ADDR); - const activeVaultsAfter = await listVaults(GOV1ADDR, walletUtils); + const activeVaultsAfter = await listVaults(GOV1ADDR, agdWalletUtils); await tryISTBalances( t, diff --git a/golang/cosmos/app/upgrade.go b/golang/cosmos/app/upgrade.go index 242fa9374ef..89857e26166 100644 --- a/golang/cosmos/app/upgrade.go +++ b/golang/cosmos/app/upgrade.go @@ -157,6 +157,22 @@ func replacePriceFeedsCoreProposal(upgradeName string) (vm.CoreProposalStep, err ) } +// func upgradeMintHolderCoreProposal(upgradeName string) (vm.CoreProposalStep, error) { +// variant := getVariantFromUpgradeName(upgradeName) + +// if variant == "" { +// return nil, nil +// } + +// return buildProposalStepWithArgs( +// "@agoric/builders/scripts/vats/upgrade-mintHolder.js", +// "defaultProposalBuilder", +// map[string]any{ +// "variant": variant, +// }, +// ) +// } + // unreleasedUpgradeHandler performs standard upgrade actions plus custom actions for the unreleased upgrade. func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Context, upgradetypes.Plan, module.VersionMap) (module.VersionMap, error) { return func(ctx sdk.Context, plan upgradetypes.Plan, fromVm module.VersionMap) (module.VersionMap, error) { @@ -216,6 +232,14 @@ 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 + // + // upgradeMintHolderStep, err := upgradeMintHolderCoreProposal(targetUpgrade) + // if err != nil { + // return nil, err + // } else if upgradeMintHolderStep != nil { + // CoreProposalSteps = append(CoreProposalSteps, upgradeMintHolderStep) + // } + // // CoreProposalSteps = append(CoreProposalSteps, // vm.CoreProposalStepForModules( // "@agoric/builders/scripts/inter-protocol/replace-feeDistributor.js", diff --git a/golang/cosmos/types/address_hooks.go b/golang/cosmos/types/address_hooks.go index b26fe1e85f4..073e9d48cf4 100644 --- a/golang/cosmos/types/address_hooks.go +++ b/golang/cosmos/types/address_hooks.go @@ -23,9 +23,9 @@ const ( BaseAddressLengthBytes = 2 ) -// AddressHookMagic is a magic byte prefix that identifies a hooked address. +// AddressHookBytePrefix is a magic prefix that identifies a hooked address. // Chosen to make bech32 address hooks that look like "agoric10rch..." -var AddressHookMagic = []byte{0x78, 0xf1, 0x70 | AddressHookVersion} +var AddressHookBytePrefix = []byte{0x78, 0xf1, 0x70 /* | AddressHookVersion */} func init() { if AddressHookVersion&0x0f != AddressHookVersion { @@ -51,12 +51,19 @@ func SplitHookedAddress(addr string) (string, []byte, error) { return "", []byte{}, err } - bz := bytes.TrimPrefix(payload, AddressHookMagic) - if len(bz) == len(payload) { + lastPrefixHighNibble := AddressHookBytePrefix[len(AddressHookBytePrefix)-1] + bz := bytes.TrimPrefix(payload, AddressHookBytePrefix[:len(AddressHookBytePrefix)-1]) + if len(bz) == len(payload) || len(bz) == 0 || bz[0]&0xf0 != lastPrefixHighNibble { // Return an unhooked address. return addr, []byte{}, nil } + version := bz[0] & 0x0f + bz = bz[1:] + if version != AddressHookVersion { + return "", []byte{}, fmt.Errorf("unsupported address hook version %d", version) + } + if len(bz) < BaseAddressLengthBytes { return "", []byte{}, fmt.Errorf("hooked address must have at least %d bytes", BaseAddressLengthBytes) } @@ -97,8 +104,9 @@ func JoinHookedAddress(baseAddr string, hookData []byte) (string, error) { return "", fmt.Errorf("base address length 0x%x is longer than the maximum 0x%x", b, maxB) } - payload := make([]byte, 0, len(AddressHookMagic)+b+len(hookData)+BaseAddressLengthBytes) - payload = append(payload, AddressHookMagic...) + payload := make([]byte, 0, len(AddressHookBytePrefix)+b+len(hookData)+BaseAddressLengthBytes) + payload = append(payload, AddressHookBytePrefix...) + payload[len(payload)-1] |= byte(AddressHookVersion) payload = append(payload, bz...) payload = append(payload, hookData...) baLen := make([]byte, BaseAddressLengthBytes) diff --git a/golang/cosmos/types/address_hooks_test.go b/golang/cosmos/types/address_hooks_test.go index 8ae9faad1e7..8dd7ed6c5b7 100644 --- a/golang/cosmos/types/address_hooks_test.go +++ b/golang/cosmos/types/address_hooks_test.go @@ -15,6 +15,53 @@ import ( "github.com/Agoric/agoric-sdk/golang/cosmos/types" ) +func TestSplitHookedAddress(t *testing.T) { + cases := []struct { + name string + hook string + baseAddr string + hookData []byte + err string + }{ + {"empty", "", "", []byte{}, "decoding bech32 failed: invalid bech32 string length 0"}, + {"no hook", "agoric1qqp0e5ys", "agoric1qqp0e5ys", []byte{}, ""}, + {"Fast USDC", "agoric10rchp4vc53apxn32q42c3zryml8xq3xshyzuhjk6405wtxy7tl3d7e0f8az423padaek6me38qekget2vdhx66mtvy6kg7nrw5uhsaekd4uhwufswqex6dtsv44hxv3cd4jkuqpqvduyhf", + "agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek", + []byte("?EUD=osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men"), + ""}, + {"version 0", + "agoric10rchqqqpqgpsgpgxquyqjzstpsxsurcszyfpxpqrqgqsq9qx0p9wp", + "agoric1qqqsyqcyq5rqwzqfpg9scrgwpugpzysn3tn9p0", + []byte{4, 3, 2, 1}, + ""}, + {"version 1 reject", + "agoric10rchzqqpqgpsgpgxquyqjzstpsxsurcszyfpxpqrqgqsq9q04n2fg", + "", + []byte{}, + "unsupported address hook version 1"}, + {"version 15 reject", + "agoric10rch7qqpqgpsgpgxquyqjzstpsxsurcszyfpxpqrqgqsq9q25ez2d", + "", + []byte{}, + "unsupported address hook version 15"}, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + baseAddr, hookData, err := types.SplitHookedAddress(tc.hook) + if len(tc.err) > 0 { + require.Error(t, err) + require.Equal(t, tc.err, err.Error()) + } else { + require.NoError(t, err) + require.Equal(t, tc.baseAddr, baseAddr) + require.Equal(t, string(tc.hookData), string(hookData)) + } + }) + } +} + func TestExtractBaseAddress(t *testing.T) { bases := []struct { name string diff --git a/multichain-testing/ava.main.config.js b/multichain-testing/ava.main.config.js index 37c7ca0b467..da0d373e10c 100644 --- a/multichain-testing/ava.main.config.js +++ b/multichain-testing/ava.main.config.js @@ -9,4 +9,5 @@ export default { concurrency: 1, serial: true, timeout: '125s', + failFast: true, }; diff --git a/multichain-testing/test/account-balance-queries.test.ts b/multichain-testing/test/account-balance-queries.test.ts index 6707d9b102e..c067ced9446 100644 --- a/multichain-testing/test/account-balance-queries.test.ts +++ b/multichain-testing/test/account-balance-queries.test.ts @@ -20,7 +20,7 @@ const contractBuilder = test.before(async t => { const { deleteTestKeys, setupTestKeys, ...rest } = await commonSetup(t); - deleteTestKeys(accounts).catch(); + await deleteTestKeys(accounts).catch(); const wallets = await setupTestKeys(accounts); t.context = { ...rest, wallets, deleteTestKeys }; const { startContract } = rest; diff --git a/multichain-testing/test/auto-stake-it.test.ts b/multichain-testing/test/auto-stake-it.test.ts index d4bfded2086..10e3bac8db8 100644 --- a/multichain-testing/test/auto-stake-it.test.ts +++ b/multichain-testing/test/auto-stake-it.test.ts @@ -18,15 +18,11 @@ const contractBuilder = test.before(async t => { const { setupTestKeys, ...common } = await commonSetup(t); - const { assetInfo, chainInfo, deleteTestKeys, startContract } = common; - deleteTestKeys(accounts).catch(); + const { commonBuilderOpts, deleteTestKeys, startContract } = common; + await deleteTestKeys(accounts).catch(); const wallets = await setupTestKeys(accounts); t.context = { ...common, wallets }; - - await startContract(contractName, contractBuilder, { - chainInfo, - assetInfo, - }); + await startContract(contractName, contractBuilder, commonBuilderOpts); }); test.after(async t => { diff --git a/multichain-testing/test/basic-flows.test.ts b/multichain-testing/test/basic-flows.test.ts index 689db323524..1d53b4aae8d 100644 --- a/multichain-testing/test/basic-flows.test.ts +++ b/multichain-testing/test/basic-flows.test.ts @@ -17,14 +17,11 @@ const contractBuilder = test.before(async t => { const { setupTestKeys, ...common } = await commonSetup(t); - const { assetInfo, chainInfo, deleteTestKeys, startContract } = common; - deleteTestKeys(accounts).catch(); + const { commonBuilderOpts, deleteTestKeys, startContract } = common; + await deleteTestKeys(accounts).catch(); const wallets = await setupTestKeys(accounts); t.context = { ...common, wallets }; - await startContract(contractName, contractBuilder, { - chainInfo, - assetInfo, - }); + await startContract(contractName, contractBuilder, commonBuilderOpts); }); test.after(async t => { diff --git a/multichain-testing/test/chain-queries.test.ts b/multichain-testing/test/chain-queries.test.ts index a9b91c97fe3..56673742582 100644 --- a/multichain-testing/test/chain-queries.test.ts +++ b/multichain-testing/test/chain-queries.test.ts @@ -28,7 +28,7 @@ const contractBuilder = test.before(async t => { const { deleteTestKeys, setupTestKeys, ...rest } = await commonSetup(t); - deleteTestKeys(accounts).catch(); + await deleteTestKeys(accounts).catch(); const wallets = await setupTestKeys(accounts); t.context = { ...rest, wallets, deleteTestKeys }; const { startContract } = rest; diff --git a/multichain-testing/test/deposit-withdraw-lca.test.ts b/multichain-testing/test/deposit-withdraw-lca.test.ts index ad7fd4824d9..7d1cbafa97c 100644 --- a/multichain-testing/test/deposit-withdraw-lca.test.ts +++ b/multichain-testing/test/deposit-withdraw-lca.test.ts @@ -15,14 +15,11 @@ const contractBuilder = test.before(async t => { const { setupTestKeys, ...common } = await commonSetup(t); - const { assetInfo, chainInfo, deleteTestKeys, startContract } = common; - deleteTestKeys(accounts).catch(); + const { commonBuilderOpts, deleteTestKeys, startContract } = common; + await deleteTestKeys(accounts).catch(); const wallets = await setupTestKeys(accounts); t.context = { ...common, wallets }; - await startContract(contractName, contractBuilder, { - chainInfo, - assetInfo, - }); + await startContract(contractName, contractBuilder, commonBuilderOpts); }); test.after(async t => { diff --git a/multichain-testing/test/deposit-withdraw-portfolio.test.ts b/multichain-testing/test/deposit-withdraw-portfolio.test.ts index ea97f1e7f17..49b7f3d8ad7 100644 --- a/multichain-testing/test/deposit-withdraw-portfolio.test.ts +++ b/multichain-testing/test/deposit-withdraw-portfolio.test.ts @@ -15,14 +15,11 @@ const contractBuilder = test.before(async t => { const { setupTestKeys, ...common } = await commonSetup(t); - const { assetInfo, chainInfo, deleteTestKeys, startContract } = common; - deleteTestKeys(accounts).catch(); + const { commonBuilderOpts, deleteTestKeys, startContract } = common; + await deleteTestKeys(accounts).catch(); const wallets = await setupTestKeys(accounts); t.context = { ...common, wallets }; - await startContract(contractName, contractBuilder, { - chainInfo, - assetInfo, - }); + await startContract(contractName, contractBuilder, commonBuilderOpts); }); test.after(async t => { diff --git a/multichain-testing/test/fast-usdc/config.ts b/multichain-testing/test/fast-usdc/config.ts new file mode 100644 index 00000000000..c7b1833ec6d --- /dev/null +++ b/multichain-testing/test/fast-usdc/config.ts @@ -0,0 +1,28 @@ +import type { IBCChannelID } from '@agoric/vats'; + +export const oracleMnemonics = { + oracle1: + 'cause eight cattle slot course mail more aware vapor slab hobby match', + oracle2: + 'flower salute inspire label latin cattle believe sausage match total bless refuse', + oracle3: + 'surge magnet typical drive cement artist stay latin chief obey word always', +}; +harden(oracleMnemonics); + +export const makeFeedPolicy = (nobleAgoricChannelId: IBCChannelID) => { + return { + nobleAgoricChannelId, + nobleDomainId: 4, + chainPolicies: { + Arbitrum: { + attenuatedCttpBridgeAddress: + '0xe298b93ffB5eA1FB628e0C0D55A43aeaC268e347', + cctpTokenMessengerAddress: '0x19330d10D9Cc8751218eaf51E8885D058642E08A', + chainId: 42161, + confirmations: 2, + }, + }, + }; +}; +harden(makeFeedPolicy); diff --git a/multichain-testing/test/fast-usdc/fast-usdc.test.ts b/multichain-testing/test/fast-usdc/fast-usdc.test.ts new file mode 100644 index 00000000000..8997384e031 --- /dev/null +++ b/multichain-testing/test/fast-usdc/fast-usdc.test.ts @@ -0,0 +1,382 @@ +import anyTest from '@endo/ses-ava/prepare-endo.js'; +import type { TestFn } from 'ava'; +import { encodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js'; +import { AmountMath } from '@agoric/ertp'; +import type { Denom } from '@agoric/orchestration'; +import { divideBy, multiplyBy } from '@agoric/zoe/src/contractSupport/ratio.js'; +import type { IBCChannelID } from '@agoric/vats'; +import { makeDoOffer, type WalletDriver } from '../../tools/e2e-tools.js'; +import { makeDenomTools } from '../../tools/asset-info.js'; +import { createWallet } from '../../tools/wallet.js'; +import { makeQueryClient } from '../../tools/query.js'; +import { commonSetup, type SetupContextWithWallets } from '../support.js'; +import { makeFeedPolicy, oracleMnemonics } from './config.js'; +import { makeRandomDigits } from '../../tools/random.js'; +import { balancesFromPurses } from '../../tools/purse.js'; + +const { keys, values, fromEntries } = Object; +const { isGTE, isEmpty, make } = AmountMath; + +const makeRandomNumber = () => Math.random(); + +const test = anyTest as TestFn< + SetupContextWithWallets & { + lpUser: WalletDriver; + oracleWds: WalletDriver[]; + nobleAgoricChannelId: IBCChannelID; + usdcOnOsmosis: Denom; + /** usdc on agoric */ + usdcDenom: Denom; + } +>; + +const accounts = [...keys(oracleMnemonics), 'lp']; +const contractName = 'fastUsdc'; +const contractBuilder = + '../packages/builders/scripts/fast-usdc/init-fast-usdc.js'; +const LP_DEPOSIT_AMOUNT = 10_000_000n; + +test.before(async t => { + const { setupTestKeys, ...common } = await commonSetup(t); + const { + chainInfo, + commonBuilderOpts, + deleteTestKeys, + faucetTools, + provisionSmartWallet, + startContract, + } = common; + await deleteTestKeys(accounts).catch(); + const wallets = await setupTestKeys(accounts, values(oracleMnemonics)); + + // provision oracle wallets first so invitation deposits don't fail + const oracleWds = await Promise.all( + keys(oracleMnemonics).map(n => + provisionSmartWallet(wallets[n], { + BLD: 100n, + }), + ), + ); + + // calculate denomHash and channelId for privateArgs / builder opts + const { getTransferChannelId, toDenomHash } = makeDenomTools(chainInfo); + const usdcDenom = toDenomHash('uusdc', 'noblelocal', 'agoric'); + const usdcOnOsmosis = toDenomHash('uusdc', 'noblelocal', 'osmosis'); + const nobleAgoricChannelId = getTransferChannelId('agoriclocal', 'noble'); + if (!nobleAgoricChannelId) throw new Error('nobleAgoricChannelId not found'); + t.log('nobleAgoricChannelId', nobleAgoricChannelId); + t.log('usdcDenom', usdcDenom); + + await startContract(contractName, contractBuilder, { + oracle: keys(oracleMnemonics).map(n => `${n}:${wallets[n]}`), + usdcDenom, + feedPolicy: JSON.stringify(makeFeedPolicy(nobleAgoricChannelId)), + ...commonBuilderOpts, + }); + + // provide faucet funds for LPs + await faucetTools.fundFaucet([['noble', 'uusdc']]); + + // save an LP in test context + const lpUser = await provisionSmartWallet(wallets['lp'], { + USDC: 100n, + BLD: 100n, + }); + + t.context = { + ...common, + lpUser, + oracleWds, + nobleAgoricChannelId, + usdcOnOsmosis, + usdcDenom, + wallets, + }; +}); + +test.after(async t => { + const { deleteTestKeys } = t.context; + deleteTestKeys(accounts); +}); + +const toOracleOfferId = (idx: number) => `oracle${idx + 1}-accept`; + +test.serial('oracles accept', async t => { + const { oracleWds, retryUntilCondition, vstorageClient, wallets } = t.context; + + const instances = await vstorageClient.queryData( + 'published.agoricNames.instance', + ); + const instance = fromEntries(instances)[contractName]; + + // accept oracle operator invitations + await Promise.all( + oracleWds.map(makeDoOffer).map((doOffer, i) => + doOffer({ + id: toOracleOfferId(i), + invitationSpec: { + source: 'purse', + instance, + description: 'oracle operator invitation', // TODO export/import INVITATION_MAKERS_DESC + }, + proposal: {}, + }), + ), + ); + + for (const name of keys(oracleMnemonics)) { + const addr = wallets[name]; + await t.notThrowsAsync(() => + retryUntilCondition( + () => vstorageClient.queryData(`published.wallet.${addr}.current`), + ({ offerToUsedInvitation }) => { + return offerToUsedInvitation[0][0] === `${name}-accept`; + }, + `${name} invitation used`, + ), + ); + } +}); + +test.serial('lp deposits', async t => { + const { lpUser, retryUntilCondition, vstorageClient, wallets } = t.context; + + const lpDoOffer = makeDoOffer(lpUser); + const brands = await vstorageClient.queryData('published.agoricNames.brand'); + const { USDC, FastLP } = Object.fromEntries(brands); + + const usdcToGive = make(USDC, LP_DEPOSIT_AMOUNT); + + const { shareWorth: currShareWorth } = await vstorageClient.queryData( + `published.${contractName}.poolMetrics`, + ); + const poolSharesWanted = divideBy(usdcToGive, currShareWorth); + + await lpDoOffer({ + id: `lp-deposit-${Date.now()}`, + invitationSpec: { + source: 'agoricContract', + instancePath: [contractName], + callPipe: [['makeDepositInvitation']], + }, + proposal: { + give: { USDC: usdcToGive }, + want: { PoolShare: poolSharesWanted }, + }, + }); + + await t.notThrowsAsync(() => + retryUntilCondition( + () => vstorageClient.queryData(`published.${contractName}.poolMetrics`), + ({ shareWorth }) => + !isGTE(currShareWorth.numerator, shareWorth.numerator), + 'share worth numerator increases from deposit', + ), + ); + + await t.notThrowsAsync(() => + retryUntilCondition( + () => + vstorageClient.queryData(`published.wallet.${wallets['lp']}.current`), + ({ purses }) => { + const currentPoolShares = balancesFromPurses(purses)[FastLP]; + return currentPoolShares && isGTE(currentPoolShares, poolSharesWanted); + }, + 'lp has pool shares', + ), + ); +}); + +test.serial('advance and settlement', async t => { + const { + nobleTools, + nobleAgoricChannelId, + oracleWds, + retryUntilCondition, + useChain, + usdcOnOsmosis, + vstorageClient, + } = t.context; + + // EUD wallet on osmosis + const eudWallet = await createWallet(useChain('osmosis').chain.bech32_prefix); + const EUD = (await eudWallet.getAccounts())[0].address; + + // parameterize agoric address + const { settlementAccount } = await vstorageClient.queryData( + `published.${contractName}`, + ); + t.log('settlementAccount address', settlementAccount); + + const recipientAddress = encodeAddressHook(settlementAccount, { EUD }); + t.log('recipientAddress', recipientAddress); + + // register forwarding address on noble + const txRes = nobleTools.registerForwardingAcct( + nobleAgoricChannelId, + recipientAddress, + ); + t.is(txRes?.code, 0, 'registered forwarding account'); + + const { address: userForwardingAddr } = nobleTools.queryForwardingAddress( + nobleAgoricChannelId, + recipientAddress, + ); + t.log('got forwardingAddress', userForwardingAddr); + + const mintAmount = 800_000n; + + // TODO export CctpTxEvidence type + const evidence = harden({ + blockHash: + '0x90d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee665', + blockNumber: 21037663n, + txHash: `0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff3875527617${makeRandomDigits(makeRandomNumber(), 2n)}`, + tx: { + amount: mintAmount, + forwardingAddress: userForwardingAddr, + }, + aux: { + forwardingChannel: nobleAgoricChannelId, + recipientAddress, + }, + chainId: 42161, + }); + + console.log('User initiates evm mint', evidence.txHash); + + // submit evidences + await Promise.all( + oracleWds.map(makeDoOffer).map((doOffer, i) => + doOffer({ + id: `${Date.now()}-evm-evidence`, + invitationSpec: { + source: 'continuing', + previousOffer: toOracleOfferId(i), + invitationMakerName: 'SubmitEvidence', + invitationArgs: [evidence], + }, + proposal: {}, + }), + ), + ); + + const queryClient = makeQueryClient( + await useChain('osmosis').getRestEndpoint(), + ); + + await t.notThrowsAsync(() => + retryUntilCondition( + () => queryClient.queryBalance(EUD, usdcOnOsmosis), + ({ balance }) => !!balance?.amount && BigInt(balance.amount) < mintAmount, + `${EUD} advance available from fast-usdc`, + { + // this resolves quickly, so _decrease_ the interval so the timing is more apparent + retryIntervalMs: 500, + }, + ), + ); + + const queryTxStatus = async () => + vstorageClient.queryData( + `published.${contractName}.status.${evidence.txHash}`, + ); + + const assertTxStatus = async (status: string) => + t.notThrowsAsync(() => + retryUntilCondition( + () => queryTxStatus(), + txStatus => { + console.log('tx status', txStatus); + return txStatus === status; + }, + `${evidence.txHash} is ${status}`, + ), + ); + + await assertTxStatus('ADVANCED'); + console.log('Advance completed, waiting for mint...'); + + nobleTools.mockCctpMint(mintAmount, userForwardingAddr); + await t.notThrowsAsync(() => + retryUntilCondition( + () => vstorageClient.queryData(`published.${contractName}.poolMetrics`), + ({ encumberedBalance }) => + encumberedBalance && isEmpty(encumberedBalance), + 'encumberedBalance returns to 0', + ), + ); + + await assertTxStatus('DISBURSED'); +}); + +test.serial('lp withdraws', async t => { + const { + lpUser, + retryUntilCondition, + useChain, + usdcDenom, + vstorageClient, + wallets, + } = t.context; + const queryClient = makeQueryClient( + await useChain('agoric').getRestEndpoint(), + ); + const lpDoOffer = makeDoOffer(lpUser); + const brands = await vstorageClient.queryData('published.agoricNames.brand'); + const { FastLP } = Object.fromEntries(brands); + t.log('FastLP brand', FastLP); + + const { shareWorth: currShareWorth } = await vstorageClient.queryData( + `published.${contractName}.poolMetrics`, + ); + const { purses } = await vstorageClient.queryData( + `published.wallet.${wallets['lp']}.current`, + ); + const currentPoolShares = balancesFromPurses(purses)[FastLP]; + t.log('currentPoolShares', currentPoolShares); + const usdcWanted = multiplyBy(currentPoolShares, currShareWorth); + t.log('usdcWanted', usdcWanted); + + const { balance: currentUSDCBalance } = await queryClient.queryBalance( + wallets['lp'], + usdcDenom, + ); + t.log(`current ${usdcDenom} balance`, currentUSDCBalance); + + await lpDoOffer({ + id: `lp-withdraw-${Date.now()}`, + invitationSpec: { + source: 'agoricContract', + instancePath: [contractName], + callPipe: [['makeWithdrawInvitation']], + }, + proposal: { + give: { PoolShare: currentPoolShares }, + want: { USDC: usdcWanted }, + }, + }); + + await t.notThrowsAsync(() => + retryUntilCondition( + () => + vstorageClient.queryData(`published.wallet.${wallets['lp']}.current`), + ({ purses }) => { + const currentPoolShares = balancesFromPurses(purses)[FastLP]; + return !currentPoolShares || isEmpty(currentPoolShares); + }, + 'lp no longer has pool shares', + ), + ); + + await t.notThrowsAsync(() => + retryUntilCondition( + () => queryClient.queryBalance(wallets['lp'], usdcDenom), + ({ balance }) => + !!balance?.amount && + BigInt(balance.amount) - BigInt(currentUSDCBalance!.amount!) > + LP_DEPOSIT_AMOUNT, + "lp's USDC balance increases", + ), + ); +}); diff --git a/multichain-testing/test/ica-channel-close.test.ts b/multichain-testing/test/ica-channel-close.test.ts index 03b0ef9ac95..6ee59dc825f 100644 --- a/multichain-testing/test/ica-channel-close.test.ts +++ b/multichain-testing/test/ica-channel-close.test.ts @@ -23,14 +23,11 @@ const contractBuilder = test.before(async t => { const { setupTestKeys, ...common } = await commonSetup(t); - const { assetInfo, chainInfo, deleteTestKeys, startContract } = common; - deleteTestKeys(accounts).catch(); + const { commonBuilderOpts, deleteTestKeys, startContract } = common; + await deleteTestKeys(accounts).catch(); const wallets = await setupTestKeys(accounts); t.context = { ...common, wallets }; - await startContract(contractName, contractBuilder, { - chainInfo, - assetInfo, - }); + await startContract(contractName, contractBuilder, commonBuilderOpts); }); test.after(async t => { diff --git a/multichain-testing/test/send-anywhere.test.ts b/multichain-testing/test/send-anywhere.test.ts index 11508f22cc6..df4bbdca8d6 100644 --- a/multichain-testing/test/send-anywhere.test.ts +++ b/multichain-testing/test/send-anywhere.test.ts @@ -21,16 +21,12 @@ const contractBuilder = test.before(async t => { const { setupTestKeys, ...common } = await commonSetup(t); - const { assetInfo, chainInfo, deleteTestKeys, faucetTools, startContract } = + const { commonBuilderOpts, deleteTestKeys, faucetTools, startContract } = common; - deleteTestKeys(accounts).catch(); + await deleteTestKeys(accounts).catch(); const wallets = await setupTestKeys(accounts); t.context = { ...common, wallets }; - - await startContract(contractName, contractBuilder, { - chainInfo, - assetInfo, - }); + await startContract(contractName, contractBuilder, commonBuilderOpts); await faucetTools.fundFaucet([ ['cosmoshub', 'uatom'], diff --git a/multichain-testing/test/stake-ica.test.ts b/multichain-testing/test/stake-ica.test.ts index af73531bf61..7ef65ad33eb 100644 --- a/multichain-testing/test/stake-ica.test.ts +++ b/multichain-testing/test/stake-ica.test.ts @@ -18,7 +18,7 @@ test.before(async t => { const { deleteTestKeys, setupTestKeys, ...rest } = await commonSetup(t); // XXX not necessary for CI, but helpful for unexpected failures in // active development (test.after cleanup doesn't run). - deleteTestKeys(accounts).catch(); + await deleteTestKeys(accounts).catch(); const wallets = await setupTestKeys(accounts); t.context = { ...rest, wallets, deleteTestKeys }; }); diff --git a/multichain-testing/test/support.ts b/multichain-testing/test/support.ts index 18e8006f155..f71e2c112cc 100644 --- a/multichain-testing/test/support.ts +++ b/multichain-testing/test/support.ts @@ -4,7 +4,6 @@ import { execa } from 'execa'; import fse from 'fs-extra'; import childProcess from 'node:child_process'; import { withChainCapabilities } from '@agoric/orchestration'; -import { objectMap } from '@endo/patterns'; import { makeAgdTools } from '../tools/agd-tools.js'; import { type E2ETools } from '../tools/e2e-tools.js'; import { @@ -42,13 +41,19 @@ const makeKeyring = async ( e2eTools: Pick, ) => { let _keys = ['user1']; - const setupTestKeys = async (keys = ['user1']) => { + const setupTestKeys = async ( + keys = ['user1'], + mnemonics?: (string | undefined)[], + ) => { _keys = keys; const wallets: Record = {}; - for (const name of keys) { - const res = await e2eTools.addKey(name, generateMnemonic()); + for (const i in keys) { + const res = await e2eTools.addKey( + keys[i], + mnemonics?.[i] || generateMnemonic(), + ); const { address } = JSON.parse(res); - wallets[name] = address; + wallets[keys[i]] = address; } return wallets; }; @@ -91,6 +96,10 @@ export const commonSetup = async (t: ExecutionContext) => { retryUntilCondition, useChain, ); + const commonBuilderOpts = harden({ + assetInfo: JSON.stringify(assetInfo), + chainInfo: JSON.stringify(chainInfo), + }); /** * Starts a contract if instance not found. Takes care of installing @@ -102,7 +111,7 @@ export const commonSetup = async (t: ExecutionContext) => { const startContract = async ( contractName: string, contractBuilder: string, - builderOpts?: Record, + builderOpts?: Record, ) => { const { vstorageClient } = tools; const instances = Object.fromEntries( @@ -112,18 +121,7 @@ export const commonSetup = async (t: ExecutionContext) => { return t.log('Contract found. Skipping installation...'); } t.log('bundle and install contract', contractName); - - const formattedOpts = builderOpts - ? objectMap( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - builderOpts as Record, - value => { - if (typeof value === 'string') return value; - return JSON.stringify(value); - }, - ) - : undefined; - await deployBuilder(contractBuilder, formattedOpts); + await deployBuilder(contractBuilder, builderOpts); await retryUntilCondition( () => vstorageClient.queryData(`published.agoricNames.instance`), res => contractName in Object.fromEntries(res), @@ -142,6 +140,7 @@ export const commonSetup = async (t: ExecutionContext) => { startContract, assetInfo, chainInfo, + commonBuilderOpts, faucetTools, }; }; diff --git a/multichain-testing/tools/agd-lib.js b/multichain-testing/tools/agd-lib.js index a28d408493b..71ed0f69cbd 100644 --- a/multichain-testing/tools/agd-lib.js +++ b/multichain-testing/tools/agd-lib.js @@ -16,7 +16,7 @@ const binaryArgs = [ ]; /** - * @param {Record} record - e.g. { color: 'blue' } + * @param {Record} record - e.g. { color: 'blue' } * @returns {string[]} - e.g. ['--color', 'blue'] */ export const flags = record => { @@ -25,7 +25,12 @@ export const flags = record => { /** @type {[string, string][]} */ // @ts-expect-error undefined is filtered out const skipUndef = Object.entries(record).filter(([_k, v]) => v !== undefined); - return skipUndef.map(([k, v]) => [`--${k}`, v]).flat(); + return skipUndef.flatMap(([key, value]) => { + if (Array.isArray(value)) { + return value.flatMap(v => [`--${key}`, v]); + } + return [`--${key}`, value]; + }); }; /** diff --git a/multichain-testing/tools/asset-info.ts b/multichain-testing/tools/asset-info.ts index ef4faf1d5ac..568cf6019c3 100644 --- a/multichain-testing/tools/asset-info.ts +++ b/multichain-testing/tools/asset-info.ts @@ -6,6 +6,31 @@ import { } from '@agoric/orchestration'; import type { IBCChannelID } from '@agoric/vats'; +export const makeDenomTools = (chainInfo: Record) => { + const getTransferChannelId = ( + destChainId: string, + fromChainName: string, + ): IBCChannelID | undefined => + chainInfo[fromChainName]?.connections?.[destChainId]?.transferChannel + .channelId; + + const toDenomHash = ( + denom: Denom, + destChainId: string, + fromChainName: string, + ): Denom => { + const channelId = getTransferChannelId(destChainId, fromChainName); + if (!channelId) { + throw new Error( + `No channel found for ${destChainId} -> ${fromChainName}`, + ); + } + return `ibc/${denomHash({ denom, channelId })}`; + }; + + return harden({ getTransferChannelId, toDenomHash }); +}; + /** * Make asset info for the current environment. * @@ -21,26 +46,7 @@ export const makeAssetInfo = ( osmosis: ['uosmo', 'uion'], }, ): [Denom, DenomDetail][] => { - const getChannelId = ( - issuingChainId: string, - holdingChainName: string, - ): IBCChannelID | undefined => - chainInfo[holdingChainName]?.connections?.[issuingChainId]?.transferChannel - .channelId; - - const toDenomHash = ( - denom: Denom, - issuingChainId: string, - holdingChainName: string, - ): Denom => { - const channelId = getChannelId(issuingChainId, holdingChainName); - if (!channelId) { - throw new Error( - `No channel found for ${issuingChainId} -> ${holdingChainName}`, - ); - } - return `ibc/${denomHash({ denom, channelId })}`; - }; + const { toDenomHash } = makeDenomTools(chainInfo); // only include chains present in `chainInfo` const tokens = Object.entries(tokenMap) diff --git a/multichain-testing/tools/deploy.ts b/multichain-testing/tools/deploy.ts index 52a460f6094..289c4d971e9 100755 --- a/multichain-testing/tools/deploy.ts +++ b/multichain-testing/tools/deploy.ts @@ -13,7 +13,7 @@ export const makeDeployBuilder = ( ) => async function deployBuilder( builder: string, - builderOpts?: Record, + builderOpts?: Record, ) { console.log(`building plan: ${builder}`); const args = ['run', builder]; diff --git a/multichain-testing/tools/e2e-tools.js b/multichain-testing/tools/e2e-tools.js index f820e7c8077..4710761e014 100644 --- a/multichain-testing/tools/e2e-tools.js +++ b/multichain-testing/tools/e2e-tools.js @@ -8,8 +8,12 @@ import { flags, makeAgd, makeCopyFiles } from './agd-lib.js'; import { makeHttpClient, makeAPI } from './makeHttpClient.js'; import { dedup, makeQueryKit, poll } from './queryKit.js'; import { makeVStorage } from './batchQuery.js'; +import { makeRetryUntilCondition } from './sleep.js'; -/** @import { EnglishMnemonic } from '@cosmjs/crypto'; */ +/** + * @import { EnglishMnemonic } from '@cosmjs/crypto'; + * @import { RetryUntilCondition } from './sleep.js'; + */ const BLD = '000000ubld'; @@ -121,6 +125,7 @@ const installBundle = async (fullPath, opts) => { * blockTool: BlockTool; * lcd: import('./makeHttpClient.js').LCD; * delay: (ms: number) => Promise; + * retryUntilCondition: RetryUntilCondition; * chainId?: string; * whale?: string; * progress?: typeof console.log; @@ -139,14 +144,15 @@ export const provisionSmartWallet = async ( whale = 'faucet', progress = console.log, q = makeQueryKit(makeVStorage(lcd)).query, + retryUntilCondition, }, ) => { // TODO: skip this query if balances is {} const vbankEntries = await q.queryData('published.agoricNames.vbankAsset'); const byName = Object.fromEntries( - vbankEntries.map(([denom, info]) => { - /// XXX better way to filter out old ATOM denom? - if (denom === 'ibc/toyatom') return [undefined, undefined]; + // reverse entries, so we get the latest view on the denom since there are + // multiple entries in the testing environment + [...vbankEntries].reverse().map(([_, info]) => { return [info.issuerName, info]; }), ); @@ -187,7 +193,12 @@ export const provisionSmartWallet = async ( { chainId, from: address, yes: true }, ); - const info = await q.queryData(`published.wallet.${address}.current`); + const info = await retryUntilCondition( + () => q.queryData(`published.wallet.${address}.current`), + result => !!result, + `wallet in vstorage ${address}`, + { log: () => {} }, // suppress logs as this is already noisy + ); progress({ provisioned: address, purses: info.purses.length, @@ -297,6 +308,8 @@ export const provisionSmartWallet = async ( return { offers, deposit, peek, query: q }; }; +/** @typedef {Awaited>} WalletDriver */ + /** * @param {{ * agd: import('./agd-lib.js').Agd; @@ -426,6 +439,7 @@ const runCoreEval = async ( * @param {string} [io.rpcAddress] * @param {string} [io.apiAddress] * @param {(...parts: string[]) => string} [io.join] + * * @param {RetryUntilCondition} [io.retryUntilCondition] */ export const makeE2ETools = async ( log, @@ -436,6 +450,7 @@ export const makeE2ETools = async ( setTimeout, rpcAddress = 'http://localhost:26657', apiAddress = 'http://localhost:1317', + retryUntilCondition = makeRetryUntilCondition({ log, setTimeout }), }, ) => { const agd = makeAgd({ execFileSync }).withOpts({ keyringBackend: 'test' }); @@ -533,6 +548,7 @@ export const makeE2ETools = async ( lcd, delay, q: vstorageClient, + retryUntilCondition, }), /** * @param {string} name diff --git a/multichain-testing/tools/noble-tools.ts b/multichain-testing/tools/noble-tools.ts index 0ece8dfd8b8..8a08e6a85bb 100644 --- a/multichain-testing/tools/noble-tools.ts +++ b/multichain-testing/tools/noble-tools.ts @@ -19,11 +19,14 @@ const makeKubeArgs = () => { ]; }; -export const makeNobleTools = ({ - execFileSync, -}: { - execFileSync: ExecSync; -}) => { +export const makeNobleTools = ( + { + execFileSync, + }: { + execFileSync: ExecSync; + }, + log: (...args: unknown[]) => void = console.log, +) => { const exec = ( args: string[], opts = { encoding: 'utf-8' as const, stdio: ['ignore', 'pipe', 'ignore'] }, @@ -38,8 +41,9 @@ export const makeNobleTools = ({ const registerForwardingAcct = ( channelId: IBCChannelID, address: ChainAddress['value'], - ) => { + ): { txhash: string; code: number; data: string; height: string } => { checkEnv(); + log('creating forwarding address', address, channelId); return JSON.parse( exec([ 'tx', @@ -57,6 +61,8 @@ export const makeNobleTools = ({ const mockCctpMint = (amount: bigint, destination: ChainAddress['value']) => { checkEnv(); + const denomAmount = `${Number(amount)}uusdc`; + log('mock cctp mint', destination, denomAmount); return JSON.parse( exec([ 'tx', @@ -64,7 +70,7 @@ export const makeNobleTools = ({ 'send', 'faucet', destination, - `${Number(amount)}uusdc`, + denomAmount, '--from=faucet', '-y', '-b', @@ -76,8 +82,9 @@ export const makeNobleTools = ({ const queryForwardingAddress = ( channelId: IBCChannelID, address: ChainAddress['value'], - ) => { + ): { address: string; exists: boolean } => { checkEnv(); + log('querying forwarding address', address, channelId); return JSON.parse( exec([ 'query', diff --git a/multichain-testing/tools/purse.ts b/multichain-testing/tools/purse.ts new file mode 100644 index 00000000000..82a76d2a3f4 --- /dev/null +++ b/multichain-testing/tools/purse.ts @@ -0,0 +1,10 @@ +import type { Amount, Brand } from '@agoric/ertp'; +const { fromEntries } = Object; + +// @ts-expect-error Type 'Brand' does not satisfy the constraint 'string | number | symbol' +type BrandToBalance = Record; + +export const balancesFromPurses = ( + purses: { balance: Amount; brand: Brand }[], +): BrandToBalance => + fromEntries(purses.map(({ balance, brand }) => [brand, balance])); diff --git a/multichain-testing/tools/random.ts b/multichain-testing/tools/random.ts new file mode 100644 index 00000000000..1928bb1ebd3 --- /dev/null +++ b/multichain-testing/tools/random.ts @@ -0,0 +1,11 @@ +/** + * @param randomN pseudorandom number between 0 and 1, e.g. Math.random() + * @param digits number of digits to generate + * @returns a string of digits + */ +export function makeRandomDigits(randomN: number, digits = 2n) { + if (digits < 1n) throw new Error('digits must be positive'); + const maxValue = Math.pow(10, Number(digits)) - 1; + const num = Math.floor(randomN * (maxValue + 1)); + return num.toString().padStart(Number(digits), '0'); +} diff --git a/multichain-testing/tools/sleep.ts b/multichain-testing/tools/sleep.ts index 1b0d1a58807..eca0392c1b7 100644 --- a/multichain-testing/tools/sleep.ts +++ b/multichain-testing/tools/sleep.ts @@ -28,11 +28,11 @@ const retryUntilCondition = async ( { maxRetries = 6, retryIntervalMs = 3500, - log = () => {}, + log = console.log, setTimeout = ambientSetTimeout, }: RetryOptions = {}, ): Promise => { - console.log({ maxRetries, retryIntervalMs, message }); + log({ maxRetries, retryIntervalMs, message }); let retries = 0; while (retries < maxRetries) { @@ -50,7 +50,7 @@ const retryUntilCondition = async ( } retries++; - console.log( + log( `Retry ${retries}/${maxRetries} - Waiting for ${retryIntervalMs}ms for ${message}...`, ); await sleep(retryIntervalMs, { log, setTimeout }); diff --git a/multichain-testing/yarn.lock b/multichain-testing/yarn.lock index 66b1b0557dc..fd3e9510370 100644 --- a/multichain-testing/yarn.lock +++ b/multichain-testing/yarn.lock @@ -6,12 +6,14 @@ __metadata: cacheKey: 10c0 "@agoric/cosmic-proto@npm:dev": - version: 0.4.1-dev-e596a01.0 - resolution: "@agoric/cosmic-proto@npm:0.4.1-dev-e596a01.0" + version: 0.4.1-dev-bdf5c17.0 + resolution: "@agoric/cosmic-proto@npm:0.4.1-dev-bdf5c17.0" dependencies: "@endo/base64": "npm:^1.0.9" "@endo/init": "npm:^1.1.7" - checksum: 10c0/2048e794ec9a346fb3a618b1b64d54985241967930b8b34c9220316b206fca4d3ecdf738e23e56021d45c3818f4513842e6d4c4d917a537dad59c13651d0ae35 + bech32: "npm:^2.0.0" + query-string: "npm:^9.1.1" + checksum: 10c0/20d4f8763a091b0b741c754fcceb82d666c4eb55bab2eaaef8821f8f7da644e2ee70c1134ef0e1cf90cc940150d61437d935913549d0da8ea17a8f0c80f2d36c languageName: node linkType: hard @@ -1028,6 +1030,13 @@ __metadata: languageName: node linkType: hard +"bech32@npm:^2.0.0": + version: 2.0.0 + resolution: "bech32@npm:2.0.0" + checksum: 10c0/45e7cc62758c9b26c05161b4483f40ea534437cf68ef785abadc5b62a2611319b878fef4f86ddc14854f183b645917a19addebc9573ab890e19194bc8f521942 + languageName: node + linkType: hard + "bfs-path@npm:^1.0.2": version: 1.0.2 resolution: "bfs-path@npm:1.0.2" @@ -1369,6 +1378,13 @@ __metadata: languageName: node linkType: hard +"decode-uri-component@npm:^0.4.1": + version: 0.4.1 + resolution: "decode-uri-component@npm:0.4.1" + checksum: 10c0/a180bbdb5398ec8270d236a3ac07cb988bbf6097428481780b85840f088951dc0318a8d8f9d56796e1a322b55b29859cea29982f22f9b03af0bc60974c54e591 + languageName: node + linkType: hard + "deep-is@npm:^0.1.3": version: 0.1.4 resolution: "deep-is@npm:0.1.4" @@ -1764,6 +1780,13 @@ __metadata: languageName: node linkType: hard +"filter-obj@npm:^5.1.0": + version: 5.1.0 + resolution: "filter-obj@npm:5.1.0" + checksum: 10c0/716e8ad2bc352e206556b3e5695b3cdff8aab80c53ea4b00c96315bbf467b987df3640575100aef8b84e812cf5ea4251db4cd672bbe33b1e78afea88400c67dd + languageName: node + linkType: hard + "find-up-simple@npm:^1.0.0": version: 1.0.0 resolution: "find-up-simple@npm:1.0.0" @@ -2924,6 +2947,17 @@ __metadata: languageName: node linkType: hard +"query-string@npm:^9.1.1": + version: 9.1.1 + resolution: "query-string@npm:9.1.1" + dependencies: + decode-uri-component: "npm:^0.4.1" + filter-obj: "npm:^5.1.0" + split-on-first: "npm:^3.0.0" + checksum: 10c0/16481f17754f660aec3cae7abb838a70e383dfcf152414d184e0d0f81fae426acf112b4d51bf754f9c256eaf83ba4241241ba907c8d58b6ed9704425e1712e8c + languageName: node + linkType: hard + "queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" @@ -3183,6 +3217,13 @@ __metadata: languageName: node linkType: hard +"split-on-first@npm:^3.0.0": + version: 3.0.0 + resolution: "split-on-first@npm:3.0.0" + checksum: 10c0/a1262eae12b68de235e1a08e011bf5b42c42621985ddf807e6221fb1e2b3304824913ae7019f18436b96b8fab8aef5f1ad80dedd2385317fdc51b521c3882cd0 + languageName: node + linkType: hard + "sprintf-js@npm:~1.0.2": version: 1.0.3 resolution: "sprintf-js@npm:1.0.3" diff --git a/packages/agoric-cli/src/commands/auction.js b/packages/agoric-cli/src/commands/auction.js index ac9306ffb45..684cd4356a4 100644 --- a/packages/agoric-cli/src/commands/auction.js +++ b/packages/agoric-cli/src/commands/auction.js @@ -1,6 +1,10 @@ // @ts-check /* eslint-env node */ -import { fetchEnvNetworkConfig, makeVstorageKit } from '@agoric/client-utils'; +import { + fetchEnvNetworkConfig, + makeAgoricNames, + makeVstorageKit, +} from '@agoric/client-utils'; import { Fail } from '@endo/errors'; import { InvalidArgumentError } from 'commander'; import { outputActionAndHint } from '../lib/wallet.js'; @@ -88,10 +92,11 @@ export const makeAuctionCommand = ( * }} opts */ async opts => { - const { agoricNames, readPublished } = await makeVstorageKit( + const { readPublished, ...vsk } = makeVstorageKit( { fetch }, networkConfig, ); + const agoricNames = await makeAgoricNames(vsk.fromBoard, vsk.vstorage); const { current } = await readPublished(`auction.governance`); diff --git a/packages/agoric-cli/src/commands/gov.js b/packages/agoric-cli/src/commands/gov.js index be116b87514..007b6b1a2ea 100644 --- a/packages/agoric-cli/src/commands/gov.js +++ b/packages/agoric-cli/src/commands/gov.js @@ -1,7 +1,11 @@ // @ts-check /* eslint-disable func-names */ /* eslint-env node */ -import { fetchEnvNetworkConfig, makeVstorageKit } from '@agoric/client-utils'; +import { + fetchEnvNetworkConfig, + makeAgoricNames, + makeVstorageKit, +} from '@agoric/client-utils'; import { execFileSync as execFileSyncAmbient } from 'child_process'; import { Command, CommanderError } from 'commander'; import { normalizeAddressWithOptions, pollBlocks } from '../lib/chain.js'; @@ -14,8 +18,10 @@ import { } from '../lib/wallet.js'; /** - * @import {OfferSpec} from '@agoric/smart-wallet/src/offers.js' - * @import {QuestionDetails} from '@agoric/governance/src/types.js' + * @import {OfferSpec} from '@agoric/smart-wallet/src/offers.js'; + * @import {AgoricNamesRemotes} from '@agoric/vats/tools/board-utils.js'; + * @import {CurrentWalletRecord} from '@agoric/smart-wallet/src/smartWallet.js'; + * @import {VstorageKit} from '@agoric/client-utils'; */ const collectValues = (val, memo) => { @@ -85,19 +91,21 @@ export const makeGovCommand = (_logger, io = {}) => { * given a sendFrom address; else print it. * * @param {{ - * toOffer: (agoricNames: *, current: import('@agoric/smart-wallet/src/smartWallet.js').CurrentWalletRecord | undefined) => OfferSpec, + * toOffer: (agoricNames: AgoricNamesRemotes, current: CurrentWalletRecord | undefined) => OfferSpec, * sendFrom?: string | undefined, * keyringBackend: string, * instanceName?: string, * }} detail - * @param {Awaited>} [optUtils] + * @param {VstorageKit} [vsk] */ const processOffer = async function ( { toOffer, sendFrom, keyringBackend }, - optUtils, + vsk, ) { - const utils = await (optUtils || makeVstorageKit({ fetch }, networkConfig)); - const { agoricNames, readPublished } = utils; + await null; + vsk ||= makeVstorageKit({ fetch }, networkConfig); + const { readPublished } = vsk; + const agoricNames = await makeAgoricNames(vsk.fromBoard, vsk.vstorage); assert(keyringBackend, 'missing keyring-backend option'); @@ -264,10 +272,11 @@ export const makeGovCommand = (_logger, io = {}) => { ) .requiredOption('--for ', 'description of the invitation') .action(async opts => { - const { agoricNames, readPublished } = await makeVstorageKit( + const { readPublished, ...vsk } = makeVstorageKit( { fetch }, networkConfig, ); + const agoricNames = await makeAgoricNames(vsk.fromBoard, vsk.vstorage); const current = await getCurrent(opts.from, { readPublished }); const known = findContinuingIds(current, agoricNames); @@ -293,10 +302,11 @@ export const makeGovCommand = (_logger, io = {}) => { normalizeAddress, ) .action(async opts => { - const { agoricNames, readPublished } = await makeVstorageKit( + const { readPublished, ...vsk } = makeVstorageKit( { fetch }, networkConfig, ); + const agoricNames = await makeAgoricNames(vsk.fromBoard, vsk.vstorage); const current = await getCurrent(opts.from, { readPublished }); const found = findContinuingIds(current, agoricNames); @@ -332,8 +342,8 @@ export const makeGovCommand = (_logger, io = {}) => { normalizeAddress, ) .action(async function (opts, options) { - const utils = await makeVstorageKit({ fetch }, networkConfig); - const { readPublished } = utils; + const vsk = makeVstorageKit({ fetch }, networkConfig); + const { readPublished } = vsk; const questionDesc = await readPublished( `committees.${opts.pathname}.latestQuestion`, @@ -383,7 +393,7 @@ export const makeGovCommand = (_logger, io = {}) => { sendFrom: opts.sendFrom, keyringBackend: options.optsWithGlobals().keyringBackend, }, - utils, + vsk, ); }); diff --git a/packages/agoric-cli/src/commands/oracle.js b/packages/agoric-cli/src/commands/oracle.js index 2882cef094a..e91d9967be7 100644 --- a/packages/agoric-cli/src/commands/oracle.js +++ b/packages/agoric-cli/src/commands/oracle.js @@ -3,6 +3,7 @@ /* eslint-env node */ import { fetchEnvNetworkConfig, + makeAgoricNames, makeVstorageKit, makeWalletUtils, storageHelper, @@ -90,19 +91,20 @@ export const makeOracleCommand = (logger, io = {}) => { env: process.env, fetch, }); - const utils = await makeVstorageKit({ fetch }, networkConfig); + const vsk = makeVstorageKit({ fetch }, networkConfig); + const agoricNames = await makeAgoricNames(vsk.fromBoard, vsk.vstorage); const lookupPriceAggregatorInstance = ([brandIn, brandOut]) => { const name = oracleBrandFeedName(brandIn, brandOut); - const instance = utils.agoricNames.instance[name]; + const instance = agoricNames.instance[name]; if (!instance) { - logger.debug('known instances:', utils.agoricNames.instance); + logger.debug('known instances:', agoricNames.instance); throw Error(`Unknown instance ${name}`); } return instance; }; - return { ...utils, networkConfig, lookupPriceAggregatorInstance }; + return { ...vsk, networkConfig, lookupPriceAggregatorInstance }; }; oracle diff --git a/packages/agoric-cli/src/commands/psm.js b/packages/agoric-cli/src/commands/psm.js index a59a8b81639..e800b5f1c16 100644 --- a/packages/agoric-cli/src/commands/psm.js +++ b/packages/agoric-cli/src/commands/psm.js @@ -3,6 +3,7 @@ /* eslint-env node */ import { fetchEnvNetworkConfig, + makeAgoricNames, makeVstorageKit, storageHelper, } from '@agoric/client-utils'; @@ -66,13 +67,14 @@ export const makePsmCommand = logger => { ); const rpcTools = async () => { - const utils = await makeVstorageKit({ fetch }, networkConfig); + const vsk = await makeVstorageKit({ fetch }, networkConfig); + const agoricNames = await makeAgoricNames(vsk.fromBoard, vsk.vstorage); const lookupPsmInstance = ([minted, anchor]) => { const name = `psm-${minted}-${anchor}`; - const instance = utils.agoricNames.instance[name]; + const instance = agoricNames.instance[name]; if (!instance) { - logger.debug('known instances:', utils.agoricNames.instance); + logger.debug('known instances:', agoricNames.instance); throw Error(`Unknown instance ${name}`); } return instance; @@ -83,20 +85,19 @@ export const makePsmCommand = logger => { * @param {[Minted: string, Anchor: string]} pair */ const getGovernanceState = async ([Minted, Anchor]) => { - const govContent = await utils.vstorage.readLatest( + const govContent = await vsk.vstorage.readLatest( `published.psm.${Minted}.${Anchor}.governance`, ); assert(govContent, 'no gov content'); const { current: governance } = last( - storageHelper.unserializeTxt(govContent, utils.fromBoard), + storageHelper.unserializeTxt(govContent, vsk.fromBoard), ); - const { [`psm.${Minted}.${Anchor}`]: instance } = - utils.agoricNames.instance; + const { [`psm.${Minted}.${Anchor}`]: instance } = agoricNames.instance; return { instance, governance }; }; - return { ...utils, lookupPsmInstance, getGovernanceState }; + return { ...vsk, agoricNames, lookupPsmInstance, getGovernanceState }; }; psm diff --git a/packages/agoric-cli/src/commands/reserve.js b/packages/agoric-cli/src/commands/reserve.js index eebb6c7d73d..9716590402e 100644 --- a/packages/agoric-cli/src/commands/reserve.js +++ b/packages/agoric-cli/src/commands/reserve.js @@ -1,7 +1,11 @@ // @ts-check /* eslint-disable func-names */ /* eslint-env node */ -import { fetchEnvNetworkConfig, makeVstorageKit } from '@agoric/client-utils'; +import { + fetchEnvNetworkConfig, + makeAgoricNames, + makeVstorageKit, +} from '@agoric/client-utils'; import { Offers } from '@agoric/inter-protocol/src/clientSupport.js'; import { Command } from 'commander'; import { outputActionAndHint } from '../lib/wallet.js'; @@ -31,7 +35,8 @@ export const makeReserveCommand = (_logger, io = {}) => { * }} opts */ async ({ collateralBrand, ...opts }) => { - const { agoricNames } = await makeVstorageKit({ fetch }, networkConfig); + const vsk = makeVstorageKit({ fetch }, networkConfig); + const agoricNames = await makeAgoricNames(vsk.fromBoard, vsk.vstorage); const offer = Offers.reserve.AddCollateral(agoricNames, { collateralBrandKey: collateralBrand, @@ -65,7 +70,8 @@ export const makeReserveCommand = (_logger, io = {}) => { 1, ) .action(async function (opts) { - const { agoricNames } = await makeVstorageKit({ fetch }, networkConfig); + const vsk = makeVstorageKit({ fetch }, networkConfig); + const agoricNames = await makeAgoricNames(vsk.fromBoard, vsk.vstorage); const reserveInstance = agoricNames.instance.reserve; assert(reserveInstance, 'missing reserve in names'); diff --git a/packages/agoric-cli/src/commands/vaults.js b/packages/agoric-cli/src/commands/vaults.js index 51032d89482..006831862e7 100644 --- a/packages/agoric-cli/src/commands/vaults.js +++ b/packages/agoric-cli/src/commands/vaults.js @@ -1,7 +1,11 @@ // @ts-check /* eslint-disable func-names */ /* eslint-env node */ -import { fetchEnvNetworkConfig, makeVstorageKit } from '@agoric/client-utils'; +import { + fetchEnvNetworkConfig, + makeAgoricNames, + makeVstorageKit, +} from '@agoric/client-utils'; import { lookupOfferIdForVault, Offers, @@ -63,7 +67,8 @@ export const makeVaultsCommand = logger => { .option('--collateralBrand ', 'Collateral brand key', 'ATOM') .action(async function (opts) { logger.warn('running with options', opts); - const { agoricNames } = await makeVstorageKit({ fetch }, networkConfig); + const vsk = makeVstorageKit({ fetch }, networkConfig); + const agoricNames = await makeAgoricNames(vsk.fromBoard, vsk.vstorage); const offer = Offers.vaults.OpenVault(agoricNames, { giveCollateral: opts.giveCollateral, @@ -98,10 +103,11 @@ export const makeVaultsCommand = logger => { .requiredOption('--vaultId ', 'Key of vault (e.g. vault1)') .action(async function (opts) { logger.warn('running with options', opts); - const { agoricNames, readPublished } = await makeVstorageKit( + const { readPublished, ...vsk } = makeVstorageKit( { fetch }, networkConfig, ); + const agoricNames = await makeAgoricNames(vsk.fromBoard, vsk.vstorage); const previousOfferId = await lookupOfferIdForVault( opts.vaultId, @@ -142,10 +148,11 @@ export const makeVaultsCommand = logger => { ) .action(async function (opts) { logger.warn('running with options', opts); - const { agoricNames, readPublished } = await makeVstorageKit( + const { readPublished, ...vsk } = makeVstorageKit( { fetch }, networkConfig, ); + const agoricNames = await makeAgoricNames(vsk.fromBoard, vsk.vstorage); const previousOfferId = await lookupOfferIdForVault( opts.vaultId, diff --git a/packages/agoric-cli/src/commands/wallet.js b/packages/agoric-cli/src/commands/wallet.js index e41258716d4..18d3d597c2b 100644 --- a/packages/agoric-cli/src/commands/wallet.js +++ b/packages/agoric-cli/src/commands/wallet.js @@ -8,7 +8,11 @@ import { makeLeader, makeLeaderFromRpcAddresses, } from '@agoric/casting'; -import { makeVstorageKit, fetchEnvNetworkConfig } from '@agoric/client-utils'; +import { + makeVstorageKit, + fetchEnvNetworkConfig, + makeAgoricNames, +} from '@agoric/client-utils'; import { execFileSync } from 'child_process'; import fs from 'fs'; import util from 'util'; @@ -214,13 +218,11 @@ export const makeWalletCommand = async command => { normalizeAddress, ) .action(async function (opts) { - const { agoricNames, unserializer, readPublished } = - await makeVstorageKit( - { - fetch, - }, - networkConfig, - ); + const { readPublished, unserializer, ...vsk } = makeVstorageKit( + { fetch }, + networkConfig, + ); + const agoricNames = await makeAgoricNames(vsk.fromBoard, vsk.vstorage); const leader = makeLeader(networkConfig.rpcAddrs[0]); const follower = await makeFollower( diff --git a/packages/agoric-cli/src/lib/wallet.js b/packages/agoric-cli/src/lib/wallet.js index e9eff2f2ca1..c4e66cf2437 100644 --- a/packages/agoric-cli/src/lib/wallet.js +++ b/packages/agoric-cli/src/lib/wallet.js @@ -149,7 +149,7 @@ export const coalesceWalletState = async (follower, invitationBrand) => { * fees?: string, * verbose?: boolean, * keyring?: {home?: string, backend: string}, - * stdout: Pick, + * stdout?: Pick, * execFileSync: typeof import('child_process').execFileSync, * delay: (ms: number) => Promise, * dryRun?: boolean, diff --git a/packages/boot/test/bootstrapTests/price-feed-replace.test.ts b/packages/boot/test/bootstrapTests/price-feed-replace.test.ts index c9a6178f0fd..d2cd27af919 100644 --- a/packages/boot/test/bootstrapTests/price-feed-replace.test.ts +++ b/packages/boot/test/bootstrapTests/price-feed-replace.test.ts @@ -105,7 +105,7 @@ test.serial('setupVaults; run updatePriceFeeds proposals', async t => { t.log('building all relevant CoreEvals'); const coreEvals = await Promise.all([ - buildProposal(priceFeedBuilder, ['MAINNET']), + buildProposal(priceFeedBuilder, ['BOOT_TEST']), buildProposal('@agoric/builders/scripts/vats/upgradeVaults.js'), buildProposal('@agoric/builders/scripts/vats/add-auction.js'), ]); diff --git a/packages/boot/test/bootstrapTests/updateGovernedParams.test.ts b/packages/boot/test/bootstrapTests/updateGovernedParams.test.ts index 5410fcc59d7..07c4e711289 100644 --- a/packages/boot/test/bootstrapTests/updateGovernedParams.test.ts +++ b/packages/boot/test/bootstrapTests/updateGovernedParams.test.ts @@ -134,7 +134,7 @@ test('modify manager & director params; update vats, check', async t => { const priceFeedBuilder = '@agoric/builders/scripts/inter-protocol/updatePriceFeeds.js'; const coreEvals = await Promise.all([ - buildProposal(priceFeedBuilder, ['MAINNET']), + buildProposal(priceFeedBuilder, ['BOOT_TEST']), buildProposal('@agoric/builders/scripts/vats/upgradeVaults.js'), buildProposal('@agoric/builders/scripts/vats/add-auction.js'), ]); diff --git a/packages/boot/test/fast-usdc/fast-usdc.test.ts b/packages/boot/test/fast-usdc/fast-usdc.test.ts index 0853f6b183b..6eae95b9ad2 100644 --- a/packages/boot/test/fast-usdc/fast-usdc.test.ts +++ b/packages/boot/test/fast-usdc/fast-usdc.test.ts @@ -187,7 +187,7 @@ test.serial('writes account addresses to vstorage', async t => { await documentStorageSchema(t, storage, doc); }); -test.skip('makes usdc advance', async t => { +test.serial('makes usdc advance', async t => { const { walletFactoryDriver: wd, storage, diff --git a/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.md b/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.md index 4b19508a1d9..f27a41da2c4 100644 --- a/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.md +++ b/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.md @@ -17,10 +17,10 @@ Generated by [AVA](https://avajs.dev). { chainPolicies: { Arbitrum: { + attenuatedCttpBridgeAddress: '0xe298b93ffB5eA1FB628e0C0D55A43aeaC268e347', cctpTokenMessengerAddress: '0x19330d10D9Cc8751218eaf51E8885D058642E08A', chainId: 42161, confirmations: 2, - nobleContractAddress: '0x19330d10D9Cc8751218eaf51E8885D058642E08A', }, }, nobleAgoricChannelId: 'channel-21', diff --git a/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.snap b/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.snap index 0245ac61053..4647d12715f 100644 Binary files a/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.snap and b/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.snap differ diff --git a/packages/builders/scripts/inter-protocol/updatePriceFeeds.js b/packages/builders/scripts/inter-protocol/updatePriceFeeds.js index f5e71a6c137..f99db1caa59 100644 --- a/packages/builders/scripts/inter-protocol/updatePriceFeeds.js +++ b/packages/builders/scripts/inter-protocol/updatePriceFeeds.js @@ -41,6 +41,16 @@ const configurations = { ], inBrandNames: ['ATOM', 'stATOM', 'stOSMO', 'stTIA', 'stkATOM'], }, + BOOT_TEST: { + oracleAddresses: [ + 'agoric144rrhh4m09mh7aaffhm6xy223ym76gve2x7y78', // DSRV + 'agoric19d6gnr9fyp6hev4tlrg87zjrzsd5gzr5qlfq2p', // Stakin + 'agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8', // 01node + 'agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr', // Simply Staking + 'agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj', // P2P + ], + inBrandNames: ['ATOM'], + }, }; const { keys } = Object; diff --git a/packages/builders/scripts/testing/recorded-retired-instances.js b/packages/builders/scripts/testing/recorded-retired-instances.js new file mode 100644 index 00000000000..58bc79c969e --- /dev/null +++ b/packages/builders/scripts/testing/recorded-retired-instances.js @@ -0,0 +1,73 @@ +import { makeTracer } from '@agoric/internal'; +import { E } from '@endo/far'; + +const trace = makeTracer('RecordedRetired', true); + +/** + * @param {BootstrapPowers & + * PromiseSpaceOf<{ retiredContractInstances: MapStore; + * }> + * } powers + */ +export const testRecordedRetiredInstances = async ({ + consume: { + contractKits, + // governedContractKits, + retiredContractInstances: retiredContractInstancesP, + }, +}) => { + trace('Start'); + const retiredContractInstances = await retiredContractInstancesP; + + const auctionIDs = Array.from(retiredContractInstances.keys()).filter(k => + k.startsWith('auction'), + ); + assert(auctionIDs); + assert(auctionIDs.length === 1); + const auctionInstance = retiredContractInstances.get(auctionIDs[0]); + trace({ auctionInstance }); + // I don't know why it's neither in governedContractKits nor contractKits + // assert(await E(governedContractKits).get(auctionInstance)); + + const committeeIDs = Array.from(retiredContractInstances.keys()).filter(k => + k.startsWith('economicCommittee'), + ); + assert(committeeIDs); + assert(committeeIDs.length === 1); + const committeeInstance = retiredContractInstances.get(committeeIDs[0]); + assert(await E(contractKits).get(committeeInstance)); + + trace('done'); +}; +harden(testRecordedRetiredInstances); + +export const getManifestForRecordedRetiredInstances = () => { + return { + manifest: { + [testRecordedRetiredInstances.name]: { + consume: { + contractKits: true, + // governedContractKits: true, + retiredContractInstances: true, + }, + }, + }, + }; +}; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ +export const defaultProposalBuilder = async () => + harden({ + sourceSpec: + '@agoric/builders/scripts/testing/recorded-retired-instances.js', + getManifestCall: ['getManifestForRecordedRetiredInstances', {}], + }); + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ +export default async (homeP, endowments) => { + // import dynamically so the module can work in CoreEval environment + const dspModule = await import('@agoric/deploy-script-support'); + const { makeHelpers } = dspModule; + const { writeCoreEval } = await makeHelpers(homeP, endowments); + await writeCoreEval('recorded-retired', defaultProposalBuilder); +}; diff --git a/packages/builders/scripts/vats/upgrade-mintHolder.js b/packages/builders/scripts/vats/upgrade-mintHolder.js new file mode 100644 index 00000000000..18cb47854c2 --- /dev/null +++ b/packages/builders/scripts/vats/upgrade-mintHolder.js @@ -0,0 +1,126 @@ +import { makeHelpers } from '@agoric/deploy-script-support'; +import { getManifestForUpgradingMintHolder } from '@agoric/vats/src/proposals/upgrade-mintHolder-proposal.js'; + +const configurations = { + A3P_INTEGRATION: { + labelList: [ + 'USDC_axl', + 'USDT_grv', + 'DAI_axl', + 'DAI_grv', + 'stATOM', + 'USDC_grv', + 'ATOM', + 'USDT_axl', + 'USDC', + 'BLD', + ], + }, + MAINNET: { + labelList: [ + 'USDT', + 'USDT_axl', + 'USDT_grv', + 'USDC', + 'USDC_axl', + 'USDC_grv', + 'DAI_axl', + 'DAI_grv', + 'ATOM', + 'stATOM', + 'stkATOM', + 'stTIA', + 'stOSMO', + ], + }, + DEVNET: { + labelList: [ + 'stATOM3', + 'stATOM', + 'dATOM', + 'stOSMO', + 'stkATOM', + 'stATOM2', + 'STOSMO', + 'stTIA', + 'ATOM', + 'AUSD', + 'USDT_grv', + 'USDC_axl', + 'USDC_grv', + 'USDT_axl', + 'BLD', + ], + }, + EMERYNET: { + labelList: [ + 'ATOM', + 'USDT', + 'DAI_axl', + 'DAI_grv', + 'USDC_axl', + 'stOSMO', + 'stATOM', + 'stkATOM', + 'stOSMO2', + 'ToyUSD', + 'BLD', + ], + }, +}; + +const { keys } = Object; +const knownVariants = keys(configurations); + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ +export const defaultProposalBuilder = async ({ publishRef, install }, opts) => { + const config = opts.config || configurations[opts.variant]; + if (!config) { + const error = `Unknown variant "${opts.variant}". Expected one of ${knownVariants.join(', ')}`; + console.error(error); + throw Error(error); + } + const { labelList } = config; + + return harden({ + sourceSpec: '@agoric/vats/src/proposals/upgrade-mintHolder-proposal.js', + getManifestCall: [ + getManifestForUpgradingMintHolder.name, + { + labelList, + contractRef: publishRef(install('@agoric/vats/src/mintHolder.js')), + }, + ], + }); +}; + +const Usage = `agoric run upgrade-mintHolder.js ${[...knownVariants, ''].join(' | ')}`; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ +export default async (homeP, endowments) => { + const { scriptArgs } = endowments; + const variantOrConfig = scriptArgs?.[0]; + console.log('upgrade-mintHolder', variantOrConfig); + + const opts = {}; + + if (typeof variantOrConfig === 'string') { + if (variantOrConfig[0] === '{') { + try { + opts.config = JSON.parse(variantOrConfig); + } catch (err) { + throw Error(`Failed to parse config argument ${variantOrConfig}`); + } + } else { + opts.variant = variantOrConfig; + } + } else { + console.error(Usage); + throw Error(Usage); + } + + const { writeCoreEval } = await makeHelpers(homeP, endowments); + await writeCoreEval(`upgrade-mintHolder`, utils => + defaultProposalBuilder(utils, opts), + ); +}; diff --git a/packages/client-utils/src/main.js b/packages/client-utils/src/main.js index fad3e63225e..decf0366b5b 100644 --- a/packages/client-utils/src/main.js +++ b/packages/client-utils/src/main.js @@ -1,6 +1,7 @@ export * from './cli.js'; export * from './network-config.js'; export * from './rpc.js'; +export * from './smart-wallet-kit.js'; export * from './sync-tools.js'; export * from './vstorage.js'; export * from './vstorage-kit.js'; diff --git a/packages/client-utils/src/smart-wallet-kit.js b/packages/client-utils/src/smart-wallet-kit.js new file mode 100644 index 00000000000..a570fe9855a --- /dev/null +++ b/packages/client-utils/src/smart-wallet-kit.js @@ -0,0 +1,113 @@ +import { makeWalletStateCoalescer } from '@agoric/smart-wallet/src/utils.js'; +import { pollBlocks } from './chain.js'; +import { makeStargateClient } from './rpc.js'; +import { makeAgoricNames, makeVstorageKit } from './vstorage-kit.js'; + +/** + * @import {Amount, Brand} from '@agoric/ertp/src/types.js' + * @import {CurrentWalletRecord, UpdateRecord} from '@agoric/smart-wallet/src/smartWallet.js'; + * @import {MinimalNetworkConfig} from './network-config.js'; + */ + +/** + * Augment VstorageKit with addtional convenience methods for working with + * Agoric smart wallets. + * + * @param {object} root0 + * @param {typeof globalThis.fetch} root0.fetch + * @param {(ms: number) => Promise} root0.delay + * @param {MinimalNetworkConfig} networkConfig + */ +export const makeSmartWalletKit = async ({ fetch, delay }, networkConfig) => { + const vsk = makeVstorageKit({ fetch }, networkConfig); + + const client = await makeStargateClient(networkConfig, { fetch }); + + const agoricNames = await makeAgoricNames(vsk.fromBoard, vsk.vstorage); + + /** + * @param {string} from + * @param {number|string} [minHeight] + */ + const storedWalletState = async (from, minHeight = undefined) => { + const history = await vsk.vstorage.readFully( + `published.wallet.${from}`, + minHeight, + ); + + /** @type {{ Invitation: Brand<'set'> }} */ + // @ts-expect-error XXX how to narrow AssetKind to set? + const { Invitation } = agoricNames.brand; + const coalescer = makeWalletStateCoalescer(Invitation); + // update with oldest first + for (const txt of history.reverse()) { + const { body, slots } = JSON.parse(txt); + const record = vsk.marshaller.fromCapData({ body, slots }); + coalescer.update(record); + } + const coalesced = coalescer.state; + harden(coalesced); + return coalesced; + }; + + /** + * Get OfferStatus by id, polling until available. + * + * @param {string} from + * @param {string|number} id + * @param {number|string} minHeight + * @param {boolean} [untilNumWantsSatisfied] + */ + const pollOffer = async ( + from, + id, + minHeight, + untilNumWantsSatisfied = false, + ) => { + const poll = pollBlocks({ + client, + delay, + retryMessage: 'offer not in wallet at block', + }); + + const lookup = async () => { + const { offerStatuses } = await storedWalletState(from, minHeight); + const offerStatus = [...offerStatuses.values()].find(s => s.id === id); + if (!offerStatus) throw Error('retry'); + harden(offerStatus); + if (untilNumWantsSatisfied && !('numWantsSatisfied' in offerStatus)) { + throw Error('retry (no numWantsSatisfied yet)'); + } + return offerStatus; + }; + return poll(lookup); + }; + + /** + * @param {string} addr + * @returns {Promise} + */ + const getLastUpdate = addr => { + return vsk.readPublished(`wallet.${addr}`); + }; + + /** + * @param {string} addr + * @returns {Promise} + */ + const getCurrentWalletRecord = addr => { + return vsk.readPublished(`wallet.${addr}.current`); + }; + + return { + // pass along all of VstorageKit + ...vsk, + agoricNames, + networkConfig, + getLastUpdate, + getCurrentWalletRecord, + storedWalletState, + pollOffer, + }; +}; +/** @typedef {Awaited>} SmartWalletKit */ diff --git a/packages/client-utils/src/vstorage-kit.js b/packages/client-utils/src/vstorage-kit.js index bca8d7b074e..6643d81fe63 100644 --- a/packages/client-utils/src/vstorage-kit.js +++ b/packages/client-utils/src/vstorage-kit.js @@ -2,6 +2,7 @@ import { boardSlottingMarshaller, makeBoardRemote, } from '@agoric/vats/tools/board-utils.js'; +import { assertAllDefined } from '@agoric/internal'; import { makeVStorage } from './vstorage.js'; export { boardSlottingMarshaller }; @@ -73,6 +74,7 @@ harden(storageHelper); * @returns {Promise} */ export const makeAgoricNames = async (ctx, vstorage) => { + assertAllDefined({ ctx, vstorage }); const reverse = {}; const entries = await Promise.all( ['brand', 'instance', 'vbankAsset'].map(async kind => { @@ -96,12 +98,10 @@ export const makeAgoricNames = async (ctx, vstorage) => { * @param {{ fetch: typeof window.fetch }} io * @param {MinimalNetworkConfig} config */ -export const makeVstorageKit = async ({ fetch }, config) => { - await null; +export const makeVstorageKit = ({ fetch }, config) => { try { const vstorage = makeVStorage({ fetch }, config); const fromBoard = makeFromBoard(); - const agoricNames = await makeAgoricNames(fromBoard, vstorage); const marshaller = boardSlottingMarshaller(fromBoard.convertSlotToVal); @@ -131,7 +131,6 @@ export const makeVstorageKit = async ({ fetch }, config) => { readLatestHead(`published.${subpath}`); return { - agoricNames, fromBoard, marshaller, readLatestHead, @@ -143,4 +142,4 @@ export const makeVstorageKit = async ({ fetch }, config) => { throw Error(`RPC failure (${config.rpcAddrs}): ${err.message}`); } }; -/** @typedef {Awaited>} VstorageKit */ +/** @typedef {ReturnType} VstorageKit */ diff --git a/packages/client-utils/src/wallet-utils.js b/packages/client-utils/src/wallet-utils.js index 2cd63a1498d..79f0d9edc96 100644 --- a/packages/client-utils/src/wallet-utils.js +++ b/packages/client-utils/src/wallet-utils.js @@ -1,113 +1,8 @@ -import { makeWalletStateCoalescer } from '@agoric/smart-wallet/src/utils.js'; -import { pollBlocks } from './chain.js'; -import { makeStargateClient } from './rpc.js'; -import { boardSlottingMarshaller, makeVstorageKit } from './vstorage-kit.js'; +/** @file backwards compat */ -/** - * @import {Amount, Brand} from '@agoric/ertp/src/types.js' - * @import {CurrentWalletRecord, UpdateRecord} from '@agoric/smart-wallet/src/smartWallet.js'; - * @import {MinimalNetworkConfig} from './network-config.js'; - */ +import { makeSmartWalletKit } from './smart-wallet-kit.js'; -// XXX this is really a SmartWalletKit -/** - * Augment VstorageKit with addtional convenience methods for working with - * Agoric smart wallets. - * - * @param {object} root0 - * @param {typeof globalThis.fetch} root0.fetch - * @param {(ms: number) => Promise} root0.delay - * @param {MinimalNetworkConfig} networkConfig - */ -export const makeWalletUtils = async ({ fetch, delay }, networkConfig) => { - const vsk = await makeVstorageKit({ fetch }, networkConfig); +/** @typedef {import('./smart-wallet-kit.js').SmartWalletKit} WalletUtils */ - const m = boardSlottingMarshaller(vsk.fromBoard.convertSlotToVal); - - const client = await makeStargateClient(networkConfig, { fetch }); - - /** - * @param {string} from - * @param {number|string} [minHeight] - */ - const storedWalletState = async (from, minHeight = undefined) => { - const history = await vsk.vstorage.readFully( - `published.wallet.${from}`, - minHeight, - ); - - /** @type {{ Invitation: Brand<'set'> }} */ - // @ts-expect-error XXX how to narrow AssetKind to set? - const { Invitation } = vsk.agoricNames.brand; - const coalescer = makeWalletStateCoalescer(Invitation); - // update with oldest first - for (const txt of history.reverse()) { - const { body, slots } = JSON.parse(txt); - const record = m.fromCapData({ body, slots }); - coalescer.update(record); - } - const coalesced = coalescer.state; - harden(coalesced); - return coalesced; - }; - - /** - * Get OfferStatus by id, polling until available. - * - * @param {string} from - * @param {string|number} id - * @param {number|string} minHeight - * @param {boolean} [untilNumWantsSatisfied] - */ - const pollOffer = async ( - from, - id, - minHeight, - untilNumWantsSatisfied = false, - ) => { - const poll = pollBlocks({ - client, - delay, - retryMessage: 'offer not in wallet at block', - }); - - const lookup = async () => { - const { offerStatuses } = await storedWalletState(from, minHeight); - const offerStatus = [...offerStatuses.values()].find(s => s.id === id); - if (!offerStatus) throw Error('retry'); - harden(offerStatus); - if (untilNumWantsSatisfied && !('numWantsSatisfied' in offerStatus)) { - throw Error('retry (no numWantsSatisfied yet)'); - } - return offerStatus; - }; - return poll(lookup); - }; - - /** - * @param {string} addr - * @returns {Promise} - */ - const getLastUpdate = addr => { - return vsk.readPublished(`wallet.${addr}`); - }; - - /** - * @param {string} addr - * @returns {Promise} - */ - const getCurrentWalletRecord = addr => { - return vsk.readPublished(`wallet.${addr}.current`); - }; - - return { - // pass along all of VstorageKit - ...vsk, - networkConfig, - getLastUpdate, - getCurrentWalletRecord, - storedWalletState, - pollOffer, - }; -}; -/** @typedef {Awaited>} WalletUtils */ +/** @deprecated use `makeSmartWalletKit` */ +export const makeWalletUtils = makeSmartWalletKit; diff --git a/packages/client-utils/test/snapshots/exports.test.js.md b/packages/client-utils/test/snapshots/exports.test.js.md index 22f45e43b82..1dc0f08ff90 100644 --- a/packages/client-utils/test/snapshots/exports.test.js.md +++ b/packages/client-utils/test/snapshots/exports.test.js.md @@ -16,6 +16,7 @@ Generated by [AVA](https://avajs.dev). 'fetchNetworkConfig', 'makeAgoricNames', 'makeFromBoard', + 'makeSmartWalletKit', 'makeStargateClient', 'makeTendermint34Client', 'makeVStorage', diff --git a/packages/client-utils/test/snapshots/exports.test.js.snap b/packages/client-utils/test/snapshots/exports.test.js.snap index c6c052973b5..f44f9a7329c 100644 Binary files a/packages/client-utils/test/snapshots/exports.test.js.snap and b/packages/client-utils/test/snapshots/exports.test.js.snap differ diff --git a/packages/client-utils/test/snapshots/vstorage-kit.test.js.md b/packages/client-utils/test/snapshots/vstorage-kit.test.js.md new file mode 100644 index 00000000000..fb788564f20 --- /dev/null +++ b/packages/client-utils/test/snapshots/vstorage-kit.test.js.md @@ -0,0 +1,352 @@ +# Snapshot report for `test/vstorage-kit.test.js` + +The actual snapshot is saved in `vstorage-kit.test.js.snap`. + +Generated by [AVA](https://avajs.dev). + +## agoricNames contains expected structure + +> agoricNames from A3P + + { + brand: { + ATOM: Object @Alleged: BoardRemoteATOM brand { + getBoardId: Function getBoardId {}, + }, + BLD: Object @Alleged: BoardRemoteBLD brand { + getBoardId: Function getBoardId {}, + }, + DAI_axl: Object @Alleged: BoardRemoteDAI_axl brand { + getBoardId: Function getBoardId {}, + }, + DAI_grv: Object @Alleged: BoardRemoteDAI_grv brand { + getBoardId: Function getBoardId {}, + }, + IST: Object @Alleged: BoardRemoteIST brand { + getBoardId: Function getBoardId {}, + }, + Invitation: Object @Alleged: BoardRemoteZoe Invitation brand { + getBoardId: Function getBoardId {}, + }, + KREAdCHARACTER: Object @Alleged: BoardRemoteKREAdCHARACTER brand { + getBoardId: Function getBoardId {}, + }, + KREAdITEM: Object @Alleged: BoardRemoteKREAdITEM brand { + getBoardId: Function getBoardId {}, + }, + USDC_axl: Object @Alleged: BoardRemoteUSDC_axl brand { + getBoardId: Function getBoardId {}, + }, + USDC_grv: Object @Alleged: BoardRemoteUSDC_grv brand { + getBoardId: Function getBoardId {}, + }, + USDT_axl: Object @Alleged: BoardRemoteUSDT_axl brand { + getBoardId: Function getBoardId {}, + }, + USDT_grv: Object @Alleged: BoardRemoteUSDT_grv brand { + getBoardId: Function getBoardId {}, + }, + stATOM: Object @Alleged: BoardRemotestATOM brand { + getBoardId: Function getBoardId {}, + }, + timer: Object @Alleged: BoardRemotetimerBrand { + getBoardId: Function getBoardId {}, + }, + }, + instance: { + 'ATOM-USD price feed': Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + Crabble: Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + CrabbleCommittee: Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + CrabbleGovernor: Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + VaultFactory: Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + VaultFactoryGovernor: Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + auctioneer: Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + econCommitteeCharter: Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + economicCommittee: Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + feeDistributor: Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + kread: Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + kreadCommittee: Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + kreadCommitteeCharter: Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + provisionPool: Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + 'psm-IST-DAI_axl': Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + 'psm-IST-DAI_grv': Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + 'psm-IST-USDC_axl': Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + 'psm-IST-USDC_grv': Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + 'psm-IST-USDT_axl': Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + 'psm-IST-USDT_grv': Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + reserve: Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + reserveGovernor: Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + 'scaledPriceAuthority-stATOM': Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + 'stATOM-USD price feed': Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + walletFactory: Object @Alleged: BoardRemoteInstanceHandle { + getBoardId: Function getBoardId {}, + }, + }, + reverse: { + board00282: 'KREAdITEM', + board00360: 'VaultFactory', + board0074: 'Invitation', + board00990: 'stATOM', + board01272: 'psm-IST-USDT_grv', + board01664: 'provisionPool', + board01744: 'USDT_axl', + board01867: 'psm-IST-DAI_axl', + board01985: 'kreadCommittee', + board02271: 'psm-IST-USDT_axl', + board02393: 'CrabbleCommittee', + board02568: 'psm-IST-DAI_grv', + board0257: 'IST', + board02963: 'ATOM-USD price feed', + board03040: 'USDC_axl', + board03138: 'DAI_grv', + board03281: 'KREAdCHARACTER', + board03365: 'reserveGovernor', + board03446: 'USDT_grv', + board03773: 'VaultFactoryGovernor', + board04091: 'stATOM-USD price feed', + board04149: 'economicCommittee', + board0425: 'timer', + board04395: 'Crabble', + board04542: 'USDC_grv', + board04661: 'econCommitteeCharter', + board04783: 'kread', + board05262: 'feeDistributor', + board05396: 'CrabbleGovernor', + board05557: 'ATOM', + board0566: 'BLD', + board05669: 'psm-IST-USDC_axl', + board05736: 'DAI_axl', + board05892: 'scaledPriceAuthority-stATOM', + board05970: 'psm-IST-USDC_grv', + board06284: 'kreadCommitteeCharter', + board06299: 'auctioneer', + board06366: 'walletFactory', + board06458: 'reserve', + }, + vbankAsset: { + 'ibc/295548A78785A1007F232DE286149A6FF512F180AF5657780FC89C009E2C348F': { + brand: Object @Alleged: BoardRemoteUSDC_axl brand { + getBoardId: Function getBoardId {}, + }, + denom: 'ibc/295548A78785A1007F232DE286149A6FF512F180AF5657780FC89C009E2C348F', + displayInfo: { + assetKind: 'nat', + decimalPlaces: 6, + }, + issuer: Object @Alleged: BoardRemoteUSDC_axl issuer { + getBoardId: Function getBoardId {}, + }, + issuerName: 'USDC_axl', + proposedName: 'USD Coin', + }, + 'ibc/386D09AE31DA7C0C93091BB45D08CB7A0730B1F697CD813F06A5446DCF02EEB2': { + brand: Object @Alleged: BoardRemoteUSDT_grv brand { + getBoardId: Function getBoardId {}, + }, + denom: 'ibc/386D09AE31DA7C0C93091BB45D08CB7A0730B1F697CD813F06A5446DCF02EEB2', + displayInfo: { + assetKind: 'nat', + decimalPlaces: 6, + }, + issuer: Object @Alleged: BoardRemoteUSDT_grv issuer { + getBoardId: Function getBoardId {}, + }, + issuerName: 'USDT_grv', + proposedName: 'Tether USD', + }, + 'ibc/3914BDEF46F429A26917E4D8D434620EC4817DC6B6E68FB327E190902F1E9242': { + brand: Object @Alleged: BoardRemoteDAI_axl brand { + getBoardId: Function getBoardId {}, + }, + denom: 'ibc/3914BDEF46F429A26917E4D8D434620EC4817DC6B6E68FB327E190902F1E9242', + displayInfo: { + assetKind: 'nat', + decimalPlaces: 18, + }, + issuer: Object @Alleged: BoardRemoteDAI_axl issuer { + getBoardId: Function getBoardId {}, + }, + issuerName: 'DAI_axl', + proposedName: 'DAI', + }, + 'ibc/3D5291C23D776C3AA7A7ABB34C7B023193ECD2BC42EA19D3165B2CF9652117E7': { + brand: Object @Alleged: BoardRemoteDAI_grv brand { + getBoardId: Function getBoardId {}, + }, + denom: 'ibc/3D5291C23D776C3AA7A7ABB34C7B023193ECD2BC42EA19D3165B2CF9652117E7', + displayInfo: { + assetKind: 'nat', + decimalPlaces: 18, + }, + issuer: Object @Alleged: BoardRemoteDAI_grv issuer { + getBoardId: Function getBoardId {}, + }, + issuerName: 'DAI_grv', + proposedName: 'DAI', + }, + 'ibc/42225F147137DDEB5FEF0F1D0A92F2AD57557AFA2C4D6F30B21E0D983001C002': { + brand: Object @Alleged: BoardRemotestATOM brand { + getBoardId: Function getBoardId {}, + }, + denom: 'ibc/42225F147137DDEB5FEF0F1D0A92F2AD57557AFA2C4D6F30B21E0D983001C002', + displayInfo: { + assetKind: 'nat', + decimalPlaces: 6, + }, + issuer: Object @Alleged: BoardRemotestATOM issuer { + getBoardId: Function getBoardId {}, + }, + issuerName: 'stATOM', + proposedName: 'stATOM', + }, + 'ibc/6831292903487E58BF9A195FDDC8A2E626B3DF39B88F4E7F41C935CADBAF54AC': { + brand: Object @Alleged: BoardRemoteUSDC_grv brand { + getBoardId: Function getBoardId {}, + }, + denom: 'ibc/6831292903487E58BF9A195FDDC8A2E626B3DF39B88F4E7F41C935CADBAF54AC', + displayInfo: { + assetKind: 'nat', + decimalPlaces: 6, + }, + issuer: Object @Alleged: BoardRemoteUSDC_grv issuer { + getBoardId: Function getBoardId {}, + }, + issuerName: 'USDC_grv', + proposedName: 'USC Coin', + }, + 'ibc/BA313C4A19DFBF943586C0387E6B11286F9E416B4DD27574E6909CABE0E342FA': { + brand: Object @Alleged: BoardRemoteATOM brand { + getBoardId: Function getBoardId {}, + }, + denom: 'ibc/BA313C4A19DFBF943586C0387E6B11286F9E416B4DD27574E6909CABE0E342FA', + displayInfo: { + assetKind: 'nat', + decimalPlaces: 6, + }, + issuer: Object @Alleged: BoardRemoteATOM issuer { + getBoardId: Function getBoardId {}, + }, + issuerName: 'ATOM', + proposedName: 'ATOM', + }, + 'ibc/F2331645B9683116188EF36FC04A809C28BD36B54555E8705A37146D0182F045': { + brand: Object @Alleged: BoardRemoteUSDT_axl brand { + getBoardId: Function getBoardId {}, + }, + denom: 'ibc/F2331645B9683116188EF36FC04A809C28BD36B54555E8705A37146D0182F045', + displayInfo: { + assetKind: 'nat', + decimalPlaces: 6, + }, + issuer: Object @Alleged: BoardRemoteUSDT_axl issuer { + getBoardId: Function getBoardId {}, + }, + issuerName: 'USDT_axl', + proposedName: 'Tether USD', + }, + ubld: { + brand: Object @Alleged: BoardRemoteBLD brand { + getBoardId: Function getBoardId {}, + }, + denom: 'ubld', + displayInfo: { + assetKind: 'nat', + decimalPlaces: 6, + }, + issuer: Object @Alleged: BoardRemoteBLD issuer { + getBoardId: Function getBoardId {}, + }, + issuerName: 'BLD', + proposedName: 'Agoric staking token', + }, + uist: { + brand: Object @Alleged: BoardRemoteIST brand { + getBoardId: Function getBoardId {}, + }, + denom: 'uist', + displayInfo: { + assetKind: 'nat', + decimalPlaces: 6, + }, + issuer: Object @Alleged: BoardRemoteIST issuer { + getBoardId: Function getBoardId {}, + }, + issuerName: 'IST', + proposedName: 'Agoric stable token', + }, + }, + } + +> priceFeed from A3P + + { + amountIn: { + brand: Object @Alleged: BoardRemoteBrand { + getBoardId: Function getBoardId {}, + }, + value: 1000000n, + }, + amountOut: { + brand: Object @Alleged: BoardRemoteBrand { + getBoardId: Function getBoardId {}, + }, + value: 12010000n, + }, + timer: Object @Alleged: BoardRemotetimerService { + getBoardId: Function getBoardId {}, + }, + timestamp: { + absValue: 1729176290n, + timerBrand: Object @Alleged: BoardRemotetimerBrand { + getBoardId: Function getBoardId {}, + }, + }, + } diff --git a/packages/client-utils/test/snapshots/vstorage-kit.test.js.snap b/packages/client-utils/test/snapshots/vstorage-kit.test.js.snap new file mode 100644 index 00000000000..b36c073f925 Binary files /dev/null and b/packages/client-utils/test/snapshots/vstorage-kit.test.js.snap differ diff --git a/packages/client-utils/test/vstorage-kit.test.js b/packages/client-utils/test/vstorage-kit.test.js new file mode 100644 index 00000000000..a6d11c6972f --- /dev/null +++ b/packages/client-utils/test/vstorage-kit.test.js @@ -0,0 +1,88 @@ +/* eslint-env node */ +import test from 'ava'; +import { makeAgoricNames, makeVstorageKit } from '../src/vstorage-kit.js'; + +const makeMockFetch = (responses = {}) => { + return async url => { + const response = responses[url] || { + result: { + response: { + code: 0, + value: Buffer.from( + JSON.stringify({ value: '{"blockHeight":1,"values":[]}' }), + ).toString('base64'), + }, + }, + }; + return { json: () => Promise.resolve(response) }; + }; +}; + +const makeTestConfig = () => ({ + chainName: 'test-chain', + rpcAddrs: ['http://localhost:26657'], +}); + +test('agoricNames contains expected structure', async t => { + /** @type {typeof window.fetch} */ + // @ts-expect-error mock + const fetch = makeMockFetch({ + 'http://localhost:26657/abci_query?path=%22/custom/vstorage/data/published.agoricNames.brand%22&height=0': + { + result: { + response: { + code: 0, + value: + // observed in a3p + 'ewogICJ2YWx1ZSI6ICJ7XCJibG9ja0hlaWdodFwiOlwiNzY5XCIsXCJ2YWx1ZXNcIjpbXCJ7XFxcImJvZHlcXFwiOlxcXCIjW1tcXFxcXFxcIkFUT01cXFxcXFxcIixcXFxcXFxcIiQwLkFsbGVnZWQ6IEFUT00gYnJhbmRcXFxcXFxcIl0sW1xcXFxcXFwiQkxEXFxcXFxcXCIsXFxcXFxcXCIkMS5BbGxlZ2VkOiBCTEQgYnJhbmRcXFxcXFxcIl0sW1xcXFxcXFwiREFJX2F4bFxcXFxcXFwiLFxcXFxcXFwiJDIuQWxsZWdlZDogREFJX2F4bCBicmFuZFxcXFxcXFwiXSxbXFxcXFxcXCJEQUlfZ3J2XFxcXFxcXCIsXFxcXFxcXCIkMy5BbGxlZ2VkOiBEQUlfZ3J2IGJyYW5kXFxcXFxcXCJdLFtcXFxcXFxcIklTVFxcXFxcXFwiLFxcXFxcXFwiJDQuQWxsZWdlZDogSVNUIGJyYW5kXFxcXFxcXCJdLFtcXFxcXFxcIkludml0YXRpb25cXFxcXFxcIixcXFxcXFxcIiQ1LkFsbGVnZWQ6IFpvZSBJbnZpdGF0aW9uIGJyYW5kXFxcXFxcXCJdLFtcXFxcXFxcIktSRUFkQ0hBUkFDVEVSXFxcXFxcXCIsXFxcXFxcXCIkNi5BbGxlZ2VkOiBLUkVBZENIQVJBQ1RFUiBicmFuZFxcXFxcXFwiXSxbXFxcXFxcXCJLUkVBZElURU1cXFxcXFxcIixcXFxcXFxcIiQ3LkFsbGVnZWQ6IEtSRUFkSVRFTSBicmFuZFxcXFxcXFwiXSxbXFxcXFxcXCJVU0RDX2F4bFxcXFxcXFwiLFxcXFxcXFwiJDguQWxsZWdlZDogVVNEQ19heGwgYnJhbmRcXFxcXFxcIl0sW1xcXFxcXFwiVVNEQ19ncnZcXFxcXFxcIixcXFxcXFxcIiQ5LkFsbGVnZWQ6IFVTRENfZ3J2IGJyYW5kXFxcXFxcXCJdLFtcXFxcXFxcIlVTRFRfYXhsXFxcXFxcXCIsXFxcXFxcXCIkMTAuQWxsZWdlZDogVVNEVF9heGwgYnJhbmRcXFxcXFxcIl0sW1xcXFxcXFwiVVNEVF9ncnZcXFxcXFxcIixcXFxcXFxcIiQxMS5BbGxlZ2VkOiBVU0RUX2dydiBicmFuZFxcXFxcXFwiXSxbXFxcXFxcXCJ0aW1lclxcXFxcXFwiLFxcXFxcXFwiJDEyLkFsbGVnZWQ6IHRpbWVyQnJhbmRcXFxcXFxcIl0sW1xcXFxcXFwic3RBVE9NXFxcXFxcXCIsXFxcXFxcXCIkMTMuQWxsZWdlZDogc3RBVE9NIGJyYW5kXFxcXFxcXCJdXVxcXCIsXFxcInNsb3RzXFxcIjpbXFxcImJvYXJkMDU1NTdcXFwiLFxcXCJib2FyZDA1NjZcXFwiLFxcXCJib2FyZDA1NzM2XFxcIixcXFwiYm9hcmQwMzEzOFxcXCIsXFxcImJvYXJkMDI1N1xcXCIsXFxcImJvYXJkMDA3NFxcXCIsXFxcImJvYXJkMDMyODFcXFwiLFxcXCJib2FyZDAwMjgyXFxcIixcXFwiYm9hcmQwMzA0MFxcXCIsXFxcImJvYXJkMDQ1NDJcXFwiLFxcXCJib2FyZDAxNzQ0XFxcIixcXFwiYm9hcmQwMzQ0NlxcXCIsXFxcImJvYXJkMDQyNVxcXCIsXFxcImJvYXJkMDA5OTBcXFwiXX1cIl19Igp9', + }, + }, + }, + 'http://localhost:26657/abci_query?path=%22/custom/vstorage/data/published.agoricNames.instance%22&height=0': + { + result: { + response: { + code: 0, + value: + // observed in a3p + 'ewogICJ2YWx1ZSI6ICJ7XCJibG9ja0hlaWdodFwiOlwiMTE0MVwiLFwidmFsdWVzXCI6W1wie1xcXCJib2R5XFxcIjpcXFwiI1tbXFxcXFxcXCJBVE9NLVVTRCBwcmljZSBmZWVkXFxcXFxcXCIsXFxcXFxcXCIkMC5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJDcmFiYmxlXFxcXFxcXCIsXFxcXFxcXCIkMS5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJDcmFiYmxlQ29tbWl0dGVlXFxcXFxcXCIsXFxcXFxcXCIkMi5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJDcmFiYmxlR292ZXJub3JcXFxcXFxcIixcXFxcXFxcIiQzLkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcIlZhdWx0RmFjdG9yeVxcXFxcXFwiLFxcXFxcXFwiJDQuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwiVmF1bHRGYWN0b3J5R292ZXJub3JcXFxcXFxcIixcXFxcXFxcIiQ1LkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcImVjb25Db21taXR0ZWVDaGFydGVyXFxcXFxcXCIsXFxcXFxcXCIkNi5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJlY29ub21pY0NvbW1pdHRlZVxcXFxcXFwiLFxcXFxcXFwiJDcuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwiZmVlRGlzdHJpYnV0b3JcXFxcXFxcIixcXFxcXFxcIiQ4LkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcImtyZWFkXFxcXFxcXCIsXFxcXFxcXCIkOS5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJrcmVhZENvbW1pdHRlZVxcXFxcXFwiLFxcXFxcXFwiJDEwLkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcImtyZWFkQ29tbWl0dGVlQ2hhcnRlclxcXFxcXFwiLFxcXFxcXFwiJDExLkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcInByb3Zpc2lvblBvb2xcXFxcXFxcIixcXFxcXFxcIiQxMi5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJwc20tSVNULURBSV9heGxcXFxcXFxcIixcXFxcXFxcIiQxMy5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJwc20tSVNULURBSV9ncnZcXFxcXFxcIixcXFxcXFxcIiQxNC5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJwc20tSVNULVVTRENfYXhsXFxcXFxcXCIsXFxcXFxcXCIkMTUuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwicHNtLUlTVC1VU0RDX2dydlxcXFxcXFwiLFxcXFxcXFwiJDE2LkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcInBzbS1JU1QtVVNEVF9heGxcXFxcXFxcIixcXFxcXFxcIiQxNy5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJwc20tSVNULVVTRFRfZ3J2XFxcXFxcXCIsXFxcXFxcXCIkMTguQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwicmVzZXJ2ZVxcXFxcXFwiLFxcXFxcXFwiJDE5LkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcInJlc2VydmVHb3Zlcm5vclxcXFxcXFwiLFxcXFxcXFwiJDIwLkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcInNjYWxlZFByaWNlQXV0aG9yaXR5LXN0QVRPTVxcXFxcXFwiLFxcXFxcXFwiJDIxLkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcInN0QVRPTS1VU0QgcHJpY2UgZmVlZFxcXFxcXFwiLFxcXFxcXFwiJDIyLkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcIndhbGxldEZhY3RvcnlcXFxcXFxcIixcXFxcXFxcIiQyMy5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXV1cXFwiLFxcXCJzbG90c1xcXCI6W1xcXCJib2FyZDAyOTYzXFxcIixcXFwiYm9hcmQwNDM5NVxcXCIsXFxcImJvYXJkMDIzOTNcXFwiLFxcXCJib2FyZDA1Mzk2XFxcIixcXFwiYm9hcmQwMDM2MFxcXCIsXFxcImJvYXJkMDM3NzNcXFwiLFxcXCJib2FyZDA0NjYxXFxcIixcXFwiYm9hcmQwNDE0OVxcXCIsXFxcImJvYXJkMDUyNjJcXFwiLFxcXCJib2FyZDA0NzgzXFxcIixcXFwiYm9hcmQwMTk4NVxcXCIsXFxcImJvYXJkMDYyODRcXFwiLFxcXCJib2FyZDAxNjY0XFxcIixcXFwiYm9hcmQwMTg2N1xcXCIsXFxcImJvYXJkMDI1NjhcXFwiLFxcXCJib2FyZDA1NjY5XFxcIixcXFwiYm9hcmQwNTk3MFxcXCIsXFxcImJvYXJkMDIyNzFcXFwiLFxcXCJib2FyZDAxMjcyXFxcIixcXFwiYm9hcmQwNjQ1OFxcXCIsXFxcImJvYXJkMDMzNjVcXFwiLFxcXCJib2FyZDA1ODkyXFxcIixcXFwiYm9hcmQwNDA5MVxcXCIsXFxcImJvYXJkMDYzNjZcXFwiXX1cIixcIntcXFwiYm9keVxcXCI6XFxcIiNbW1xcXFxcXFwiQVRPTS1VU0QgcHJpY2UgZmVlZFxcXFxcXFwiLFxcXFxcXFwiJDAuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwiQ3JhYmJsZVxcXFxcXFwiLFxcXFxcXFwiJDEuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwiQ3JhYmJsZUNvbW1pdHRlZVxcXFxcXFwiLFxcXFxcXFwiJDIuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwiQ3JhYmJsZUdvdmVybm9yXFxcXFxcXCIsXFxcXFxcXCIkMy5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJWYXVsdEZhY3RvcnlcXFxcXFxcIixcXFxcXFxcIiQ0LkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcIlZhdWx0RmFjdG9yeUdvdmVybm9yXFxcXFxcXCIsXFxcXFxcXCIkNS5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJlY29uQ29tbWl0dGVlQ2hhcnRlclxcXFxcXFwiLFxcXFxcXFwiJDYuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwiZWNvbm9taWNDb21taXR0ZWVcXFxcXFxcIixcXFxcXFxcIiQ3LkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcImZlZURpc3RyaWJ1dG9yXFxcXFxcXCIsXFxcXFxcXCIkOC5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJrcmVhZFxcXFxcXFwiLFxcXFxcXFwiJDkuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwia3JlYWRDb21taXR0ZWVcXFxcXFxcIixcXFxcXFxcIiQxMC5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJrcmVhZENvbW1pdHRlZUNoYXJ0ZXJcXFxcXFxcIixcXFxcXFxcIiQxMS5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJwcm92aXNpb25Qb29sXFxcXFxcXCIsXFxcXFxcXCIkMTIuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwicHNtLUlTVC1EQUlfYXhsXFxcXFxcXCIsXFxcXFxcXCIkMTMuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwicHNtLUlTVC1EQUlfZ3J2XFxcXFxcXCIsXFxcXFxcXCIkMTQuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwicHNtLUlTVC1VU0RDX2F4bFxcXFxcXFwiLFxcXFxcXFwiJDE1LkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcInBzbS1JU1QtVVNEQ19ncnZcXFxcXFxcIixcXFxcXFxcIiQxNi5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJwc20tSVNULVVTRFRfYXhsXFxcXFxcXCIsXFxcXFxcXCIkMTcuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwicHNtLUlTVC1VU0RUX2dydlxcXFxcXFwiLFxcXFxcXFwiJDE4LkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcInJlc2VydmVcXFxcXFxcIixcXFxcXFxcIiQxOS5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJyZXNlcnZlR292ZXJub3JcXFxcXFxcIixcXFxcXFxcIiQyMC5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJzY2FsZWRQcmljZUF1dGhvcml0eS1zdEFUT01cXFxcXFxcIixcXFxcXFxcIiQyMS5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJzdEFUT00tVVNEIHByaWNlIGZlZWRcXFxcXFxcIixcXFxcXFxcIiQyMi5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJ3YWxsZXRGYWN0b3J5XFxcXFxcXCIsXFxcXFxcXCIkMjMuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwiYXVjdGlvbmVlclxcXFxcXFwiLFxcXFxcXFwiJDI0LkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdXVxcXCIsXFxcInNsb3RzXFxcIjpbXFxcImJvYXJkMDI5NjNcXFwiLFxcXCJib2FyZDA0Mzk1XFxcIixcXFwiYm9hcmQwMjM5M1xcXCIsXFxcImJvYXJkMDUzOTZcXFwiLFxcXCJib2FyZDAwMzYwXFxcIixcXFwiYm9hcmQwMzc3M1xcXCIsXFxcImJvYXJkMDQ2NjFcXFwiLFxcXCJib2FyZDA0MTQ5XFxcIixcXFwiYm9hcmQwNTI2MlxcXCIsXFxcImJvYXJkMDQ3ODNcXFwiLFxcXCJib2FyZDAxOTg1XFxcIixcXFwiYm9hcmQwNjI4NFxcXCIsXFxcImJvYXJkMDE2NjRcXFwiLFxcXCJib2FyZDAxODY3XFxcIixcXFwiYm9hcmQwMjU2OFxcXCIsXFxcImJvYXJkMDU2NjlcXFwiLFxcXCJib2FyZDA1OTcwXFxcIixcXFwiYm9hcmQwMjI3MVxcXCIsXFxcImJvYXJkMDEyNzJcXFwiLFxcXCJib2FyZDA2NDU4XFxcIixcXFwiYm9hcmQwMzM2NVxcXCIsXFxcImJvYXJkMDU4OTJcXFwiLFxcXCJib2FyZDA0MDkxXFxcIixcXFwiYm9hcmQwNjM2NlxcXCIsXFxcImJvYXJkMDYyOTlcXFwiXX1cIixcIntcXFwiYm9keVxcXCI6XFxcIiNbW1xcXFxcXFwiQVRPTS1VU0QgcHJpY2UgZmVlZFxcXFxcXFwiLFxcXFxcXFwiJDAuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwiQ3JhYmJsZVxcXFxcXFwiLFxcXFxcXFwiJDEuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwiQ3JhYmJsZUNvbW1pdHRlZVxcXFxcXFwiLFxcXFxcXFwiJDIuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwiQ3JhYmJsZUdvdmVybm9yXFxcXFxcXCIsXFxcXFxcXCIkMy5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJWYXVsdEZhY3RvcnlcXFxcXFxcIixcXFxcXFxcIiQ0LkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcIlZhdWx0RmFjdG9yeUdvdmVybm9yXFxcXFxcXCIsXFxcXFxcXCIkNS5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJhdWN0aW9uZWVyXFxcXFxcXCIsXFxcXFxcXCIkNi5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJlY29uQ29tbWl0dGVlQ2hhcnRlclxcXFxcXFwiLFxcXFxcXFwiJDcuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwiZWNvbm9taWNDb21taXR0ZWVcXFxcXFxcIixcXFxcXFxcIiQ4LkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcImZlZURpc3RyaWJ1dG9yXFxcXFxcXCIsXFxcXFxcXCIkOS5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJrcmVhZFxcXFxcXFwiLFxcXFxcXFwiJDEwLkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcImtyZWFkQ29tbWl0dGVlXFxcXFxcXCIsXFxcXFxcXCIkMTEuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwia3JlYWRDb21taXR0ZWVDaGFydGVyXFxcXFxcXCIsXFxcXFxcXCIkMTIuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwicHJvdmlzaW9uUG9vbFxcXFxcXFwiLFxcXFxcXFwiJDEzLkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcInBzbS1JU1QtREFJX2F4bFxcXFxcXFwiLFxcXFxcXFwiJDE0LkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcInBzbS1JU1QtREFJX2dydlxcXFxcXFwiLFxcXFxcXFwiJDE1LkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcInBzbS1JU1QtVVNEQ19heGxcXFxcXFxcIixcXFxcXFxcIiQxNi5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJwc20tSVNULVVTRENfZ3J2XFxcXFxcXCIsXFxcXFxcXCIkMTcuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwicHNtLUlTVC1VU0RUX2F4bFxcXFxcXFwiLFxcXFxcXFwiJDE4LkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdLFtcXFxcXFxcInBzbS1JU1QtVVNEVF9ncnZcXFxcXFxcIixcXFxcXFxcIiQxOS5BbGxlZ2VkOiBJbnN0YW5jZUhhbmRsZVxcXFxcXFwiXSxbXFxcXFxcXCJyZXNlcnZlXFxcXFxcXCIsXFxcXFxcXCIkMjAuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwicmVzZXJ2ZUdvdmVybm9yXFxcXFxcXCIsXFxcXFxcXCIkMjEuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwic2NhbGVkUHJpY2VBdXRob3JpdHktc3RBVE9NXFxcXFxcXCIsXFxcXFxcXCIkMjIuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwic3RBVE9NLVVTRCBwcmljZSBmZWVkXFxcXFxcXCIsXFxcXFxcXCIkMjMuQWxsZWdlZDogSW5zdGFuY2VIYW5kbGVcXFxcXFxcIl0sW1xcXFxcXFwid2FsbGV0RmFjdG9yeVxcXFxcXFwiLFxcXFxcXFwiJDI0LkFsbGVnZWQ6IEluc3RhbmNlSGFuZGxlXFxcXFxcXCJdXVxcXCIsXFxcInNsb3RzXFxcIjpbXFxcImJvYXJkMDI5NjNcXFwiLFxcXCJib2FyZDA0Mzk1XFxcIixcXFwiYm9hcmQwMjM5M1xcXCIsXFxcImJvYXJkMDUzOTZcXFwiLFxcXCJib2FyZDAwMzYwXFxcIixcXFwiYm9hcmQwMzc3M1xcXCIsXFxcImJvYXJkMDYyOTlcXFwiLFxcXCJib2FyZDA0NjYxXFxcIixcXFwiYm9hcmQwNDE0OVxcXCIsXFxcImJvYXJkMDUyNjJcXFwiLFxcXCJib2FyZDA0NzgzXFxcIixcXFwiYm9hcmQwMTk4NVxcXCIsXFxcImJvYXJkMDYyODRcXFwiLFxcXCJib2FyZDAxNjY0XFxcIixcXFwiYm9hcmQwMTg2N1xcXCIsXFxcImJvYXJkMDI1NjhcXFwiLFxcXCJib2FyZDA1NjY5XFxcIixcXFwiYm9hcmQwNTk3MFxcXCIsXFxcImJvYXJkMDIyNzFcXFwiLFxcXCJib2FyZDAxMjcyXFxcIixcXFwiYm9hcmQwNjQ1OFxcXCIsXFxcImJvYXJkMDMzNjVcXFwiLFxcXCJib2FyZDA1ODkyXFxcIixcXFwiYm9hcmQwNDA5MVxcXCIsXFxcImJvYXJkMDYzNjZcXFwiXX1cIl19Igp9', + }, + }, + }, + 'http://localhost:26657/abci_query?path=%22/custom/vstorage/data/published.agoricNames.vbankAsset%22&height=0': + { + result: { + response: { + code: 0, + value: + // observed in a3p + 'ewogICJ2YWx1ZSI6ICJ7XCJibG9ja0hlaWdodFwiOlwiNzY5XCIsXCJ2YWx1ZXNcIjpbXCJ7XFxcImJvZHlcXFwiOlxcXCIjW1tcXFxcXFxcImliYy8yOTU1NDhBNzg3ODVBMTAwN0YyMzJERTI4NjE0OUE2RkY1MTJGMTgwQUY1NjU3NzgwRkM4OUMwMDlFMkMzNDhGXFxcXFxcXCIse1xcXFxcXFwiYnJhbmRcXFxcXFxcIjpcXFxcXFxcIiQwLkFsbGVnZWQ6IFVTRENfYXhsIGJyYW5kXFxcXFxcXCIsXFxcXFxcXCJkZW5vbVxcXFxcXFwiOlxcXFxcXFwiaWJjLzI5NTU0OEE3ODc4NUExMDA3RjIzMkRFMjg2MTQ5QTZGRjUxMkYxODBBRjU2NTc3ODBGQzg5QzAwOUUyQzM0OEZcXFxcXFxcIixcXFxcXFxcImRpc3BsYXlJbmZvXFxcXFxcXCI6e1xcXFxcXFwiYXNzZXRLaW5kXFxcXFxcXCI6XFxcXFxcXCJuYXRcXFxcXFxcIixcXFxcXFxcImRlY2ltYWxQbGFjZXNcXFxcXFxcIjo2fSxcXFxcXFxcImlzc3VlclxcXFxcXFwiOlxcXFxcXFwiJDEuQWxsZWdlZDogVVNEQ19heGwgaXNzdWVyXFxcXFxcXCIsXFxcXFxcXCJpc3N1ZXJOYW1lXFxcXFxcXCI6XFxcXFxcXCJVU0RDX2F4bFxcXFxcXFwiLFxcXFxcXFwicHJvcG9zZWROYW1lXFxcXFxcXCI6XFxcXFxcXCJVU0QgQ29pblxcXFxcXFwifV0sW1xcXFxcXFwiaWJjLzM4NkQwOUFFMzFEQTdDMEM5MzA5MUJCNDVEMDhDQjdBMDczMEIxRjY5N0NEODEzRjA2QTU0NDZEQ0YwMkVFQjJcXFxcXFxcIix7XFxcXFxcXCJicmFuZFxcXFxcXFwiOlxcXFxcXFwiJDIuQWxsZWdlZDogVVNEVF9ncnYgYnJhbmRcXFxcXFxcIixcXFxcXFxcImRlbm9tXFxcXFxcXCI6XFxcXFxcXCJpYmMvMzg2RDA5QUUzMURBN0MwQzkzMDkxQkI0NUQwOENCN0EwNzMwQjFGNjk3Q0Q4MTNGMDZBNTQ0NkRDRjAyRUVCMlxcXFxcXFwiLFxcXFxcXFwiZGlzcGxheUluZm9cXFxcXFxcIjp7XFxcXFxcXCJhc3NldEtpbmRcXFxcXFxcIjpcXFxcXFxcIm5hdFxcXFxcXFwiLFxcXFxcXFwiZGVjaW1hbFBsYWNlc1xcXFxcXFwiOjZ9LFxcXFxcXFwiaXNzdWVyXFxcXFxcXCI6XFxcXFxcXCIkMy5BbGxlZ2VkOiBVU0RUX2dydiBpc3N1ZXJcXFxcXFxcIixcXFxcXFxcImlzc3Vlck5hbWVcXFxcXFxcIjpcXFxcXFxcIlVTRFRfZ3J2XFxcXFxcXCIsXFxcXFxcXCJwcm9wb3NlZE5hbWVcXFxcXFxcIjpcXFxcXFxcIlRldGhlciBVU0RcXFxcXFxcIn1dLFtcXFxcXFxcImliYy8zOTE0QkRFRjQ2RjQyOUEyNjkxN0U0RDhENDM0NjIwRUM0ODE3REM2QjZFNjhGQjMyN0UxOTA5MDJGMUU5MjQyXFxcXFxcXCIse1xcXFxcXFwiYnJhbmRcXFxcXFxcIjpcXFxcXFxcIiQ0LkFsbGVnZWQ6IERBSV9heGwgYnJhbmRcXFxcXFxcIixcXFxcXFxcImRlbm9tXFxcXFxcXCI6XFxcXFxcXCJpYmMvMzkxNEJERUY0NkY0MjlBMjY5MTdFNEQ4RDQzNDYyMEVDNDgxN0RDNkI2RTY4RkIzMjdFMTkwOTAyRjFFOTI0MlxcXFxcXFwiLFxcXFxcXFwiZGlzcGxheUluZm9cXFxcXFxcIjp7XFxcXFxcXCJhc3NldEtpbmRcXFxcXFxcIjpcXFxcXFxcIm5hdFxcXFxcXFwiLFxcXFxcXFwiZGVjaW1hbFBsYWNlc1xcXFxcXFwiOjE4fSxcXFxcXFxcImlzc3VlclxcXFxcXFwiOlxcXFxcXFwiJDUuQWxsZWdlZDogREFJX2F4bCBpc3N1ZXJcXFxcXFxcIixcXFxcXFxcImlzc3Vlck5hbWVcXFxcXFxcIjpcXFxcXFxcIkRBSV9heGxcXFxcXFxcIixcXFxcXFxcInByb3Bvc2VkTmFtZVxcXFxcXFwiOlxcXFxcXFwiREFJXFxcXFxcXCJ9XSxbXFxcXFxcXCJpYmMvM0Q1MjkxQzIzRDc3NkMzQUE3QTdBQkIzNEM3QjAyMzE5M0VDRDJCQzQyRUExOUQzMTY1QjJDRjk2NTIxMTdFN1xcXFxcXFwiLHtcXFxcXFxcImJyYW5kXFxcXFxcXCI6XFxcXFxcXCIkNi5BbGxlZ2VkOiBEQUlfZ3J2IGJyYW5kXFxcXFxcXCIsXFxcXFxcXCJkZW5vbVxcXFxcXFwiOlxcXFxcXFwiaWJjLzNENTI5MUMyM0Q3NzZDM0FBN0E3QUJCMzRDN0IwMjMxOTNFQ0QyQkM0MkVBMTlEMzE2NUIyQ0Y5NjUyMTE3RTdcXFxcXFxcIixcXFxcXFxcImRpc3BsYXlJbmZvXFxcXFxcXCI6e1xcXFxcXFwiYXNzZXRLaW5kXFxcXFxcXCI6XFxcXFxcXCJuYXRcXFxcXFxcIixcXFxcXFxcImRlY2ltYWxQbGFjZXNcXFxcXFxcIjoxOH0sXFxcXFxcXCJpc3N1ZXJcXFxcXFxcIjpcXFxcXFxcIiQ3LkFsbGVnZWQ6IERBSV9ncnYgaXNzdWVyXFxcXFxcXCIsXFxcXFxcXCJpc3N1ZXJOYW1lXFxcXFxcXCI6XFxcXFxcXCJEQUlfZ3J2XFxcXFxcXCIsXFxcXFxcXCJwcm9wb3NlZE5hbWVcXFxcXFxcIjpcXFxcXFxcIkRBSVxcXFxcXFwifV0sW1xcXFxcXFwiaWJjLzQyMjI1RjE0NzEzN0RERUI1RkVGMEYxRDBBOTJGMkFENTc1NTdBRkEyQzRENkYzMEIyMUUwRDk4MzAwMUMwMDJcXFxcXFxcIix7XFxcXFxcXCJicmFuZFxcXFxcXFwiOlxcXFxcXFwiJDguQWxsZWdlZDogc3RBVE9NIGJyYW5kXFxcXFxcXCIsXFxcXFxcXCJkZW5vbVxcXFxcXFwiOlxcXFxcXFwiaWJjLzQyMjI1RjE0NzEzN0RERUI1RkVGMEYxRDBBOTJGMkFENTc1NTdBRkEyQzRENkYzMEIyMUUwRDk4MzAwMUMwMDJcXFxcXFxcIixcXFxcXFxcImRpc3BsYXlJbmZvXFxcXFxcXCI6e1xcXFxcXFwiYXNzZXRLaW5kXFxcXFxcXCI6XFxcXFxcXCJuYXRcXFxcXFxcIixcXFxcXFxcImRlY2ltYWxQbGFjZXNcXFxcXFxcIjo2fSxcXFxcXFxcImlzc3VlclxcXFxcXFwiOlxcXFxcXFwiJDkuQWxsZWdlZDogc3RBVE9NIGlzc3VlclxcXFxcXFwiLFxcXFxcXFwiaXNzdWVyTmFtZVxcXFxcXFwiOlxcXFxcXFwic3RBVE9NXFxcXFxcXCIsXFxcXFxcXCJwcm9wb3NlZE5hbWVcXFxcXFxcIjpcXFxcXFxcInN0QVRPTVxcXFxcXFwifV0sW1xcXFxcXFwiaWJjLzY4MzEyOTI5MDM0ODdFNThCRjlBMTk1RkREQzhBMkU2MjZCM0RGMzlCODhGNEU3RjQxQzkzNUNBREJBRjU0QUNcXFxcXFxcIix7XFxcXFxcXCJicmFuZFxcXFxcXFwiOlxcXFxcXFwiJDEwLkFsbGVnZWQ6IFVTRENfZ3J2IGJyYW5kXFxcXFxcXCIsXFxcXFxcXCJkZW5vbVxcXFxcXFwiOlxcXFxcXFwiaWJjLzY4MzEyOTI5MDM0ODdFNThCRjlBMTk1RkREQzhBMkU2MjZCM0RGMzlCODhGNEU3RjQxQzkzNUNBREJBRjU0QUNcXFxcXFxcIixcXFxcXFxcImRpc3BsYXlJbmZvXFxcXFxcXCI6e1xcXFxcXFwiYXNzZXRLaW5kXFxcXFxcXCI6XFxcXFxcXCJuYXRcXFxcXFxcIixcXFxcXFxcImRlY2ltYWxQbGFjZXNcXFxcXFxcIjo2fSxcXFxcXFxcImlzc3VlclxcXFxcXFwiOlxcXFxcXFwiJDExLkFsbGVnZWQ6IFVTRENfZ3J2IGlzc3VlclxcXFxcXFwiLFxcXFxcXFwiaXNzdWVyTmFtZVxcXFxcXFwiOlxcXFxcXFwiVVNEQ19ncnZcXFxcXFxcIixcXFxcXFxcInByb3Bvc2VkTmFtZVxcXFxcXFwiOlxcXFxcXFwiVVNDIENvaW5cXFxcXFxcIn1dLFtcXFxcXFxcImliYy9CQTMxM0M0QTE5REZCRjk0MzU4NkMwMzg3RTZCMTEyODZGOUU0MTZCNEREMjc1NzRFNjkwOUNBQkUwRTM0MkZBXFxcXFxcXCIse1xcXFxcXFwiYnJhbmRcXFxcXFxcIjpcXFxcXFxcIiQxMi5BbGxlZ2VkOiBBVE9NIGJyYW5kXFxcXFxcXCIsXFxcXFxcXCJkZW5vbVxcXFxcXFwiOlxcXFxcXFwiaWJjL0JBMzEzQzRBMTlERkJGOTQzNTg2QzAzODdFNkIxMTI4NkY5RTQxNkI0REQyNzU3NEU2OTA5Q0FCRTBFMzQyRkFcXFxcXFxcIixcXFxcXFxcImRpc3BsYXlJbmZvXFxcXFxcXCI6e1xcXFxcXFwiYXNzZXRLaW5kXFxcXFxcXCI6XFxcXFxcXCJuYXRcXFxcXFxcIixcXFxcXFxcImRlY2ltYWxQbGFjZXNcXFxcXFxcIjo2fSxcXFxcXFxcImlzc3VlclxcXFxcXFwiOlxcXFxcXFwiJDEzLkFsbGVnZWQ6IEFUT00gaXNzdWVyXFxcXFxcXCIsXFxcXFxcXCJpc3N1ZXJOYW1lXFxcXFxcXCI6XFxcXFxcXCJBVE9NXFxcXFxcXCIsXFxcXFxcXCJwcm9wb3NlZE5hbWVcXFxcXFxcIjpcXFxcXFxcIkFUT01cXFxcXFxcIn1dLFtcXFxcXFxcImliYy9GMjMzMTY0NUI5NjgzMTE2MTg4RUYzNkZDMDRBODA5QzI4QkQzNkI1NDU1NUU4NzA1QTM3MTQ2RDAxODJGMDQ1XFxcXFxcXCIse1xcXFxcXFwiYnJhbmRcXFxcXFxcIjpcXFxcXFxcIiQxNC5BbGxlZ2VkOiBVU0RUX2F4bCBicmFuZFxcXFxcXFwiLFxcXFxcXFwiZGVub21cXFxcXFxcIjpcXFxcXFxcImliYy9GMjMzMTY0NUI5NjgzMTE2MTg4RUYzNkZDMDRBODA5QzI4QkQzNkI1NDU1NUU4NzA1QTM3MTQ2RDAxODJGMDQ1XFxcXFxcXCIsXFxcXFxcXCJkaXNwbGF5SW5mb1xcXFxcXFwiOntcXFxcXFxcImFzc2V0S2luZFxcXFxcXFwiOlxcXFxcXFwibmF0XFxcXFxcXCIsXFxcXFxcXCJkZWNpbWFsUGxhY2VzXFxcXFxcXCI6Nn0sXFxcXFxcXCJpc3N1ZXJcXFxcXFxcIjpcXFxcXFxcIiQxNS5BbGxlZ2VkOiBVU0RUX2F4bCBpc3N1ZXJcXFxcXFxcIixcXFxcXFxcImlzc3Vlck5hbWVcXFxcXFxcIjpcXFxcXFxcIlVTRFRfYXhsXFxcXFxcXCIsXFxcXFxcXCJwcm9wb3NlZE5hbWVcXFxcXFxcIjpcXFxcXFxcIlRldGhlciBVU0RcXFxcXFxcIn1dLFtcXFxcXFxcInVibGRcXFxcXFxcIix7XFxcXFxcXCJicmFuZFxcXFxcXFwiOlxcXFxcXFwiJDE2LkFsbGVnZWQ6IEJMRCBicmFuZFxcXFxcXFwiLFxcXFxcXFwiZGVub21cXFxcXFxcIjpcXFxcXFxcInVibGRcXFxcXFxcIixcXFxcXFxcImRpc3BsYXlJbmZvXFxcXFxcXCI6e1xcXFxcXFwiYXNzZXRLaW5kXFxcXFxcXCI6XFxcXFxcXCJuYXRcXFxcXFxcIixcXFxcXFxcImRlY2ltYWxQbGFjZXNcXFxcXFxcIjo2fSxcXFxcXFxcImlzc3VlclxcXFxcXFwiOlxcXFxcXFwiJDE3LkFsbGVnZWQ6IEJMRCBpc3N1ZXJcXFxcXFxcIixcXFxcXFxcImlzc3Vlck5hbWVcXFxcXFxcIjpcXFxcXFxcIkJMRFxcXFxcXFwiLFxcXFxcXFwicHJvcG9zZWROYW1lXFxcXFxcXCI6XFxcXFxcXCJBZ29yaWMgc3Rha2luZyB0b2tlblxcXFxcXFwifV0sW1xcXFxcXFwidWlzdFxcXFxcXFwiLHtcXFxcXFxcImJyYW5kXFxcXFxcXCI6XFxcXFxcXCIkMTguQWxsZWdlZDogSVNUIGJyYW5kXFxcXFxcXCIsXFxcXFxcXCJkZW5vbVxcXFxcXFwiOlxcXFxcXFwidWlzdFxcXFxcXFwiLFxcXFxcXFwiZGlzcGxheUluZm9cXFxcXFxcIjp7XFxcXFxcXCJhc3NldEtpbmRcXFxcXFxcIjpcXFxcXFxcIm5hdFxcXFxcXFwiLFxcXFxcXFwiZGVjaW1hbFBsYWNlc1xcXFxcXFwiOjZ9LFxcXFxcXFwiaXNzdWVyXFxcXFxcXCI6XFxcXFxcXCIkMTkuQWxsZWdlZDogSVNUIGlzc3VlclxcXFxcXFwiLFxcXFxcXFwiaXNzdWVyTmFtZVxcXFxcXFwiOlxcXFxcXFwiSVNUXFxcXFxcXCIsXFxcXFxcXCJwcm9wb3NlZE5hbWVcXFxcXFxcIjpcXFxcXFxcIkFnb3JpYyBzdGFibGUgdG9rZW5cXFxcXFxcIn1dXVxcXCIsXFxcInNsb3RzXFxcIjpbXFxcImJvYXJkMDMwNDBcXFwiLFxcXCJib2FyZDA1MTQxXFxcIixcXFwiYm9hcmQwMzQ0NlxcXCIsXFxcImJvYXJkMDE1NDdcXFwiLFxcXCJib2FyZDA1NzM2XFxcIixcXFwiYm9hcmQwMjQzN1xcXCIsXFxcImJvYXJkMDMxMzhcXFwiLFxcXCJib2FyZDA1MDM5XFxcIixcXFwiYm9hcmQwMDk5MFxcXCIsXFxcImJvYXJkMDA2ODlcXFwiLFxcXCJib2FyZDA0NTQyXFxcIixcXFwiYm9hcmQwMDQ0M1xcXCIsXFxcImJvYXJkMDU1NTdcXFwiLFxcXCJib2FyZDAyNjU2XFxcIixcXFwiYm9hcmQwMTc0NFxcXCIsXFxcImJvYXJkMDY0NDVcXFwiLFxcXCJib2FyZDA1NjZcXFwiLFxcXCJib2FyZDA1OTJcXFwiLFxcXCJib2FyZDAyNTdcXFwiLFxcXCJib2FyZDAyMjNcXFwiXX1cIl19Igp9', + }, + }, + }, + 'http://localhost:26657/abci_query?path=%22/custom/vstorage/data/published.priceFeed.ATOM-USD_price_feed%22&height=0': + { + result: { + response: { + code: 0, + value: + // observed in a3p + 'ewogICJ2YWx1ZSI6ICJ7XCJibG9ja0hlaWdodFwiOlwiNTA4XCIsXCJ2YWx1ZXNcIjpbXCJ7XFxcImJvZHlcXFwiOlxcXCIje1xcXFxcXFwiYW1vdW50SW5cXFxcXFxcIjp7XFxcXFxcXCJicmFuZFxcXFxcXFwiOlxcXFxcXFwiJDAuQWxsZWdlZDogQnJhbmRcXFxcXFxcIixcXFxcXFxcInZhbHVlXFxcXFxcXCI6XFxcXFxcXCIrMTAwMDAwMFxcXFxcXFwifSxcXFxcXFxcImFtb3VudE91dFxcXFxcXFwiOntcXFxcXFxcImJyYW5kXFxcXFxcXCI6XFxcXFxcXCIkMS5BbGxlZ2VkOiBCcmFuZFxcXFxcXFwiLFxcXFxcXFwidmFsdWVcXFxcXFxcIjpcXFxcXFxcIisxMjAxMDAwMFxcXFxcXFwifSxcXFxcXFxcInRpbWVyXFxcXFxcXCI6XFxcXFxcXCIkMi5BbGxlZ2VkOiB0aW1lclNlcnZpY2VcXFxcXFxcIixcXFxcXFxcInRpbWVzdGFtcFxcXFxcXFwiOntcXFxcXFxcImFic1ZhbHVlXFxcXFxcXCI6XFxcXFxcXCIrMTcyOTE3NjI5MFxcXFxcXFwiLFxcXFxcXFwidGltZXJCcmFuZFxcXFxcXFwiOlxcXFxcXFwiJDMuQWxsZWdlZDogdGltZXJCcmFuZFxcXFxcXFwifX1cXFwiLFxcXCJzbG90c1xcXCI6W1xcXCJib2FyZDAzOTM1XFxcIixcXFwiYm9hcmQwMTAzNFxcXCIsXFxcImJvYXJkMDU2NzRcXFwiLFxcXCJib2FyZDA0MjVcXFwiXX1cIixcIntcXFwiYm9keVxcXCI6XFxcIiN7XFxcXFxcXCJhbW91bnRJblxcXFxcXFwiOntcXFxcXFxcImJyYW5kXFxcXFxcXCI6XFxcXFxcXCIkMC5BbGxlZ2VkOiBCcmFuZFxcXFxcXFwiLFxcXFxcXFwidmFsdWVcXFxcXFxcIjpcXFxcXFxcIisxMDAwMDAwXFxcXFxcXCJ9LFxcXFxcXFwiYW1vdW50T3V0XFxcXFxcXCI6e1xcXFxcXFwiYnJhbmRcXFxcXFxcIjpcXFxcXFxcIiQxLkFsbGVnZWQ6IEJyYW5kXFxcXFxcXCIsXFxcXFxcXCJ2YWx1ZVxcXFxcXFwiOlxcXFxcXFwiKzEyMDEwMDAwXFxcXFxcXCJ9LFxcXFxcXFwidGltZXJcXFxcXFxcIjpcXFxcXFxcIiQyLkFsbGVnZWQ6IHRpbWVyU2VydmljZVxcXFxcXFwiLFxcXFxcXFwidGltZXN0YW1wXFxcXFxcXCI6e1xcXFxcXFwiYWJzVmFsdWVcXFxcXFxcIjpcXFxcXFxcIisxNzI5MTc2MjkwXFxcXFxcXCIsXFxcXFxcXCJ0aW1lckJyYW5kXFxcXFxcXCI6XFxcXFxcXCIkMy5BbGxlZ2VkOiB0aW1lckJyYW5kXFxcXFxcXCJ9fVxcXCIsXFxcInNsb3RzXFxcIjpbXFxcImJvYXJkMDM5MzVcXFwiLFxcXCJib2FyZDAxMDM0XFxcIixcXFwiYm9hcmQwNTY3NFxcXCIsXFxcImJvYXJkMDQyNVxcXCJdfVwiXX0iCn0', + }, + }, + }, + }); + + const vstorageKit = makeVstorageKit({ fetch }, makeTestConfig()); + const agoricNames = await makeAgoricNames( + vstorageKit.fromBoard, + vstorageKit.vstorage, + ); + + t.snapshot(agoricNames, 'agoricNames from A3P'); + + const priceFeed = await vstorageKit.readPublished( + 'priceFeed.ATOM-USD_price_feed', + ); + t.snapshot(priceFeed, 'priceFeed from A3P'); +}); diff --git a/packages/cosmic-proto/src/address-hooks.js b/packages/cosmic-proto/src/address-hooks.js index 073f0783c71..c1dcb2e415a 100644 --- a/packages/cosmic-proto/src/address-hooks.js +++ b/packages/cosmic-proto/src/address-hooks.js @@ -23,7 +23,7 @@ * const decoded = decodeAddressHook(addressHook); * // { * // baseAddress: 'agoric1qqp0e5ys', - * // query: [Object: null prototype] { foo: [ 'bar', 'baz' ], key: 'value' } + * // query: { foo: [ 'bar', 'baz' ], key: 'value' } * // } */ @@ -31,6 +31,10 @@ import { bech32 } from 'bech32'; import queryString from 'query-string'; +/* global globalThis */ +/** @type {(x: T) => T} */ +const harden = globalThis.harden || Object.freeze; + // ADDRESS_HOOK_VERSION is the version of the address hook format used in this // module. const ADDRESS_HOOK_VERSION = 0; @@ -39,13 +43,14 @@ if ((ADDRESS_HOOK_VERSION & 0x0f) !== ADDRESS_HOOK_VERSION) { throw Error(`ADDRESS_HOOK_VERSION ${ADDRESS_HOOK_VERSION} exceeds 0x0f`); } -// AddressHookMagic is a magic byte prefix that identifies a hooked address. +// ADDRESS_HOOK_BYTE_PREFIX is a magic prefix that identifies a hooked address. // Chosen to make bech32 address hooks that look like "agoric10rch..." -const ADDRESS_HOOK_MAGIC = new Uint8Array([ +const ADDRESS_HOOK_BYTE_PREFIX = [ 0x78, 0xf1, - 0x70 | ADDRESS_HOOK_VERSION, -]); + 0x70, // | ADDRESS_HOOK_VERSION +]; +harden(ADDRESS_HOOK_BYTE_PREFIX); /** * The default maximum number of characters in a bech32-encoded hooked address. @@ -63,6 +68,9 @@ export const DEFAULT_HOOKED_ADDRESS_CHAR_LIMIT = 1024; * { key: ['value1', null, 'value3'] } // '?key=value1&key&key=value3' */ +/** + * How many bytes are used to store the length of the base address. + */ export const BASE_ADDRESS_LENGTH_BYTES = 2; /** @@ -78,8 +86,9 @@ export const decodeBech32 = ( const rawBytes = bech32.fromWords(words); const bytes = new Uint8Array(rawBytes); - return { prefix, bytes }; + return harden({ prefix, bytes }); }; +harden(decodeBech32); /** * @param {string} humanReadablePart @@ -95,6 +104,7 @@ export const encodeBech32 = ( const words = bech32.toWords(bytes); return bech32.encode(humanReadablePart, words, charLimit); }; +harden(encodeBech32); /** * Join raw base address bytes and hook data into a bech32-encoded hooked @@ -140,13 +150,14 @@ export const joinHookedAddress = ( throw RangeError(`Hook data length ${hd} is not a non-negative integer`); } - const magicLength = ADDRESS_HOOK_MAGIC.length; + const prefixLength = ADDRESS_HOOK_BYTE_PREFIX.length; const hookBuf = new Uint8Array( - magicLength + b + hd + BASE_ADDRESS_LENGTH_BYTES, + prefixLength + b + hd + BASE_ADDRESS_LENGTH_BYTES, ); - hookBuf.set(ADDRESS_HOOK_MAGIC, 0); - hookBuf.set(bytes, magicLength); - hookBuf.set(hookData, magicLength + b); + hookBuf.set(ADDRESS_HOOK_BYTE_PREFIX, 0); + hookBuf[prefixLength - 1] |= ADDRESS_HOOK_VERSION; + hookBuf.set(bytes, prefixLength); + hookBuf.set(hookData, prefixLength + b); // Append the address length bytes, since we've already ensured these do not // exceed maxBaseAddressLength above. These are big-endian because the length @@ -161,6 +172,7 @@ export const joinHookedAddress = ( return encodeBech32(prefix, hookBuf, charLimit); }; +harden(joinHookedAddress); /** * @param {string} baseAddress @@ -174,11 +186,13 @@ export const encodeAddressHook = (baseAddress, query, charLimit) => { const hookData = te.encode(`?${queryStr}`); return joinHookedAddress(baseAddress, hookData, charLimit); }; +harden(encodeAddressHook); /** * @param {string} addressHook * @param {number} [charLimit] * @returns {{ baseAddress: string; query: HookQuery }} + * @throws {Error} if no hook string or hook string does not start with `?` */ export const decodeAddressHook = (addressHook, charLimit) => { const { baseAddress, hookData } = splitHookedAddress(addressHook, charLimit); @@ -187,64 +201,69 @@ export const decodeAddressHook = (addressHook, charLimit) => { throw Error(`Hook data does not start with '?': ${hookStr}`); } - /** @type {HookQuery} */ - const query = queryString.parse(hookStr); - return { baseAddress, query }; + const parsedQuery = queryString.parse(hookStr); + + /** + * @type {HookQuery} + */ + const query = harden({ ...parsedQuery }); + return harden({ baseAddress, query }); }; +harden(decodeAddressHook); /** * @param {string} specimen * @param {number} [charLimit] - * @returns {string | { baseAddress: string; hookData: Uint8Array }} + * @returns {{ baseAddress: string; hookData: Uint8Array }} */ -export const splitHookedAddressUnsafe = ( +export const splitHookedAddress = ( specimen, charLimit = DEFAULT_HOOKED_ADDRESS_CHAR_LIMIT, ) => { const { prefix, bytes } = decodeBech32(specimen, charLimit); - const magicLength = ADDRESS_HOOK_MAGIC.length; - for (let i = 0; i < magicLength; i += 1) { - if (bytes[i] !== ADDRESS_HOOK_MAGIC[i]) { - return { baseAddress: specimen, hookData: new Uint8Array() }; + const prefixLength = ADDRESS_HOOK_BYTE_PREFIX.length; + let version = 0xff; + for (let i = 0; i < prefixLength; i += 1) { + let maybeMagicByte = bytes[i]; + if (i === prefixLength - 1) { + // Final byte has a low version nibble and a high magic nibble. + version = maybeMagicByte & 0x0f; + maybeMagicByte &= 0xf0; } + if (maybeMagicByte !== ADDRESS_HOOK_BYTE_PREFIX[i]) { + return harden({ baseAddress: specimen, hookData: new Uint8Array() }); + } + } + + if (version !== ADDRESS_HOOK_VERSION) { + throw TypeError(`Unsupported address hook version ${version}`); } let len = 0; for (let i = BASE_ADDRESS_LENGTH_BYTES - 1; i >= 0; i -= 1) { const byte = bytes.at(-i - 1); if (byte === undefined) { - return `Cannot get base address length from byte ${-i - 1} of ${bytes.length}`; + throw TypeError( + `Cannot get base address length from byte ${-i - 1} of ${bytes.length}`, + ); } len <<= 8; len |= byte; } const b = len; - if (b > bytes.length - BASE_ADDRESS_LENGTH_BYTES - magicLength) { - return `Base address length 0x${b.toString(16)} is longer than specimen length ${bytes.length - BASE_ADDRESS_LENGTH_BYTES - magicLength}`; + if (b > bytes.length - BASE_ADDRESS_LENGTH_BYTES - prefixLength) { + throw TypeError( + `Base address length 0x${b.toString(16)} is longer than specimen length ${bytes.length - BASE_ADDRESS_LENGTH_BYTES - prefixLength}`, + ); } - const baseAddressBuf = bytes.subarray(magicLength, magicLength + b); + const baseAddressBuf = bytes.subarray(prefixLength, prefixLength + b); const baseAddress = encodeBech32(prefix, baseAddressBuf, charLimit); - const hookData = bytes.subarray(magicLength + b, -BASE_ADDRESS_LENGTH_BYTES); + const hookData = bytes.subarray(prefixLength + b, -BASE_ADDRESS_LENGTH_BYTES); - return { baseAddress, hookData }; -}; - -/** - * @param {string} specimen - * @param {number} [charLimit] - * @returns {{ - * baseAddress: string; - * hookData: Uint8Array; - * }} - */ -export const splitHookedAddress = (specimen, charLimit) => { - const result = splitHookedAddressUnsafe(specimen, charLimit); - if (typeof result === 'object') { - return result; - } - throw Error(result); + return harden({ baseAddress, hookData }); }; +harden(splitHookedAddress); diff --git a/packages/cosmic-proto/test/address-hooks.test.js b/packages/cosmic-proto/test/address-hooks.test.js index 31ce2334f10..a216fc3159d 100644 --- a/packages/cosmic-proto/test/address-hooks.test.js +++ b/packages/cosmic-proto/test/address-hooks.test.js @@ -42,6 +42,62 @@ test.before(async t => { t.context = await makeTestContext(); }); +/** + * @type {import('ava').Macro< + * [addressHook: string, baseAddress: string, hookDataStr: string, error?: any], + * { addressHooks: import('../src/address-hooks.js') } + * >} + */ +const splitMacro = test.macro({ + title(providedTitle = '', addressHook, _baseAddress, _hookDataStr, _error) { + return `${providedTitle} split ${addressHook}`; + }, + exec(t, addressHook, baseAddress, hookDataStr, error) { + const { splitHookedAddress } = t.context.addressHooks; + if (error) { + t.throws(() => splitHookedAddress(addressHook), error); + return; + } + const { baseAddress: ba, hookData: hd } = splitHookedAddress(addressHook); + t.is(ba, baseAddress); + const hookData = new TextEncoder().encode(hookDataStr); + t.deepEqual(hd, hookData); + }, +}); + +test('empty', splitMacro, '', '', '', { message: ' too short' }); +test('no hook', splitMacro, 'agoric1qqp0e5ys', 'agoric1qqp0e5ys', '', ''); +test( + 'Fast USDC', + splitMacro, + 'agoric10rchp4vc53apxn32q42c3zryml8xq3xshyzuhjk6405wtxy7tl3d7e0f8az423padaek6me38qekget2vdhx66mtvy6kg7nrw5uhsaekd4uhwufswqex6dtsv44hxv3cd4jkuqpqvduyhf', + 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek', + '?EUD=osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men', +); +test( + 'version 0', + splitMacro, + 'agoric10rchqqqpqgpsgpgxquyqjzstpsxsurcszyfpxpqrqgqsq9qx0p9wp', + 'agoric1qqqsyqcyq5rqwzqfpg9scrgwpugpzysn3tn9p0', + '\x04\x03\x02\x01', +); +test( + 'version 1 reject', + splitMacro, + 'agoric10rchzqqpqgpsgpgxquyqjzstpsxsurcszyfpxpqrqgqsq9q04n2fg', + '', + '', + { message: 'Unsupported address hook version 1' }, +); +test( + 'version 15 reject', + splitMacro, + 'agoric10rch7qqpqgpsgpgxquyqjzstpsxsurcszyfpxpqrqgqsq9q25ez2d', + '', + '', + { message: 'Unsupported address hook version 15' }, +); + /** * @type {import('ava').Macro< * [string, ArrayLike | undefined, ArrayLike, string], diff --git a/packages/fast-usdc/package.json b/packages/fast-usdc/package.json index 9f8e87b89ad..30e20a49253 100644 --- a/packages/fast-usdc/package.json +++ b/packages/fast-usdc/package.json @@ -33,6 +33,7 @@ }, "dependencies": { "@agoric/client-utils": "^0.1.0", + "@agoric/cosmic-proto": "^0.4.0", "@agoric/ertp": "^0.16.2", "@agoric/internal": "^0.3.2", "@agoric/notifier": "^0.6.2", diff --git a/packages/fast-usdc/src/cli/lp-commands.js b/packages/fast-usdc/src/cli/lp-commands.js index 4b829cfbfba..afcbd063650 100644 --- a/packages/fast-usdc/src/cli/lp-commands.js +++ b/packages/fast-usdc/src/cli/lp-commands.js @@ -1,3 +1,4 @@ +/* eslint-env node */ /** * @import {Command} from 'commander'; * @import {OfferSpec} from '@agoric/smart-wallet/src/offers.js'; @@ -5,17 +6,22 @@ * @import {USDCProposalShapes} from '../pool-share-math.js'; */ -import { fetchEnvNetworkConfig, makeVstorageKit } from '@agoric/client-utils'; -import { InvalidArgumentError } from 'commander'; +import { + fetchEnvNetworkConfig, + makeSmartWalletKit, +} from '@agoric/client-utils'; +import { AmountMath } from '@agoric/ertp'; import { assertParsableNumber, ceilDivideBy, multiplyBy, parseRatio, } from '@agoric/zoe/src/contractSupport/ratio.js'; -import { AmountMath } from '@agoric/ertp'; +import { InvalidArgumentError } from 'commander'; import { outputActionAndHint } from './bridge-action.js'; +export const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); + /** @param {string} arg */ const parseDecimal = arg => { try { @@ -41,7 +47,7 @@ const parseUSDCAmount = (amountString, usdc) => { * @param {Command} program * @param {{ * fetch?: Window['fetch']; - * vstorageKit?: Awaited>; + * smartWalletKit?: import('@agoric/client-utils').SmartWalletKit; * stdout: typeof process.stdout; * stderr: typeof process.stderr; * env: typeof process.env; @@ -50,18 +56,18 @@ const parseUSDCAmount = (amountString, usdc) => { */ export const addLPCommands = ( program, - { fetch, vstorageKit, stderr, stdout, env, now }, + { fetch, smartWalletKit, stderr, stdout, env, now }, ) => { - const loadVsk = async () => { - if (vstorageKit) { - return vstorageKit; + const loadSwk = async () => { + if (smartWalletKit) { + return smartWalletKit; } assert(fetch); const networkConfig = await fetchEnvNetworkConfig({ env, fetch }); - return makeVstorageKit({ fetch }, networkConfig); + return makeSmartWalletKit({ delay, fetch }, networkConfig); }; - /** @type {undefined | ReturnType} */ - let vskP; + /** @type {undefined | ReturnType} */ + let swkP; program .command('deposit') @@ -73,11 +79,11 @@ export const addLPCommands = ( .requiredOption('--amount ', 'USDC amount', parseDecimal) .option('--offerId ', 'Offer id', String, `lpDeposit-${now()}`) .action(async opts => { - vskP ||= loadVsk(); - const vsk = await vskP; + swkP ||= loadSwk(); + const swk = await swkP; /** @type {Brand<'nat'>} */ // @ts-expect-error it doesnt recognize usdc as a Brand type - const usdc = vsk.agoricNames.brand.USDC; + const usdc = swk.agoricNames.brand.USDC; assert(usdc, 'USDC brand not in agoricNames'); const usdcAmount = parseUSDCAmount(opts.amount, usdc); @@ -106,7 +112,7 @@ export const addLPCommands = ( offer, }; - outputActionAndHint(bridgeAction, { stderr, stdout }, vsk.marshaller); + outputActionAndHint(bridgeAction, { stderr, stdout }, swk.marshaller); }); program @@ -119,24 +125,25 @@ export const addLPCommands = ( .requiredOption('--amount ', 'USDC amount', parseDecimal) .option('--offerId ', 'Offer id', String, `lpWithdraw-${now()}`) .action(async opts => { - vskP ||= loadVsk(); - const vsk = await vskP; + swkP ||= loadSwk(); + swkP ||= loadSwk(); + const swk = await swkP; /** @type {Brand<'nat'>} */ // @ts-expect-error it doesnt recognize FastLP as a Brand type - const poolShare = vsk.agoricNames.brand.FastLP; + const poolShare = swk.agoricNames.brand.FastLP; assert(poolShare, 'FastLP brand not in agoricNames'); /** @type {Brand<'nat'>} */ // @ts-expect-error it doesnt recognize usdc as a Brand type - const usdc = vsk.agoricNames.brand.USDC; + const usdc = swk.agoricNames.brand.USDC; assert(usdc, 'USDC brand not in agoricNames'); const usdcAmount = parseUSDCAmount(opts.amount, usdc); /** @type {import('../types.js').PoolMetrics} */ // @ts-expect-error it treats this as "unknown" - const metrics = await vsk.readPublished('fastUsdc.poolMetrics'); + const metrics = await swk.readPublished('fastUsdc.poolMetrics'); const fastLPAmount = ceilDivideBy(usdcAmount, metrics.shareWorth); /** @type {USDCProposalShapes['withdraw']} */ @@ -163,7 +170,7 @@ export const addLPCommands = ( outputActionAndHint( { method: 'executeOffer', offer }, { stderr, stdout }, - vsk.marshaller, + swk.marshaller, ); }); diff --git a/packages/fast-usdc/src/cli/operator-commands.js b/packages/fast-usdc/src/cli/operator-commands.js index 196a48af8a4..8f849f760db 100644 --- a/packages/fast-usdc/src/cli/operator-commands.js +++ b/packages/fast-usdc/src/cli/operator-commands.js @@ -1,3 +1,4 @@ +/* eslint-env node */ /** * @import {Command} from 'commander'; * @import {OfferSpec} from '@agoric/smart-wallet/src/offers.js'; @@ -5,7 +6,10 @@ * @import {OperatorKit} from '../exos/operator-kit.js'; */ -import { fetchEnvNetworkConfig, makeVstorageKit } from '@agoric/client-utils'; +import { + fetchEnvNetworkConfig, + makeSmartWalletKit, +} from '@agoric/client-utils'; import { mustMatch } from '@agoric/internal'; import { Nat } from '@endo/nat'; import { InvalidArgumentError } from 'commander'; @@ -13,6 +17,8 @@ import { INVITATION_MAKERS_DESC } from '../exos/transaction-feed.js'; import { CctpTxEvidenceShape } from '../type-guards.js'; import { outputActionAndHint } from './bridge-action.js'; +export const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); + /** @param {string} arg */ const parseNat = arg => { const n = Nat(BigInt(arg)); @@ -53,8 +59,9 @@ export const addOperatorCommands = ( .option('--offerId ', 'Offer id', String, `operatorAccept-${now()}`) .action(async opts => { const networkConfig = await fetchEnvNetworkConfig({ env, fetch }); - const vsk = await makeVstorageKit({ fetch }, networkConfig); - const instance = vsk.agoricNames.instance.fastUsdc; + + const swk = await makeSmartWalletKit({ delay, fetch }, networkConfig); + const instance = swk.agoricNames.instance.fastUsdc; assert(instance, 'fastUsdc instance not in agoricNames'); /** @type {OfferSpec} */ diff --git a/packages/fast-usdc/src/cli/transfer.js b/packages/fast-usdc/src/cli/transfer.js index 9120f93be0a..41719ac1c37 100644 --- a/packages/fast-usdc/src/cli/transfer.js +++ b/packages/fast-usdc/src/cli/transfer.js @@ -6,6 +6,7 @@ import { makeVStorage, pickEndpoint, } from '@agoric/client-utils'; +import { encodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js'; import { queryFastUSDCLocalChainAccount } from '../util/agoric.js'; import { depositForBurn, makeProvider } from '../util/cctp.js'; import { @@ -22,7 +23,7 @@ import { const transfer = async ( /** @type {File} */ configFile, /** @type {string} */ amount, - /** @type {string} */ destination, + /** @type {string} */ EUD, out = console, fetch = globalThis.fetch, /** @type {VStorage | undefined} */ vstorage, @@ -39,13 +40,13 @@ const transfer = async ( { chainName: 'agoric', rpcAddrs: [pickEndpoint(netConfig)] }, ); const agoricAddr = await queryFastUSDCLocalChainAccount(vstorage, out); - const appendedAddr = `${agoricAddr}?EUD=${destination}`; - out.log(`forwarding destination ${appendedAddr}`); + const encodedAddr = encodeAddressHook(agoricAddr, { EUD }); + out.log(`forwarding destination ${encodedAddr}`); const { exists, address } = await queryForwardingAccount( config.nobleApi, config.nobleToAgoricChannel, - appendedAddr, + encodedAddr, out, fetch, ); @@ -58,13 +59,13 @@ const transfer = async ( signer, signerAddress, config.nobleToAgoricChannel, - appendedAddr, + encodedAddr, out, ); out.log(res); } catch (e) { out.error( - `Error registering noble forwarding account for ${appendedAddr} on channel ${config.nobleToAgoricChannel}`, + `Error registering noble forwarding account for ${encodedAddr} on channel ${config.nobleToAgoricChannel}`, ); throw e; } diff --git a/packages/fast-usdc/src/exos/advancer.js b/packages/fast-usdc/src/exos/advancer.js index 5fc01abfa38..f63c2b7474c 100644 --- a/packages/fast-usdc/src/exos/advancer.js +++ b/packages/fast-usdc/src/exos/advancer.js @@ -1,17 +1,16 @@ +import { decodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js'; import { AmountMath } from '@agoric/ertp'; import { assertAllDefined, makeTracer } from '@agoric/internal'; import { AnyNatAmountShape, ChainAddressShape } from '@agoric/orchestration'; import { pickFacet } from '@agoric/vat-data'; import { VowShape } from '@agoric/vow'; -import { q } from '@endo/errors'; import { E } from '@endo/far'; -import { M } from '@endo/patterns'; +import { M, mustMatch } from '@endo/patterns'; import { CctpTxEvidenceShape, - EudParamShape, + AddressHookShape, EvmHashShape, } from '../type-guards.js'; -import { addressTools } from '../utils/address.js'; import { makeFeeTools } from '../utils/fees.js'; /** @@ -22,7 +21,7 @@ import { makeFeeTools } from '../utils/fees.js'; * @import {ZoeTools} from '@agoric/orchestration/src/utils/zoe-tools.js'; * @import {VowTools} from '@agoric/vow'; * @import {Zone} from '@agoric/zone'; - * @import {CctpTxEvidence, EvmHash, FeeConfig, LogFn, NobleAddress} from '../types.js'; + * @import {CctpTxEvidence, AddressHook, EvmHash, FeeConfig, LogFn, NobleAddress} from '../types.js'; * @import {StatusManager} from './status-manager.js'; * @import {LiquidityPoolKit} from './liquidity-pool.js'; */ @@ -148,11 +147,10 @@ export const prepareAdvancerKit = ( const { borrowerFacet, poolAccount } = this.state; const { recipientAddress } = evidence.aux; - // throws if EUD is not found - const { EUD } = addressTools.getQueryParams( - recipientAddress, - EudParamShape, - ); + const decoded = decodeAddressHook(recipientAddress); + mustMatch(decoded, AddressHookShape); + const { EUD } = /** @type {AddressHook['query']} */ (decoded.query); + log(`decoded EUD: ${EUD}`); // throws if the bech32 prefix is not found const destination = chainHub.makeChainAddress(EUD); @@ -161,9 +159,8 @@ export const prepareAdvancerKit = ( const advanceAmount = feeTools.calculateAdvance(fullAmount); const { zcfSeat: tmpSeat } = zcf.makeEmptySeatKit(); - const amountKWR = harden({ USDC: advanceAmount }); // throws if the pool has insufficient funds - borrowerFacet.borrow(tmpSeat, amountKWR); + borrowerFacet.borrow(tmpSeat, advanceAmount); // this cannot throw since `.isSeen()` is called in the same turn statusManager.advance(evidence); @@ -172,7 +169,7 @@ export const prepareAdvancerKit = ( tmpSeat, // @ts-expect-error LocalAccountMethods vs OrchestrationAccount poolAccount, - amountKWR, + harden({ USDC: advanceAmount }), ); void watch(depositV, this.facets.depositHandler, { fullAmount, @@ -182,8 +179,8 @@ export const prepareAdvancerKit = ( tmpSeat, txHash: evidence.txHash, }); - } catch (e) { - log('Advancer error:', q(e).toString()); + } catch (error) { + log('Advancer error:', error); statusManager.observe(evidence); } }, @@ -212,18 +209,28 @@ export const prepareAdvancerKit = ( }); }, /** + * We do not expect this to be a common failure. it should only occur + * if USDC is not registered in vbank or the tmpSeat has less than + * `advanceAmount`. + * + * If we do hit this path, we return funds to the Liquidity Pool and + * notify of Advancing failure. + * * @param {Error} error * @param {AdvancerVowCtx & { tmpSeat: ZCFSeat }} ctx */ - onRejected(error, { tmpSeat }) { - // TODO return seat allocation from ctx to LP? - log('🚨 advance deposit failed', q(error).toString()); - // TODO #10510 (comprehensive error testing) determine - // course of action here + onRejected(error, { tmpSeat, advanceAmount, ...restCtx }) { log( - 'TODO live payment on seat to return to LP', - q(tmpSeat).toString(), + '⚠️ deposit to localOrchAccount failed, attempting to return payment to LP', + error, ); + try { + const { borrowerFacet, notifyFacet } = this.state; + notifyFacet.notifyAdvancingResult(restCtx, false); + borrowerFacet.returnToPool(tmpSeat, advanceAmount); + } catch (e) { + log('🚨 deposit to localOrchAccount failure recovery failed', e); + } }, }, transferHandler: { @@ -234,10 +241,11 @@ export const prepareAdvancerKit = ( onFulfilled(result, ctx) { const { notifyFacet } = this.state; const { advanceAmount, destination, ...detail } = ctx; - log( - 'Advance transfer fulfilled', - q({ advanceAmount, destination, result }).toString(), - ); + log('Advance transfer fulfilled', { + advanceAmount, + destination, + result, + }); // During development, due to a bug, this call threw. // The failure was silent (no diagnostics) due to: // - #10576 Vows do not report unhandled rejections @@ -252,7 +260,7 @@ export const prepareAdvancerKit = ( */ onRejected(error, ctx) { const { notifyFacet } = this.state; - log('Advance transfer rejected', q(error).toString()); + log('Advance transfer rejected', error); notifyFacet.notifyAdvancingResult(ctx, false); }, }, diff --git a/packages/fast-usdc/src/exos/liquidity-pool.js b/packages/fast-usdc/src/exos/liquidity-pool.js index d8b2991a4df..5df4c86bbf2 100644 --- a/packages/fast-usdc/src/exos/liquidity-pool.js +++ b/packages/fast-usdc/src/exos/liquidity-pool.js @@ -28,7 +28,7 @@ import { * @import {PoolStats} from '../types.js'; */ -const { add, isEqual, makeEmpty } = AmountMath; +const { add, isEqual, isGTE, makeEmpty } = AmountMath; /** @param {Brand} brand */ const makeDust = brand => AmountMath.make(brand, 1n); @@ -84,10 +84,8 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => { 'Liquidity Pool', { borrower: M.interface('borrower', { - borrow: M.call( - SeatShape, - harden({ USDC: makeNatAmountShape(USDC, 1n) }), - ).returns(), + borrow: M.call(SeatShape, makeNatAmountShape(USDC, 1n)).returns(), + returnToPool: M.call(SeatShape, makeNatAmountShape(USDC, 1n)).returns(), }), repayer: M.interface('repayer', { repay: M.call( @@ -153,32 +151,48 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => { borrower: { /** * @param {ZCFSeat} toSeat - * @param {{ USDC: Amount<'nat'>}} amountKWR + * @param {Amount<'nat'>} amount */ - borrow(toSeat, amountKWR) { + borrow(toSeat, amount) { const { encumberedBalance, poolSeat, poolStats } = this.state; // Validate amount is available in pool const post = borrowCalc( - amountKWR.USDC, + amount, poolSeat.getAmountAllocated('USDC', USDC), encumberedBalance, poolStats, ); // COMMIT POINT - try { - zcf.atomicRearrange(harden([[poolSeat, toSeat, amountKWR]])); - } catch (cause) { - const reason = Error('🚨 cannot commit borrow', { cause }); - console.error(reason.message, cause); - zcf.shutdownWithFailure(reason); - } + // UNTIL #10684: ability to terminate an incarnation w/o terminating the contract + zcf.atomicRearrange(harden([[poolSeat, toSeat, { USDC: amount }]])); Object.assign(this.state, post); this.facets.external.publishPoolMetrics(); }, - // TODO method to repay failed `LOA.deposit()` + /** + * If something fails during advance, return funds to the pool. + * + * @param {ZCFSeat} borrowSeat + * @param {Amount<'nat'>} amount + */ + returnToPool(borrowSeat, amount) { + const { zcfSeat: repaySeat } = zcf.makeEmptySeatKit(); + const returnAmounts = harden({ + Principal: amount, + PoolFee: makeEmpty(USDC), + ContractFee: makeEmpty(USDC), + }); + const borrowSeatAllocation = borrowSeat.getCurrentAllocation(); + isGTE(borrowSeatAllocation.USDC, amount) || + Fail`⚠️ borrowSeatAllocation ${q(borrowSeatAllocation)} less than amountKWR ${q(amount)}`; + // arrange payments in a format repay is expecting + zcf.atomicRearrange( + harden([[borrowSeat, repaySeat, { USDC: amount }, returnAmounts]]), + ); + return this.facets.repayer.repay(repaySeat, returnAmounts); + }, }, repayer: { /** @@ -208,23 +222,18 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => { const { ContractFee, ...rest } = amounts; // COMMIT POINT - try { - zcf.atomicRearrange( - harden([ - [ - fromSeat, - poolSeat, - rest, - { USDC: add(amounts.PoolFee, amounts.Principal) }, - ], - [fromSeat, feeSeat, { ContractFee }, { USDC: ContractFee }], - ]), - ); - } catch (cause) { - const reason = Error('🚨 cannot commit repay', { cause }); - console.error(reason.message, cause); - zcf.shutdownWithFailure(reason); - } + // UNTIL #10684: ability to terminate an incarnation w/o terminating the contract + zcf.atomicRearrange( + harden([ + [ + fromSeat, + poolSeat, + rest, + { USDC: add(amounts.PoolFee, amounts.Principal) }, + ], + [fromSeat, feeSeat, { ContractFee }, { USDC: ContractFee }], + ]), + ); Object.assign(this.state, post); this.facets.external.publishPoolMetrics(); @@ -259,9 +268,8 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => { const post = depositCalc(shareWorth, proposal); // COMMIT POINT - + const mint = shareMint.mintGains(post.payouts); try { - const mint = shareMint.mintGains(post.payouts); this.state.shareWorth = post.shareWorth; zcf.atomicRearrange( harden([ @@ -271,12 +279,12 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => { [mint, lp, post.payouts], ]), ); + } catch (cause) { + // UNTIL #10684: ability to terminate an incarnation w/o terminating the contract + throw new Error('🚨 cannot commit deposit', { cause }); + } finally { lp.exit(); mint.exit(); - } catch (cause) { - const reason = Error('🚨 cannot commit deposit', { cause }); - console.error(reason.message, cause); - zcf.shutdownWithFailure(reason); } external.publishPoolMetrics(); }, @@ -296,7 +304,6 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => { const post = withdrawCalc(shareWorth, proposal); // COMMIT POINT - try { this.state.shareWorth = post.shareWorth; zcf.atomicRearrange( @@ -308,12 +315,12 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => { ]), ); shareMint.burnLosses(proposal.give, burn); + } catch (cause) { + // UNTIL #10684: ability to terminate an incarnation w/o terminating the contract + throw new Error('🚨 cannot commit withdraw', { cause }); + } finally { lp.exit(); burn.exit(); - } catch (cause) { - const reason = Error('🚨 cannot commit withdraw', { cause }); - console.error(reason.message, cause); - zcf.shutdownWithFailure(reason); } external.publishPoolMetrics(); }, diff --git a/packages/fast-usdc/src/exos/settler.js b/packages/fast-usdc/src/exos/settler.js index 5d092dd87cb..931961c01b7 100644 --- a/packages/fast-usdc/src/exos/settler.js +++ b/packages/fast-usdc/src/exos/settler.js @@ -5,8 +5,8 @@ import { atob } from '@endo/base64'; import { E } from '@endo/far'; import { M } from '@endo/patterns'; +import { decodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js'; import { PendingTxStatus } from '../constants.js'; -import { addressTools } from '../utils/address.js'; import { makeFeeTools } from '../utils/fees.js'; import { EvmHashShape } from '../type-guards.js'; @@ -151,14 +151,19 @@ export const prepareSettler = ( return; } - if (!addressTools.hasQueryParams(tx.receiver)) { - console.log('not query params', tx.receiver); - return; - } - - const { EUD } = addressTools.getQueryParams(tx.receiver); - if (!EUD) { - console.log('no EUD parameter', tx.receiver); + let EUD; + try { + ({ EUD } = decodeAddressHook(tx.receiver).query); + if (!EUD) { + log('no EUD parameter', tx.receiver); + return; + } + if (typeof EUD !== 'string') { + log('EUD is not a string', EUD); + return; + } + } catch (e) { + log('no query params', tx.receiver); return; } @@ -237,13 +242,13 @@ export const prepareSettler = ( const split = calculateSplit(received); log('disbursing', split); - // TODO: what if this throws? - // arguably, it cannot. Even if deposits - // and notifications get out of order, - // we don't ever withdraw more than has been deposited. + // If this throws, which arguably can't occur since we don't ever + // withdraw more than has been deposited (as denoted by + // `FungibleTokenPacketData`), funds will remain in the + // `settlementAccount`. A remediation can occur in a future upgrade. await vowTools.when( withdrawToSeat( - // @ts-expect-error Vow vs. Promise stuff. TODO: is this OK??? + // @ts-expect-error LocalAccountMethods vs OrchestrationAccount settlementAccount, settlingSeat, harden({ In: received }), diff --git a/packages/fast-usdc/src/type-guards.js b/packages/fast-usdc/src/type-guards.js index a645f90bae4..5281521ef12 100644 --- a/packages/fast-usdc/src/type-guards.js +++ b/packages/fast-usdc/src/type-guards.js @@ -6,7 +6,7 @@ import { PendingTxStatus } from './constants.js'; * @import {TypedPattern} from '@agoric/internal'; * @import {FastUsdcTerms} from './fast-usdc.contract.js'; * @import {USDCProposalShapes} from './pool-share-math.js'; - * @import {CctpTxEvidence, FeeConfig, PendingTx, PoolMetrics, ChainPolicy, FeedPolicy} from './types.js'; + * @import {CctpTxEvidence, FeeConfig, PendingTx, PoolMetrics, ChainPolicy, FeedPolicy, AddressHook} from './types.js'; */ /** @@ -67,10 +67,12 @@ export const PendingTxShape = { }; harden(PendingTxShape); -export const EudParamShape = { - EUD: M.string(), +/** @type {TypedPattern} */ +export const AddressHookShape = { + baseAddress: M.string(), + query: { EUD: M.string() }, }; -harden(EudParamShape); +harden(AddressHookShape); const NatAmountShape = { brand: BrandShape, value: M.nat() }; /** @type {TypedPattern} */ @@ -94,16 +96,13 @@ export const PoolMetricsShape = { harden(PoolMetricsShape); /** @type {TypedPattern} */ -export const ChainPoliciesShape = M.splitRecord( - { - nobleContractAddress: EvmHashShape, - cctpTokenMessengerAddress: EvmHashShape, - confirmations: M.number(), - chainId: M.number(), - }, - { chainType: M.number() }, -); -harden(ChainPoliciesShape); +export const ChainPolicyShape = { + attenuatedCttpBridgeAddress: EvmHashShape, + cctpTokenMessengerAddress: EvmHashShape, + confirmations: M.number(), + chainId: M.number(), +}; +harden(ChainPolicyShape); /** * @type {TypedPattern} @@ -115,7 +114,7 @@ export const FeedPolicyShape = M.splitRecord( { nobleDomainId: M.number(), nobleAgoricChannelId: M.string(), - chainPolicies: M.recordOf(M.string(), ChainPoliciesShape), + chainPolicies: M.recordOf(M.string(), ChainPolicyShape), }, { eventFilter: M.string() }, ); diff --git a/packages/fast-usdc/src/types.ts b/packages/fast-usdc/src/types.ts index ee9a90efffd..30dd64acbbc 100644 --- a/packages/fast-usdc/src/types.ts +++ b/packages/fast-usdc/src/types.ts @@ -58,11 +58,14 @@ export interface PoolMetrics extends PoolStats { } export interface ChainPolicy { - nobleContractAddress: EvmHash; + /** `msg.sender` of DepositAndBurn to TokenMessenger must be an attenuated wrapper contract that does not contain `replaceDepositForBurn` */ + attenuatedCttpBridgeAddress: EvmHash; + /** @see {@link https://developers.circle.com/stablecoins/evm-smart-contracts} */ cctpTokenMessengerAddress: EvmHash; - confirmations: number; + /** e.g., `1` for ETH mainnet 42161 for Arbitrum One. @see {@link https://chainlist.org/} */ chainId: EvmChainID; - chainType?: number; + /** the number of block confirmations to observe before reporting */ + confirmations: number; } export interface FeedPolicy { @@ -82,5 +85,14 @@ export type FastUSDCConfig = { assetInfo: [Denom, DenomDetail & { brandKey?: string }][]; } & CopyRecord; +/** decoded address hook parameters */ +export type AddressHook = { + baseAddress: string; + query: { + /** end user destination address */ + EUD: string; + }; +}; + export type * from './constants.js'; export type { LiquidityPoolKit } from './exos/liquidity-pool.js'; diff --git a/packages/fast-usdc/src/utils/address.js b/packages/fast-usdc/src/utils/address.js deleted file mode 100644 index a84e8f0c5c9..00000000000 --- a/packages/fast-usdc/src/utils/address.js +++ /dev/null @@ -1,71 +0,0 @@ -import { makeError, q } from '@endo/errors'; -import { M, mustMatch } from '@endo/patterns'; - -/** - * @import {Pattern} from '@endo/patterns'; - */ - -/** - * Default pattern matcher for `getQueryParams`. - * Does not assert keys exist, but ensures existing keys are strings. - */ -const QueryParamsShape = M.splitRecord( - {}, - {}, - M.recordOf(M.string(), M.string()), -); - -/** - * Very minimal 'URL query string'-like parser that handles: - * - Query string delimiter (?) - * - Key-value separator (=) - * - Query parameter separator (&) - * - * Does not handle: - * - Subpaths (`agoric1bech32addr/opt/account?k=v`) - * - URI encoding/decoding (`%20` -> ` `) - * - note: `decodeURIComponent` seems to be available in XS - * - Multiple question marks (foo?bar=1?baz=2) - * - Empty parameters (foo=) - * - Array parameters (`foo?k=v1&k=v2` -> k: [v1, v2]) - * - Parameters without values (foo&bar=2) - */ -export const addressTools = { - /** - * @param {string} address - * @returns {boolean} - */ - hasQueryParams: address => { - try { - const params = addressTools.getQueryParams(address); - return Object.keys(params).length > 0; - } catch { - return false; - } - }, - /** - * @param {string} address - * @param {Pattern} [shape] - * @returns {Record} - * @throws {Error} if the address cannot be parsed or params do not match `shape` - */ - getQueryParams: (address, shape = QueryParamsShape) => { - const parts = address.split('?'); - if (parts.length !== 2) { - throw makeError(`Unable to parse query params: ${q(address)}`); - } - /** @type {Record} */ - const result = {}; - const paramPairs = parts[1].split('&'); - for (const pair of paramPairs) { - const [key, value] = pair.split('='); - if (!key || !value) { - throw makeError(`Invalid parameter format in pair: ${q(pair)}`); - } - result[key] = value; - } - harden(result); - mustMatch(result, shape); - return result; - }, -}; diff --git a/packages/fast-usdc/src/utils/deploy-config.js b/packages/fast-usdc/src/utils/deploy-config.js index 653d95ff293..7b160a7996c 100644 --- a/packages/fast-usdc/src/utils/deploy-config.js +++ b/packages/fast-usdc/src/utils/deploy-config.js @@ -64,11 +64,12 @@ export const configurations = { nobleDomainId: 4, chainPolicies: { Arbitrum: { + attenuatedCttpBridgeAddress: + '0xe298b93ffB5eA1FB628e0C0D55A43aeaC268e347', cctpTokenMessengerAddress: '0x19330d10D9Cc8751218eaf51E8885D058642E08A', chainId: 42161, confirmations: 2, - nobleContractAddress: '0x19330d10D9Cc8751218eaf51E8885D058642E08A', }, }, }, @@ -92,11 +93,12 @@ export const configurations = { nobleDomainId: 4, chainPolicies: { Arbitrum: { + attenuatedCttpBridgeAddress: + '0xe298b93ffB5eA1FB628e0C0D55A43aeaC268e347', cctpTokenMessengerAddress: '0x19330d10D9Cc8751218eaf51E8885D058642E08A', chainId: 42161, confirmations: 2, - nobleContractAddress: '0x19330d10D9Cc8751218eaf51E8885D058642E08A', }, }, }, @@ -118,10 +120,10 @@ export const configurations = { nobleDomainId: 4, chainPolicies: { Arbitrum: { + attenuatedCttpBridgeAddress: '0xTODO', cctpTokenMessengerAddress: '0xTODO', chainId: 421614, confirmations: 2, - nobleContractAddress: '0xTODO', }, }, }, @@ -140,10 +142,10 @@ export const configurations = { nobleDomainId: 4, chainPolicies: { Arbitrum: { + attenuatedCttpBridgeAddress: '0xTODO', cctpTokenMessengerAddress: '0xTODO', chainId: 421614, confirmations: 2, - nobleContractAddress: '0xTODO', }, }, }, diff --git a/packages/fast-usdc/test/cli/lp-commands.test.ts b/packages/fast-usdc/test/cli/lp-commands.test.ts index 367dc9a78f6..0ca642aa704 100644 --- a/packages/fast-usdc/test/cli/lp-commands.test.ts +++ b/packages/fast-usdc/test/cli/lp-commands.test.ts @@ -32,7 +32,7 @@ const makeTestContext = () => { const now = () => 1234; addLPCommands(program, { - vstorageKit: { + smartWalletKit: { // @ts-expect-error fake brands agoricNames: { brand: { FastLP, USDC } }, marshaller, diff --git a/packages/fast-usdc/test/cli/snapshots/transfer.test.ts.md b/packages/fast-usdc/test/cli/snapshots/transfer.test.ts.md index d88e5f3f969..156d050eba9 100644 --- a/packages/fast-usdc/test/cli/snapshots/transfer.test.ts.md +++ b/packages/fast-usdc/test/cli/snapshots/transfer.test.ts.md @@ -15,7 +15,7 @@ Generated by [AVA](https://avajs.dev). typeUrl: '/noble.forwarding.v1.MsgRegisterAccount', value: { channel: 'channel-test-7', - recipient: 'agoric123456?EUD=dydx1234', + recipient: 'agoric10rchp4vc53apxn32q42c3zryml8xq3xshyzuhjk6405wtxy7tl3d7e0f8az423pav3ukg7p3xgengqpq4066gy', signer: 'noble09876', }, }, diff --git a/packages/fast-usdc/test/cli/snapshots/transfer.test.ts.snap b/packages/fast-usdc/test/cli/snapshots/transfer.test.ts.snap index 4bce5856ec4..816eae48527 100644 Binary files a/packages/fast-usdc/test/cli/snapshots/transfer.test.ts.snap and b/packages/fast-usdc/test/cli/snapshots/transfer.test.ts.snap differ diff --git a/packages/fast-usdc/test/cli/transfer.test.ts b/packages/fast-usdc/test/cli/transfer.test.ts index 729f40f63cc..28cd3d41569 100644 --- a/packages/fast-usdc/test/cli/transfer.test.ts +++ b/packages/fast-usdc/test/cli/transfer.test.ts @@ -1,4 +1,5 @@ import test from 'ava'; +import { encodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js'; import transfer from '../../src/cli/transfer.js'; import { mockOut, @@ -63,14 +64,18 @@ test('Transfer registers the noble forwarding account if it does not exist', asy }; const out = mockOut(); const file = mockFile(path, JSON.stringify(config)); - const agoricSettlementAccount = 'agoric123456'; + const agoricSettlementAccount = + 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek'; const settlementAccountVstoragePath = 'published.fastUsdc.settlementAccount'; const vstorageMock = makeVstorageMock({ [settlementAccountVstoragePath]: agoricSettlementAccount, }); const amount = '150'; - const destination = 'dydx1234'; - const nobleFwdAccountQuery = `${nobleApi}/noble/forwarding/v1/address/${nobleToAgoricChannel}/${agoricSettlementAccount}${encodeURIComponent('?EUD=')}${destination}/`; + const EUD = 'dydx1234'; + const nobleFwdAccountQuery = `${nobleApi}/noble/forwarding/v1/address/${nobleToAgoricChannel}/${encodeAddressHook( + agoricSettlementAccount, + { EUD }, + )}/`; const fetchMock = makeFetchMock({ [nobleFwdAccountQuery]: { address: 'noble14lwerrcfzkzrv626w49pkzgna4dtga8c5x479h', @@ -84,7 +89,7 @@ test('Transfer registers the noble forwarding account if it does not exist', asy await transfer.transfer( file, amount, - destination, + EUD, // @ts-expect-error mocking console out, fetchMock.fetch, @@ -114,14 +119,18 @@ test('Transfer signs and broadcasts the depositForBurn message on Ethereum', asy }; const out = mockOut(); const file = mockFile(path, JSON.stringify(config)); - const agoricSettlementAccount = 'agoric123456'; + const agoricSettlementAccount = + 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek'; const settlementAccountVstoragePath = 'published.fastUsdc.settlementAccount'; const vstorageMock = makeVstorageMock({ [settlementAccountVstoragePath]: agoricSettlementAccount, }); const amount = '150'; - const destination = 'dydx1234'; - const nobleFwdAccountQuery = `${nobleApi}/noble/forwarding/v1/address/${nobleToAgoricChannel}/${agoricSettlementAccount}${encodeURIComponent('?EUD=')}${destination}/`; + const EUD = 'dydx1234'; + const nobleFwdAccountQuery = `${nobleApi}/noble/forwarding/v1/address/${nobleToAgoricChannel}/${encodeAddressHook( + agoricSettlementAccount, + { EUD }, + )}/`; const fetchMock = makeFetchMock({ [nobleFwdAccountQuery]: { address: 'noble14lwerrcfzkzrv626w49pkzgna4dtga8c5x479h', @@ -135,7 +144,7 @@ test('Transfer signs and broadcasts the depositForBurn message on Ethereum', asy await transfer.transfer( file, amount, - destination, + EUD, // @ts-expect-error mocking console out, fetchMock.fetch, diff --git a/packages/fast-usdc/test/exos/advancer.test.ts b/packages/fast-usdc/test/exos/advancer.test.ts index 908880d3fb4..b91e52485a9 100644 --- a/packages/fast-usdc/test/exos/advancer.test.ts +++ b/packages/fast-usdc/test/exos/advancer.test.ts @@ -7,12 +7,15 @@ import { Far } from '@endo/pass-style'; import type { NatAmount } from '@agoric/ertp'; import { type ZoeTools } from '@agoric/orchestration/src/utils/zoe-tools.js'; import { q } from '@endo/errors'; +import { + decodeAddressHook, + encodeAddressHook, +} from '@agoric/cosmic-proto/address-hooks.js'; import { PendingTxStatus } from '../../src/constants.js'; import { prepareAdvancer } from '../../src/exos/advancer.js'; import type { SettlerKit } from '../../src/exos/settler.js'; import { prepareStatusManager } from '../../src/exos/status-manager.js'; import { makeFeeTools } from '../../src/utils/fees.js'; -import { addressTools } from '../../src/utils/address.js'; import { commonSetup } from '../supports.js'; import { MockCctpTxEvidences, intermediateRecipient } from '../fixtures.js'; import { @@ -20,6 +23,7 @@ import { makeTestLogger, prepareMockOrchAccounts, } from '../mocks.js'; +import type { LiquidityPoolKit } from '../../src/types.js'; const LOCAL_DENOM = `ibc/${denomHash({ denom: 'uusdc', @@ -62,6 +66,11 @@ const createTestExtensions = (t, common: CommonSetup) => { // pretend funds move from tmpSeat to poolAccount localTransferVK.resolver.resolve(); }; + const rejectLocalTransfeferV = () => { + localTransferVK.resolver.reject( + new Error('One or more deposits failed: simulated error'), + ); + }; const mockZoeTools = Far('MockZoeTools', { localTransfer(...args: Parameters) { console.log('ZoeTools.localTransfer called with', args); @@ -94,18 +103,17 @@ const createTestExtensions = (t, common: CommonSetup) => { }, }); + const mockBorrowerFacetCalls: { + borrow: Parameters[]; + returnToPool: Parameters[]; + } = { borrow: [], returnToPool: [] }; + const mockBorrowerF = Far('LiquidityPool Borrow Facet', { - borrow: (seat: ZCFSeat, amounts: { USDC: NatAmount }) => { - console.log('LP.borrow called with', amounts); + borrow: (seat: ZCFSeat, amount: NatAmount) => { + mockBorrowerFacetCalls.borrow.push([seat, amount]); }, - }); - - const mockBorrowerErrorF = Far('LiquidityPool Borrow Facet', { - borrow: (seat: ZCFSeat, amounts: { USDC: NatAmount }) => { - console.log('LP.borrow called with', amounts); - throw new Error( - `Cannot borrow. Requested ${q(amounts.USDC)} must be less than pool balance ${q(usdc.make(1n))}.`, - ); + returnToPool: (seat: ZCFSeat, amount: NatAmount) => { + mockBorrowerFacetCalls.returnToPool.push([seat, amount]); }, }); @@ -124,12 +132,13 @@ const createTestExtensions = (t, common: CommonSetup) => { helpers: { inspectLogs, inspectNotifyCalls: () => harden(notifyAdvancingResultCalls), + inspectBorrowerFacetCalls: () => harden(mockBorrowerFacetCalls), }, mocks: { ...mockAccounts, - mockBorrowerErrorF, mockNotifyF, resolveLocalTransferV, + rejectLocalTransfeferV, }, services: { advancer, @@ -181,9 +190,23 @@ test('updates status to ADVANCING in happy path', async t => { 'ADVANCED status in happy path', ); - t.deepEqual(inspectLogs(0), [ - 'Advance transfer fulfilled', - '{"advanceAmount":{"brand":"[Alleged: USDC brand]","value":"[146999999n]"},"destination":{"chainId":"osmosis-1","encoding":"bech32","value":"osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men"},"result":"[undefined]"}', + t.deepEqual(inspectLogs(), [ + ['decoded EUD: osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men'], + [ + 'Advance transfer fulfilled', + { + advanceAmount: { + brand: usdc.brand, + value: 146999999n, + }, + destination: { + chainId: 'osmosis-1', + encoding: 'bech32', + value: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men', + }, + result: undefined, + }, + ], ]); // We expect to see an `Advanced` update, but that is now Settler's job. @@ -195,8 +218,7 @@ test('updates status to ADVANCING in happy path', async t => { forwardingAddress: mockEvidence.tx.forwardingAddress, fullAmount: usdc.make(mockEvidence.tx.amount), destination: { - value: addressTools.getQueryParams(mockEvidence.aux.recipientAddress) - .EUD, + value: decodeAddressHook(mockEvidence.aux.recipientAddress).query.EUD, }, }, true, // indicates transfer succeeded @@ -206,17 +228,27 @@ test('updates status to ADVANCING in happy path', async t => { test('updates status to OBSERVED on insufficient pool funds', async t => { const { + brands: { usdc }, bootstrap: { storage }, extensions: { services: { makeAdvancer, statusManager }, helpers: { inspectLogs }, - mocks: { mockPoolAccount, mockBorrowerErrorF, mockNotifyF }, + mocks: { mockPoolAccount, mockNotifyF }, }, } = t.context; + const mockBorrowerFacet = Far('LiquidityPool Borrow Facet', { + borrow: (seat: ZCFSeat, amount: NatAmount) => { + throw new Error( + `Cannot borrow. Requested ${q(amount)} must be less than pool balance ${q(usdc.make(1n))}.`, + ); + }, + returnToPool: () => {}, // not expecting this to be called + }); + // make a new advancer that intentionally throws const advancer = makeAdvancer({ - borrowerFacet: mockBorrowerErrorF, + borrowerFacet: mockBorrowerFacet, notifyFacet: mockNotifyF, poolAccount: mockPoolAccount.account, intermediateRecipient, @@ -232,9 +264,14 @@ test('updates status to OBSERVED on insufficient pool funds', async t => { 'OBSERVED status on insufficient pool funds', ); - t.deepEqual(inspectLogs(0), [ - 'Advancer error:', - '"[Error: Cannot borrow. Requested {\\"brand\\":\\"[Alleged: USDC brand]\\",\\"value\\":\\"[294999999n]\\"} must be less than pool balance {\\"brand\\":\\"[Alleged: USDC brand]\\",\\"value\\":\\"[1n]\\"}.]"', + t.deepEqual(inspectLogs(), [ + ['decoded EUD: dydx183dejcnmkka5dzcu9xw6mywq0p2m5peks28men'], + [ + 'Advancer error:', + Error( + `Cannot borrow. Requested ${q(usdc.make(294999999n))} must be less than pool balance ${q(usdc.make(1n))}.`, + ), + ], ]); }); @@ -256,9 +293,12 @@ test('updates status to OBSERVED if makeChainAddress fails', async t => { 'OBSERVED status on makeChainAddress failure', ); - t.deepEqual(inspectLogs(0), [ - 'Advancer error:', - '"[Error: Chain info not found for bech32Prefix \\"random\\"]"', + t.deepEqual(inspectLogs(), [ + ['decoded EUD: random1addr'], + [ + 'Advancer error:', + Error('Chain info not found for bech32Prefix "random"'), + ], ]); }); @@ -289,9 +329,9 @@ test('calls notifyAdvancingResult (AdvancedFailed) on failed transfer', async t mockPoolAccount.transferVResolver.reject(new Error('simulated error')); await eventLoopIteration(); - t.deepEqual(inspectLogs(0), [ - 'Advance transfer rejected', - '"[Error: simulated error]"', + t.deepEqual(inspectLogs(), [ + ['decoded EUD: dydx183dejcnmkka5dzcu9xw6mywq0p2m5peks28men'], + ['Advance transfer rejected', Error('simulated error')], ]); // We expect to see an `AdvancedFailed` update, but that is now Settler's job. @@ -306,8 +346,7 @@ test('calls notifyAdvancingResult (AdvancedFailed) on failed transfer', async t usdc.make(mockEvidence.tx.amount), ), destination: { - value: addressTools.getQueryParams(mockEvidence.aux.recipientAddress) - .EUD, + value: decodeAddressHook(mockEvidence.aux.recipientAddress).query.EUD, }, }, false, // this indicates transfer failed @@ -334,9 +373,32 @@ test('updates status to OBSERVED if pre-condition checks fail', async t => { 'tx is recorded as OBSERVED', ); - t.deepEqual(inspectLogs(0), [ - 'Advancer error:', - '"[Error: Unable to parse query params: \\"agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek\\"]"', + t.deepEqual(inspectLogs(), [ + [ + 'Advancer error:', + Error('query: {} - Must have missing properties ["EUD"]'), + ], + ]); + + await advancer.handleTransactionEvent({ + ...MockCctpTxEvidences.AGORIC_NO_PARAMS( + encodeAddressHook( + 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek', + { EUD: 'osmo1234', extra: 'value' }, + ), + ), + txHash: + '0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761799', + }); + + const [, ...remainingLogs] = inspectLogs(); + t.deepEqual(remainingLogs, [ + [ + 'Advancer error:', + Error( + 'query: {"EUD":"osmo1234","extra":"value"} - Must not have unexpected properties: ["extra"]', + ), + ], ]); }); @@ -347,6 +409,7 @@ test('will not advance same txHash:chainId evidence twice', async t => { helpers: { inspectLogs }, mocks: { mockPoolAccount, resolveLocalTransferV }, }, + brands: { usdc }, } = t.context; const mockEvidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); @@ -357,20 +420,160 @@ test('will not advance same txHash:chainId evidence twice', async t => { mockPoolAccount.transferVResolver.resolve(); await eventLoopIteration(); - t.deepEqual(inspectLogs(0), [ - 'Advance transfer fulfilled', - '{"advanceAmount":{"brand":"[Alleged: USDC brand]","value":"[146999999n]"},"destination":{"chainId":"osmosis-1","encoding":"bech32","value":"osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men"},"result":"[undefined]"}', + t.deepEqual(inspectLogs(), [ + ['decoded EUD: osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men'], + [ + 'Advance transfer fulfilled', + { + advanceAmount: { brand: usdc.brand, value: 146999999n }, + destination: { + chainId: 'osmosis-1', + encoding: 'bech32', + value: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men', + }, + result: undefined, + }, + ], ]); // Second attempt void advancer.handleTransactionEvent(mockEvidence); await eventLoopIteration(); - t.deepEqual(inspectLogs(1), [ - 'txHash already seen:', - '0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702', + const [, , ...remainingLogs] = inspectLogs(); + t.deepEqual(remainingLogs, [ + [ + 'txHash already seen:', + '0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702', + ], ]); }); -test.todo( - '#10510 zoeTools.localTransfer fails to deposit borrowed USDC to LOA', -); +test('returns payment to LP if zoeTools.localTransfer fails', async t => { + const { + extensions: { + services: { advancer }, + helpers: { inspectLogs, inspectBorrowerFacetCalls, inspectNotifyCalls }, + mocks: { rejectLocalTransfeferV }, + }, + brands: { usdc }, + } = t.context; + const mockEvidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); + + void advancer.handleTransactionEvent(mockEvidence); + rejectLocalTransfeferV(); + + await eventLoopIteration(); + + t.deepEqual( + inspectLogs(), + [ + ['decoded EUD: osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men'], + [ + '⚠️ deposit to localOrchAccount failed, attempting to return payment to LP', + Error('One or more deposits failed: simulated error'), + ], + ], + 'contract logs report error', + ); + + const { borrow, returnToPool } = inspectBorrowerFacetCalls(); + + const expectedArguments = [ + Far('MockZCFSeat', {}), + usdc.make(146999999n), // net of fees + ]; + + t.is(borrow.length, 1, 'borrow is called before zt.localTransfer fails'); + t.deepEqual(borrow[0], expectedArguments, 'borrow arguments match expected'); + + t.is( + returnToPool.length, + 1, + 'returnToPool is called after zt.localTransfer fails', + ); + t.deepEqual( + returnToPool[0], + expectedArguments, + 'same amount borrowed is returned to LP', + ); + + t.like( + inspectNotifyCalls(), + [ + [ + { + txHash: mockEvidence.txHash, + forwardingAddress: mockEvidence.tx.forwardingAddress, + }, + false, // indicates advance failed + ], + ], + 'Advancing tx is recorded as AdvanceFailed', + ); +}); + +test('alerts if `returnToPool` fallback fails', async t => { + const { + brands: { usdc }, + extensions: { + services: { makeAdvancer }, + helpers: { inspectLogs, inspectNotifyCalls }, + mocks: { mockPoolAccount, mockNotifyF, rejectLocalTransfeferV }, + }, + } = t.context; + + const mockBorrowerFacet = Far('LiquidityPool Borrow Facet', { + borrow: (seat: ZCFSeat, amount: NatAmount) => { + // note: will not be tracked by `inspectBorrowerFacetCalls` + }, + returnToPool: (seat: ZCFSeat, amount: NatAmount) => { + throw new Error( + `⚠️ borrowSeatAllocation ${q({ USDC: usdc.make(0n) })} less than amountKWR ${q(amount)}`, + ); + }, + }); + + // make a new advancer that intentionally throws during returnToPool + const advancer = makeAdvancer({ + borrowerFacet: mockBorrowerFacet, + notifyFacet: mockNotifyF, + poolAccount: mockPoolAccount.account, + intermediateRecipient, + }); + + const mockEvidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO(); + void advancer.handleTransactionEvent(mockEvidence); + rejectLocalTransfeferV(); + + await eventLoopIteration(); + + t.deepEqual(inspectLogs(), [ + ['decoded EUD: osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men'], + [ + '⚠️ deposit to localOrchAccount failed, attempting to return payment to LP', + Error('One or more deposits failed: simulated error'), + ], + [ + '🚨 deposit to localOrchAccount failure recovery failed', + Error( + `⚠️ borrowSeatAllocation ${q({ USDC: usdc.make(0n) })} less than amountKWR ${q(usdc.make(146999999n))}`, + ), + ], + ]); + + await eventLoopIteration(); + + t.like( + inspectNotifyCalls(), + [ + [ + { + txHash: mockEvidence.txHash, + forwardingAddress: mockEvidence.tx.forwardingAddress, + }, + false, // indicates advance failed + ], + ], + 'Advancing tx is recorded as AdvanceFailed', + ); +}); diff --git a/packages/fast-usdc/test/fast-usdc.contract.test.ts b/packages/fast-usdc/test/fast-usdc.contract.test.ts index 872bffc2c0a..f7869f3def1 100644 --- a/packages/fast-usdc/test/fast-usdc.contract.test.ts +++ b/packages/fast-usdc/test/fast-usdc.contract.test.ts @@ -27,11 +27,14 @@ import { E } from '@endo/far'; import { matches, objectMap } from '@endo/patterns'; import { makePromiseKit } from '@endo/promise-kit'; import path from 'path'; +import { + decodeAddressHook, + encodeAddressHook, +} from '@agoric/cosmic-proto/address-hooks.js'; import type { OperatorKit } from '../src/exos/operator-kit.js'; import type { FastUsdcSF } from '../src/fast-usdc.contract.js'; import { PoolMetricsShape } from '../src/type-guards.js'; import type { CctpTxEvidence, FeeConfig, PoolMetrics } from '../src/types.js'; -import { addressTools } from '../src/utils/address.js'; import { makeFeeTools } from '../src/utils/fees.js'; import { MockCctpTxEvidences } from './fixtures.js'; import { commonSetup, uusdcOnAgoric } from './supports.js'; @@ -382,7 +385,7 @@ const makeCustomer = ( return enough; }, sendFast: async (t: ExecutionContext, amount: bigint, EUD: string) => { - const recipientAddress = `${settleAddr}?EUD=${EUD}`; + const recipientAddress = encodeAddressHook(settleAddr, { EUD }); // KLUDGE: UI would ask noble for a forwardingAddress // "cctp" here has some noble stuff mixed in. const tx = cctp.makeTx(amount, recipientAddress); @@ -411,9 +414,7 @@ const makeCustomer = ( t.deepEqual(bank, []); // no vbank GIVE / GRAB } - const { EUD } = addressTools.getQueryParams( - evidence.aux.recipientAddress, - ); + const { EUD } = decodeAddressHook(evidence.aux.recipientAddress).query; const myMsg = local.find(lm => { if (lm.type !== 'VLOCALCHAIN_EXECUTE_TX') return false; @@ -442,7 +443,7 @@ const makeCustomer = ( 'C4', ); t.log(who, 'sees', ibcTransferMsg.token, 'sent to', EUD); - if (!EUD.startsWith('noble')) { + if (!(EUD as string).startsWith('noble')) { t.like( JSON.parse(ibcTransferMsg.memo), { diff --git a/packages/fast-usdc/test/fixtures.ts b/packages/fast-usdc/test/fixtures.ts index cbd83ccb657..7ffcedafb73 100644 --- a/packages/fast-usdc/test/fixtures.ts +++ b/packages/fast-usdc/test/fixtures.ts @@ -1,7 +1,8 @@ -import type { VTransferIBCEvent } from '@agoric/vats'; +import { encodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js'; import { buildVTransferEvent } from '@agoric/orchestration/tools/ibc-mocks.js'; import fetchedChainInfo from '@agoric/orchestration/src/fetched-chain-info.js'; import type { ChainAddress } from '@agoric/orchestration'; +import type { VTransferIBCEvent } from '@agoric/vats'; import type { CctpTxEvidence } from '../src/types.js'; const mockScenarios = [ @@ -31,7 +32,10 @@ export const MockCctpTxEvidences: Record< forwardingChannel: 'channel-21', recipientAddress: receiverAddress || - 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek?EUD=osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men', + encodeAddressHook( + 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek', + { EUD: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men' }, + ), }, chainId: 1, }), @@ -49,7 +53,10 @@ export const MockCctpTxEvidences: Record< forwardingChannel: 'channel-21', recipientAddress: receiverAddress || - 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek?EUD=dydx183dejcnmkka5dzcu9xw6mywq0p2m5peks28men', + encodeAddressHook( + 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek', + { EUD: 'dydx183dejcnmkka5dzcu9xw6mywq0p2m5peks28men' }, + ), }, chainId: 1, }), @@ -85,7 +92,10 @@ export const MockCctpTxEvidences: Record< forwardingChannel: 'channel-21', recipientAddress: receiverAddress || - 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek?EUD=random1addr', + encodeAddressHook( + 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek', + { EUD: 'random1addr' }, + ), }, chainId: 1, }), diff --git a/packages/fast-usdc/test/utils/address.test.ts b/packages/fast-usdc/test/utils/address.test.ts deleted file mode 100644 index d1c6ea23a3f..00000000000 --- a/packages/fast-usdc/test/utils/address.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; -import { M } from '@endo/patterns'; - -import { addressTools } from '../../src/utils/address.js'; -import { EudParamShape } from '../../src/type-guards.js'; - -const FIXTURES = { - AGORIC_WITH_DYDX: - 'agoric1bech32addr?EUD=dydx183dejcnmkka5dzcu9xw6mywq0p2m5peks28men', - AGORIC_WITH_OSMO: - 'agoric1bech32addr?EUD=osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men', - AGORIC_WITH_MULTIPLE: - 'agoric1bech32addr?EUD=osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men&CID=dydx-mainnet-1', - AGORIC_NO_PARAMS: 'agoric1bech32addr', - INVALID_MULTIPLE_QUESTION: 'agoric1bech32addr?param1=value1?param2=value2', - INVALID_PARAM_FORMAT: 'agoric1bech32addr?invalidparam', -} as const; - -// hasQueryParams tests -test('hasQueryParams: returns true when address has parameters', t => { - t.true(addressTools.hasQueryParams(FIXTURES.AGORIC_WITH_DYDX)); - t.true(addressTools.hasQueryParams(FIXTURES.AGORIC_WITH_OSMO)); - t.true(addressTools.hasQueryParams(FIXTURES.AGORIC_WITH_MULTIPLE)); -}); - -test('hasQueryParams: returns false when address has no parameters', t => { - t.false(addressTools.hasQueryParams(FIXTURES.AGORIC_NO_PARAMS)); -}); - -test('hasQueryParams: returns false for invalid parameter formats', t => { - t.false(addressTools.hasQueryParams(FIXTURES.INVALID_MULTIPLE_QUESTION)); - t.false(addressTools.hasQueryParams(FIXTURES.INVALID_PARAM_FORMAT)); -}); - -// getQueryParams tests - positive cases -test('getQueryParams: correctly parses address with single EUD parameter', t => { - const result = addressTools.getQueryParams( - FIXTURES.AGORIC_WITH_DYDX, - EudParamShape, - ); - t.deepEqual(result, { - EUD: 'dydx183dejcnmkka5dzcu9xw6mywq0p2m5peks28men', - }); -}); - -test('getQueryParams: correctly parses address with multiple parameters', t => { - const pattern = harden({ EUD: M.string(), CID: M.string() }); - const result = addressTools.getQueryParams( - FIXTURES.AGORIC_WITH_MULTIPLE, - pattern, - ); - t.deepEqual(result, { - EUD: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men', - CID: 'dydx-mainnet-1', - }); -}); - -test('getQueryParams: returns all parameters when no shape is provided', t => { - const result = addressTools.getQueryParams(FIXTURES.AGORIC_WITH_MULTIPLE); - t.deepEqual(result, { - EUD: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men', - CID: 'dydx-mainnet-1', - }); -}); - -test('getQueryParams: correctly handles address with no parameters', t => { - t.throws(() => addressTools.getQueryParams(FIXTURES.AGORIC_NO_PARAMS), { - message: 'Unable to parse query params: "agoric1bech32addr"', - }); -}); - -// getQueryParams tests - negative cases -test('getQueryParams: throws error for multiple question marks', t => { - t.throws( - () => addressTools.getQueryParams(FIXTURES.INVALID_MULTIPLE_QUESTION), - { - message: - 'Unable to parse query params: "agoric1bech32addr?param1=value1?param2=value2"', - }, - ); -}); - -test('getQueryParams: throws error for invalid parameter format', t => { - t.throws(() => addressTools.getQueryParams(FIXTURES.INVALID_PARAM_FORMAT), { - message: 'Invalid parameter format in pair: "invalidparam"', - }); -}); diff --git a/packages/governance/src/contractHelper.js b/packages/governance/src/contractHelper.js index c3a0896ea1d..e47b0709f54 100644 --- a/packages/governance/src/contractHelper.js +++ b/packages/governance/src/contractHelper.js @@ -250,6 +250,7 @@ const facetHelpers = (zcf, paramManager) => { * @param {M} paramTypesMap * @param {ERef} [storageNode] * @param {ERef} [marshaller] + * @param {object} [overrides] */ const handleParamGovernance = ( zcf, @@ -257,6 +258,7 @@ const handleParamGovernance = ( paramTypesMap, storageNode, marshaller, + overrides, ) => { /** @type {import('@agoric/notifier').StoredPublisherKit} */ const publisherKit = makeStoredPublisherKit( @@ -269,6 +271,7 @@ const handleParamGovernance = ( zcf, { Electorate: initialPoserInvitation }, paramTypesMap, + overrides, ); return facetHelpers(zcf, paramManager); diff --git a/packages/inter-protocol/src/proposals/add-auction.js b/packages/inter-protocol/src/proposals/add-auction.js index 6814fbdcc87..76dd57a0c6b 100644 --- a/packages/inter-protocol/src/proposals/add-auction.js +++ b/packages/inter-protocol/src/proposals/add-auction.js @@ -1,8 +1,9 @@ import { deeplyFulfilledObject, makeTracer } from '@agoric/internal'; import { makeStorageNodeChild } from '@agoric/internal/src/lib-chainStorage.js'; -import { E } from '@endo/far'; import { Stable } from '@agoric/internal/src/tokens.js'; +import { E } from '@endo/far'; import { makeGovernedTerms as makeGovernedATerms } from '../auction/params.js'; +import { provideRetiredInstances } from './utils.js'; const trace = makeTracer('NewAuction', true); @@ -11,6 +12,7 @@ const trace = makeTracer('NewAuction', true); * auctionUpgradeNewInstance: Instance; * auctionUpgradeNewGovCreator: any; * newContractGovBundleId: string; + * retiredContractInstances: MapStore; * }>} interlockPowers */ @@ -35,6 +37,7 @@ export const addAuction = async ( economicCommitteeCreatorFacet: electorateCreatorFacet, governedContractKits: governedContractKitsP, priceAuthority8400, + retiredContractInstances: retiredContractInstancesP, zoe, }, produce: { @@ -42,6 +45,7 @@ export const addAuction = async ( auctionUpgradeNewInstance, auctionUpgradeNewGovCreator, newContractGovBundleId, + retiredContractInstances: produceRetiredInstances, }, instance: { consume: { reserve: reserveInstance }, @@ -79,6 +83,16 @@ export const addAuction = async ( auctioneerInstallationP, ]); + const retiredInstances = await provideRetiredInstances( + retiredContractInstancesP, + produceRetiredInstances, + ); + + // save the auctioneer instance so we can manage it later + const boardID = await E(board).getId(legacyKit.instance); + const identifier = `auctioneer-${boardID}`; + retiredInstances.init(identifier, legacyKit.instance); + // Each field has an extra layer of type + value: // AuctionStartDelay: { type: 'relativeTime', value: { relValue: 2n, timerBrand: Object [Alleged: timerBrand] {} } } /** @type {any} */ @@ -210,6 +224,7 @@ export const ADD_AUCTION_MANIFEST = harden({ economicCommitteeCreatorFacet: true, governedContractKits: true, priceAuthority8400: true, + retiredContractInstances: true, zoe: true, }, produce: { @@ -217,6 +232,7 @@ export const ADD_AUCTION_MANIFEST = harden({ auctionUpgradeNewInstance: true, auctionUpgradeNewGovCreator: true, newContractGovBundleId: true, + retiredContractInstances: true, }, instance: { consume: { reserve: true }, diff --git a/packages/inter-protocol/src/proposals/deploy-price-feeds.js b/packages/inter-protocol/src/proposals/deploy-price-feeds.js index f98f62223f2..0ecbea49098 100644 --- a/packages/inter-protocol/src/proposals/deploy-price-feeds.js +++ b/packages/inter-protocol/src/proposals/deploy-price-feeds.js @@ -5,6 +5,7 @@ import { E } from '@endo/far'; import { unitAmount } from '@agoric/zoe/src/contractSupport/priceQuote.js'; import { oracleBrandFeedName, + provideRetiredInstances, reserveThenDeposit, sanitizePathSegment, } from './utils.js'; @@ -84,7 +85,8 @@ export const ensureOracleBrand = async ( }; /** - * @param {EconomyBootstrapPowers} powers + * @param {EconomyBootstrapPowers & + * PromiseSpaceOf<{ retiredContractInstances: MapStore }>} powers * @param {{ * AGORIC_INSTANCE_NAME: string; * contractTerms: import('@agoric/inter-protocol/src/price/fluxAggregatorKit.js').ChainlinkConfig; @@ -96,6 +98,7 @@ export const ensureOracleBrand = async ( const startPriceAggregatorInstance = async ( { consume: { + agoricNames, board, chainStorage, chainTimerService, @@ -103,8 +106,10 @@ const startPriceAggregatorInstance = async ( highPrioritySendersManager, namesByAddressAdmin, startGovernedUpgradable, + retiredContractInstances: retiredContractInstancesP, }, instance: { produce: produceInstance }, + produce: { retiredContractInstances: produceRetiredInstances }, }, { AGORIC_INSTANCE_NAME, contractTerms, brandIn, brandOut }, installation, @@ -139,6 +144,22 @@ const startPriceAggregatorInstance = async ( // @ts-expect-error GovernableStartFn vs. fluxAggregatorContract.js start installation, }); + const retiredContractInstances = await provideRetiredInstances( + retiredContractInstancesP, + produceRetiredInstances, + ); + + // save the instance so we can manage it later + const retiringInstance = await E(agoricNames).lookup( + 'instance', + AGORIC_INSTANCE_NAME, + ); + const boardID = await E(board).getId(retiringInstance); + retiredContractInstances.init( + `priceFeed-${AGORIC_INSTANCE_NAME}-${boardID}`, + retiringInstance, + ); + produceInstance[AGORIC_INSTANCE_NAME].reset(); produceInstance[AGORIC_INSTANCE_NAME].resolve(governedKit.instance); trace( @@ -191,7 +212,9 @@ const distributeInvitations = async ( }; /** - * @param {EconomyBootstrapPowers & NamedVatPowers} powers + * @param {EconomyBootstrapPowers & + * NamedVatPowers & + * PromiseSpaceOf<{ retiredContractInstances: MapStore }>} powers * @param {{ * options: PriceFeedConfig & { * priceAggregatorRef: { bundleID: string }; @@ -300,6 +323,7 @@ export const getManifestForPriceFeeds = async ( namesByAddressAdmin: t, priceAuthority: t, priceAuthorityAdmin: t, + retiredContractInstances: t, startGovernedUpgradable: t, startUpgradable: t, zoe: t, @@ -307,9 +331,10 @@ export const getManifestForPriceFeeds = async ( installation: { produce: { priceAggregator: t } }, instance: { produce: t, + consume: t, }, oracleBrand: { produce: t }, - produce: { priceAuthority8400: t }, + produce: { priceAuthority8400: t, retiredContractInstances: t }, }, }, options: { ...priceFeedOptions }, diff --git a/packages/inter-protocol/src/proposals/replaceElectorate.js b/packages/inter-protocol/src/proposals/replaceElectorate.js index cb862be5a67..adde9adf747 100644 --- a/packages/inter-protocol/src/proposals/replaceElectorate.js +++ b/packages/inter-protocol/src/proposals/replaceElectorate.js @@ -15,7 +15,7 @@ import { assertPathSegment, makeStorageNodeChild, } from '@agoric/internal/src/lib-chainStorage.js'; -import { reserveThenDeposit } from './utils.js'; +import { provideRetiredInstances, reserveThenDeposit } from './utils.js'; /** @import {EconomyBootstrapPowers} from './econ-behaviors.js' */ /** @import {EconCharterStartResult} from './econ-behaviors.js' */ @@ -181,8 +181,10 @@ const inviteToEconCharter = async ( * Starts a new Economic Committee (EC) by creating an instance with the * provided committee specifications. * - * @param {EconomyBootstrapPowers} powers - The resources and capabilities - * required to start the committee. + * @param {EconomyBootstrapPowers & + * PromiseSpaceOf<{ retiredContractInstances: MapStore }>} powers + * - The resources and capabilities required to start the committee. + * * @param {{ * options: { * committeeName: string; @@ -196,12 +198,22 @@ const inviteToEconCharter = async ( */ const startNewEconomicCommittee = async ( { - consume: { board, chainStorage, startUpgradable }, - produce: { economicCommitteeKit, economicCommitteeCreatorFacet }, + consume: { + board, + chainStorage, + startUpgradable, + retiredContractInstances: retiredInstancesP, + }, + produce: { + economicCommitteeKit, + economicCommitteeCreatorFacet, + retiredContractInstances: produceRetiredInstances, + }, installation: { consume: { committee }, }, instance: { + consume: { economicCommittee: economicCommitteeOriginalP }, produce: { economicCommittee }, }, }, @@ -214,6 +226,19 @@ const startNewEconomicCommittee = async ( trace(`committeeName ${committeeName}`); trace(`committeeSize ${committeeSize}`); + const retiredInstances = await provideRetiredInstances( + retiredInstancesP, + produceRetiredInstances, + ); + + // Record the retired electorate instance so we can manage it later. + const economicCommitteeOriginal = await economicCommitteeOriginalP; + const boardID = await E(board).getId(economicCommitteeOriginal); + retiredInstances.init( + `economicCommittee-${boardID}`, + economicCommitteeOriginal, + ); + const committeesNode = await makeStorageNodeChild( chainStorage, COMMITTEES_ROOT, @@ -309,6 +334,7 @@ const startNewEconCharter = async ({ * @typedef {PromiseSpaceOf<{ * auctionUpgradeNewInstance: Instance; * auctionUpgradeNewGovCreator: any; + * retiredContractInstances: MapStore; * }>} interlockPowers */ @@ -485,6 +511,7 @@ export const getManifestForReplaceAllElectorates = async ( manifest: { [replaceAllElectorates.name]: { consume: { + agoricNames: true, auctionUpgradeNewGovCreator: true, auctionUpgradeNewInstance: true, psmKit: true, @@ -492,6 +519,7 @@ export const getManifestForReplaceAllElectorates = async ( chainStorage: true, highPrioritySendersManager: true, namesByAddressAdmin: true, + retiredContractInstances: true, // Rest of these are designed to be widely shared board: true, startUpgradable: true, @@ -501,6 +529,7 @@ export const getManifestForReplaceAllElectorates = async ( economicCommitteeKit: true, economicCommitteeCreatorFacet: true, auctionUpgradeNewGovCreator: true, + retiredContractInstances: true, }, installation: { consume: { @@ -514,6 +543,7 @@ export const getManifestForReplaceAllElectorates = async ( economicCommittee: true, econCommitteeCharter: true, }, + consume: { economicCommittee: true }, }, }, }, diff --git a/packages/inter-protocol/src/proposals/utils.js b/packages/inter-protocol/src/proposals/utils.js index 42894a27148..165731e4838 100644 --- a/packages/inter-protocol/src/proposals/utils.js +++ b/packages/inter-protocol/src/proposals/utils.js @@ -3,6 +3,7 @@ import { E } from '@endo/far'; import { WalletName } from '@agoric/internal'; import { getCopyMapEntries, makeCopyMap } from '@agoric/store'; import { assertPathSegment } from '@agoric/internal/src/lib-chainStorage.js'; +import { makeScalarBigMapStore } from '@agoric/vat-data'; /** @import {CopyMap} from '@endo/patterns'; */ @@ -171,3 +172,22 @@ export const sanitizePathSegment = name => { assertPathSegment(candidate); return candidate; }; + +/** + * Idempotently provide an empty MapStore for the `retiredContractInstances` + * value in promise space + * + * @param {Promise} consume + * @param {Producer} produce + * @returns {Promise} + */ +export const provideRetiredInstances = async (consume, produce) => { + // Promise space has no way to look for an existing value other than awaiting a promise, + // but it does allow extra production so it's safe to do this redundantly. + produce.resolve( + makeScalarBigMapStore('retiredContractInstances', { + durable: true, + }), + ); + return consume; +}; diff --git a/packages/inter-protocol/src/provisionPool.js b/packages/inter-protocol/src/provisionPool.js index de96cfabaab..ee002f1a011 100644 --- a/packages/inter-protocol/src/provisionPool.js +++ b/packages/inter-protocol/src/provisionPool.js @@ -53,6 +53,7 @@ harden(meta); * storageNode: StorageNode; * marshaller: Marshal; * metricsOverride?: import('./provisionPoolKit.js').MetricsNotification; + * governedParamOverrides?: Record; * }} privateArgs * @param {import('@agoric/vat-data').Baggage} baggage */ @@ -74,6 +75,7 @@ export const start = async (zcf, privateArgs, baggage) => { }, privateArgs.storageNode, privateArgs.marshaller, + privateArgs.governedParamOverrides, ); const zone = makeDurableZone(baggage); diff --git a/packages/orchestration/src/exos/local-orchestration-account.js b/packages/orchestration/src/exos/local-orchestration-account.js index 362ac8950d7..41d151475fd 100644 --- a/packages/orchestration/src/exos/local-orchestration-account.js +++ b/packages/orchestration/src/exos/local-orchestration-account.js @@ -690,7 +690,7 @@ export const prepareLocalOrchestrationAccountKit = ( 'agoric', forwardOpts, ); - trace('got transfer route', q(route).toString()); + trace('got transfer route', route); // set a `timeoutTimestamp` if caller does not supply either `timeoutHeight` or `timeoutTimestamp` // TODO #9324 what's a reasonable default? currently 5 minutes diff --git a/packages/vats/src/proposals/upgrade-mintHolder-proposal.js b/packages/vats/src/proposals/upgrade-mintHolder-proposal.js new file mode 100644 index 00000000000..4b231abdc16 --- /dev/null +++ b/packages/vats/src/proposals/upgrade-mintHolder-proposal.js @@ -0,0 +1,74 @@ +import { makeTracer } from '@agoric/internal'; +import { E } from '@endo/far'; + +const trace = makeTracer('upgrade mintHolder', true); + +export const upgradeMintHolder = async ( + { + consume: { + contractKits: contractKitsP, + instancePrivateArgs: instancePrivateArgsP, + }, + }, + options, +) => { + const { contractRef, labelList } = options.options; + assert(contractRef.bundleID, 'mintHolder bundleID not found'); + assert(labelList, 'mintHolder bank asset label list not found'); + + trace(`Start mintHolder contract upgrade`); + trace(`Assets: `, labelList); + + const [contractKits, instancePrivateArgs] = await Promise.all([ + contractKitsP, + instancePrivateArgsP, + ]); + + for (const assetLabel of labelList) { + const mintHolderKit = Array.from(contractKits.values()).find( + kit => kit.label && kit.label === assetLabel, + ); + if (!mintHolderKit) { + console.error( + `ERROR: failed to upgrade ${assetLabel} mintHolder, contractKit not found`, + ); + continue; + } + + trace(`${assetLabel} mintHolderKit: `, mintHolderKit); + + const { publicFacet, adminFacet, instance } = mintHolderKit; + + /* + * Ensure that publicFacet holds an issuer by verifying that has + * the makeEmptyPurse method. + */ + await E(publicFacet).makeEmptyPurse(); + + const privateArgs = instancePrivateArgs.get(instance); + + const upgradeResult = await E(adminFacet).upgradeContract( + contractRef.bundleID, + privateArgs, + ); + + trace(`${assetLabel} upgrade result: `, upgradeResult); + } + + trace(`Finished mintHolder contract upgrade`); +}; + +export const getManifestForUpgradingMintHolder = ( + _powers, + { contractRef, labelList }, +) => ({ + manifest: { + [upgradeMintHolder.name]: { + consume: { + contractKits: true, + instancePrivateArgs: true, + }, + }, + }, + options: { contractRef, labelList }, +}); diff --git a/packages/vats/src/proposals/upgrade-provisionPool-proposal.js b/packages/vats/src/proposals/upgrade-provisionPool-proposal.js index a94fd6ef418..c2d8e7c02b9 100644 --- a/packages/vats/src/proposals/upgrade-provisionPool-proposal.js +++ b/packages/vats/src/proposals/upgrade-provisionPool-proposal.js @@ -2,7 +2,7 @@ import { E } from '@endo/far'; import { deeplyFulfilled } from '@endo/marshal'; import { makeTracer } from '@agoric/internal'; -const tracer = makeTracer('UpgradeProvisionPool'); +const trace = makeTracer('UpgradeProvisionPool'); /** * @param {BootstrapPowers & { @@ -30,7 +30,7 @@ export const upgradeProvisionPool = async ( const { provisionPoolRef } = options.options; assert(provisionPoolRef.bundleID); - tracer(`PROVISION POOL BUNDLE ID: `, provisionPoolRef); + trace(`PROVISION POOL BUNDLE ID: `, provisionPoolRef); const [ provisionPoolStartResult, @@ -49,6 +49,7 @@ export const upgradeProvisionPool = async ( adminFacet, instance, creatorFacet: ppCreatorFacet, + publicFacet: ppPublicFacet, } = provisionPoolStartResult; const { creatorFacet: wfCreatorFacet } = walletFactoryStartResult; @@ -59,9 +60,16 @@ export const upgradeProvisionPool = async ( E(electorateCreatorFacet).getPoserInvitation(), ]); + const params = await E(ppPublicFacet).getGovernedParams(); + const governedParamOverrides = harden({ + PerAccountInitialAmount: params.PerAccountInitialAmount.value, + }); + trace('governedParamOverrides: ', { governedParamOverrides }); + const newPrivateArgs = harden({ ...originalPrivateArgs, initialPoserInvitation: poserInvitation, + governedParamOverrides, }); const upgradeResult = await E(adminFacet).upgradeContract( @@ -69,7 +77,7 @@ export const upgradeProvisionPool = async ( newPrivateArgs, ); - tracer('ProvisionPool upgraded: ', upgradeResult); + trace('ProvisionPool upgraded: ', upgradeResult); const references = { bankManager, @@ -77,17 +85,17 @@ export const upgradeProvisionPool = async ( walletFactory: wfCreatorFacet, }; - tracer('Calling setReferences with: ', references); + trace('Calling setReferences with: ', references); await E(ppCreatorFacet).setReferences(references); - tracer('Creating bridgeHandler...'); + trace('Creating bridgeHandler...'); const bridgeHandler = await E(ppCreatorFacet).makeHandler(); - tracer('Setting new bridgeHandler...'); + trace('Setting new bridgeHandler...'); // @ts-expect-error casting await E(provisionWalletBridgeManager).setHandler(bridgeHandler); - tracer('Done.'); + trace('Done.'); }; export const getManifestForUpgradingProvisionPool = (