Skip to content

Commit

Permalink
Fix upgrade behaviors (#9526)
Browse files Browse the repository at this point in the history
## Description
- Upgrades Zoe and repair KREAd
  - KREAd proposal and tests are a port from the release branch version used during upgrade 14
- restores the old probe zcf test behavior now that #9249 was fixed in u15
- Includes #9536

### Security Considerations
None

### Scaling Considerations
None

### Documentation Considerations
None

### Testing Considerations
Restores previously disabled or omited tests

### Upgrade Considerations
Change to core proposal in the next software upgrade (upgrade 16)
  • Loading branch information
mergify[bot] authored Jun 22, 2024
2 parents 44926a7 + e1d961e commit 8d05faf
Show file tree
Hide file tree
Showing 14 changed files with 235 additions and 44 deletions.
55 changes: 44 additions & 11 deletions a3p-integration/proposals/a:upgrade-next/agd-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];

Expand Down
2 changes: 1 addition & 1 deletion a3p-integration/proposals/a:upgrade-next/initial.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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);
});
30 changes: 30 additions & 0 deletions a3p-integration/proposals/a:upgrade-next/upgradeVaults.js
Original file line number Diff line number Diff line change
@@ -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);
33 changes: 8 additions & 25 deletions a3p-integration/proposals/a:upgrade-next/upgradeVaults.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import {
} from '@agoric/synthetic-chain';

import {
addOraclesForBrand,
bankSend,
BID_OFFER_ID,
checkForOracle,
createBid,
generateOracleMap,
getLiveOffers,
getPriceQuote,
pushPrices,
Expand All @@ -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');
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -130,5 +113,5 @@ test('liquidation post upgrade', async t => {
await triggerAuction(t);

t.log('make new auction');
await makeNewAuctionVat(t);
await checkAuctionVat(t);
});
2 changes: 2 additions & 0 deletions a3p-integration/proposals/a:upgrade-next/use.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
19 changes: 19 additions & 0 deletions a3p-integration/proposals/z:acceptance/create-kread-item-test.sh
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
@@ -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);
2 changes: 2 additions & 0 deletions a3p-integration/proposals/z:acceptance/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 4 additions & 2 deletions golang/cosmos/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
13 changes: 13 additions & 0 deletions packages/builders/scripts/vats/revive-kread.js
Original file line number Diff line number Diff line change
@@ -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);
};
24 changes: 24 additions & 0 deletions packages/vats/src/proposals/kread-proposal.js
Original file line number Diff line number Diff line change
@@ -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 },
},
},
});
4 changes: 1 addition & 3 deletions packages/vats/src/proposals/probeZcfBundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 //////////
};
Expand Down

0 comments on commit 8d05faf

Please sign in to comment.