From c72ee8990701e7d7569d652e65645358ac91e1d4 Mon Sep 17 00:00:00 2001 From: rabi-siddique Date: Thu, 3 Oct 2024 15:10:16 +0500 Subject: [PATCH 01/10] chore: use startUpgradable --- packages/inter-protocol/src/proposals/replaceElectorate.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/inter-protocol/src/proposals/replaceElectorate.js b/packages/inter-protocol/src/proposals/replaceElectorate.js index 1eacc1bb832..a98dce10d8a 100644 --- a/packages/inter-protocol/src/proposals/replaceElectorate.js +++ b/packages/inter-protocol/src/proposals/replaceElectorate.js @@ -206,7 +206,6 @@ const startNewEconomicCommittee = async ( const { instance, creatorFacet } = startResult; trace('Started new EC Committee Instance Successfully'); - economicCommitteeKit.reset(); economicCommitteeKit.resolve( harden({ ...startResult, label: 'economicCommittee' }), From 20a0b18e801978e6e24eb2af0dd9b61fb49d58d9 Mon Sep 17 00:00:00 2001 From: Fraz Arshad Date: Fri, 27 Sep 2024 14:00:47 +0500 Subject: [PATCH 02/10] test: bootstrap test for replaceElectorate From dc546f964d91661f5a6eba394e3531cfd785cd29 Mon Sep 17 00:00:00 2001 From: rabi-siddique Date: Fri, 27 Sep 2024 16:42:38 +0500 Subject: [PATCH 03/10] chore: change buildProposal definition to accept args From 100acf0a1893f69b94871fb26aa3f368686bac1c Mon Sep 17 00:00:00 2001 From: rabi-siddique Date: Fri, 27 Sep 2024 18:27:04 +0500 Subject: [PATCH 04/10] feat: core eval for replacing charter members --- .../inter-protocol/replace-electorate-core.js | 6 - .../src/proposals/replaceElectorate.js | 135 +++++++++++++++++- 2 files changed, 132 insertions(+), 9 deletions(-) diff --git a/packages/builders/scripts/inter-protocol/replace-electorate-core.js b/packages/builders/scripts/inter-protocol/replace-electorate-core.js index a856177f7d0..80d8dc8c7ad 100644 --- a/packages/builders/scripts/inter-protocol/replace-electorate-core.js +++ b/packages/builders/scripts/inter-protocol/replace-electorate-core.js @@ -24,12 +24,6 @@ export const defaultProposalBuilder = async ({ publishRef, install }, opts) => { getManifestForReplaceAllElectorates.name, { ...opts, - economicCommitteeRef: publishRef( - install( - '@agoric/governance/src/committee.js', - '../bundles/bundle-committee.js', - ), - ), }, ], }); diff --git a/packages/inter-protocol/src/proposals/replaceElectorate.js b/packages/inter-protocol/src/proposals/replaceElectorate.js index a98dce10d8a..d38d85bdbf8 100644 --- a/packages/inter-protocol/src/proposals/replaceElectorate.js +++ b/packages/inter-protocol/src/proposals/replaceElectorate.js @@ -137,6 +137,22 @@ const inviteECMembers = async ( await distributeInvitations(zip(values(voterAddresses), invitations)); }; +const inviteToEconCharter = async ( + { consume: { namesByAddressAdmin } }, + { options: { voterAddresses, econCharterKit } }, +) => { + const { creatorFacet } = E.get(econCharterKit); + + void Promise.all( + values(voterAddresses).map(async addr => { + const debugName = `econ charter member ${addr}`; + reserveThenDeposit(debugName, namesByAddressAdmin, addr, [ + E(creatorFacet).makeCharterMemberInvitation(), + ]).catch(err => console.error(`failed deposit to ${debugName}`, err)); + }), + ); +}; + /** * Starts a new Economic Committee (EC) by creating an instance with the * provided committee specifications. @@ -219,6 +235,94 @@ const startNewEconomicCommittee = async ( return creatorFacet; }; +const startNewEconCharter = async ({ + consume: { zoe }, + produce: { econCharterKit }, + installation: { + consume: { binaryVoteCounter: counterP, econCommitteeCharter: installP }, + }, + instance: { + produce: { econCommitteeCharter }, + }, +}) => { + const [charterInstall, counterInstall] = await Promise.all([ + installP, + counterP, + ]); + const terms = await harden({ + binaryVoteCounterInstallation: counterInstall, + }); + + trace('Starting new EC Charter Instance'); + const startResult = E(zoe).startInstance( + charterInstall, + undefined, + terms, + undefined, + 'econCommitteeCharter', + ); + trace('Started new EC Charter Instance Successfully'); + + econCommitteeCharter.reset(); + econCommitteeCharter.resolve(E.get(startResult).instance); + + econCharterKit.reset(); + econCharterKit.resolve(startResult); + return startResult; +}; + +const addGovernorsToEconCharter = async ( + { + consume: { + reserveKit, + vaultFactoryKit, + auctioneerKit, + psmKit, + provisionPoolStartResult, + }, + instance: { + consume: { reserve, VaultFactory, auctioneer, provisionPool }, + }, + }, + { options: { econCharterKit } }, +) => { + const { creatorFacet } = E.get(econCharterKit); + + const psmKitMap = await psmKit; + + for (const { psm, psmGovernorCreatorFacet, label } of psmKitMap.values()) { + E(creatorFacet).addInstance(psm, psmGovernorCreatorFacet, label); + } + + await Promise.all( + [ + { + label: 'reserve', + instanceP: reserve, + facetP: E.get(reserveKit).governorCreatorFacet, + }, + { + label: 'VaultFactory', + instanceP: VaultFactory, + facetP: E.get(vaultFactoryKit).governorCreatorFacet, + }, + { + label: 'auctioneer', + instanceP: auctioneer, + facetP: E.get(auctioneerKit).governorCreatorFacet, + }, + { + label: 'provisionPool', + instanceP: provisionPool, + facetP: E.get(provisionPoolStartResult).governorCreatorFacet, + }, + ].map(async ({ label, instanceP, facetP }) => { + const [instance, govFacet] = await Promise.all([instanceP, facetP]); + + return E(creatorFacet).addInstance(instance, govFacet, label); + }), + ); +}; /** * Replaces the electorate for governance contracts by creating a new Economic @@ -299,6 +403,17 @@ export const replaceAllElectorates = async (permittedPowers, config) => { }); trace('Installed New Economic Committee'); + + const econCharterKit = await startNewEconCharter(permittedPowers); + await addGovernorsToEconCharter(permittedPowers, { + options: { econCharterKit }, + }); + + await inviteToEconCharter(permittedPowers, { + options: { voterAddresses, econCharterKit }, + }); + + trace('Installed New EC Charter'); }; harden(replaceAllElectorates); @@ -320,14 +435,28 @@ export const getManifestForReplaceAllElectorates = async ( startUpgradable: true, }, produce: { + econCharterKit: true, economicCommitteeKit: true, - economicCommitteeCreatorFacet: 'economicCommittee', + economicCommitteeCreatorFacet: true, }, installation: { - consume: { committee: 'zoe' }, + consume: { + committee: true, + binaryVoteCounter: true, + econCommitteeCharter: true, + }, }, instance: { - produce: { economicCommittee: 'economicCommittee' }, + produce: { + economicCommittee: true, + econCommitteeCharter: true, + }, + consume: { + reserve: true, + VaultFactory: true, + auctioneer: true, + provisionPool: true, + }, }, }, }, From 95ebf022aea3d042e95366e795f7a8a911bd3873 Mon Sep 17 00:00:00 2001 From: Fraz Arshad Date: Mon, 30 Sep 2024 17:26:26 +0500 Subject: [PATCH 05/10] test: test for replacing instance of charter contract --- .../bootstrapTests/ec-replace-charter.test.ts | 267 ++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 packages/boot/test/bootstrapTests/ec-replace-charter.test.ts diff --git a/packages/boot/test/bootstrapTests/ec-replace-charter.test.ts b/packages/boot/test/bootstrapTests/ec-replace-charter.test.ts new file mode 100644 index 00000000000..7ff4c7d2c70 --- /dev/null +++ b/packages/boot/test/bootstrapTests/ec-replace-charter.test.ts @@ -0,0 +1,267 @@ +import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; +import { TestFn } from 'ava'; +import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; +import { + makeAgoricNamesRemotesFromFakeStorage, + slotToBoardRemote, + unmarshalFromVstorage, +} from '@agoric/vats/tools/board-utils.js'; +import { makeMarshal } from '@endo/marshal'; + +import { makeSwingsetTestKit } from '../../tools/supports.js'; +import { + makeGovernanceDriver, + makeWalletFactoryDriver, +} from '../../tools/drivers.js'; + +const wallets = [ + 'agoric1gx9uu7y6c90rqruhesae2t7c2vlw4uyyxlqxrx', + 'agoric1d4228cvelf8tj65f4h7n2td90sscavln2283h5', + 'agoric14543m33dr28x7qhwc558hzlj9szwhzwzpcmw6a', + 'agoric13p9adwk0na5npfq64g22l6xucvqdmu3xqe70wq', + 'agoric1el6zqs8ggctj5vwyukyk4fh50wcpdpwgugd5l5', + 'agoric1zayxg4e9vd0es9c9jlpt36qtth255txjp6a8yc', +]; + +const highPrioritySenderKey = 'highPrioritySenders'; + +const offerIds = { + propose: { outgoing: 'outgoing_propose', incoming: 'incoming_propose' }, + vote: { outgoing: 'outgoing_vote', incoming: 'incoming_vote' }, +}; + +const getQuestionId = id => `propose-question-${id}`; +const getVoteId = id => `vote-${id}`; + +export const makeZoeTestContext = async t => { + console.time('ZoeTestContext'); + const swingsetTestKit = await makeSwingsetTestKit(t.log, undefined, { + configSpecifier: '@agoric/vm-config/decentral-main-vaults-config.json', + }); + + const { runUtils, storage } = swingsetTestKit; + console.timeLog('DefaultTestContext', 'swingsetTestKit'); + const { EV } = runUtils; + + await eventLoopIteration(); + + // We don't need vaults, but this gets the brand, which is checked somewhere + // Wait for ATOM to make it into agoricNames + await EV.vat('bootstrap').consumeItem('vaultFactoryKit'); + console.timeLog('DefaultTestContext', 'vaultFactoryKit'); + + // has to be late enough for agoricNames data to have been published + const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); + console.timeLog('DefaultTestContext', 'agoricNamesRemotes'); + + console.timeEnd('DefaultTestContext'); + const walletFactoryDriver = await makeWalletFactoryDriver( + runUtils, + storage, + agoricNamesRemotes, + ); + + const { fromCapData } = makeMarshal(undefined, slotToBoardRemote); + + const getUpdatedDebtLimit = () => { + const atomGovernance = unmarshalFromVstorage( + storage.data, + 'published.vaultFactory.managers.manager0.governance', + fromCapData, + -1, + ); + return atomGovernance.current.DebtLimit.value.value; + }; + + const governanceDriver = await makeGovernanceDriver( + swingsetTestKit, + agoricNamesRemotes, + walletFactoryDriver, + wallets, + ); + + return { + ...swingsetTestKit, + storage, + getUpdatedDebtLimit, + governanceDriver, + }; +}; +const test = anyTest as TestFn>>; + +test.before(async t => { + t.context = await makeZoeTestContext(t); +}); + +test.serial('normal running of committee', async t => { + const { advanceTimeBy, storage, getUpdatedDebtLimit, governanceDriver } = + t.context; + + const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); + const { VaultFactory, economicCommittee } = agoricNamesRemotes.instance; + const { ATOM: collateralBrand, IST: debtBrand } = agoricNamesRemotes.brand; + + const committee = governanceDriver.ecMembers; + + t.log('Accepting all invitations for original committee'); + await null; + for (const member of committee) { + await member.acceptCharterInvitation(offerIds.propose.outgoing); + await member.acceptCommitteeInvitation(offerIds.vote.outgoing); + } + + t.log('Proposing a question using first wallet'); + await governanceDriver.proposeParams( + VaultFactory, + { DebtLimit: { brand: debtBrand, value: 100_000_000n } }, + { paramPath: { key: { collateralBrand } } }, + committee[0], + getQuestionId(1), + offerIds.propose.outgoing, + ); + + t.log('Checking if question proposal passed'); + t.like(committee[0].getLatestUpdateRecord(), { + status: { id: getQuestionId(1), numWantsSatisfied: 1 }, + }); + + t.log('Voting on question using first 3 wallets'); + await governanceDriver.enactLatestProposal( + committee, + getVoteId(1), + offerIds.vote.outgoing, + ); + + t.log('Checking if votes passed'); + for (const w of committee.slice(0, 3)) { + t.like(w.getLatestUpdateRecord(), { + status: { id: getVoteId(1), numWantsSatisfied: 1 }, + }); + } + + t.log('Waiting for period to end'); + await advanceTimeBy(1, 'minutes'); + + t.log('Verifying outcome'); + + const lastOutcome = await governanceDriver.getLatestOutcome(); + console.log(lastOutcome); + t.deepEqual(getUpdatedDebtLimit(), 100_000_000n); + t.assert(lastOutcome.outcome === 'win'); +}); + +test.serial('replace committee', async t => { + const { buildProposal, evalProposal } = t.context; + await evalProposal( + buildProposal( + '@agoric/builders/scripts/inter-protocol/replace-electorate-core.js', + ['BOOTSTRAP_TEST'], + ), + ); + await eventLoopIteration(); + t.true(true); // just to avoid failure +}); + +test.serial('successful proposal and vote by continuing members', async t => { + const { storage, advanceTimeBy, getUpdatedDebtLimit, governanceDriver } = + t.context; + const newCommittee = governanceDriver.ecMembers.slice(0, 3); + + const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); + const { economicCommittee, VaultFactory, econCommitteeCharter } = + agoricNamesRemotes.instance; + const { ATOM: collateralBrand, IST: debtBrand } = agoricNamesRemotes.brand; + + t.log('Accepting all new invitations for voters'); + await null; + for (const member of newCommittee) { + await member.acceptCharterInvitation( + offerIds.propose.incoming, + econCommitteeCharter, + ); + await member.acceptCommitteeInvitation( + offerIds.vote.incoming, + economicCommittee, + ); + } + + t.log('Proposing question using new charter invitation'); + await governanceDriver.proposeParams( + VaultFactory, + { DebtLimit: { brand: debtBrand, value: 200_000_000n } }, + { paramPath: { key: { collateralBrand } } }, + newCommittee[0], + getQuestionId(2), + offerIds.propose.incoming, + ); + + t.like(newCommittee[0].getLatestUpdateRecord(), { + status: { id: getQuestionId(2), numWantsSatisfied: 1 }, + }); + + t.log('Voting on question using first 2 wallets'); + await governanceDriver.enactLatestProposal( + newCommittee.slice(0, 2), + getVoteId(2), + offerIds.vote.incoming, + ); + for (const w of newCommittee.slice(0, 2)) { + t.like(w.getLatestUpdateRecord(), { + status: { id: getVoteId(2), numWantsSatisfied: 1 }, + }); + } + + t.log('Waiting for period to end'); + await advanceTimeBy(1, 'minutes'); + + t.log('Verifying outcome'); + const lastOutcome = await governanceDriver.getLatestOutcome(); + t.deepEqual(getUpdatedDebtLimit(), 200_000_000n); + t.assert(lastOutcome.outcome === 'win'); +}); + +test.serial('successful proposal by outgoing member', async t => { + // Ability to propose by outgoing member should still exist + const { storage, advanceTimeBy, getUpdatedDebtLimit, governanceDriver } = + t.context; + const newCommittee = governanceDriver.ecMembers.slice(0, 3); + const outgoingMember = governanceDriver.ecMembers[3]; + + const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); + const { VaultFactory } = agoricNamesRemotes.instance; + const { ATOM: collateralBrand, IST: debtBrand } = agoricNamesRemotes.brand; + + t.log('Proposing question using old charter invitation'); + await governanceDriver.proposeParams( + VaultFactory, + { DebtLimit: { brand: debtBrand, value: 300_000_000n } }, + { paramPath: { key: { collateralBrand } } }, + outgoingMember, + getQuestionId(3), + offerIds.propose.outgoing, + ); + + t.like(outgoingMember.getLatestUpdateRecord(), { + status: { id: getQuestionId(3), numWantsSatisfied: 1 }, + }); + + t.log('Voting on question using first 2 wallets'); + await governanceDriver.enactLatestProposal( + newCommittee.slice(0, 2), + getVoteId(3), + offerIds.vote.incoming, + ); + for (const w of newCommittee.slice(0, 2)) { + t.like(w.getLatestUpdateRecord(), { + status: { id: getVoteId(3), numWantsSatisfied: 1 }, + }); + } + + t.log('Waiting for period to end'); + await advanceTimeBy(1, 'minutes'); + + t.log('Verifying outcome'); + const lastOutcome = await governanceDriver.getLatestOutcome(); + t.deepEqual(getUpdatedDebtLimit(), 300_000_000n); + t.assert(lastOutcome.outcome === 'win'); +}); From 40eec818e148b7137ca7da2a7652e23119931364 Mon Sep 17 00:00:00 2001 From: rabi-siddique Date: Tue, 1 Oct 2024 11:51:23 +0500 Subject: [PATCH 06/10] chore: changes to incorporate governedContractKits --- .../src/proposals/replaceElectorate.js | 58 ++++--------------- 1 file changed, 11 insertions(+), 47 deletions(-) diff --git a/packages/inter-protocol/src/proposals/replaceElectorate.js b/packages/inter-protocol/src/proposals/replaceElectorate.js index d38d85bdbf8..2c786fa457b 100644 --- a/packages/inter-protocol/src/proposals/replaceElectorate.js +++ b/packages/inter-protocol/src/proposals/replaceElectorate.js @@ -272,56 +272,26 @@ const startNewEconCharter = async ({ }; const addGovernorsToEconCharter = async ( - { - consume: { - reserveKit, - vaultFactoryKit, - auctioneerKit, - psmKit, - provisionPoolStartResult, - }, - instance: { - consume: { reserve, VaultFactory, auctioneer, provisionPool }, - }, - }, + { consume: { psmKit, governedContractKits } }, { options: { econCharterKit } }, ) => { - const { creatorFacet } = E.get(econCharterKit); + const { creatorFacet: ecCreatorFacet } = E.get(econCharterKit); const psmKitMap = await psmKit; for (const { psm, psmGovernorCreatorFacet, label } of psmKitMap.values()) { - E(creatorFacet).addInstance(psm, psmGovernorCreatorFacet, label); + E(ecCreatorFacet).addInstance(psm, psmGovernorCreatorFacet, label); } - await Promise.all( - [ - { - label: 'reserve', - instanceP: reserve, - facetP: E.get(reserveKit).governorCreatorFacet, - }, - { - label: 'VaultFactory', - instanceP: VaultFactory, - facetP: E.get(vaultFactoryKit).governorCreatorFacet, - }, - { - label: 'auctioneer', - instanceP: auctioneer, - facetP: E.get(auctioneerKit).governorCreatorFacet, - }, - { - label: 'provisionPool', - instanceP: provisionPool, - facetP: E.get(provisionPoolStartResult).governorCreatorFacet, - }, - ].map(async ({ label, instanceP, facetP }) => { - const [instance, govFacet] = await Promise.all([instanceP, facetP]); + const governedContractKitMap = await governedContractKits; - return E(creatorFacet).addInstance(instance, govFacet, label); - }), - ); + for (const { + instance, + governorCreatorFacet, + label, + } of governedContractKitMap.values()) { + E(ecCreatorFacet).addInstance(instance, governorCreatorFacet, label); + } }; /** @@ -451,12 +421,6 @@ export const getManifestForReplaceAllElectorates = async ( economicCommittee: true, econCommitteeCharter: true, }, - consume: { - reserve: true, - VaultFactory: true, - auctioneer: true, - provisionPool: true, - }, }, }, }, From 6226b30e49a4df03f4deeb55b7f9e4221e6ca2d7 Mon Sep 17 00:00:00 2001 From: Fraz Arshad Date: Wed, 2 Oct 2024 16:36:05 +0500 Subject: [PATCH 07/10] test: added more tests for each individual contract --- .../ec-membership-update.test.ts | 427 ++++++++++++++++-- .../bootstrapTests/ec-replace-charter.test.ts | 267 ----------- packages/boot/tools/drivers.ts | 63 ++- .../inter-protocol/replace-electorate-core.js | 6 + .../src/proposals/replaceElectorate.js | 4 +- 5 files changed, 462 insertions(+), 305 deletions(-) delete mode 100644 packages/boot/test/bootstrapTests/ec-replace-charter.test.ts diff --git a/packages/boot/test/bootstrapTests/ec-membership-update.test.ts b/packages/boot/test/bootstrapTests/ec-membership-update.test.ts index dd41e017f87..e98b18cc89d 100644 --- a/packages/boot/test/bootstrapTests/ec-membership-update.test.ts +++ b/packages/boot/test/bootstrapTests/ec-membership-update.test.ts @@ -6,13 +6,15 @@ import { slotToBoardRemote, unmarshalFromVstorage, } from '@agoric/vats/tools/board-utils.js'; -import { makeMarshal } from '@endo/marshal'; +import { makeMarshal, passStyleOf } from '@endo/marshal'; +import { NonNullish } from '@agoric/internal'; import { makeSwingsetTestKit } from '../../tools/supports.js'; import { makeGovernanceDriver, makeWalletFactoryDriver, } from '../../tools/drivers.js'; +import { makeLiquidationTestKit } from '../../tools/liquidation.js'; const wallets = [ 'agoric1gx9uu7y6c90rqruhesae2t7c2vlw4uyyxlqxrx', @@ -23,11 +25,18 @@ const wallets = [ 'agoric1zayxg4e9vd0es9c9jlpt36qtth255txjp6a8yc', ]; +const managerGovernanceKey = + 'published.vaultFactory.managers.manager0.governance'; +const auctioneerParamsKey = 'published.auction.governance'; +const provisionPoolParamsKey = 'published.provisionPool.governance'; +const reserveParamsKey = 'published.reserve.metrics'; +const getPsmKey = brand => `published.psm.IST.${brand}.governance`; + const highPrioritySenderKey = 'highPrioritySenders'; const offerIds = { - propose: { outgoing: 'outgoing_propose' }, - vote: { outgoing: 'outgoing_vote', incoming: 'incoming_vote' }, + propose: { outgoing: 'charterMembership', incoming: 'incoming_propose' }, + vote: { outgoing: 'committeeMembership', incoming: 'incoming_vote' }, }; const getQuestionId = id => `propose-question-${id}`; @@ -63,14 +72,9 @@ export const makeZoeTestContext = async t => { const { fromCapData } = makeMarshal(undefined, slotToBoardRemote); - const getUpdatedDebtLimit = () => { - const atomGovernance = unmarshalFromVstorage( - storage.data, - 'published.vaultFactory.managers.manager0.governance', - fromCapData, - -1, - ); - return atomGovernance.current.DebtLimit.value.value; + const getVstorageData = key => { + const data = unmarshalFromVstorage(storage.data, key, fromCapData, -1); + return data; }; const governanceDriver = await makeGovernanceDriver( @@ -80,10 +84,19 @@ export const makeZoeTestContext = async t => { wallets, ); + const liquidationTestKit = await makeLiquidationTestKit({ + swingsetTestKit, + agoricNamesRemotes, + walletFactoryDriver, + governanceDriver, + t, + }); + return { ...swingsetTestKit, + ...liquidationTestKit, storage, - getUpdatedDebtLimit, + getVstorageData, governanceDriver, }; }; @@ -94,11 +107,11 @@ test.before(async t => { }); test.serial('normal running of committee', async t => { - const { advanceTimeBy, storage, getUpdatedDebtLimit, governanceDriver } = + const { advanceTimeBy, storage, getVstorageData, governanceDriver } = t.context; const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); - const { VaultFactory, economicCommittee } = agoricNamesRemotes.instance; + const { VaultFactory } = agoricNamesRemotes.instance; const { ATOM: collateralBrand, IST: debtBrand } = agoricNamesRemotes.brand; const committee = governanceDriver.ecMembers; @@ -106,8 +119,8 @@ test.serial('normal running of committee', async t => { t.log('Accepting all invitations for original committee'); await null; for (const member of committee) { - await member.acceptCharterInvitation(offerIds.propose.outgoing); - await member.acceptCommitteeInvitation(offerIds.vote.outgoing); + await member.acceptOutstandingCharterInvitation(offerIds.propose.outgoing); + await member.acceptOutstandingCommitteeInvitation(offerIds.vote.outgoing); } t.log('Proposing a question using first wallet'); @@ -146,7 +159,8 @@ test.serial('normal running of committee', async t => { const lastOutcome = await governanceDriver.getLatestOutcome(); console.log(lastOutcome); - t.deepEqual(getUpdatedDebtLimit(), 100_000_000n); + const managerParams = getVstorageData(managerGovernanceKey); + t.deepEqual(managerParams.current.DebtLimit.value.value, 100_000_000n); t.assert(lastOutcome.outcome === 'win'); }); @@ -163,6 +177,42 @@ test.serial( }, ); +test.serial('Update reserve metrics', async t => { + // Need to update metrics before membership upgrade for tests related to vault params later + const { advanceTimeTo, setupVaults, priceFeedDrivers, readLatest } = + t.context; + const setup = { + vaults: [ + { + atom: 15, + ist: 100, + debt: 100.5, + }, + ], + bids: [], + price: { + starting: 12.34, + trigger: 9.99, + }, + auction: { + start: { + collateral: 45, + debt: 309.54, + }, + end: { + collateral: 31.414987, + debt: 209.54, + }, + }, + }; + + await setupVaults('ATOM', 0, setup); + await priceFeedDrivers.ATOM.setPrice(setup.price.trigger); + const liveSchedule = readLatest('published.auction.schedule'); + await advanceTimeTo(NonNullish(liveSchedule.nextDescendingStepTime)); + t.pass(); +}); + test.serial('replace committee', async t => { const { buildProposal, evalProposal, storage } = t.context; @@ -196,19 +246,24 @@ test.serial( }, ); -test.serial('successful vote by 2 continuing members', async t => { - const { storage, advanceTimeBy, getUpdatedDebtLimit, governanceDriver } = +test.serial('successful proposal and vote by 2 continuing members', async t => { + const { storage, advanceTimeBy, getVstorageData, governanceDriver } = t.context; const newCommittee = governanceDriver.ecMembers.slice(0, 3); const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); - const { economicCommittee, VaultFactory } = agoricNamesRemotes.instance; + const { economicCommittee, econCommitteeCharter, VaultFactory } = + agoricNamesRemotes.instance; const { ATOM: collateralBrand, IST: debtBrand } = agoricNamesRemotes.brand; t.log('Accepting all new invitations for voters'); await null; for (const member of newCommittee) { - await member.acceptCommitteeInvitation( + await member.acceptOutstandingCharterInvitation( + offerIds.propose.incoming, + econCommitteeCharter, + ); + await member.acceptOutstandingCommitteeInvitation( offerIds.vote.incoming, economicCommittee, ); @@ -221,7 +276,7 @@ test.serial('successful vote by 2 continuing members', async t => { { paramPath: { key: { collateralBrand } } }, newCommittee[0], getQuestionId(2), - offerIds.propose.outgoing, + offerIds.propose.incoming, ); t.like(newCommittee[0].getLatestUpdateRecord(), { @@ -245,12 +300,13 @@ test.serial('successful vote by 2 continuing members', async t => { t.log('Verifying outcome'); const lastOutcome = await governanceDriver.getLatestOutcome(); - t.deepEqual(getUpdatedDebtLimit(), 200_000_000n); + const managerParams = getVstorageData(managerGovernanceKey); + t.deepEqual(managerParams.current.DebtLimit.value.value, 200_000_000n); t.assert(lastOutcome.outcome === 'win'); }); test.serial('unsuccessful vote by 2 outgoing members', async t => { - const { governanceDriver, storage, advanceTimeBy, getUpdatedDebtLimit } = + const { governanceDriver, storage, advanceTimeBy, getVstorageData } = t.context; const outgoingCommittee = governanceDriver.ecMembers.slice(3); @@ -292,14 +348,15 @@ test.serial('unsuccessful vote by 2 outgoing members', async t => { await advanceTimeBy(1, 'minutes'); const lastOutcome = await governanceDriver.getLatestOutcome(); - t.notDeepEqual(getUpdatedDebtLimit(), 300_000_000n); + const managerParams = getVstorageData(managerGovernanceKey); + t.notDeepEqual(managerParams.current.DebtLimit.value.value, 300_000_000n); t.assert(lastOutcome.outcome === 'fail'); }); test.serial( 'successful vote by 2 continuing and 1 outgoing members', async t => { - const { storage, advanceTimeBy, getUpdatedDebtLimit, governanceDriver } = + const { storage, advanceTimeBy, getVstorageData, governanceDriver } = t.context; const committee = [ ...governanceDriver.ecMembers.slice(0, 2), @@ -346,7 +403,8 @@ test.serial( await advanceTimeBy(1, 'minutes'); const lastOutcome = await governanceDriver.getLatestOutcome(); - t.deepEqual(getUpdatedDebtLimit(), 400_000_000n); + const managerParams = getVstorageData(managerGovernanceKey); + t.deepEqual(managerParams.current.DebtLimit.value.value, 400_000_000n); t.assert(lastOutcome.outcome === 'win'); }, ); @@ -354,7 +412,7 @@ test.serial( test.serial( 'unsuccessful vote by 1 continuing and 2 outgoing members', async t => { - const { storage, advanceTimeBy, getUpdatedDebtLimit, governanceDriver } = + const { storage, advanceTimeBy, getVstorageData, governanceDriver } = t.context; const committee = [ governanceDriver.ecMembers[0], @@ -401,7 +459,318 @@ test.serial( await advanceTimeBy(1, 'minutes'); const lastOutcome = await governanceDriver.getLatestOutcome(); - t.notDeepEqual(getUpdatedDebtLimit(), 500_000_000n); + const managerParams = getVstorageData(managerGovernanceKey); + t.notDeepEqual(managerParams.current.DebtLimit.value.value, 500_000_000n); t.assert(lastOutcome.outcome === 'fail'); }, ); + +// Will fail until https://github.com/Agoric/agoric-sdk/issues/10136 is completed +test.failing('outgoing member should not be able to propose', async t => { + // Ability to propose by outgoing member should still exist + const { storage, advanceTimeBy, getVstorageData, governanceDriver } = + t.context; + const newCommittee = governanceDriver.ecMembers.slice(0, 3); + const outgoingMember = governanceDriver.ecMembers[3]; + + const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); + const { VaultFactory } = agoricNamesRemotes.instance; + const { ATOM: collateralBrand, IST: debtBrand } = agoricNamesRemotes.brand; + + t.log('Proposing question using old charter invitation'); + await governanceDriver.proposeParams( + VaultFactory, + { DebtLimit: { brand: debtBrand, value: 300_000_000n } }, + { paramPath: { key: { collateralBrand } } }, + outgoingMember, + getQuestionId(3), + offerIds.propose.outgoing, + ); + + t.like(outgoingMember.getLatestUpdateRecord(), { + status: { id: getQuestionId(3), numWantsSatisfied: 1 }, + }); + + t.log('Voting on question using first 2 wallets'); + await governanceDriver.enactLatestProposal( + newCommittee.slice(0, 2), + getVoteId(3), + offerIds.vote.incoming, + ); + for (const w of newCommittee.slice(0, 2)) { + t.like(w.getLatestUpdateRecord(), { + status: { id: getVoteId(3), numWantsSatisfied: 1 }, + }); + } + + t.log('Waiting for period to end'); + await advanceTimeBy(1, 'minutes'); + + t.log('Verifying outcome'); + const lastOutcome = await governanceDriver.getLatestOutcome(); + const managerParams = getVstorageData(managerGovernanceKey); + t.notDeepEqual(managerParams.current.DebtLimit.value.value, 300_000_000n); + t.assert(lastOutcome.outcome === 'win'); +}); + +test.serial('EC can govern auctioneer parameter', async t => { + const { storage, advanceTimeBy, getVstorageData, governanceDriver } = + t.context; + const newCommittee = governanceDriver.ecMembers.slice(0, 3); + + const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); + const { auctioneer } = agoricNamesRemotes.instance; + + t.log('Proposing question using new charter invitation'); + await governanceDriver.proposeParams( + auctioneer, + { LowestRate: 100_000_000n }, + { paramPath: { key: 'governedParams' } }, + newCommittee[0], + getQuestionId(4), + offerIds.propose.incoming, + ); + + t.like(newCommittee[0].getLatestUpdateRecord(), { + status: { id: getQuestionId(4), numWantsSatisfied: 1 }, + }); + + t.log('Voting on question using first 2 wallets'); + await governanceDriver.enactLatestProposal( + newCommittee.slice(0, 2), + getVoteId(4), + offerIds.vote.incoming, + ); + for (const w of newCommittee.slice(0, 2)) { + t.like(w.getLatestUpdateRecord(), { + status: { id: getVoteId(4), numWantsSatisfied: 1 }, + }); + } + + t.log('Waiting for period to end'); + await advanceTimeBy(1, 'minutes'); + + t.log('Verifying outcome'); + const lastOutcome = await governanceDriver.getLatestOutcome(); + const auctioneerParams = getVstorageData(auctioneerParamsKey); + t.deepEqual(auctioneerParams.current.LowestRate.value, 100_000_000n); + t.assert(lastOutcome.outcome === 'win'); +}); + +test.serial('EC can govern provisionPool parameter', async t => { + const { storage, advanceTimeBy, getVstorageData, governanceDriver } = + t.context; + const newCommittee = governanceDriver.ecMembers.slice(0, 3); + + const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); + const { provisionPool } = agoricNamesRemotes.instance; + const { IST } = agoricNamesRemotes.brand; + + t.log('Proposing question using new charter invitation'); + await governanceDriver.proposeParams( + provisionPool, + { PerAccountInitialAmount: { brand: IST, value: 100_000_000n } }, + { paramPath: { key: 'governedParams' } }, + newCommittee[0], + getQuestionId(5), + offerIds.propose.incoming, + ); + + t.like(newCommittee[0].getLatestUpdateRecord(), { + status: { id: getQuestionId(5), numWantsSatisfied: 1 }, + }); + + t.log('Voting on question using first 2 wallets'); + await governanceDriver.enactLatestProposal( + newCommittee.slice(0, 2), + getVoteId(5), + offerIds.vote.incoming, + ); + for (const w of newCommittee.slice(0, 2)) { + t.like(w.getLatestUpdateRecord(), { + status: { id: getVoteId(5), numWantsSatisfied: 1 }, + }); + } + + t.log('Waiting for period to end'); + await advanceTimeBy(1, 'minutes'); + + t.log('Verifying outcome'); + const lastOutcome = await governanceDriver.getLatestOutcome(); + const provisionPoolParams = getVstorageData(provisionPoolParamsKey); + t.deepEqual( + provisionPoolParams.current.PerAccountInitialAmount.value.value, + 100_000_000n, + ); + t.assert(lastOutcome.outcome === 'win'); +}); + +test.serial('EC can govern reserve parameter', async t => { + const { storage, advanceTimeBy, governanceDriver, getVstorageData } = + t.context; + const newCommittee = governanceDriver.ecMembers.slice(0, 3); + + const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); + const { reserve } = agoricNamesRemotes.instance; + const { IST } = agoricNamesRemotes.brand; + + t.log('Proposing question using new charter invitation for reserve'); + await governanceDriver.proposeApiCall( + reserve, + 'burnFeesToReduceShortfall', + [{ brand: IST, value: 1000n }], + newCommittee[0], + getQuestionId(6), + offerIds.propose.incoming, + ); + + t.like(newCommittee[0].getLatestUpdateRecord(), { + status: { id: getQuestionId(6), numWantsSatisfied: 1 }, + }); + + t.log('Voting on question using first 2 wallets'); + await governanceDriver.enactLatestProposal( + newCommittee.slice(0, 2), + getVoteId(6), + offerIds.vote.incoming, + ); + for (const w of newCommittee.slice(0, 2)) { + t.like(w.getLatestUpdateRecord(), { + status: { id: getVoteId(6), numWantsSatisfied: 1 }, + }); + } + + t.log('no oracle invitation should exist before vote passing'); + const oracleInvitation = + await governanceDriver.ecMembers[0].findOracleInvitation(); + t.is(oracleInvitation, undefined); + + t.log('Checking params before passing proposal'); + const reserveParams = getVstorageData(reserveParamsKey); + t.is(reserveParams.totalFeeBurned.value, 0n); + + t.log('Waiting for period to end'); + await advanceTimeBy(1, 'minutes'); + + t.log('Verifying outcome'); + const lastOutcome = await governanceDriver.getLatestOutcome(); + t.assert(lastOutcome.outcome === 'win'); + + const reserveParamsPostUpdate = getVstorageData(reserveParamsKey); + t.is(reserveParamsPostUpdate.totalFeeBurned.value, 1000n); +}); + +test.serial('EC can govern psm parameter', async t => { + const { storage, advanceTimeBy, getVstorageData, governanceDriver } = + t.context; + const newCommittee = governanceDriver.ecMembers.slice(0, 3); + + const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); + const { IST } = agoricNamesRemotes.brand; + + const psmInstances = Object.keys(agoricNamesRemotes.instance) + .map(instance => { + const regex = /^psm-IST-(?.*)$/; + return regex.exec(instance); + }) + .filter(instance => instance !== null); + + await null; + for (const instance of psmInstances) { + const brand = instance.groups?.brand; + const instanceName = instance[0]; + t.log('Proposing question using new charter invitation for', instanceName); + await governanceDriver.proposeParams( + agoricNamesRemotes.instance[instanceName], + { MintLimit: { brand: IST, value: 100_000_000n } }, + { paramPath: { key: 'governedParams' } }, + newCommittee[0], + getQuestionId(instanceName), + offerIds.propose.incoming, + ); + + t.like(newCommittee[0].getLatestUpdateRecord(), { + status: { id: getQuestionId(instanceName), numWantsSatisfied: 1 }, + }); + + t.log('Voting on question using first 2 wallets'); + await governanceDriver.enactLatestProposal( + newCommittee.slice(0, 2), + getVoteId(instanceName), + offerIds.vote.incoming, + ); + for (const w of newCommittee.slice(0, 2)) { + t.like(w.getLatestUpdateRecord(), { + status: { id: getVoteId(instanceName), numWantsSatisfied: 1 }, + }); + } + + t.log('Waiting for period to end'); + await advanceTimeBy(1, 'minutes'); + + t.log('Verifying outcome'); + const lastOutcome = await governanceDriver.getLatestOutcome(); + const psmParams = getVstorageData(getPsmKey(brand)); + t.deepEqual(psmParams.current.MintLimit.value.value, 100_000_000n); + t.assert(lastOutcome.outcome === 'win'); + } +}); + +test.serial('EC can make calls to price feed governed APIs', async t => { + const { storage, advanceTimeBy, governanceDriver } = t.context; + const newCommittee = governanceDriver.ecMembers.slice(0, 3); + + const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); + + const priceFeedInstances = Object.keys(agoricNamesRemotes.instance).filter( + instance => { + const regex = /^(.*)-(.*) price feed$/; + return regex.exec(instance); + }, + ); + + await null; + for (const instanceName of priceFeedInstances) { + t.log('Proposing question using new charter invitation for', instanceName); + await governanceDriver.proposeApiCall( + agoricNamesRemotes.instance[instanceName], + 'addOracles', + [[wallets[0]]], + newCommittee[0], + getQuestionId(instanceName), + offerIds.propose.incoming, + ); + + t.like(newCommittee[0].getLatestUpdateRecord(), { + status: { id: getQuestionId(instanceName), numWantsSatisfied: 1 }, + }); + + t.log('Voting on question using first 2 wallets'); + await governanceDriver.enactLatestProposal( + newCommittee.slice(0, 2), + getVoteId(instanceName), + offerIds.vote.incoming, + ); + for (const w of newCommittee.slice(0, 2)) { + t.like(w.getLatestUpdateRecord(), { + status: { id: getVoteId(instanceName), numWantsSatisfied: 1 }, + }); + } + + t.log('no oracle invitation should exist before vote passing'); + const oracleInvitation = + await governanceDriver.ecMembers[0].findOracleInvitation(); + t.is(oracleInvitation, undefined); + + t.log('Waiting for period to end'); + await advanceTimeBy(1, 'minutes'); + + t.log('Verifying outcome'); + const lastOutcome = await governanceDriver.getLatestOutcome(); + t.assert(lastOutcome.outcome === 'win'); + + const oracleInvitationAfterProposal = + await governanceDriver.ecMembers[0].findOracleInvitation(); + t.is(passStyleOf(oracleInvitationAfterProposal), 'copyRecord'); + } +}); diff --git a/packages/boot/test/bootstrapTests/ec-replace-charter.test.ts b/packages/boot/test/bootstrapTests/ec-replace-charter.test.ts deleted file mode 100644 index 7ff4c7d2c70..00000000000 --- a/packages/boot/test/bootstrapTests/ec-replace-charter.test.ts +++ /dev/null @@ -1,267 +0,0 @@ -import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; -import { TestFn } from 'ava'; -import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; -import { - makeAgoricNamesRemotesFromFakeStorage, - slotToBoardRemote, - unmarshalFromVstorage, -} from '@agoric/vats/tools/board-utils.js'; -import { makeMarshal } from '@endo/marshal'; - -import { makeSwingsetTestKit } from '../../tools/supports.js'; -import { - makeGovernanceDriver, - makeWalletFactoryDriver, -} from '../../tools/drivers.js'; - -const wallets = [ - 'agoric1gx9uu7y6c90rqruhesae2t7c2vlw4uyyxlqxrx', - 'agoric1d4228cvelf8tj65f4h7n2td90sscavln2283h5', - 'agoric14543m33dr28x7qhwc558hzlj9szwhzwzpcmw6a', - 'agoric13p9adwk0na5npfq64g22l6xucvqdmu3xqe70wq', - 'agoric1el6zqs8ggctj5vwyukyk4fh50wcpdpwgugd5l5', - 'agoric1zayxg4e9vd0es9c9jlpt36qtth255txjp6a8yc', -]; - -const highPrioritySenderKey = 'highPrioritySenders'; - -const offerIds = { - propose: { outgoing: 'outgoing_propose', incoming: 'incoming_propose' }, - vote: { outgoing: 'outgoing_vote', incoming: 'incoming_vote' }, -}; - -const getQuestionId = id => `propose-question-${id}`; -const getVoteId = id => `vote-${id}`; - -export const makeZoeTestContext = async t => { - console.time('ZoeTestContext'); - const swingsetTestKit = await makeSwingsetTestKit(t.log, undefined, { - configSpecifier: '@agoric/vm-config/decentral-main-vaults-config.json', - }); - - const { runUtils, storage } = swingsetTestKit; - console.timeLog('DefaultTestContext', 'swingsetTestKit'); - const { EV } = runUtils; - - await eventLoopIteration(); - - // We don't need vaults, but this gets the brand, which is checked somewhere - // Wait for ATOM to make it into agoricNames - await EV.vat('bootstrap').consumeItem('vaultFactoryKit'); - console.timeLog('DefaultTestContext', 'vaultFactoryKit'); - - // has to be late enough for agoricNames data to have been published - const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); - console.timeLog('DefaultTestContext', 'agoricNamesRemotes'); - - console.timeEnd('DefaultTestContext'); - const walletFactoryDriver = await makeWalletFactoryDriver( - runUtils, - storage, - agoricNamesRemotes, - ); - - const { fromCapData } = makeMarshal(undefined, slotToBoardRemote); - - const getUpdatedDebtLimit = () => { - const atomGovernance = unmarshalFromVstorage( - storage.data, - 'published.vaultFactory.managers.manager0.governance', - fromCapData, - -1, - ); - return atomGovernance.current.DebtLimit.value.value; - }; - - const governanceDriver = await makeGovernanceDriver( - swingsetTestKit, - agoricNamesRemotes, - walletFactoryDriver, - wallets, - ); - - return { - ...swingsetTestKit, - storage, - getUpdatedDebtLimit, - governanceDriver, - }; -}; -const test = anyTest as TestFn>>; - -test.before(async t => { - t.context = await makeZoeTestContext(t); -}); - -test.serial('normal running of committee', async t => { - const { advanceTimeBy, storage, getUpdatedDebtLimit, governanceDriver } = - t.context; - - const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); - const { VaultFactory, economicCommittee } = agoricNamesRemotes.instance; - const { ATOM: collateralBrand, IST: debtBrand } = agoricNamesRemotes.brand; - - const committee = governanceDriver.ecMembers; - - t.log('Accepting all invitations for original committee'); - await null; - for (const member of committee) { - await member.acceptCharterInvitation(offerIds.propose.outgoing); - await member.acceptCommitteeInvitation(offerIds.vote.outgoing); - } - - t.log('Proposing a question using first wallet'); - await governanceDriver.proposeParams( - VaultFactory, - { DebtLimit: { brand: debtBrand, value: 100_000_000n } }, - { paramPath: { key: { collateralBrand } } }, - committee[0], - getQuestionId(1), - offerIds.propose.outgoing, - ); - - t.log('Checking if question proposal passed'); - t.like(committee[0].getLatestUpdateRecord(), { - status: { id: getQuestionId(1), numWantsSatisfied: 1 }, - }); - - t.log('Voting on question using first 3 wallets'); - await governanceDriver.enactLatestProposal( - committee, - getVoteId(1), - offerIds.vote.outgoing, - ); - - t.log('Checking if votes passed'); - for (const w of committee.slice(0, 3)) { - t.like(w.getLatestUpdateRecord(), { - status: { id: getVoteId(1), numWantsSatisfied: 1 }, - }); - } - - t.log('Waiting for period to end'); - await advanceTimeBy(1, 'minutes'); - - t.log('Verifying outcome'); - - const lastOutcome = await governanceDriver.getLatestOutcome(); - console.log(lastOutcome); - t.deepEqual(getUpdatedDebtLimit(), 100_000_000n); - t.assert(lastOutcome.outcome === 'win'); -}); - -test.serial('replace committee', async t => { - const { buildProposal, evalProposal } = t.context; - await evalProposal( - buildProposal( - '@agoric/builders/scripts/inter-protocol/replace-electorate-core.js', - ['BOOTSTRAP_TEST'], - ), - ); - await eventLoopIteration(); - t.true(true); // just to avoid failure -}); - -test.serial('successful proposal and vote by continuing members', async t => { - const { storage, advanceTimeBy, getUpdatedDebtLimit, governanceDriver } = - t.context; - const newCommittee = governanceDriver.ecMembers.slice(0, 3); - - const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); - const { economicCommittee, VaultFactory, econCommitteeCharter } = - agoricNamesRemotes.instance; - const { ATOM: collateralBrand, IST: debtBrand } = agoricNamesRemotes.brand; - - t.log('Accepting all new invitations for voters'); - await null; - for (const member of newCommittee) { - await member.acceptCharterInvitation( - offerIds.propose.incoming, - econCommitteeCharter, - ); - await member.acceptCommitteeInvitation( - offerIds.vote.incoming, - economicCommittee, - ); - } - - t.log('Proposing question using new charter invitation'); - await governanceDriver.proposeParams( - VaultFactory, - { DebtLimit: { brand: debtBrand, value: 200_000_000n } }, - { paramPath: { key: { collateralBrand } } }, - newCommittee[0], - getQuestionId(2), - offerIds.propose.incoming, - ); - - t.like(newCommittee[0].getLatestUpdateRecord(), { - status: { id: getQuestionId(2), numWantsSatisfied: 1 }, - }); - - t.log('Voting on question using first 2 wallets'); - await governanceDriver.enactLatestProposal( - newCommittee.slice(0, 2), - getVoteId(2), - offerIds.vote.incoming, - ); - for (const w of newCommittee.slice(0, 2)) { - t.like(w.getLatestUpdateRecord(), { - status: { id: getVoteId(2), numWantsSatisfied: 1 }, - }); - } - - t.log('Waiting for period to end'); - await advanceTimeBy(1, 'minutes'); - - t.log('Verifying outcome'); - const lastOutcome = await governanceDriver.getLatestOutcome(); - t.deepEqual(getUpdatedDebtLimit(), 200_000_000n); - t.assert(lastOutcome.outcome === 'win'); -}); - -test.serial('successful proposal by outgoing member', async t => { - // Ability to propose by outgoing member should still exist - const { storage, advanceTimeBy, getUpdatedDebtLimit, governanceDriver } = - t.context; - const newCommittee = governanceDriver.ecMembers.slice(0, 3); - const outgoingMember = governanceDriver.ecMembers[3]; - - const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); - const { VaultFactory } = agoricNamesRemotes.instance; - const { ATOM: collateralBrand, IST: debtBrand } = agoricNamesRemotes.brand; - - t.log('Proposing question using old charter invitation'); - await governanceDriver.proposeParams( - VaultFactory, - { DebtLimit: { brand: debtBrand, value: 300_000_000n } }, - { paramPath: { key: { collateralBrand } } }, - outgoingMember, - getQuestionId(3), - offerIds.propose.outgoing, - ); - - t.like(outgoingMember.getLatestUpdateRecord(), { - status: { id: getQuestionId(3), numWantsSatisfied: 1 }, - }); - - t.log('Voting on question using first 2 wallets'); - await governanceDriver.enactLatestProposal( - newCommittee.slice(0, 2), - getVoteId(3), - offerIds.vote.incoming, - ); - for (const w of newCommittee.slice(0, 2)) { - t.like(w.getLatestUpdateRecord(), { - status: { id: getVoteId(3), numWantsSatisfied: 1 }, - }); - } - - t.log('Waiting for period to end'); - await advanceTimeBy(1, 'minutes'); - - t.log('Verifying outcome'); - const lastOutcome = await governanceDriver.getLatestOutcome(); - t.deepEqual(getUpdatedDebtLimit(), 300_000_000n); - t.assert(lastOutcome.outcome === 'win'); -}); diff --git a/packages/boot/tools/drivers.ts b/packages/boot/tools/drivers.ts index c042ec0f4ec..93cb37b4ed4 100644 --- a/packages/boot/tools/drivers.ts +++ b/packages/boot/tools/drivers.ts @@ -232,12 +232,24 @@ export const makeGovernanceDriver = async ( ), ); + const findInvitation = (wallet, descriptionSubstr) => { + return wallet + .getCurrentWalletRecord() + .purses[0].balance.value.find(v => + v.description.startsWith(descriptionSubstr), + ); + }; + const ecMembers = smartWallets.map(w => ({ ...w, - acceptCharterInvitation: async ( + acceptOutstandingCharterInvitation: async ( charterOfferId = charterMembershipId, instance = agoricNamesRemotes.instance.econCommitteeCharter, ) => { + if (!findInvitation(w, 'charter member invitation')) { + console.log('No charter member invitation found'); + return; + } await w.executeOffer({ id: charterOfferId, invitationSpec: { @@ -248,18 +260,21 @@ export const makeGovernanceDriver = async ( proposal: {}, }); }, - acceptCommitteeInvitation: async ( + acceptOutstandingCommitteeInvitation: async ( committeeOfferId = committeeMembershipId, instance = agoricNamesRemotes.instance.economicCommittee, ) => { - const description = - w.getCurrentWalletRecord().purses[0].balance.value[0].description; + const invitation = findInvitation(w, 'Voter'); + if (!invitation) { + console.log('No committee member invitation found'); + return; + } await w.executeOffer({ id: committeeOfferId, invitationSpec: { source: 'purse', instance, - description, + description: invitation.description, }, proposal: {}, }); @@ -289,6 +304,17 @@ export const makeGovernanceDriver = async ( proposal: {}, }); }, + findOracleInvitation: async () => { + const purse = w + .getCurrentWalletRecord() + // TODO: manage brands by object identity #10167 + .purses.find(p => p.brand.toString().includes('Invitation')); + // @ts-expect-error + const invitation = purse?.balance.value.find( + v => v.description === 'oracle invitation', + ); + return invitation; + }, })); const ensureInvitationsAccepted = async () => { @@ -297,8 +323,8 @@ export const makeGovernanceDriver = async ( } await null; for (const member of ecMembers) { - await member.acceptCharterInvitation(); - await member.acceptCommitteeInvitation(); + await member.acceptOutstandingCharterInvitation(); + await member.acceptOutstandingCommitteeInvitation(); } invitationsAccepted = true; }; @@ -330,6 +356,28 @@ export const makeGovernanceDriver = async ( }); }; + const proposeApiCall = async ( + instance, + methodName: string, + methodArgs: any[], + ecMember: (typeof ecMembers)[0] | null = null, + questionId = 'propose', + charterOfferId = charterMembershipId, + ) => { + const now = await EV(chainTimerService).getCurrentTimestamp(); + const deadline = SECONDS_PER_MINUTE + now.absValue; + await (ecMember || ecMembers[0]).executeOffer({ + id: questionId, + invitationSpec: { + invitationMakerName: 'VoteOnApiCall', + previousOffer: charterOfferId, + source: 'continuing', + invitationArgs: [instance, methodName, methodArgs, deadline], + }, + proposal: {}, + }); + }; + const enactLatestProposal = async ( members = ecMembers, voteId = 'voteInNewLimit', @@ -352,6 +400,7 @@ export const makeGovernanceDriver = async ( return { proposeParams, + proposeApiCall, enactLatestProposal, getLatestOutcome, async changeParams(instance: Instance, params: Object, path?: object) { diff --git a/packages/builders/scripts/inter-protocol/replace-electorate-core.js b/packages/builders/scripts/inter-protocol/replace-electorate-core.js index 80d8dc8c7ad..a856177f7d0 100644 --- a/packages/builders/scripts/inter-protocol/replace-electorate-core.js +++ b/packages/builders/scripts/inter-protocol/replace-electorate-core.js @@ -24,6 +24,12 @@ export const defaultProposalBuilder = async ({ publishRef, install }, opts) => { getManifestForReplaceAllElectorates.name, { ...opts, + economicCommitteeRef: publishRef( + install( + '@agoric/governance/src/committee.js', + '../bundles/bundle-committee.js', + ), + ), }, ], }); diff --git a/packages/inter-protocol/src/proposals/replaceElectorate.js b/packages/inter-protocol/src/proposals/replaceElectorate.js index 2c786fa457b..0780b692418 100644 --- a/packages/inter-protocol/src/proposals/replaceElectorate.js +++ b/packages/inter-protocol/src/proposals/replaceElectorate.js @@ -280,7 +280,7 @@ const addGovernorsToEconCharter = async ( const psmKitMap = await psmKit; for (const { psm, psmGovernorCreatorFacet, label } of psmKitMap.values()) { - E(ecCreatorFacet).addInstance(psm, psmGovernorCreatorFacet, label); + await E(ecCreatorFacet).addInstance(psm, psmGovernorCreatorFacet, label); } const governedContractKitMap = await governedContractKits; @@ -290,7 +290,7 @@ const addGovernorsToEconCharter = async ( governorCreatorFacet, label, } of governedContractKitMap.values()) { - E(ecCreatorFacet).addInstance(instance, governorCreatorFacet, label); + await E(ecCreatorFacet).addInstance(instance, governorCreatorFacet, label); } }; From 6ebf5254bba322e6c9926a077268cd41aff9593d Mon Sep 17 00:00:00 2001 From: rabi-siddique Date: Thu, 3 Oct 2024 17:22:36 +0500 Subject: [PATCH 08/10] docs: add JSDoc comments to enhance function documentation --- .../src/proposals/replaceElectorate.js | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/packages/inter-protocol/src/proposals/replaceElectorate.js b/packages/inter-protocol/src/proposals/replaceElectorate.js index 0780b692418..8e8d39398cf 100644 --- a/packages/inter-protocol/src/proposals/replaceElectorate.js +++ b/packages/inter-protocol/src/proposals/replaceElectorate.js @@ -18,6 +18,7 @@ import { import { reserveThenDeposit } from './utils.js'; /** @import {EconomyBootstrapPowers} from './econ-behaviors.js' */ +/** @import {EconCharterStartResult} from './econ-behaviors.js' */ /** @import {CommitteeElectorateCreatorFacet} from '@agoric/governance/src/committee.js'; */ const trace = (...args) => console.log('GovReplaceCommiteeAndCharter', ...args); @@ -137,6 +138,29 @@ const inviteECMembers = async ( await distributeInvitations(zip(values(voterAddresses), invitations)); }; +/** + * Invites members to the Economic Charter by creating and distributing charter + * member invitations to the specified voter addresses. + * + * @param {EconomyBootstrapPowers} powers - The bootstrap powers required for + * economic operations, including `namesByAddressAdmin` used for managing + * names and deposits. + * @param {{ + * options: { + * voterAddresses: Record; + * econCharterKit: { + * creatorFacet: { + * makeCharterMemberInvitation: () => Promise; + * }; + * }; + * }; + * }} opts + * - The configuration object containing voter addresses and the econ charter kit + * for creating charter member invitations. + * + * @returns {Promise} This function does not explicitly return a value. It + * processes all charter member invitations asynchronously. + */ const inviteToEconCharter = async ( { consume: { namesByAddressAdmin } }, { options: { voterAddresses, econCharterKit } }, @@ -235,6 +259,16 @@ const startNewEconomicCommittee = async ( return creatorFacet; }; + +/** + * Starts a new Economic Committee Charter by creating an instance with the + * provided committee specifications. + * + * @param {EconomyBootstrapPowers} powers - The resources and capabilities + * required to start the committee. + * @returns {Promise} A promise that resolves to the + * charter kit result. + */ const startNewEconCharter = async ({ consume: { zoe }, produce: { econCharterKit }, @@ -271,6 +305,22 @@ const startNewEconCharter = async ({ return startResult; }; +/** + * Adds governors to an existing Economic Committee Charter + * + * - @param {EconomyBootstrapPowers} powers - The resources and capabilities + * required to start the committee. + * + * @param {{ + * options: { + * econCharterKit: EconCharterStartResult; + * }; + * }} config + * - Configuration object containing the name and size of the committee. + * + * @returns {Promise} A promise that resolves once all the governors have + * been successfully added to the economic charter + */ const addGovernorsToEconCharter = async ( { consume: { psmKit, governedContractKits } }, { options: { econCharterKit } }, From 78581b1635c3dfa32253d2abf3660940d0265ba1 Mon Sep 17 00:00:00 2001 From: rabi-siddique Date: Thu, 3 Oct 2024 17:24:33 +0500 Subject: [PATCH 09/10] chore: use startUpgradable --- .../src/proposals/replaceElectorate.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/inter-protocol/src/proposals/replaceElectorate.js b/packages/inter-protocol/src/proposals/replaceElectorate.js index 8e8d39398cf..ede08ff8cb4 100644 --- a/packages/inter-protocol/src/proposals/replaceElectorate.js +++ b/packages/inter-protocol/src/proposals/replaceElectorate.js @@ -270,7 +270,7 @@ const startNewEconomicCommittee = async ( * charter kit result. */ const startNewEconCharter = async ({ - consume: { zoe }, + consume: { startUpgradable }, produce: { econCharterKit }, installation: { consume: { binaryVoteCounter: counterP, econCommitteeCharter: installP }, @@ -288,13 +288,13 @@ const startNewEconCharter = async ({ }); trace('Starting new EC Charter Instance'); - const startResult = E(zoe).startInstance( - charterInstall, - undefined, + + const startResult = await E(startUpgradable)({ + label: 'econCommitteeCharter', + installation: charterInstall, terms, - undefined, - 'econCommitteeCharter', - ); + }); + trace('Started new EC Charter Instance Successfully'); econCommitteeCharter.reset(); From 5f789e8ea8079f9176723ca318c6aeabb3b1ccfa Mon Sep 17 00:00:00 2001 From: Jeancarlo Date: Mon, 12 Aug 2024 23:36:30 -0600 Subject: [PATCH 10/10] fix: remove unesesary error checks and returns --- golang/cosmos/x/vbank/vbank.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/golang/cosmos/x/vbank/vbank.go b/golang/cosmos/x/vbank/vbank.go index ec1c8f888a5..1a0ad037cd0 100644 --- a/golang/cosmos/x/vbank/vbank.go +++ b/golang/cosmos/x/vbank/vbank.go @@ -144,11 +144,9 @@ func (ch portHandler) Receive(cctx context.Context, str string) (ret string, err } coin := keeper.GetBalance(ctx, addr, msg.Denom) packet := coin.Amount.String() + bytes, err := json.Marshal(&packet) if err == nil { - bytes, err := json.Marshal(&packet) - if err == nil { - ret = string(bytes) - } + ret = string(bytes) } case "VBANK_GRAB": @@ -216,9 +214,6 @@ func (ch portHandler) Receive(cctx context.Context, str string) (ret string, err if err := keeper.StoreRewardCoins(ctx, coins); err != nil { return "", fmt.Errorf("cannot store reward %s coins: %s", coins.Sort().String(), err) } - if err != nil { - return "", err - } state := keeper.GetState(ctx) state.RewardPool = state.RewardPool.Add(coins...) keeper.SetState(ctx, state)