diff --git a/a3p-integration/proposals/a:upgrade-next/agd-tools.js b/a3p-integration/proposals/a:upgrade-next/agd-tools.js index f9da630c785..2c3fd01ad1a 100644 --- a/a3p-integration/proposals/a:upgrade-next/agd-tools.js +++ b/a3p-integration/proposals/a:upgrade-next/agd-tools.js @@ -48,30 +48,63 @@ export const checkForOracle = async (t, name) => { t.truthy(instance); }; -export const addOraclesForBrand = async (brandIn, oraclesByBrand) => { +export const registerOraclesForBrand = async (brandIn, oraclesByBrand) => { await null; const promiseArray = []; - const oraclesWithID = []; - // newOfferId() waits 1 second - const offerIdBase = await newOfferId(); - for (let i = 0; i < ORACLE_ADDRESSES.length; i += 1) { - const oracleAddress = ORACLE_ADDRESSES[i]; - const offerId = `${offerIdBase}.${i}`; - oraclesWithID.push({ address: oracleAddress, offerId }); - + const oraclesWithID = oraclesByBrand.get(brandIn); + for (const oracle of oraclesWithID) { + const { address, offerId } = oracle; promiseArray.push( executeOffer( - oracleAddress, + address, agops.oracle('accept', '--offerId', offerId, `--pair ${brandIn}.USD`), ), ); } - oraclesByBrand.set(brandIn, oraclesWithID); return Promise.all(promiseArray); }; +/** + * Generate a consistent map of oracleIDs for a brand that can be used to + * register oracles or to push prices. The baseID changes each time new + * invitations are sent/accepted, and need to be maintained as constants in + * scripts that use the oracles. Each oracleAddress and brand needs a unique + * offerId, so we create recoverable IDs using the brandName and oracle id, + * mixed with the upgrade at which the invitations were accepted. + * + * @param {string} baseId + * @param {string} brandName + */ +const addOraclesForBrand = (baseId, brandName) => { + const oraclesWithID = []; + for (let i = 0; i < ORACLE_ADDRESSES.length; i += 1) { + const oracleAddress = ORACLE_ADDRESSES[i]; + const offerId = `${brandName}.${baseId}.${i}`; + oraclesWithID.push({ address: oracleAddress, offerId }); + } + return oraclesWithID; +}; + +/** + * Generate a consistent map of oracleIDs and brands that can be used to + * register oracles or to push prices. The baseID changes each time new + * invitations are sent/accepted, and need to be maintained as constants in + * scripts that use these records to push prices. + * + * @param {string} baseId + * @param {string[]} brandNames + */ +export const generateOracleMap = (baseId, brandNames) => { + const oraclesByBrand = new Map(); + for (const brandName of brandNames) { + const oraclesWithID = addOraclesForBrand(baseId, brandName); + oraclesByBrand.set(brandName, oraclesWithID); + } + return oraclesByBrand; +}; + export const pushPrices = (price, brandIn, oraclesByBrand) => { const promiseArray = []; diff --git a/a3p-integration/proposals/a:upgrade-next/initial.test.js b/a3p-integration/proposals/a:upgrade-next/initial.test.js index 15b12c33697..649a08be4f5 100644 --- a/a3p-integration/proposals/a:upgrade-next/initial.test.js +++ b/a3p-integration/proposals/a:upgrade-next/initial.test.js @@ -7,7 +7,7 @@ const vats = { ibc: { incarnation: 0 }, localchain: { incarnation: 0 }, walletFactory: { incarnation: 3 }, - zoe: { incarnation: 1 }, + zoe: { incarnation: 2 }, }; test(`vat details`, async t => { diff --git a/a3p-integration/proposals/a:upgrade-next/probeZcfBundleCap.test.js b/a3p-integration/proposals/a:upgrade-next/probeZcfBundleCap.test.js index ef27a73f649..75237d2f013 100644 --- a/a3p-integration/proposals/a:upgrade-next/probeZcfBundleCap.test.js +++ b/a3p-integration/proposals/a:upgrade-next/probeZcfBundleCap.test.js @@ -10,7 +10,7 @@ const SUBMISSION_DIR = 'probe-submission'; test('upgrade Zoe to verify ZcfBundleCap endures', async t => { await null; - t.assert((await getIncarnation('zoe')) === 1, 'zoe incarnation must be one'); + t.assert((await getIncarnation('zoe')) === 2, 'zoe incarnation must be one'); // Before the test, the Wallet Factory should be using the legacy ZCF const detailsBefore = await getVatDetails('walletFactory'); @@ -24,4 +24,13 @@ test('upgrade Zoe to verify ZcfBundleCap endures', async t => { detailsBefore.incarnation + 2, 'wf incarnation must increase by 2', ); + + // The test restarts the WalletFactory, so it'll use the recently assigned + // ZCF bundle. It then restarts Zoe, so it'll revert to whichever ZCF bundle + // made it to persistent store. We then restart the Wallet Factory and see if + // it's gone back to the ZCF that Zoe initially knew about. If we could get the + // ZCF bundleID here from the probe, we'd explicitly check for that. Instead, + // we have to be content that it did indeed use the newly assigned bundle in + // manual tests. + t.not(detailsAfter.bundleID, detailsBefore.bundleID); }); diff --git a/a3p-integration/proposals/a:upgrade-next/upgradeVaults.js b/a3p-integration/proposals/a:upgrade-next/upgradeVaults.js new file mode 100644 index 00000000000..981daeba326 --- /dev/null +++ b/a3p-integration/proposals/a:upgrade-next/upgradeVaults.js @@ -0,0 +1,30 @@ +#!/usr/bin/env node + +import assert from 'node:assert/strict'; +import { + generateOracleMap, + getPriceQuote, + pushPrices, + registerOraclesForBrand, +} from './agd-tools.js'; + +const BRANDNAMES = ['ATOM', 'stATOM', 'stTIA', 'stOSMO', 'stkATOM']; +const oraclesByBrand = generateOracleMap('u16', BRANDNAMES); + +// There are no old prices for the other currencies. +const atomOutPre = await getPriceQuote('ATOM'); +assert.equal(atomOutPre, '+12010000'); + +console.log('adding oracle for each brand'); +await registerOraclesForBrand('ATOM', oraclesByBrand); +await registerOraclesForBrand('stATOM', oraclesByBrand); +await registerOraclesForBrand('stTIA', oraclesByBrand); +await registerOraclesForBrand('stOSMO', oraclesByBrand); +await registerOraclesForBrand('stkATOM', oraclesByBrand); + +console.log('pushing new prices'); +await pushPrices(11.2, 'ATOM', oraclesByBrand); +await pushPrices(11.3, 'stTIA', oraclesByBrand); +await pushPrices(11.4, 'stATOM', oraclesByBrand); +await pushPrices(11.5, 'stOSMO', oraclesByBrand); +await pushPrices(11.6, 'stkATOM', oraclesByBrand); diff --git a/a3p-integration/proposals/a:upgrade-next/upgradeVaults.test.js b/a3p-integration/proposals/a:upgrade-next/upgradeVaults.test.js index 1dc1c662232..d53bed02e5b 100644 --- a/a3p-integration/proposals/a:upgrade-next/upgradeVaults.test.js +++ b/a3p-integration/proposals/a:upgrade-next/upgradeVaults.test.js @@ -10,11 +10,11 @@ import { } from '@agoric/synthetic-chain'; import { - addOraclesForBrand, bankSend, BID_OFFER_ID, checkForOracle, createBid, + generateOracleMap, getLiveOffers, getPriceQuote, pushPrices, @@ -40,27 +40,10 @@ const checkPriceFeedVatsUpdated = async t => { ]); }; -const oraclesByBrand = new Map(); - -const tryPushPrices = async t => { - // There are no old prices for the other currencies. - const atomOutPre = await getPriceQuote('ATOM'); - t.is(atomOutPre, '+12010000'); - - t.log('adding oracle for each brand'); - await addOraclesForBrand('ATOM', oraclesByBrand); - await addOraclesForBrand('stATOM', oraclesByBrand); - await addOraclesForBrand('stTIA', oraclesByBrand); - await addOraclesForBrand('stOSMO', oraclesByBrand); - await addOraclesForBrand('stkATOM', oraclesByBrand); - - t.log('pushing new prices'); - await pushPrices(11.2, 'ATOM', oraclesByBrand); - await pushPrices(11.3, 'stTIA', oraclesByBrand); - await pushPrices(11.4, 'stATOM', oraclesByBrand); - await pushPrices(11.5, 'stOSMO', oraclesByBrand); - await pushPrices(11.6, 'stkATOM', oraclesByBrand); +const BRANDNAMES = ['ATOM', 'stATOM', 'stTIA', 'stOSMO', 'stkATOM']; +const oraclesByBrand = generateOracleMap('u16', BRANDNAMES); +const checkNewQuotes = async t => { t.log('awaiting new quotes'); const atomOut = await getPriceQuote('ATOM'); t.is(atomOut, '+11200000'); @@ -106,7 +89,7 @@ const triggerAuction = async t => { t.is(atomOut, '+5200000'); }; -const makeNewAuctionVat = async t => { +const checkAuctionVat = async t => { const details = await getDetailsMatchingVats('auctioneer'); // This query matches both the auction and its governor, so double the count t.true(Object.keys(details).length > 2); @@ -117,8 +100,8 @@ test('liquidation post upgrade', async t => { t.log('starting upgrade vaults test'); await checkPriceFeedVatsUpdated(t); - t.log('starting pushPrices'); - await tryPushPrices(t); + t.log('check new price quotes'); + await checkNewQuotes(t); t.log('create a new Bid for the auction'); await createNewBid(t); @@ -130,5 +113,5 @@ test('liquidation post upgrade', async t => { await triggerAuction(t); t.log('make new auction'); - await makeNewAuctionVat(t); + await checkAuctionVat(t); }); diff --git a/a3p-integration/proposals/a:upgrade-next/use.sh b/a3p-integration/proposals/a:upgrade-next/use.sh index 094b3f7f881..dff35e765b1 100755 --- a/a3p-integration/proposals/a:upgrade-next/use.sh +++ b/a3p-integration/proposals/a:upgrade-next/use.sh @@ -4,3 +4,5 @@ # actions are executed in the upgraded chain software and the effects are # persisted in the generated image for the upgrade, so they can be used in # later steps, such as the "test" step, or further proposal layers. + +./upgradeVaults.js diff --git a/a3p-integration/proposals/b:enable-orchestration/initial.test.js b/a3p-integration/proposals/b:enable-orchestration/initial.test.js index 1ad89f65612..2d1615bcbc9 100644 --- a/a3p-integration/proposals/b:enable-orchestration/initial.test.js +++ b/a3p-integration/proposals/b:enable-orchestration/initial.test.js @@ -9,7 +9,7 @@ const vats = { orchestration: { incarnation: 0 }, transfer: { incarnation: 0 }, walletFactory: { incarnation: 3 }, - zoe: { incarnation: 1 }, + zoe: { incarnation: 2 }, }; test(`vat details`, async t => { diff --git a/a3p-integration/proposals/z:acceptance/create-kread-item-test.sh b/a3p-integration/proposals/z:acceptance/create-kread-item-test.sh new file mode 100755 index 00000000000..89b175b7340 --- /dev/null +++ b/a3p-integration/proposals/z:acceptance/create-kread-item-test.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -euo pipefail + +source /usr/src/upgrade-test-scripts/env_setup.sh + +OFFER=$(mktemp -t agops.XXX) +agops vaults open --wantMinted 6.00 --giveCollateral 9.0 >| "$OFFER" +agoric wallet send --offer "$OFFER" --from $GOV1ADDR --keyring-backend="test" + +govamount="200000000ubld" +provisionSmartWallet $GOV1ADDR $govamount + +KREAD_ITEM_OFFER=$(mktemp -t kreadItem.XXX) +node ./generate-kread-item-request.mjs > $KREAD_ITEM_OFFER +agops perf satisfaction --from $GOV1ADDR --executeOffer $KREAD_ITEM_OFFER --keyring-backend=test + +agd query vstorage data published.wallet.$GOV1ADDR.current -o json >&gov1.out +name=$(jq '.value | fromjson | .values[2] | fromjson | .body[1:] | fromjson | .purses[1].balance.value.payload[0][0].name ' gov1.out) +test_val $name \"ephemeral_Ace\" "found KREAd character" diff --git a/a3p-integration/proposals/z:acceptance/generate-kread-item-request.mjs b/a3p-integration/proposals/z:acceptance/generate-kread-item-request.mjs new file mode 100644 index 00000000000..be9e07d6dde --- /dev/null +++ b/a3p-integration/proposals/z:acceptance/generate-kread-item-request.mjs @@ -0,0 +1,76 @@ +/* global process */ +import assert from 'assert'; +import { execFile } from 'child_process'; + +const ISTunit = 1_000_000n; // aka displayInfo: { decimalPlaces: 6 } + +const id = `KREAd-test-${Date.now()}`; + +// poor-man's zx +const $ = cmd => { + const [file, ...args] = cmd.split(' '); + + return new Promise((resolve, reject) => { + execFile(file, args, { encoding: 'utf8' }, (err, out) => { + if (err) return reject(err); + resolve(out); + }); + }); +}; + +const zip = (xs, ys) => xs.map((x, i) => [x, ys[i]]); + +const fromSmallCapsEntries = txt => { + const { body, slots } = JSON.parse(txt); + const theEntries = zip(JSON.parse(body.slice(1)), slots).map( + ([[name, ref], boardID]) => { + const iface = ref.replace(/^\$\d+\./, ''); + return [name, { iface, boardID }]; + }, + ); + return Object.fromEntries(theEntries); +}; + +const brand = fromSmallCapsEntries( + await $('agoric follow -lF :published.agoricNames.brand -o text'), +); +assert(brand.IST); + +const slots = []; // XXX global mutable state + +const smallCaps = { + Nat: n => `+${n}`, + // XXX mutates obj + ref: obj => { + if (obj.ix) return obj.ix; + const ix = slots.length; + slots.push(obj.boardID); + obj.ix = `$${ix}.Alleged: ${obj.iface}`; + return obj.ix; + }, +}; + +const body = { + method: 'executeOffer', + offer: { + id, + invitationSpec: { + source: 'agoricContract', + instancePath: ['kread'], + callPipe: [['makeMintCharacterInvitation', []]], + }, + offerArgs: { name: 'ephemeral_Ace' }, + proposal: { + give: { + Price: { + brand: smallCaps.ref(brand.IST), + value: smallCaps.Nat(5n * ISTunit) } + }, + }, + }, +}; + +const capData = { body: `#${JSON.stringify(body)}`, slots }; +const action = JSON.stringify(capData); + +console.log(action); diff --git a/a3p-integration/proposals/z:acceptance/test.sh b/a3p-integration/proposals/z:acceptance/test.sh index f165b2bc613..38ffa27a4ae 100755 --- a/a3p-integration/proposals/z:acceptance/test.sh +++ b/a3p-integration/proposals/z:acceptance/test.sh @@ -9,3 +9,5 @@ yarn ava initial.test.js # test more, in ways that change system state GLOBIGNORE=initial.test.js yarn ava ./*.test.js + +./create-kread-item-test.sh diff --git a/golang/cosmos/app/app.go b/golang/cosmos/app/app.go index 83b1fb0bdff..6ba478544c2 100644 --- a/golang/cosmos/app/app.go +++ b/golang/cosmos/app/app.go @@ -934,8 +934,10 @@ func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Conte // Each CoreProposalStep runs sequentially, and can be constructed from // one or more modules executing in parallel within the step. CoreProposalSteps = []vm.CoreProposalStep{ - // Upgrade ZCF only - vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/upgrade-zcf.js"), + // Upgrade Zoe + ZCF + vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/replace-zoe.js"), + // Revive KREAd characters + vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/revive-kread.js"), // upgrade the provisioning vat vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/replace-provisioning.js"), diff --git a/packages/builders/scripts/vats/revive-kread.js b/packages/builders/scripts/vats/revive-kread.js new file mode 100644 index 00000000000..72d22da477c --- /dev/null +++ b/packages/builders/scripts/vats/revive-kread.js @@ -0,0 +1,13 @@ +import { makeHelpers } from '@agoric/deploy-script-support'; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ +export const defaultProposalBuilder = async () => + harden({ + sourceSpec: '@agoric/vats/src/proposals/kread-proposal.js', + getManifestCall: ['getManifestForKread'], + }); + +export default async (homeP, endowments) => { + const { writeCoreProposal } = await makeHelpers(homeP, endowments); + await writeCoreProposal('revive-kread', defaultProposalBuilder); +}; diff --git a/packages/vats/src/proposals/kread-proposal.js b/packages/vats/src/proposals/kread-proposal.js new file mode 100644 index 00000000000..7b8affda75a --- /dev/null +++ b/packages/vats/src/proposals/kread-proposal.js @@ -0,0 +1,24 @@ +import { E } from '@endo/far'; + +/** + * @param {BootstrapPowers & { + * consume: { kreadKit: any }; + * }} powers + */ +export const repairKread = async ({ consume: { kreadKit: kreadKitP } }) => { + console.log('repairSubscribers'); + + const kreadKit = await kreadKitP; + const creatorFacet = kreadKit.creatorFacet; + console.log(`KREAd creatorFacet`, creatorFacet); + await E(creatorFacet).reviveMarketExitSubscribers(); + console.log('KREAd subscribers were revived!'); +}; + +export const getManifestForKread = _powers => ({ + manifest: { + [repairKread.name]: { + consume: { kreadKit: true }, + }, + }, +}); diff --git a/packages/vats/src/proposals/probeZcfBundle.js b/packages/vats/src/proposals/probeZcfBundle.js index e2a853b4516..ee44affb2c6 100644 --- a/packages/vats/src/proposals/probeZcfBundle.js +++ b/packages/vats/src/proposals/probeZcfBundle.js @@ -48,9 +48,7 @@ export const probeZcfBundleCap = async ( await E(adminNode).upgrade(zoeBundleCap, {}); // STEP 4: restart WF //////////////////////// - // Need to use `upgradeContract` instead of `restartContract` - // See https://github.com/Agoric/agoric-sdk/issues/9249 - await E(walletAdminFacet).upgradeContract(walletRef.bundleID, privateArgs); + await E(walletAdminFacet).restartContract(privateArgs); // ////// See which zcf bundle was used ////////// };