diff --git a/a3p-integration/proposals/p:upgrade-19/.gitignore b/a3p-integration/proposals/p:upgrade-19/.gitignore index ffa9f22d144..e02eb30fb97 100644 --- a/a3p-integration/proposals/p:upgrade-19/.gitignore +++ b/a3p-integration/proposals/p:upgrade-19/.gitignore @@ -7,3 +7,4 @@ publishTestInfo/ upgrade-mintHolder/ upgradeAssetReserve/ terminate-governor/ +upgradePSM/ diff --git a/a3p-integration/proposals/p:upgrade-19/package.json b/a3p-integration/proposals/p:upgrade-19/package.json index 638d622ecf4..d70003a54e0 100644 --- a/a3p-integration/proposals/p:upgrade-19/package.json +++ b/a3p-integration/proposals/p:upgrade-19/package.json @@ -6,6 +6,7 @@ "testing/add-USD-LEMONS.js addUsdLemons", "vats/upgrade-provisionPool.js upgradeProvisionPool", "vats/upgrade-asset-reserve.js upgradeAssetReserve", + "vats/upgrade-psm.js upgradePSM", "vats/upgrade-paRegistry.js", "vats/upgrade-agoricNames.js agoricNamesCoreEvals/upgradeAgoricNames", "testing/add-USD-OLIVES.js agoricNamesCoreEvals/addUsdOlives", diff --git a/a3p-integration/proposals/p:upgrade-19/psm.test.js b/a3p-integration/proposals/p:upgrade-19/psm.test.js new file mode 100644 index 00000000000..90ff2060d98 --- /dev/null +++ b/a3p-integration/proposals/p:upgrade-19/psm.test.js @@ -0,0 +1,81 @@ +/* eslint-env node */ +/** + * @file The goal of this file is to make sure all the PSM contacts are upgrade. Each set of vats are different + * per chain, but for mainnet, we expect this to be: V37-V42, V73, V76 + * + * The test scenario is as follows; + * 1. Simulate trade of IST and USDC + * 2. Upgrade all PSMs + * 3. Verify metrics are the same after the upgrade + * 4. Verify trading is still possible after the upgrade + */ + +import '@endo/init'; +import test from 'ava'; +import { evalBundles } from '@agoric/synthetic-chain'; +import { makeVstorageKit } from '@agoric/client-utils'; + +const UPGRADE_PSM_DIR = 'upgradePSM'; +const SWAP_ANCHOR = 'swapAnchorForMintedSeat'; + +test.before(async t => { + const vstorageKit = await makeVstorageKit( + { fetch }, + { rpcAddrs: ['http://localhost:26657'], chainName: 'agoriclocal' }, + ); + + t.context = { + vstorageKit, + }; +}); + +test.serial('simulate trade of IST and USDC', async t => { + // @ts-expect-error casting + const { vstorageKit } = t.context; + + await evalBundles(SWAP_ANCHOR); + + const metrics = await vstorageKit.readLatestHead( + 'published.psm.IST.USDC.metrics', + ); + + t.is(metrics.anchorPoolBalance.value, 500000n); + t.is(metrics.feePoolBalance.value, 0n); + t.is(metrics.mintedPoolBalance.value, 500000n); + t.is(metrics.totalAnchorProvided.value, 0n); + t.is(metrics.totalMintedProvided.value, 500000n); +}); + +test.serial('upgrade PSMs', async t => { + // @ts-expect-error casting + const { vstorageKit } = t.context; + + await evalBundles(UPGRADE_PSM_DIR); + + const metrics = await vstorageKit.readLatestHead( + 'published.psm.IST.USDC.metrics', + ); + + t.is(metrics.anchorPoolBalance.value, 500000n); + t.is(metrics.feePoolBalance.value, 0n); + t.is(metrics.mintedPoolBalance.value, 500000n); + t.is(metrics.totalAnchorProvided.value, 0n); + t.is(metrics.totalMintedProvided.value, 500000n); +}); + +test.serial('verify trading after upgrade', async t => { + // @ts-expect-error casting + const { vstorageKit } = t.context; + + await evalBundles(SWAP_ANCHOR); + + const metrics = await vstorageKit.readLatestHead( + 'published.psm.IST.USDC.metrics', + ); + + t.is(metrics.anchorPoolBalance.value, 1000000n); + t.is(metrics.feePoolBalance.value, 0n); + t.is(metrics.mintedPoolBalance.value, 1000000n); + t.is(metrics.totalAnchorProvided.value, 0n); + t.is(metrics.totalMintedProvided.value, 1000000n); +}); diff --git a/a3p-integration/proposals/p:upgrade-19/swapAnchorForMintedSeat/swap-anchor-for-minted-seat-permit.json b/a3p-integration/proposals/p:upgrade-19/swapAnchorForMintedSeat/swap-anchor-for-minted-seat-permit.json new file mode 100644 index 00000000000..067c22846cd --- /dev/null +++ b/a3p-integration/proposals/p:upgrade-19/swapAnchorForMintedSeat/swap-anchor-for-minted-seat-permit.json @@ -0,0 +1,8 @@ +{ + "consume": { + "contractKits": true, + "zoe": true, + "agoricNames": true, + "psmKit": true + } +} diff --git a/a3p-integration/proposals/p:upgrade-19/swapAnchorForMintedSeat/swap-anchor-for-minted-seat.js b/a3p-integration/proposals/p:upgrade-19/swapAnchorForMintedSeat/swap-anchor-for-minted-seat.js new file mode 100644 index 00000000000..1f26f84cce2 --- /dev/null +++ b/a3p-integration/proposals/p:upgrade-19/swapAnchorForMintedSeat/swap-anchor-for-minted-seat.js @@ -0,0 +1,59 @@ +// @ts-nocheck +/* eslint-disable no-undef */ + +const swapAnchorForMintedSeat = async powers => { + const { + consume: { zoe, contractKits: contractKitsP, psmKit: psmKitP, agoricNames }, + } = powers; + + const [contractKits, psmKit, usdcIssuer, usdcBrand] = await Promise.all([ + contractKitsP, + psmKitP, + E(agoricNames).lookup('issuer', 'USDC'), + E(agoricNames).lookup('brand', 'USDC'), + ]); + + console.log('CONTRACT_KITS', contractKits); + console.log('ISSUER', usdcIssuer); + + let govCreatorFacet; + for (const { psmGovernorCreatorFacet, label } of psmKit.values()) { + if (label === 'psm-IST-USDC') { + govCreatorFacet = psmGovernorCreatorFacet; + console.log('psm-IST-USDC found', label); + } + } + + const psmPublicFacet = await E(govCreatorFacet).getPublicFacet(); + + let usdcMint; + for (const { publicFacet, creatorFacet: mint } of contractKits.values()) { + if (publicFacet === usdcIssuer) { + usdcMint = mint; + console.log('USDC found', mint); + break; + } + } + + console.log('Minting USDC'); + assert(usdcMint, 'USDC mint not found'); + + const amt = harden({ brand: usdcBrand, value: 500000n }); + const payment = await E(usdcMint).mintPayment(amt); + + const seat = E(zoe).offer( + E(psmPublicFacet).makeWantMintedInvitation(), + harden({ + give: { In: amt }, + }), + harden({ In: payment }), + ); + + console.log(await E(seat).getPayouts()); + + // We'll check for success in the tests that run this proposal by validating the metric values in + // vstorage + console.log('Done.'); +}; + +swapAnchorForMintedSeat; diff --git a/a3p-integration/proposals/p:upgrade-19/test.sh b/a3p-integration/proposals/p:upgrade-19/test.sh index 9b175d8e6e0..97b3e694e04 100644 --- a/a3p-integration/proposals/p:upgrade-19/test.sh +++ b/a3p-integration/proposals/p:upgrade-19/test.sh @@ -8,5 +8,6 @@ yarn ava provisionPool.test.js yarn ava agoricNames.test.js yarn ava assetReserve.test.js +yarn ava psm.test.js yarn ava registry.test.js diff --git a/golang/cosmos/app/upgrade.go b/golang/cosmos/app/upgrade.go index 7f682710986..ba513a24888 100644 --- a/golang/cosmos/app/upgrade.go +++ b/golang/cosmos/app/upgrade.go @@ -304,6 +304,9 @@ func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Conte // vm.CoreProposalStepForModules( // "@agoric/builders/scripts/vats/upgrade-asset-reserve.js", // ), + // vm.CoreProposalStepForModules( + // "@agoric/builders/scripts/vats/upgrade-psm.js", + // ), // ) terminateOldGovernor, err := terminateGovernorCoreProposal(targetUpgrade) diff --git a/packages/builders/scripts/vats/upgrade-psm.js b/packages/builders/scripts/vats/upgrade-psm.js new file mode 100644 index 00000000000..678f4a5449b --- /dev/null +++ b/packages/builders/scripts/vats/upgrade-psm.js @@ -0,0 +1,19 @@ +import { makeHelpers } from '@agoric/deploy-script-support'; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ +export const defaultProposalBuilder = async ({ publishRef, install }) => + harden({ + sourceSpec: '@agoric/vats/src/proposals/upgrade-psm-proposal.js', + getManifestCall: [ + 'getManifestForUpgradingPSM', + { + psmRef: publishRef(install('@agoric/inter-protocol/src/psm/psm.js')), + }, + ], + }); + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ +export default async (homeP, endowments) => { + const { writeCoreProposal } = await makeHelpers(homeP, endowments); + await writeCoreProposal('upgrade-psm', defaultProposalBuilder); +}; diff --git a/packages/vats/src/proposals/upgrade-psm-proposal.js b/packages/vats/src/proposals/upgrade-psm-proposal.js new file mode 100644 index 00000000000..1151077c61a --- /dev/null +++ b/packages/vats/src/proposals/upgrade-psm-proposal.js @@ -0,0 +1,74 @@ +import { E } from '@endo/far'; +import { deeplyFulfilled } from '@endo/marshal'; +import { makeTracer } from '@agoric/internal'; + +const trace = makeTracer('UpgradePSM'); + +/** + * @param {BootstrapPowers & { + * consume: { + * economicCommitteeCreatorFacet: any; + * psmKit: any; + * }; + * }} powers + * @param {object} options + * @param {{ + * psmRef: VatSourceRef; + * }} options.options + */ +export const upgradePSMProposal = async ( + { + consume: { + psmKit: psmKitP, + economicCommitteeCreatorFacet: electorateCreatorFacet, + instancePrivateArgs: instancePrivateArgsP, + }, + }, + options, +) => { + const { psmRef } = options.options; + + trace(`PSM BUNDLE ID: `, psmRef); + + const [psmKitMap, instancePrivateArgs] = await Promise.all([ + psmKitP, + instancePrivateArgsP, + ]); + + for (const { psm, psmAdminFacet, label } of psmKitMap.values()) { + const [originalPrivateArgs, poserInvitation] = await Promise.all([ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore Local tsc sees this as an error but typedoc does not + deeplyFulfilled(instancePrivateArgs.get(psm)), + E(electorateCreatorFacet).getPoserInvitation(), + ]); + + const newPrivateArgs = harden({ + ...originalPrivateArgs, + initialPoserInvitation: poserInvitation, + }); + + const upgradeResult = await E(psmAdminFacet).upgradeContract( + psmRef.bundleID, + newPrivateArgs, + ); + + trace(`PSM ${label} upgraded: `, upgradeResult); + } + + trace('Done.'); +}; + +export const getManifestForUpgradingPSM = (_powers, { psmRef }) => ({ + manifest: { + [upgradePSMProposal.name]: { + consume: { + psmKit: true, + economicCommitteeCreatorFacet: true, + instancePrivateArgs: true, + }, + produce: {}, + }, + }, + options: { psmRef }, +});