Skip to content

Commit

Permalink
feat: add null upgrade of vaultFactoryGovernor to upgrade-vaults.js (#…
Browse files Browse the repository at this point in the history
…9953)

refs: #5200

## Description

Do a null upgrade of the vaultFactory governor contract in the coreEval  that upgrades the vaultFactory contract.

### Security Considerations

If the governor is correctly connected to the contract (tests in process), and can govern parameters and enable offer filters, then everything is connected as it was, and there are no new security issues.

### Scaling Considerations

None.

### Documentation Considerations

No user-visible changes.

### Testing Considerations

Test governance of the vaultFactory thoroughly on a testnet.

### Upgrade Considerations

It appears that contract governor may be upgradeable..
  • Loading branch information
mergify[bot] committed Aug 24, 2024
2 parents a9dd2ce + 6010064 commit 7296b47
Show file tree
Hide file tree
Showing 8 changed files with 627 additions and 8 deletions.
10 changes: 8 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,14 @@ typings/
# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity
# Yarn (https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored)
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

# dotenv environment variables file
.env
Expand Down
209 changes: 209 additions & 0 deletions a3p-integration/proposals/z:acceptance/lib/vaults.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/* eslint-disable @jessie.js/safe-await-separator */
/* eslint-env node */

import { strict as assert } from 'node:assert';

import {
addUser,
agops,
agoric,
ATOM_DENOM,
executeOffer,
GOV1ADDR,
GOV2ADDR,
GOV3ADDR,
provisionSmartWallet,
waitForBlock,
} from '@agoric/synthetic-chain';

const govAccounts = [GOV1ADDR, GOV2ADDR, GOV3ADDR];

export const ISTunit = 1_000_000n; // aka displayInfo: { decimalPlaces: 6 }

// XXX likely longer than necessary
const VOTING_WAIT_MS = 65 * 1000;

const proposeNewAuctionParams = async (
address: string,
startFequency: any,
clockStep: any,
priceLockPeriod: any,
) => {
const charterAcceptOfferId = await agops.ec(
'find-continuing-id',
'--for',
`${'charter\\ member\\ invitation'}`,
'--from',
address,
);

return executeOffer(
address,
agops.auctioneer(
'proposeParamChange',
'--charterAcceptOfferId',
charterAcceptOfferId,
'--start-frequency',
startFequency,
'--clock-step',
clockStep,
'--price-lock-period',
priceLockPeriod,
),
);
};

const voteForNewParams = (accounts: string[], position: number) => {
console.log('ACTIONS voting for position', position, 'using', accounts);
return Promise.all(
accounts.map((account: string) =>
agops.ec('vote', '--forPosition', position, '--send-from', account),
),
);
};

const paramChangeOfferGeneration = async (
previousOfferId: string,
voteDur: number,
debtLimit: number | bigint,
) => {
const voteDurSec = BigInt(voteDur);
const debtLimitValue = BigInt(debtLimit) * ISTunit;
const toSec = (ms: number) => BigInt(Math.round(ms / 1000));

const id = `propose-${Date.now()}`;
const deadline = toSec(Date.now()) + voteDurSec;

const zip = (xs: any[], ys: { [x: string]: any }) =>
xs.map((x: any, i: string | number) => [x, ys[i]]);
const fromSmallCapsEntries = (txt: string) => {
const { body, slots } = JSON.parse(txt);
const theEntries = zip(JSON.parse(body.slice(1)), slots).map(
([[name, ref], boardID]) => {
const iface = ref.replace(/^\$\d+\./, '');
return [name, { iface, boardID }];
},
);
return Object.fromEntries(theEntries);
};

const slots = [] as string[]; // XXX global mutable state
const smallCaps = {
Nat: (n: any) => `+${n}`,
// XXX mutates obj
ref: (obj: { ix: string; boardID: any; iface: any }) => {
if (obj.ix) return obj.ix;
const ix = slots.length;
slots.push(obj.boardID);
obj.ix = `$${ix}.Alleged: ${obj.iface}`;
return obj.ix;
},
};

const instance = fromSmallCapsEntries(
await agoric.follow('-lF', ':published.agoricNames.instance', '-o', 'text'),
);
assert(instance.VaultFactory);

const brand = fromSmallCapsEntries(
await agoric.follow('-lF', ':published.agoricNames.brand', '-o', 'text'),
);
assert(brand.IST);
assert(brand.ATOM);

const body = {
method: 'executeOffer',
offer: {
id,
invitationSpec: {
invitationMakerName: 'VoteOnParamChange',
previousOffer: previousOfferId,
source: 'continuing',
},
offerArgs: {
deadline: smallCaps.Nat(deadline),
instance: smallCaps.ref(instance.VaultFactory),
params: {
DebtLimit: {
brand: smallCaps.ref(brand.IST),
value: smallCaps.Nat(debtLimitValue),
},
},
path: {
paramPath: {
key: {
collateralBrand: smallCaps.ref(brand.ATOM),
},
},
},
},
proposal: {},
},
};

const capData = { body: `#${JSON.stringify(body)}`, slots };
return JSON.stringify(capData);
};
export const provisionWallet = async (user: string) => {
const userAddress = await addUser(user);

await provisionSmartWallet(
userAddress,
`20000000ubld,100000000${ATOM_DENOM}`,
);
await waitForBlock();
};

