diff --git a/a3p-integration/proposals/p:upgrade-19/.gitignore b/a3p-integration/proposals/p:upgrade-19/.gitignore index 57c4873daf7..b1f2bfa095d 100644 --- a/a3p-integration/proposals/p:upgrade-19/.gitignore +++ b/a3p-integration/proposals/p:upgrade-19/.gitignore @@ -6,3 +6,4 @@ upgradeProvisionPool/ upgradeAgoricNames/ publishTestInfo/ upgrade-mintHolder/ +upgradeAssetReserve/ diff --git a/a3p-integration/proposals/p:upgrade-19/addCollateral/add-collateral-permit.json b/a3p-integration/proposals/p:upgrade-19/addCollateral/add-collateral-permit.json new file mode 100644 index 00000000000..211b499fe4c --- /dev/null +++ b/a3p-integration/proposals/p:upgrade-19/addCollateral/add-collateral-permit.json @@ -0,0 +1,8 @@ +{ + "consume": { + "contractKits": true, + "zoe": true, + "agoricNames": true, + "reserveKit": true + } +} diff --git a/a3p-integration/proposals/p:upgrade-19/addCollateral/add-collateral.js b/a3p-integration/proposals/p:upgrade-19/addCollateral/add-collateral.js new file mode 100644 index 00000000000..c14306cf0eb --- /dev/null +++ b/a3p-integration/proposals/p:upgrade-19/addCollateral/add-collateral.js @@ -0,0 +1,59 @@ +// @ts-nocheck +/* eslint-disable no-undef */ + +const addCollateral = async powers => { + const { + consume: { + contractKits: contractKitsP, + reserveKit: reserveKitP, + zoe, + agoricNames, + }, + } = powers; + + const [contractKits, reserveKit, usdLemonsIssuer, usdLemonsBrand] = + await Promise.all([ + contractKitsP, + reserveKitP, + E(agoricNames).lookup('issuer', 'USD_LEMONS'), + E(agoricNames).lookup('brand', 'USD_LEMONS'), + ]); + + console.log('[CONTRACT_KITS]', contractKits); + console.log('[ISSUER]', usdLemonsIssuer); + + const { governorCreatorFacet } = reserveKit; + + const arPublicFacet = await E(governorCreatorFacet).getPublicFacet(); + const arLimitedFacet = await E(governorCreatorFacet).getCreatorFacet(); + + let usdLemonsMint; + for (const { publicFacet, creatorFacet: mint } of contractKits.values()) { + if (publicFacet === usdLemonsIssuer) { + usdLemonsMint = mint; + console.log('USD_LEMONS found', mint); + break; + } + } + + await E(arLimitedFacet).addIssuer(usdLemonsIssuer, 'USD_LEMONS'); + + console.log('Minting USD_LEMONS'); + const amt = harden({ brand: usdLemonsBrand, value: 500000n }); + const helloPayment = await E(usdLemonsMint).mintPayment(amt); + + console.log('Adding to the reserve...'); + + const seat = E(zoe).offer( + E(arPublicFacet).makeAddCollateralInvitation(), + harden({ + give: { Collateral: amt }, + }), + harden({ Collateral: helloPayment }), + ); + + console.log(await E(seat).getOfferResult()); + console.log('Done.'); +}; + +addCollateral; diff --git a/a3p-integration/proposals/p:upgrade-19/assetReserve.test.js b/a3p-integration/proposals/p:upgrade-19/assetReserve.test.js new file mode 100644 index 00000000000..a3bda12bde3 --- /dev/null +++ b/a3p-integration/proposals/p:upgrade-19/assetReserve.test.js @@ -0,0 +1,92 @@ +/* eslint-env node */ +/** + * @file The goal of this file is to make sure v36-reserve upgraded. + * + * The test scenario is as follows; + * 1. Add asset USD_LEMONS + * 2. Add collateral to the reserve + * 3. Upgrade reserve + * 4. Ensure that the collateral is still in the reserve + */ + +import '@endo/init'; +import test from 'ava'; +import { + evalBundles, + agd as agdAmbient, + agoric, + getDetailsMatchingVats, +} from '@agoric/synthetic-chain'; +import { + makeVstorageKit, + waitUntilContractDeployed, +} from '@agoric/client-utils'; + +const ADD_PSM_DIR = 'addUsdLemons'; +const UPGRADE_AR_DIR = 'upgradeAssetReserve'; +const ADD_COLLATERAL = 'addCollateral'; + +const ambientAuthority = { + query: agdAmbient.query, + follow: agoric.follow, + setTimeout, + log: console.log, +}; + +/** + * @typedef {import('@agoric/ertp').NatAmount} NatAmount + * @typedef {{ + * allocations: { Fee: NatAmount, USD_LEMONS: NatAmount }, + * }} ReserveAllocations + */ + +test.before(async t => { + const vstorageKit = await makeVstorageKit( + { fetch }, + { rpcAddrs: ['http://localhost:26657'], chainName: 'agoriclocal' }, + ); + + t.context = { + vstorageKit, + }; +}); + +test.serial('add collatoral to reserve', async t => { + // @ts-expect-error casting + const { vstorageKit } = t.context; + + // Introduce USD_LEMONS + await evalBundles(ADD_PSM_DIR); + await waitUntilContractDeployed('psm-IST-USD_LEMONS', ambientAuthority, { + errorMessage: 'psm-IST-USD_LEMONS instance not observed.', + }); + + await evalBundles(ADD_COLLATERAL); + + const metrics = /** @type {ReserveAllocations} */ ( + await vstorageKit.readLatestHead('published.reserve.metrics') + ); + + t.truthy(Object.keys(metrics.allocations).includes('USD_LEMONS')); + t.is(metrics.allocations.USD_LEMONS.value, 500000n); +}); + +test.serial('upgrade', async t => { + // @ts-expect-error casting + const { vstorageKit } = t.context; + + await evalBundles(UPGRADE_AR_DIR); + + const vatDetailsAfter = await getDetailsMatchingVats('reserve'); + const { incarnation } = vatDetailsAfter.find(vat => vat.vatID === 'v36'); // assetReserve is v36 + + t.log(vatDetailsAfter); + t.is(incarnation, 1, 'incorrect incarnation'); + + const metrics = /** @type {ReserveAllocations} */ ( + await vstorageKit.readLatestHead('published.reserve.metrics') + ); + + t.truthy(Object.keys(metrics.allocations).includes('USD_LEMONS')); + t.is(metrics.allocations.USD_LEMONS.value, 500000n); +}); diff --git a/a3p-integration/proposals/p:upgrade-19/package.json b/a3p-integration/proposals/p:upgrade-19/package.json index bdd9490ec35..666230406a8 100644 --- a/a3p-integration/proposals/p:upgrade-19/package.json +++ b/a3p-integration/proposals/p:upgrade-19/package.json @@ -5,6 +5,7 @@ "testing/replace-feeDistributor-short.js replaceFeeDistributor", "testing/add-USD-LEMONS.js addUsdLemons", "vats/upgrade-provisionPool.js upgradeProvisionPool", + "vats/upgrade-asset-reserve.js upgradeAssetReserve", "vats/upgrade-paRegistry.js", "vats/upgrade-board.js", "testing/test-upgraded-board.js testUpgradedBoard", diff --git a/a3p-integration/proposals/p:upgrade-19/test.sh b/a3p-integration/proposals/p:upgrade-19/test.sh index f42147483ef..193e27da231 100644 --- a/a3p-integration/proposals/p:upgrade-19/test.sh +++ b/a3p-integration/proposals/p:upgrade-19/test.sh @@ -4,4 +4,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 + +yarn ava assetReserve.test.js diff --git a/golang/cosmos/app/upgrade.go b/golang/cosmos/app/upgrade.go index 8e6a2c68bcd..89857e26166 100644 --- a/golang/cosmos/app/upgrade.go +++ b/golang/cosmos/app/upgrade.go @@ -259,6 +259,9 @@ func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Conte // vm.CoreProposalStepForModules( // "@agoric/builders/scripts/vats/upgrade-agoricNames.js", // ), + // vm.CoreProposalStepForModules( + // "@agoric/builders/scripts/vats/upgrade-asset-reserve.js", + // ), // ) } diff --git a/packages/builders/scripts/vats/upgrade-asset-reserve.js b/packages/builders/scripts/vats/upgrade-asset-reserve.js new file mode 100644 index 00000000000..b7d70d8e205 --- /dev/null +++ b/packages/builders/scripts/vats/upgrade-asset-reserve.js @@ -0,0 +1,21 @@ +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-asset-reserve-proposal.js', + getManifestCall: [ + 'getManifestForUpgradingAssetReserve', + { + assetReserveRef: publishRef( + install('@agoric/inter-protocol/src/reserve/assetReserve.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-asset-reserve', defaultProposalBuilder); +}; diff --git a/packages/inter-protocol/src/proposals/econ-behaviors.js b/packages/inter-protocol/src/proposals/econ-behaviors.js index 9b58897e6c9..a90744fca9a 100644 --- a/packages/inter-protocol/src/proposals/econ-behaviors.js +++ b/packages/inter-protocol/src/proposals/econ-behaviors.js @@ -157,9 +157,10 @@ export const setupReserve = async ({ 'reserve.governor', ); - const [creatorFacet, publicFacet, instance] = await Promise.all([ + const [creatorFacet, publicFacet, adminFacet, instance] = await Promise.all([ E(g.creatorFacet).getCreatorFacet(), E(g.creatorFacet).getPublicFacet(), + E(g.creatorFacet).getAdminFacet(), E(g.publicFacet).getGovernedContract(), ]); @@ -169,7 +170,7 @@ export const setupReserve = async ({ instance, publicFacet, creatorFacet, - adminFacet: g.adminFacet, + adminFacet, governor: g.instance, governorCreatorFacet: g.creatorFacet, diff --git a/packages/inter-protocol/src/reserve/assetReserve.js b/packages/inter-protocol/src/reserve/assetReserve.js index 322172e0e0f..f8ed8204564 100644 --- a/packages/inter-protocol/src/reserve/assetReserve.js +++ b/packages/inter-protocol/src/reserve/assetReserve.js @@ -57,22 +57,13 @@ export const start = async (zcf, privateArgs, baggage) => { privateArgs.marshaller, ); - /** @type {() => Promise>} */ - const takeFeeMint = async () => { - if (baggage.has('feeMint')) { - return baggage.get('feeMint'); - } - - const feeMintTemp = await zcf.registerFeeMint( - 'Fee', - privateArgs.feeMintAccess, - ); - baggage.init('feeMint', feeMintTemp); - return feeMintTemp; - }; - trace('awaiting takeFeeMint'); - const feeMint = await takeFeeMint(); const storageNode = await privateArgs.storageNode; + + trace('awaiting feeMint'); + const { feeMint } = await provideAll(baggage, { + feeMint: () => zcf.registerFeeMint('Fee', privateArgs.feeMintAccess), + }); + const makeAssetReserveKit = await prepareAssetReserveKit(baggage, { feeMint, makeRecorderKit, diff --git a/packages/inter-protocol/test/swingsetTests/reserve/bootstrap-assetReserve-upgrade.js b/packages/inter-protocol/test/swingsetTests/reserve/bootstrap-assetReserve-upgrade.js index d6bb13d4d50..91318683cb5 100644 --- a/packages/inter-protocol/test/swingsetTests/reserve/bootstrap-assetReserve-upgrade.js +++ b/packages/inter-protocol/test/swingsetTests/reserve/bootstrap-assetReserve-upgrade.js @@ -271,6 +271,11 @@ export const buildRootObject = async () => { metricsRecord = await E(metrics).getUpdateSince(); + // verify allocations + const allocations = await E(arLimitedFacet).getAllocations(); + assert.equal(allocations.Moola.value, 100_000n); + assert.equal(allocations.Moola.brand, moola.brand); + // same as last assert.equal(metricsRecord.updateCount, 2n); diff --git a/packages/vats/src/proposals/upgrade-asset-reserve-proposal.js b/packages/vats/src/proposals/upgrade-asset-reserve-proposal.js new file mode 100644 index 00000000000..89fbf0042c3 --- /dev/null +++ b/packages/vats/src/proposals/upgrade-asset-reserve-proposal.js @@ -0,0 +1,93 @@ +import { E } from '@endo/far'; +import { deeplyFulfilled } from '@endo/marshal'; +import { makeTracer } from '@agoric/internal'; + +const tracer = makeTracer('UpgradeAssetReserve'); + +/** + * @param {BootstrapPowers & { + * consume: { + * economicCommitteeCreatorFacet: any; + * reserveKit: any; + * }; + * produce: { + * reserveKit: any; + * }; + * }} powers + * @param {object} options + * @param {{ assetReserveRef: VatSourceRef }} options.options + */ +export const upgradeAssetReserve = async ( + { + consume: { + economicCommitteeCreatorFacet: electorateCreatorFacet, + reserveKit: reserveKitP, + instancePrivateArgs: instancePrivateArgsP, + }, + produce: { reserveKit: reserveKitWriter }, + }, + options, +) => { + const { assetReserveRef } = options.options; + + assert(assetReserveRef.bundleID); + tracer(`ASSET RESERVE BUNDLE ID: `, assetReserveRef); + + const [reserveKit, instancePrivateArgs] = await Promise.all([ + reserveKitP, + instancePrivateArgsP, + ]); + const { governorCreatorFacet, instance } = reserveKit; + + 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(instance)), + E(electorateCreatorFacet).getPoserInvitation(), + ]); + + const newPrivateArgs = harden({ + ...originalPrivateArgs, + initialPoserInvitation: poserInvitation, + }); + + const adminFacet = await E(governorCreatorFacet).getAdminFacet(); + + // We need to reset the kit and produce a new adminFacet because the + // original contract is producing an admin facet that is for the + // governor, not the reserve. + reserveKitWriter.reset(); + reserveKitWriter.resolve( + harden({ + ...reserveKit, + adminFacet, + }), + ); + + const upgradeResult = await E(adminFacet).upgradeContract( + assetReserveRef.bundleID, + newPrivateArgs, + ); + + tracer('AssetReserve upgraded: ', upgradeResult); + tracer('Done.'); +}; + +export const getManifestForUpgradingAssetReserve = ( + _powers, + { assetReserveRef }, +) => ({ + manifest: { + [upgradeAssetReserve.name]: { + consume: { + economicCommitteeCreatorFacet: true, + instancePrivateArgs: true, + reserveKit: true, + }, + produce: { + reserveKit: true, + }, + }, + }, + options: { assetReserveRef }, +});