Skip to content

Commit

Permalink
feat: durability for governance
Browse files Browse the repository at this point in the history
All existing tests in governance, zoe, boot, and inter-protocol pass.

deployment tests are next
  • Loading branch information
Chris-Hibbert committed Sep 5, 2023
1 parent 52bc8ec commit 8f9233f
Show file tree
Hide file tree
Showing 72 changed files with 2,223 additions and 2,627 deletions.
8 changes: 7 additions & 1 deletion packages/ERTP/src/typeGuards.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const SetValueShape = M.arrayOf(M.key());
*/
const CopyBagValueShape = M.bag();

const AmountValueShape = M.or(
export const AmountValueShape = M.or(
NatValueShape,
CopySetValueShape,
SetValueShape,
Expand Down Expand Up @@ -228,3 +228,9 @@ export const makeIssuerInterfaces = (
});
};
harden(makeIssuerInterfaces);

/** @param {Amount} amount */
export const makeBrandedAmountPattern = amount => {
return { brand: amount.brand, value: M.nat() };
};
harden(makeBrandedAmountPattern);
4 changes: 2 additions & 2 deletions packages/agoric-cli/src/commands/auction.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import { outputActionAndHint } from '../lib/wallet.js';

const { Fail } = assert;

/** @typedef {import('@agoric/governance/src/contractGovernance/typedParamManager.js').ParamTypesMap} ParamTypesMap */
/** @typedef {import('@agoric/governance/src/contractGovernance/paramManager.js').ParamTypesMap} ParamTypesMap */

/**
* @template {ParamStateRecord} M
* @typedef {import('@agoric/governance/src/contractGovernance/typedParamManager.js').ParamTypesMapFromRecord<M>} ParamTypesMapFromRecord
* @typedef {import('@agoric/governance/src/contractGovernance/paramManager.js').ParamTypesMapFromRecord<M>} ParamTypesMapFromRecord
*/

