Skip to content

Commit 8cc7122

Browse files
feat: add upgrade-15 (proposal 74) (#157)
Co-Authored-By: 0xPatrick <[email protected]> Co-authored-by: Richard Gibson <[email protected]>
1 parent 7646a5c commit 8cc7122

File tree

10 files changed

+2515
-0
lines changed

10 files changed

+2515
-0
lines changed

proposals/74:upgrade-15/.yarnrc.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nodeLinker: node-modules

proposals/74:upgrade-15/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Proposal to upgrade the chain software to upgrade-15
2+
3+
This software upgrade executes core proposals during the upgrade block, as
4+
defined by the `agoric-upgrade-15` upgrade handler. See `upgrade15Handler` in
5+
`agoric-sdk/golang/cosmos/app/app.go`.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import test from 'ava';
2+
import { $ } from 'execa';
3+
import { execFileSync } from 'node:child_process';
4+
import { makeAgd, waitForBlock } from '@agoric/synthetic-chain';
5+
6+
const offerId = 'bad-invitation-15'; // cf. prepare.sh
7+
const from = 'gov1';
8+
9+
test('exitOffer tool reclaims stuck payment', async t => {
10+
const showAndExec = (file, args, opts) => {
11+
console.log('$', file, ...args);
12+
return execFileSync(file, args, opts);
13+
};
14+
15+
// @ts-expect-error string is not assignable to Buffer
16+
const agd = makeAgd({ execFileSync: showAndExec }).withOpts({
17+
keyringBackend: 'test',
18+
});
19+
20+
const addr = await agd.lookup(from);
21+
t.log(from, 'addr', addr);
22+
23+
const getBalance = async target => {
24+
const { balances } = await agd.query(['bank', 'balances', addr]);
25+
const { amount } = balances.find(({ denom }) => denom === target);
26+
return Number(amount);
27+
};
28+
29+
const before = await getBalance('uist');
30+
t.log('uist balance before:', before);
31+
32+
await $`node ./exitOffer.js --id ${offerId} --from ${from}`;
33+
34+
await waitForBlock(2);
35+
const after = await getBalance('uist');
36+
t.log('uist balance after:', after);
37+
t.true(after > before);
38+
});

proposals/74:upgrade-15/exitOffer.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Note: limit imports to node modules for portability
2+
import { parseArgs, promisify } from 'node:util';
3+
import { execFile } from 'node:child_process';
4+
import { writeFile, mkdtemp, rm } from 'node:fs/promises';
5+
import { join } from 'node:path';
6+
7+
const options = /** @type {const} */ ({
8+
id: { type: 'string' },
9+
from: { type: 'string' },
10+
bin: { type: 'string', default: '/usr/src/agoric-sdk/node_modules/.bin' },
11+
});
12+
13+
const Usage = `
14+
Try to exit an offer, reclaiming any associated payments.
15+
16+
node exitOffer.js --id ID --from FROM [--bin PATH]
17+
18+
Options:
19+
--id <offer id>
20+
--from <address or key name>
21+
22+
--bin <path to agoric and agd> default: ${options.bin.default}
23+
`;
24+
25+
const badUsage = () => {
26+
const reason = new Error(Usage);
27+
reason.name = 'USAGE';
28+
throw reason;
29+
};
30+
31+
const { stringify: q } = JSON;
32+
// limited to JSON data: no remotables/promises; no undefined.
33+
const toCapData = data => ({ body: `#${q(data)}`, slots: [] });
34+
35+
const { entries } = Object;
36+
/**
37+
* @param {Record<string, string>} obj - e.g. { color: 'blue' }
38+
* @returns {string[]} - e.g. ['--color', 'blue']
39+
*/
40+
const flags = obj =>
41+
entries(obj)
42+
.map(([k, v]) => [`--${k}`, v])
43+
.flat();
44+
45+
const execP = promisify(execFile);
46+
47+
const showAndRun = (file, args) => {
48+
console.log('$', file, ...args);
49+
return execP(file, args);
50+
};
51+
52+
const withTempFile = async (tail, fn) => {
53+
const tmpDir = await mkdtemp('offers-');
54+
const tmpFile = join(tmpDir, tail);
55+
try {
56+
const result = await fn(tmpFile);
57+
return result;
58+
} finally {
59+
await rm(tmpDir, { recursive: true, force: true }).catch(err =>
60+
console.error(err),
61+
);
62+
}
63+
};
64+
65+
const doAction = async (action, from) => {
66+
await withTempFile('offer.json', async tmpOffer => {
67+
await writeFile(tmpOffer, q(toCapData(action)));
68+
69+
const out = await showAndRun('agoric', [
70+
'wallet',
71+
...flags({ 'keyring-backend': 'test' }),
72+
'send',
73+
...flags({ offer: tmpOffer, from }),
74+
]);
75+
return out.stdout;
76+
});
77+
};
78+
79+
const main = async (argv, env) => {
80+
const { values } = parseArgs({ args: argv.slice(2), options });
81+
const { id: offerId, from, bin } = values;
82+
(offerId && from) || badUsage();
83+
84+
env.PATH = `${bin}:${env.PATH}`;
85+
const action = { method: 'tryExitOffer', offerId };
86+
const out = await doAction(action, from);
87+
console.log(out);
88+
};
89+
90+
main(process.argv, process.env).catch(e => {
91+
if (e.name === 'USAGE' || e.code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION') {
92+
console.error(e.message);
93+
} else {
94+
console.error(e);
95+
}
96+
process.exit(1);
97+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import test from 'ava';
2+
3+
import { getVatDetails } from '@agoric/synthetic-chain';
4+
5+
const vats = {
6+
walletFactory: { incarnation: 3 },
7+
zoe: { incarnation: 1 },
8+
};
9+
10+
test(`vat details`, async t => {
11+
await null;
12+
for (const [vatName, expected] of Object.entries(vats)) {
13+
const actual = await getVatDetails(vatName);
14+
t.like(actual, expected, `${vatName} details mismatch`);
15+
}
16+
});

proposals/74:upgrade-15/package.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"agoricProposal": {
3+
"releaseNotes": "https://github.com/Agoric/agoric-sdk/releases/tag/agoric-upgrade-15",
4+
"sdkImageTag": "43",
5+
"planName": "agoric-upgrade-15",
6+
"upgradeInfo": {
7+
"binaries": {
8+
"any": "https://github.com/Agoric/agoric-sdk/archive/734e8635002e01b3e27477e93998dda942c7fae8.zip//agoric-sdk-734e8635002e01b3e27477e93998dda942c7fae8?checksum=sha256:2d8ace2ab8b3f998336c63847925de63fa9801bc3b5219129d1b193e2b12270a"
9+
},
10+
"source": "https://github.com/Agoric/agoric-sdk/archive/734e8635002e01b3e27477e93998dda942c7fae8.zip?checksum=sha256:2d8ace2ab8b3f998336c63847925de63fa9801bc3b5219129d1b193e2b12270a"
11+
},
12+
"type": "Software Upgrade Proposal"
13+
},
14+
"type": "module",
15+
"license": "Apache-2.0",
16+
"dependencies": {
17+
"@agoric/synthetic-chain": "^0.1.0",
18+
"ava": "^5.3.1"
19+
},
20+
"ava": {
21+
"concurrency": 1,
22+
"serial": true
23+
},
24+
"scripts": {
25+
"agops": "yarn --cwd /usr/src/agoric-sdk/ --silent agops"
26+
},
27+
"packageManager": "[email protected]"
28+
}

proposals/74:upgrade-15/prepare.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/bin/bash
2+
3+
# Exit when any command fails
4+
set -uxeo pipefail
5+
6+
# Place here any actions that should happen before the upgrade is proposed. The
7+
# actions are executed in the previous chain software, and the effects are
8+
# persisted so they can be used in the steps after the upgrade is complete,
9+
# such as in the "use" or "test" steps, or further proposal layers.
10+
11+
printISTBalance() {
12+
addr=$(agd keys show -a "$1" --keyring-backend=test)
13+
agd query bank balances "$addr" -o json \
14+
| jq -c '.balances[] | select(.denom=="uist")'
15+
16+
}
17+
18+
echo TEST: Offer with bad invitation
19+
printISTBalance gov1
20+
21+
badInvitationOffer=$(mktemp)
22+
cat > "$badInvitationOffer" << 'EOF'
23+
{"body":"#{\"method\":\"executeOffer\",\"offer\":{\"id\":\"bad-invitation-15\",\"invitationSpec\":{\"callPipe\":[[\"badMethodName\"]],\"instancePath\":[\"reserve\"],\"source\":\"agoricContract\"},\"proposal\":{\"give\":{\"Collateral\":{\"brand\":\"$0.Alleged: IST brand\",\"value\":\"+15000\"}}}}}","slots":["board0257"]}
24+
EOF
25+
26+
PATH=/usr/src/agoric-sdk/node_modules/.bin:$PATH
27+
agops perf satisfaction --keyring-backend=test send --executeOffer "$badInvitationOffer" --from gov1 || true
28+
29+
printISTBalance gov1

proposals/74:upgrade-15/test.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/bash
2+
3+
# Place here any test that should be executed using the executed proposal.
4+
# The effects of this step are not persisted in further proposal layers.
5+
6+
yarn ava

proposals/74:upgrade-15/tsconfig.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"compilerOptions": {
3+
"target": "esnext",
4+
"module": "esnext",
5+
"moduleResolution": "node",
6+
"allowJs": true,
7+
"checkJs": true,
8+
"strict": false,
9+
"strictNullChecks": true,
10+
"noImplicitThis": true
11+
}
12+
}

0 commit comments

Comments
 (0)