export const implementNewAuctionParams = async (
address: string,
oracles: { address: string; id: string }[],
startFequency: number,
clockStep: number,
priceLockPeriod: number,
) => {
await waitForBlock(3);

await proposeNewAuctionParams(
address,
startFequency,
clockStep,
priceLockPeriod,
);

console.log('ACTIONS voting for new auction params');
await voteForNewParams(govAccounts, 0);

console.log('ACTIONS wait for the vote deadline to pass');
await new Promise(r => setTimeout(r, VOTING_WAIT_MS));
};

export const proposeNewDebtCeiling = async (
address: string,
debtLimit: number | bigint,
) => {
const charterAcceptOfferId = await agops.ec(
'find-continuing-id',
'--for',
`${'charter\\ member\\ invitation'}`,
'--from',
address,
);

return executeOffer(
address,
paramChangeOfferGeneration(charterAcceptOfferId, 30, debtLimit),
);
};

export const setDebtLimit = async (
address: string,
debtLimit: number | bigint,
) => {
console.log('ACTIONS Setting debt limit');

await proposeNewDebtCeiling(address, debtLimit);
await voteForNewParams(govAccounts, 0);

console.log('ACTIONS wait for the vote to pass');
await new Promise(r => setTimeout(r, VOTING_WAIT_MS));
};
10 changes: 7 additions & 3 deletions a3p-integration/proposals/z:acceptance/package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"agoricProposal": {
"type": "/cosmos.params.v1beta1.ParameterChangeProposal"
"type": "/agoric.swingset.CoreEvalProposal"
},
"type": "module",
"license": "Apache-2.0",
"dependencies": {
"@agoric/synthetic-chain": "^0.1.0",
"ava": "^6.1.2"
"ava": "^6.1.2",
"tsx": "^4.17.0"
},
"ava": {
"concurrency": 1,
Expand All @@ -18,5 +19,8 @@
"scripts": {
"agops": "yarn --cwd /usr/src/agoric-sdk/ --silent agops"
},
"packageManager": "yarn@4.2.2"
"packageManager": "yarn@4.2.2",
"devDependencies": {
"typescript": "^5.5.4"
}
}
55 changes: 55 additions & 0 deletions a3p-integration/proposals/z:acceptance/scripts/test-vaults.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env tsx
/* eslint-disable @jessie.js/safe-await-separator */

import {
agoric,
GOV1ADDR,
GOV2ADDR,
newOfferId,
} from '@agoric/synthetic-chain';
import assert from 'node:assert/strict';
import {
implementNewAuctionParams,
ISTunit,
provisionWallet,
setDebtLimit,
} from '../lib/vaults.mjs';

const START_FREQUENCY = 600; // StartFrequency: 600s (auction runs every 10m)
const CLOCK_STEP = 20; // ClockStep: 20s (ensures auction completes in time)
const PRICE_LOCK_PERIOD = 300;
const oraclesAddresses = [GOV1ADDR, GOV2ADDR];

const oracles = [] as { address: string; id: string }[];
for (const oracle of oraclesAddresses) {
const offerId = await newOfferId();
oracles.push({ address: oracle, id: offerId });
}

console.log('Ensure user2 provisioned');
await provisionWallet('user2');

console.log('Ensure auction params have changed');
await implementNewAuctionParams(
GOV1ADDR,
oracles,
START_FREQUENCY,
CLOCK_STEP,
PRICE_LOCK_PERIOD,
);

const govParams = await agoric.follow('-lF', ':published.auction.governance');
assert.equal(govParams.current.ClockStep.value.relValue, CLOCK_STEP.toString());
assert.equal(
govParams.current.StartFrequency.value.relValue,
START_FREQUENCY.toString(),
);

console.log('Ensure debt ceiling changes');
const limit = 45_000_000n;
await setDebtLimit(GOV1ADDR, limit);
const params = await agoric.follow(
'-lF',
':published.vaultFactory.managers.manager0.governance',
);
assert.equal(params.current.DebtLimit.value.value, String(limit * ISTunit));
5 changes: 4 additions & 1 deletion a3p-integration/proposals/z:acceptance/test.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/bash

set -ueo pipefail
# Place here any test that should be executed using the executed proposal.
# The effects of this step are not persisted in further proposal layers.

Expand All @@ -10,6 +10,9 @@ yarn ava initial.test.js
GLOBIGNORE=initial.test.js
yarn ava ./*.test.js

npm install -g tsx
scripts/test-vaults.mts

./create-kread-item-test.sh

./state-sync-snapshots-test.sh
Expand Down
7 changes: 5 additions & 2 deletions a3p-integration/proposals/z:acceptance/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
"compilerOptions": {
"noEmit": true,
"target": "esnext",
"module": "esnext",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"allowJs": true,
"checkJs": true,
"strict": false,
"strictNullChecks": true,
"noImplicitThis": true
"noImplicitThis": true,
// XXX synthetic-chain has some errors
"skipLibCheck": true,
}
}
Loading

0 comments on commit 7296b47

Please sign in to comment.