/**
Expand Down
1 change: 1 addition & 0 deletions packages/boot/test/bootstrapTests/test-vaults-upgrade.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Offers } from '@agoric/inter-protocol/src/clientSupport.js';
import { Far, makeMarshal } from '@endo/marshal';
import { SECONDS_PER_YEAR } from '@agoric/inter-protocol/src/interest.js';
import { makeAgoricNamesRemotesFromFakeStorage } from '@agoric/vats/tools/board-utils.js';

import { makeSwingsetTestKit } from './supports.js';
import { makeWalletFactoryDriver } from './drivers.js';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,48 @@ SDK=${SDK:-/usr/src/agoric-sdk}
# Enable debugging
set -x

# hacky restore of pruned artifacts
killAgd
EXPORT_DIR=$(mktemp -t -d swing-store-export-upgrade-11-XXX)
WITHOUT_GENESIS_EXPORT=1 make_swing_store_snapshot $EXPORT_DIR --artifact-mode debug || fail "Couldn't make swing-store snapshot"
HISTORICAL_ARTIFACTS="$(cd $HOME/.agoric/data/agoric/swing-store-historical-artifacts/; for i in *; do echo -n "[\"$i\",\"$i\"],"; done)"
mv -n $HOME/.agoric/data/agoric/swing-store-historical-artifacts/* $EXPORT_DIR || fail "some historical artifacts not pruned"
mv $EXPORT_DIR/export-manifest.json $EXPORT_DIR/export-manifest-original.json
cat $EXPORT_DIR/export-manifest-original.json | jq -r ".artifacts = .artifacts + [${HISTORICAL_ARTIFACTS%%,}] | del(.artifactMode)" > $EXPORT_DIR/export-manifest.json
restore_swing_store_snapshot $EXPORT_DIR || fail "Couldn't restore swing-store snapshot"
startAgd
rm -rf $EXPORT_DIR
test_not_val "$(agops vaults list --from $GOV1ADDR)" "" "gov1 has no vaults"
# open up a vault
OFFER=$(mktemp -t agops.XXX)
agops vaults open --wantMinted 7.00 --giveCollateral 11.0 >|"$OFFER"
agops perf satisfaction --from "$GOV1ADDR" --executeOffer "$OFFER" --keyring-backend=test
# put some IST in
OFFER=$(mktemp -t agops.XXX)
agops vaults adjust --vaultId vault3 --giveMinted 1.5 --from $GOV1ADDR --keyring-backend=test >|"$OFFER"
agops perf satisfaction --from "$GOV1ADDR" --executeOffer "$OFFER" --keyring-backend=test
# add some collateral
OFFER=$(mktemp -t agops.XXX)
agops vaults adjust --vaultId vault3 --giveCollateral 2.0 --from $GOV1ADDR --keyring-backend="test" >|"$OFFER"
agops perf satisfaction --from "$GOV1ADDR" --executeOffer "$OFFER" --keyring-backend=test
# close out
OFFER=$(mktemp -t agops.XXX)
agops vaults close --vaultId vault3 --giveMinted 5.75 --from $GOV1ADDR --keyring-backend="test" >|"$OFFER"
agops perf satisfaction --from "$GOV1ADDR" --executeOffer "$OFFER" --keyring-backend=test
test_val $(agoric follow -l -F :published.vaultFactory.managers.manager0.vaults.vault3 -o jsonlines | jq -r '.vaultState') "closed" "vault3 is closed"
test_val $(agoric follow -l -F :published.vaultFactory.managers.manager0.vaults.vault3 -o jsonlines | jq -r '.locked.value') "0" "vault3 contains no collateral"
test_val $(agoric follow -l -F :published.vaultFactory.managers.manager0.vaults.vault3 -o jsonlines | jq -r '.debtSnapshot.debt.value') "0" "vault3 has no debt"
## hacky restore of pruned artifacts
#killAgd
#EXPORT_DIR=$(mktemp -t -d swing-store-export-upgrade-11-XXX)
#WITHOUT_GENESIS_EXPORT=1 make_swing_store_snapshot $EXPORT_DIR --artifact-mode debug || fail "Couldn't make swing-store snapshot"
#HISTORICAL_ARTIFACTS="$(cd $HOME/.agoric/data/agoric/swing-store-historical-artifacts/; for i in *; do echo -n "[\"$i\",\"$i\"],"; done)"
#mv -n $HOME/.agoric/data/agoric/swing-store-historical-artifacts/* $EXPORT_DIR || fail "some historical artifacts not pruned"
#mv $EXPORT_DIR/export-manifest.json $EXPORT_DIR/export-manifest-original.json
#cat $EXPORT_DIR/export-manifest-original.json | jq -r ".artifacts = .artifacts + [${HISTORICAL_ARTIFACTS%%,}] | del(.artifactMode)" > $EXPORT_DIR/export-manifest.json
#restore_swing_store_snapshot $EXPORT_DIR || fail "Couldn't restore swing-store snapshot"
#startAgd
#rm -rf $EXPORT_DIR
#
#test_not_val "$(agops vaults list --from $GOV1ADDR)" "" "gov1 has no vaults"
#
## open up a vault
#OFFER=$(mktemp -t agops.XXX)
#agops vaults open --wantMinted 7.00 --giveCollateral 11.0 >|"$OFFER"
#agops perf satisfaction --from "$GOV1ADDR" --executeOffer "$OFFER" --keyring-backend=test
#
## put some IST in
#OFFER=$(mktemp -t agops.XXX)
#agops vaults adjust --vaultId vault3 --giveMinted 1.5 --from $GOV1ADDR --keyring-backend=test >|"$OFFER"
#agops perf satisfaction --from "$GOV1ADDR" --executeOffer "$OFFER" --keyring-backend=test
#
## add some collateral
#OFFER=$(mktemp -t agops.XXX)
#agops vaults adjust --vaultId vault3 --giveCollateral 2.0 --from $GOV1ADDR --keyring-backend="test" >|"$OFFER"
#agops perf satisfaction --from "$GOV1ADDR" --executeOffer "$OFFER" --keyring-backend=test
#
## close out
#OFFER=$(mktemp -t agops.XXX)
#agops vaults close --vaultId vault3 --giveMinted 5.75 --from $GOV1ADDR --keyring-backend="test" >|"$OFFER"
#agops perf satisfaction --from "$GOV1ADDR" --executeOffer "$OFFER" --keyring-backend=test
#
#test_val $(agoric follow -l -F :published.vaultFactory.managers.manager0.vaults.vault3 -o jsonlines | jq -r '.vaultState') "closed" "vault3 is closed"
#test_val $(agoric follow -l -F :published.vaultFactory.managers.manager0.vaults.vault3 -o jsonlines | jq -r '.locked.value') "0" "vault3 contains no collateral"
#test_val $(agoric follow -l -F :published.vaultFactory.managers.manager0.vaults.vault3 -o jsonlines | jq -r '.debtSnapshot.debt.value') "0" "vault3 has no debt"

# CWD is agoric-sdk
upgrade11=./upgrade-test-scripts/agoric-upgrade-11


echo $upgrade11

$upgrade11/more_actions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"consume": {
"vatAdminSvc": true,
"vaultFactoryKit": true,
"chainStorage": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// to turn on ts-check:
/* global E */

