Skip to content

Commit

Permalink
feat(cli): psm governance
Browse files Browse the repository at this point in the history
  • Loading branch information
turadg committed Sep 15, 2022
1 parent 1ef4970 commit 19cf0fb
Show file tree
Hide file tree
Showing 4 changed files with 263 additions and 8 deletions.
182 changes: 174 additions & 8 deletions packages/agoric-cli/src/commands/psm.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ import {

const last = xs => xs[xs.length - 1];

function collectValues(val, memo) {
memo.push(val);
return memo;
}

const { vstorage, fromBoard, agoricNames } = await makeRpcUtils({ fetch });

/**
Expand Down Expand Up @@ -81,7 +86,7 @@ export const makePsmCommand = async logger => {

const marshaller = boardSlottingMarshaller();

const lookupInstance = ([minted, anchor]) => {
const lookupPsmInstance = ([minted, anchor]) => {
const name = `psm-${minted}-${anchor}`;
const instance = agoricNames.instance[name];
if (!instance) {
Expand Down Expand Up @@ -113,6 +118,7 @@ export const makePsmCommand = async logger => {
psm
.command('info')
.description('show governance info about the PSM (BROKEN)')
// TODO DRY with https://github.com/Agoric/agoric-sdk/issues/6181
.requiredOption(
'--pair [Minted.Anchor]',
'token pair (Minted.Anchor)',
Expand Down Expand Up @@ -155,24 +161,59 @@ export const makePsmCommand = async logger => {
.action(async function () {
const opts = this.opts();
console.warn('running with options', opts);
const instance = await lookupInstance(opts.pair);
const instance = await lookupPsmInstance(opts.pair);
// @ts-expect-error RpcRemote types not real instances
const spendAction = makePSMSpendAction(instance, agoricNames.brand, opts);
outputAction(spendAction);
});

psm
.command('vote')
.description('prepare an offer to vote')
.option('--offerId [number]', 'Offer id', String(Date.now()))
.action(function () {
.command('committee')
.description('join the economic committee')
.option('--offerId [number]', 'Offer id', Number, Date.now())
.action(async function () {
const opts = this.opts();

const { economicCommittee } = agoricNames.instance;
assert(economicCommittee, 'missing economicCommittee');

/** @type {import('../lib/psm.js').OfferSpec} */
const offer = {
id: Number(opts.offerId),
invitationSpec: {
source: 'purse',
// @ts-expect-error rpc
instance: economicCommittee,
description: 'Voter0', // XXX it may not always be
},
proposal: {},
};

outputAction({
method: 'executeOffer',
offer,
});

console.warn('Now execute the prepared offer');
});

psm
.command('charter')
.description('prepare an offer to accept the charter invitation')
.option('--offerId [number]', 'Offer id', Number, Date.now())
.action(async function () {
const opts = this.opts();

const { psmCharter } = agoricNames.instance;
assert(psmCharter, 'missing psmCharter');

/** @type {import('../lib/psm.js').OfferSpec} */
const offer = {
id: opts.offerId,
id: Number(opts.offerId),
invitationSpec: {
source: 'purse',
instance: opts.instance,
// @ts-expect-error rpc
instance: psmCharter,
description: 'PSM charter member invitation',
},
proposal: {},
Expand All @@ -182,6 +223,131 @@ export const makePsmCommand = async logger => {
method: 'executeOffer',
offer,
});

console.warn('Now execute the prepared offer');
});

