Skip to content

Commit

Permalink
test: prepare a3p test for vault upgrade
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris-Hibbert committed Mar 29, 2024
1 parent 0865195 commit 627e966
Show file tree
Hide file tree
Showing 10 changed files with 252 additions and 31 deletions.
2 changes: 2 additions & 0 deletions golang/cosmos/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,8 @@ func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Conte
),
// Add new auction contract. The old one will be retired shortly.
vm.CoreProposalStepForModules( "@agoric/builders/scripts/vats/add-auction.js"),
// upgrade vaultFactory.
vm.CoreProposalStepForModules( "@agoric/builders/scripts/vats/upgradeVaults.js"),
}
}

Expand Down
23 changes: 23 additions & 0 deletions packages/builders/scripts/vats/upgradeVaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { makeHelpers } from '@agoric/deploy-script-support';

/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */
export const defaultProposalBuilder = async ({ publishRef, install }) =>
harden({
sourceSpec: '@agoric/inter-protocol/src/proposals/upgrade-vaults.js',
getManifestCall: [
'getManifestForUpgradeVaults',
{
vaultsRef: publishRef(
install(
'@agoric/inter-protocol/src/vaultFactory/vaultFactory.js',
'../bundles/bundle-vaultFactory.js',
),
),
},
],
});

export default async (homeP, endowments) => {
const { writeCoreProposal } = await makeHelpers(homeP, endowments);
await writeCoreProposal('upgrade-vaults', defaultProposalBuilder);
};
8 changes: 4 additions & 4 deletions packages/governance/src/contractGovernance/paramManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,10 @@ const makeParamManagerBuilder = (publisherKit, zoe) => {
if (!zoe) {
throw Fail`zoe must be provided for governed Invitations ${zoe}`;
}
const { instance, installation } = await E(zoe).getInvitationDetails(i);
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- the build config doesn't expect an error here
// @ts-ignore typedefs say they're guaranteed truthy but just to be safe
assert(instance && installation, 'must be an invitation');

// local check on isLive() gives better report than .getInvitationDetails()
const isLive = await E(E(zoe).getInvitationIssuer()).isLive(i);
isLive || Fail`Invitation passed to paramManager is not live ${i}`;
};

/**
Expand Down
25 changes: 10 additions & 15 deletions packages/governance/src/contractHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import { Far } from '@endo/marshal';
import { makeStoredPublisherKit } from '@agoric/notifier';
import { getMethodNames, objectMap } from '@agoric/internal';
import { ignoreContext, prepareExo } from '@agoric/vat-data';
import { keyEQ, M } from '@agoric/store';
import { M } from '@agoric/store';
import { AmountShape, BrandShape } from '@agoric/ertp';
import { RelativeTimeRecordShape, TimestampRecordShape } from '@agoric/time';
import { E } from '@endo/eventual-send';
import { assertElectorateMatches } from './contractGovernance/paramManager.js';
import { makeParamManagerFromTerms } from './contractGovernance/typedParamManager.js';
import { GovernorFacetShape } from './typeGuards.js';
import { CONTRACT_ELECTORATE } from './contractGovernance/governParam.js';

const { Fail, quote: q } = assert;
const { Fail } = assert;

export const GOVERNANCE_STORAGE_KEY = 'governance';

Expand All @@ -31,22 +31,17 @@ const publicMixinAPI = harden({
});

/**
* Verify that the electorate is represented by a live invitation.
*
* @param {ZCF<GovernanceTerms<{}> & {}>} zcf
* @param {import('./contractGovernance/typedParamManager.js').TypedParamManager<any>} paramManager
*/
export const validateElectorate = (zcf, paramManager) => {
const { governedParams } = zcf.getTerms();
return E.when(paramManager.getParams(), finishedParams => {
try {
keyEQ(governedParams, finishedParams) ||
Fail`The 'governedParams' term must be an object like ${q(
finishedParams,
)}, but was ${q(governedParams)}`;
assertElectorateMatches(paramManager, governedParams);
} catch (err) {
zcf.shutdownWithFailure(err);
}
});
const invitation = paramManager.getInternalParamValue(CONTRACT_ELECTORATE);
return E.when(
E(zcf.getInvitationIssuer()).isLive(invitation),
isLive => isLive || Fail`Electorate invitation is not live.`,
);
};
harden(validateElectorate);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export const createPriceFeed = async (
),
err =>
console.error(
`🚨 failed to update priceAggregator instance for ${AGORIC_INSTANCE_NAME}`,
`🚨 failed to update priceAggregator installation for ${AGORIC_INSTANCE_NAME}`,
err,
),
);
Expand Down
163 changes: 163 additions & 0 deletions packages/inter-protocol/src/proposals/upgrade-vaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { E } from '@endo/far';
import { makeNotifierFromAsyncIterable } from '@agoric/notifier';
import { AmountMath } from '@agoric/ertp/src/index.js';
import { makeScalarMapStore } from '@agoric/store/src/index.js';