// import { E } from "@endo/far";

const GOV_BUNDLE_ID = 'b1-';
const VAULTS_BUNDLE_ID = 'b1-';

console.info('Vaults upgrade: evaluating script');

/*
* Test an upgrade of the VaultFactory and its governing contract.
*/
const upgradeVaultFactory = async powers => {
console.info('upgrade vaultFactory');
const {
consume: {
chainStorage,
vatAdminSvc,
vaultFactoryKit: {
governorAdminFacet,
adminFacet: vaultsAdminFacet,
instance,
privateArgs,
},
},
} = powers;

const newGovernorBundleCap = await E(vatAdminSvc).getBundleCap(GOV_BUNDLE_ID);
const newVaultsBundleCap = await E(vatAdminSvc).getBundleCap(
VAULTS_BUNDLE_ID,
);

// consider modifying privateArgs.
await E(governorAdminFacet).upgrade(newGovernorBundleCap, {});

// not write, but perhaps visible?
const storageNode = await E(chainStorage).makeChildNode('vaults');
await E(vaultsAdminFacet).upgrade(newVaultsBundleCap, { storageNode });
};

upgradeVaultFactory;
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/bin/bash

. ./upgrade-test-scripts/env_setup.sh

# Enable debugging
set -x

# CWD is agoric-sdk
upgrade11=./upgrade-test-scripts/agoric-upgrade-11

yarn --silent bundle-source --cache-json /tmp packages/governance/src/contractGovernor.js contractGovernor-upgrade
yarn --silent bundle-source --cache-json /tmp packages/inter-protocol/src/psm/psm.js psm-upgrade
yarn --silent bundle-source --cache-json /tmp packages/inter-protocol/src/reserve/reserve.js reserve-upgrade
yarn --silent bundle-source --cache-json /tmp packages/inter-protocol/src/auction/auctioneer.js auctioneer-upgrade
yarn --silent bundle-source --cache-json /tmp packages/inter-protocol/src/vaultFactory/vaultFactory.js vaultFactory-upgrade

# fluxAggregator, smartWallet

# Start by upgrading the governance facet, which will do a null upgrad on the
# contract, and then upgrade the contract itself.