psm
.command('proposePauseOffers')
.description('propose a vote')
.option('--offerId [number]', 'Offer id', Number, Date.now())
.requiredOption(
'--pair [Minted.Anchor]',
'token pair (Minted.Anchor)',
s => s.split('.'),
['IST', 'AUSD'],
)
.requiredOption(
'--previousOfferId [number]',
'offer that had continuing invitation result',
Number,
)
.requiredOption(
'--substring [string]',
'an offer string to pause (can be repeated)',
collectValues,
[],
)
.option(
'--deadline [minutes]',
'minutes from now to close the vote',
Number,
1,
)
.action(async function () {
const opts = this.opts();

const psmInstance = lookupPsmInstance(opts.pair);

/** @type {import('../lib/psm.js').OfferSpec} */
const offer = {
id: Number(opts.offerId),
invitationSpec: {
source: 'continuing',
previousOffer: opts.previousOfferId,
invitationMakerName: 'VoteOnPauseOffers',
// ( instance, strings list, timer deadline seconds )
invitationArgs: harden([
psmInstance,
opts.substring,
BigInt(opts.deadline * 60 + Math.round(Date.now() / 1000)),
]),
},
proposal: {},
};

outputAction({
method: 'executeOffer',
offer,
});

console.warn('Now execute the prepared offer');
});

psm
.command('vote')
.description('vote on a question (hard-coded for now))')
.option('--offerId [number]', 'Offer id', Number, Date.now())
.requiredOption(
'--previousOfferId [number]',
'offer that had continuing invitation result',
Number,
)
.requiredOption(
'--pair [Minted.Anchor]',
'token pair (Minted.Anchor)',
s => s.split('.'),
['IST', 'AUSD'],
)
.requiredOption(
'--forPosition [number]',
'index of one position to vote for (within the question description.positions); ',
Number,
)
.action(async function () {
const opts = this.opts();

const questionHandleCapDataStr = await vstorage.read(
'published.committees.Initial_Economic_Committee.latestQuestion',
);
const questionDescriptions = storageHelper.unserialize(
questionHandleCapDataStr,
fromBoard,
);

assert(questionDescriptions, 'missing questionDescriptions');
assert(
questionDescriptions.length === 1,
'multiple questions not supported',
);

const questionDesc = questionDescriptions[0];
// TODO support multiple position arguments
const chosenPositions = [questionDesc.positions[opts.forPosition]];
assert(chosenPositions, `undefined position index ${opts.forPosition}`);

/** @type {import('../lib/psm.js').OfferSpec} */
const offer = {
id: Number(opts.offerId),
invitationSpec: {
source: 'continuing',
previousOffer: opts.previousOfferId,
invitationMakerName: 'makeVoteInvitation',
// (positionList, questionHandle)
invitationArgs: harden([
chosenPositions,
questionDesc.questionHandle,
]),
},
proposal: {},
};

outputAction({
method: 'executeOffer',
offer,
});

console.warn('Now execute the prepared offer');
});

return psm;
Expand Down
1 change: 1 addition & 0 deletions packages/agoric-cli/src/lib/psm.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const simpleOffers = (state, agoricNames) => {
payouts,
} = o;
const entry = Object.entries(agoricNames.instance).find(
// @ts-expect-error xxx RpcRemote
([_name, candidate]) => candidate === instance,
);
const instanceName = entry ? entry[0] : '???';
Expand Down
88 changes: 88 additions & 0 deletions packages/agoric-cli/test/agops-governance-smoketest.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/bin/sh

if [ -z "$AGORIC_NET" ]; then
echo "AGORIC_NET env not set"
echo
echo "e.g. AGORIC_NET=ollinet (or export to save typing it each time)"
echo
echo "To test locally, AGORIC_NET=local and have the following running:
# freshen sdk
cd agoric-sdk
yarn install && yarn build
# (new tab)
# Start the chain
cd packages/cosmic-swingset
make scenario2-setup scenario2-run-chain-psm
# (new tab)
cd packages/cosmic-swingset
# Fund the pool
make fund-provision-pool
# Copy the agoric address from your keplr wallet or 'agd keys list', starts with 'agoric1'
KEY=<yoursBech32>
# Provision your wallet
make ACCT_ADDR=$KEY AGORIC_POWERS=SMART_WALLET fund-acct provision-acct
# verify
agoric wallet list
agoric wallet show --from \$KEY
"
exit 1
fi

KEY=$1