// stand-in for Promise.any() which isn't available at this point.
const any = promises =>
new Promise((resolve, reject) => {
for (const promise of promises) {
promise.then(resolve);
}
void Promise.allSettled(promises).then(results => {
const rejects = results.filter(({ status }) => status === 'rejected');
if (rejects.length === results.length) {
// @ts-expect-error TypeScript doesn't know enough
const messages = rejects.map(({ message }) => message);
const aggregate = new Error(messages.join(';'));
// @ts-expect-error TypeScript doesn't know enough
aggregate.errors = rejects.map(({ reason }) => reason);
reject(aggregate);
}
});
});

/**
* @param {import('../../src/proposals/econ-behaviors').EconomyBootstrapPowers} powers
* @param {{ options: { vaultsRef: { bundleID: string } } }} options
*/
export const upgradeVaults = async (powers, { options }) => {
const {
consume: {
agoricNamesAdmin,
auctioneerKit: auctioneerKitP,
priceAuthority,
vaultFactoryKit,
zoe,
economicCommitteeCreatorFacet: electorateCreatorFacet,
reserveKit,
},
} = powers;
const { vaultsRef } = options;
const kit = await vaultFactoryKit;
const auctioneerKit = await auctioneerKitP;
const { instance: directorInstance } = kit;
const allBrands = await E(zoe).getBrands(directorInstance);
const { Minted: istBrand, ...vaultBrands } = allBrands;

const bundleID = vaultsRef.bundleID;
console.log(`upgradeVaults: bundleId`, bundleID);
let installationP;
await null;
if (vaultsRef) {
if (bundleID) {
installationP = E(zoe).installBundleID(bundleID);
await E.when(
installationP,
installation =>
E(E(agoricNamesAdmin).lookupAdmin('installation')).update(
'vaultFactory',
installation,
),
err =>
console.error(`🚨 failed to update vaultFactory installation`, err),
);
}
}

const readManagerParams = async () => {
const { publicFacet: directorPF } = kit;

await null;

const params = {};
for (const kwd of Object.keys(vaultBrands)) {
const b = vaultBrands[kwd];
const subscription = E(directorPF).getSubscription({
collateralBrand: b,
});
const notifier = makeNotifierFromAsyncIterable(subscription);
const { value } = await notifier.getUpdateSince();
params[kwd] = harden({
brand: b,
debtLimit: value.current.DebtLimit.value,
interestRate: value.current.InterestRate.value,
liquidationMargin: value.current.LiquidationMargin.value,
liquidationPadding: value.current.LiquidationPadding.value,
liquidationPenalty: value.current.LiquidationPenalty.value,
mintFee: value.current.MintFee.value,
});
}
return params;
};
const managerParamValues = await readManagerParams();

// upgrade the vaultFactory
const upgradeVaultFactory = async () => {
// @ts-expect-error cast XXX privateArgs missing from type
const { privateArgs } = kit;

const shortfallInvitation = await E(
E.get(reserveKit).creatorFacet,
).makeShortfallReportingInvitation();

const poserInvitation = await E(
electorateCreatorFacet,
).getPoserInvitation();
/** @type {import('../../src/vaultFactory/vaultFactory').VaultFactoryContract['privateArgs']} */
const newPrivateArgs = harden({
...privateArgs,
auctioneerInstance: auctioneerKit.instance,
initialPoserInvitation: poserInvitation,
initialShortfallInvitation: shortfallInvitation,
managerParams: managerParamValues,
});

const upgradeResult = await E(kit.adminFacet).upgradeContract(
bundleID,
newPrivateArgs,
);

console.log('upgraded vaultFactory.', upgradeResult);
};

// Wait for at least one new price feed to be ready before upgrading Vaults
void E.when(
any(
Object.values(vaultBrands).map(brand =>
E(priceAuthority).quoteGiven(AmountMath.make(brand, 10n), istBrand),
),
),
() => upgradeVaultFactory(),
);

console.log(`upgradeVaults scheduled; waiting for priceFeeds`);
};