GOV_HASH=`jq -r .endoZipBase64Sha512 /tmp/bundle-contractGovernor-upgrade.json
echo bundle-contractGovernor-upgrade.json $GOV_HASH
PSM_HASH=`jq -r .endoZipBase64Sha512 /tmp/bundle-psm-upgrade.json
echo bundle-psm-upgrade.json $PSM_HASH
RESERVE_HASH=`jq -r .endoZipBase64Sha512 /tmp/bundle-reserve-upgrade.json
echo bundle-reserve-upgrade.json $RESERVE_HASH
AUCTIONEER_HASH=`jq -r .endoZipBase64Sha512 /tmp/bundle-auctioneer-upgrade.json
echo bundle-auctioneer-upgrade.json $AUCTIONEER_HASH
VAULTS_HASH=`jq -r .endozipbase64sha512 /tmp/bundle-vaultfactory-upgrade.json
echo bundle-vaultfactory-upgrade.json $vaults_hash
# grep for hashes in upgrade scripts
echo +++++ install bundles +++++
for f in /tmp/bundle-[a-v]*-upgrade.json; do
echo installing $f
agd tx swingset install-bundle "@$f" \
--from gov1 --keyring-backend=test --gas=auto \
--chain-id=agoriclocal -bblock --yes
done
# upgrade the vaultFactory's governing contract, then upgrade the vaultFactory
# itself.
echo +++++ upgrade VaultFactory +++++
$upgrade11/zoe-full-upgrade/zcf-upgrade-driver.sh
69 changes: 36 additions & 33 deletions packages/governance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ the Electorate is a required parameter in all governed contracts. Invitations
are an unusual kind of managed parameter. Most parameters are copy-objects that
don't carry any power. Since invitations convey rights, only the
invitation's amount appears in `terms`. The actual invitation must
be passed to the contract using `privateArg`. This combination makes it
be passed to the contract using `privateArgs`. This combination makes it
possible for clients to see what the invitation is for, but only the contract
has the ability to exercise it. Similarly, when there will be a vote to change
the Electorate (or any other Invitation-valued parameter), observers can see the
Expand All @@ -166,36 +166,46 @@ exercised if/when the vote is successful.
### ParamManager

`ContractGovernor` expects to work with contracts that use `ParamManager` to
manage their parameters. `makeParamManager()` is designed to be called
within the managed contract so that internal access to the parameter values is
manage their parameters. In order to support upgrade, all governed contracts
will be durable, upgradeable contracts. When using the `contractHelper`, the way
to create a paramManager is to call `handleParamGovernance` from within the
governed contract so that internal access to the parameter values is
synchronous. A separate facet allows visible management of changes to the
parameter values.

`makeParamManager(zoe)` makes a ParamManager:
`handleParamGovernance(zcf, invitation, paramType, ...)` makes a ParamManager:

```javascript
const paramManager = await makeParamManager(
const facetHelpers = await handleParamGovernance(
zcf,
invitation,
{
'MyChangeableNumber': ['nat', startingValue],
'ContractElectorate': ['invitation', initialPoserInvitation],
},
zcf.getZoeService(),
makeRecorderKit,
storageNode,
);

paramManager.getMyChangeableNumber() === startingValue;
paramManager.updatetMyChangeableNumber((newValue);
paramManager.getMyChangeableNumber() === newValue;
const { publicMixin, publicMixinGuards } = facetHelpers;
const { augmentPublicFacet, makeGovernorFacet, params } = facetHelpers;
```

If you don't need any parameters that depend on the Zoe service, there's
an alternative function that returns synchronously:
```javascript
const paramManager = await makeParamManagerSync(
{
'Collateral': ['brand', drachmaBrand],
},
);
```
`augmentPublicFacet` is a function that can be applied to a `publicFacet` to
produce a publicFacet that also includes accessors for all the defined
parameters as well as `getParamDescriptions` and `getPublicTopics`.

Similarly, `makeGovernorFacet` can be applied to the `creatorFacet` to create
the facet that the contractGovernor will use as well as the
`limitedCreatorFacet` that can be handed out to those outside of governance who
should have access to the creator functionality of the governed contract.

`makeRecorderKit` and `storageNode` are provided to paramGovernance so it can
publish the original values and any updates to governed values to the off-chain
storage. `makeRecorderKit` is a function that creates a durable recorderKit.
Since durable constructors must be defined exactly once per vat, and
recorderKits will be needed elsewhere in the contract, it has to be passed in.
`storageNode` is the node where governance will be able to write updates.

See [ParamTypes definition](./src/constants.js) for all supported types. More
types will be supported as we learn what contracts need to manage. (If you find
Expand All @@ -212,7 +222,7 @@ the methods to be called.
### Governed Contracts

`contractHelper` provides support for the vast majority of expected clients that
will have a single set of parameters to manage. A contract only has to define
will have a single set of parameters to manage. A contract only has to declare
the parameters (including `CONTRACT_ELECTORATE`) in a call to
`handleParamGovernance()`, and add any needed methods to the public and creator
facets. This will
Expand All @@ -222,16 +232,10 @@ facets. This will
It's convenient for the contract to export a function (e.g. `makeParamTerms`)
for the use of those starting up the contract to insert in the `terms`. They
would otherwise need to write boilerplate functions to declare all the required
parameters.
When a governed contract starts up, it should get the parameter declarations
from `terms`, use them to create a paramManager, and pass that to
`handleParamGovernance`. `handleParamGovernance()` returns functions
(`augmentPublicFacet()` and `makeGovernorFacet()`) that add
required methods to the public and creator facets. Since the governed contract
uses the values passed in `terms` to create the paramManager, reviewers of the
contract can verify that all and only the declared parameters are under the
control of the paramManager and made visible to the contract's clients.
parameters. Since the governed contract uses the values passed in `terms` to
create the paramManager, reviewers of the contract can verify that all and only
the declared parameters are under the control of the paramManager and made
visible to the contract's clients.

Governed methods and parameters must be included in terms.

Expand All @@ -249,10 +253,9 @@ Governed methods and parameters must be included in terms.
```

When a contract is written without benefit of `contractHelper`, it is
responsible for adding `getSubscription`, and
`getGovernedParams` to its `PublicFacet`, and for adding
`getParamMgrRetriever`, `getInvitation` and `getLimitedCreatorFacet` to its
`CreatorFacet`.
responsible for adding `getParamDescriptions`, and `getPublicTopics` to its
`PublicFacet`, and for adding `getParamMgrRetriever`, `getInvitation` and
`getLimitedCreatorFacet` to its `CreatorFacet`.

## Scenarios

Expand Down
Loading

0 comments on commit 8f9233f

Please sign in to comment.