if [ -z "$KEY" ]; then
echo "USAGE: $0 key"
echo "You can reference by name: agd keys list"
echo "Make sure it has been provisioned by the faucet: https://$AGORIC_NET.faucet.agoric.net/"
echo "and that it's the sole member of economicCommitteeAddresses in decentral-psm-config.json"
exit 1
fi

set -x

# NB: fee percentages must be at least the governed param values

# Accept invitation to economic committee
COMMITTEE_OFFER=$(mktemp -t agops.XXX)
bin/agops psm committee >|"$COMMITTEE_OFFER"
jq ".body | fromjson" <"$COMMITTEE_OFFER"
agoric wallet send --from "$KEY" --offer "$COMMITTEE_OFFER"
COMMITTEE_OFFER_ID=$(jq ".body | fromjson | .offer.id" <"$COMMITTEE_OFFER")

# Accept invitation to be a charter member
CHARTER_OFFER=$(mktemp -t agops.XXX)
bin/agops psm charter >|"$CHARTER_OFFER"
jq ".body | fromjson" <"$CHARTER_OFFER"
agoric wallet send --from "$KEY" --offer "$CHARTER_OFFER"
CHARTER_OFFER_ID=$(jq ".body | fromjson | .offer.id" <"$CHARTER_OFFER")

### Now we have the continuing invitationMakers saved in the wallet

# Use invitation result, with continuing invitationMakers to propose a vote
PROPOSAL_OFFER=$(mktemp -t agops.XXX)
bin/agops psm proposePauseOffers --substring wantMinted --previousOfferId "$CHARTER_OFFER_ID" >|"$PROPOSAL_OFFER"
jq ".body | fromjson" <"$PROPOSAL_OFFER"
agoric wallet send --from "$KEY" --offer "$PROPOSAL_OFFER"

# vote on the question that was made
VOTE_OFFER=$(mktemp -t agops.XXX)
bin/agops psm vote --forPosition 0 --previousOfferId "$COMMITTEE_OFFER_ID" >|"$VOTE_OFFER"
jq ".body | fromjson" <"$VOTE_OFFER"
agoric wallet send --from "$KEY" --offer "$VOTE_OFFER"
## wait for the election to be resolved (1m in commands/psm.js)

# check that the dictatorial vote was executed
# TODO use vote outcome data https://github.com/Agoric/agoric-sdk/issues/6198
SWAP_OFFER=$(mktemp -t agops.XXX)
bin/agops psm swap --wantMinted 0.01 --feePct 0.01 >|"$SWAP_OFFER"
agoric wallet send --from "$KEY" --offer "$SWAP_OFFER"

# chain logs should read like:
# vat: v15: walletFactory: { wallet: Object [Alleged: SmartWallet self] {}, actionCapData: { body: '{"method":"executeOffer","offer":{"id":1663182246304,"invitationSpec":{"source":"contract","instance":{"@qclass":"slot","index":0},"publicInvitationMaker":"makeWantMintedInvitation"},"proposal":{"give":{"In":{"brand":{"@qclass":"slot","index":1},"value":{"@qclass":"bigint","digits":"10002"}}},"want":{"Out":{"brand":{"@qclass":"slot","index":2},"value":{"@qclass":"bigint","digits":"10000"}}}}}}', slots: [ 'board04312', 'board0223', 'board0639' ] } }
# vat: v15: wallet agoric109q3uc0xt8aavne94rgd6rfeucavrx924e0ztf starting executeOffer 1663182246304
# vat: v14: bank balance update { address: 'agoric109q3uc0xt8aavne94rgd6rfeucavrx924e0ztf', amount: '1121979996', denom: 'ibc/usdc1234' }
# ls: v6: Logging sent error stack (Error#1)
# ls: v6: Error#1: not accepting offer with description wantMinted
# ls: v6: Error: not accepting offer with description "wantMinted"
File renamed without changes.

0 comments on commit 19cf0fb

Please sign in to comment.