const t = 'upgradeVaults';
/**
* Return the manifest, installations, and options for upgrading Vaults.
*
* @param {object} _ign
* @param {any} vaultUpgradeOptions
*/
export const getManifestForUpgradeVaults = async (
_ign,
vaultUpgradeOptions,
) => ({
manifest: {
[upgradeVaults.name]: {
consume: {
agoricNamesAdmin: t,
auctioneerKit: t,
economicCommitteeCreatorFacet: t,
priceAuthority: t,
reserveKit: t,
vaultFactoryKit: t,
board: t,
zoe: t,
},
},
},
options: { ...vaultUpgradeOptions },
});
41 changes: 37 additions & 4 deletions packages/inter-protocol/src/vaultFactory/params.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,19 @@ export const makeGovernedTerms = ({
});
};
harden(makeGovernedTerms);

// XXX Better to declare this as VaultManagerParamValues + brand. How?
/**
* @typedef {object} VaultManagerParams
* @property {Brand} brand
* @property {Ratio} liquidationMargin
* @property {Ratio} liquidationPenalty
* @property {Ratio} interestRate
* @property {Ratio} mintFee
* @property {Amount<'nat'>} debtLimit
* @property {Ratio} [liquidationPadding]
*/

/**
* Stop-gap which restores initial param values UNTIL
* https://github.com/Agoric/agoric-sdk/issues/5200
Expand All @@ -171,8 +184,14 @@ harden(makeGovernedTerms);
*
* @param {import('@agoric/vat-data').Baggage} baggage
* @param {ERef<Marshaller>} marshaller
* @param {Record<string, VaultManagerParams>} managerParamValues - sets of
* parameters (plus brand:) keyed by Keyword. override stored initial values
*/
export const provideVaultParamManagers = (baggage, marshaller) => {
export const provideVaultParamManagers = (
baggage,
marshaller,
managerParamValues,
) => {
/** @type {MapStore<Brand, VaultParamManager>} */
const managers = makeScalarMapStore();

Expand All @@ -197,10 +216,24 @@ export const provideVaultParamManagers = (baggage, marshaller) => {
return manager;
};

// restore from baggage
// [...managerArgs.entries()].map(([brand, args]) => makeManager(brand, args));
// restore from baggage, unless `managerParamValues` overrides.
for (const [brand, args] of managerArgs.entries()) {
makeManager(brand, args);
let values;
for (const key of Object.keys(managerParamValues)) {
// For a couple of runs, changing to managerParamValues[+key] worked,
// but then that stopped working. Dunno why
// eslint-disable-next-line no-restricted-syntax
if (managerParamValues[key].brand === brand) {
values = managerParamValues[+key];
break;
}
}

if (values) {
makeManager(brand, { ...args, initialParamValues: values });
} else {
makeManager(brand, args);
}
}

return {
Expand Down
11 changes: 7 additions & 4 deletions packages/inter-protocol/src/vaultFactory/vaultDirector.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ const prepareVaultDirector = (
marshaller,
makeRecorderKit,
makeERecorderKit,
managerParams,
) => {
/** @type {import('../reserve/assetReserve.js').ShortfallReporter} */
let shortfallReporter;
Expand All @@ -120,7 +121,11 @@ const prepareVaultDirector = (
// Non-durable map because param managers aren't durable.
// In the event they're needed they can be reconstructed from contract terms and off-chain data.
/** a powerful object; can modify parameters */
const vaultParamManagers = provideVaultParamManagers(baggage, marshaller);
const vaultParamManagers = provideVaultParamManagers(
baggage,
marshaller,
managerParams,
);

const metricsNode = E(storageNode).makeChildNode('metrics');

Expand All @@ -146,12 +151,10 @@ const prepareVaultDirector = (
const oldInvitation = baggage.has(shortfallInvitationKey)
? baggage.get(shortfallInvitationKey)
: undefined;
console.log('@@@@@ Old Invitation', oldInvitation);

const newInvitation = await directorParamManager.getInternalParamValue(
SHORTFALL_INVITATION_KEY,
);
console.log('@@@@@ New Invitation', newInvitation);

if (newInvitation === oldInvitation) {
shortfallReporter ||
Expand Down Expand Up @@ -309,7 +312,7 @@ const prepareVaultDirector = (
SubscriberShape,
),
getElectorateSubscription: M.call().returns(SubscriberShape),
getGovernedParams: M.call({ collateralBrand: BrandShape }).returns(
getGovernedParams: M.callWhen({ collateralBrand: BrandShape }).returns(
M.record(),
),
getInvitationAmount: M.call(M.string()).returns(AmountShape),
Expand Down
Loading

0 comments on commit 627e966

Please sign in to comment.