From d87e70c38f147f348ef3de8b1fd56be1400e2112 Mon Sep 17 00:00:00 2001 From: Fraz Arshad Date: Tue, 8 Oct 2024 17:42:50 +0500 Subject: [PATCH 01/56] test(a3p): create a3p test for replace electorate core eval --- .../proposals/n:upgrade-next/.gitignore | 1 + .../proposals/n:upgrade-next/acceptInvites.js | 19 ++ .../proposals/n:upgrade-next/addGov4.js | 20 ++ .../proposals/n:upgrade-next/agoric-tools.js | 33 +++ .../proposals/n:upgrade-next/package.json | 5 +- .../n:upgrade-next/replaceElectorate.test.js | 44 ++++ .../proposals/n:upgrade-next/test.sh | 2 + .../proposals/n:upgrade-next/use.sh | 7 + .../proposals/n:upgrade-next/yarn.lock | 246 ++++++++++++++---- 9 files changed, 331 insertions(+), 46 deletions(-) create mode 100644 a3p-integration/proposals/n:upgrade-next/acceptInvites.js create mode 100644 a3p-integration/proposals/n:upgrade-next/addGov4.js create mode 100644 a3p-integration/proposals/n:upgrade-next/agoric-tools.js create mode 100644 a3p-integration/proposals/n:upgrade-next/replaceElectorate.test.js create mode 100644 a3p-integration/proposals/n:upgrade-next/use.sh diff --git a/a3p-integration/proposals/n:upgrade-next/.gitignore b/a3p-integration/proposals/n:upgrade-next/.gitignore index da0da987d7b..10c30e2fde7 100644 --- a/a3p-integration/proposals/n:upgrade-next/.gitignore +++ b/a3p-integration/proposals/n:upgrade-next/.gitignore @@ -4,3 +4,4 @@ add-OLIVES/ upgrade-bank/ upgrade-provisionPool/ upgrade-orch-core/ +replace-electorate/ diff --git a/a3p-integration/proposals/n:upgrade-next/acceptInvites.js b/a3p-integration/proposals/n:upgrade-next/acceptInvites.js new file mode 100644 index 00000000000..72245bf6d3e --- /dev/null +++ b/a3p-integration/proposals/n:upgrade-next/acceptInvites.js @@ -0,0 +1,19 @@ +#!/usr/bin/env node +import '@endo/init'; +import { agops, GOV1ADDR, GOV2ADDR } from '@agoric/synthetic-chain'; +import { GOV4ADDR } from './agoric-tools.js'; + +// New GOV4 account to be added +const addresses = [GOV1ADDR, GOV2ADDR, GOV4ADDR]; + +await Promise.all( + addresses.map((addr, idx) => + agops.ec('committee', '--send-from', addr, '--voter', `${idx}`), + ), +); + +await Promise.all( + addresses.map(addr => + agops.ec('charter', '--send-from', addr, '--name', 'econCommitteeCharter'), + ), +); diff --git a/a3p-integration/proposals/n:upgrade-next/addGov4.js b/a3p-integration/proposals/n:upgrade-next/addGov4.js new file mode 100644 index 00000000000..d84ec7f5890 --- /dev/null +++ b/a3p-integration/proposals/n:upgrade-next/addGov4.js @@ -0,0 +1,20 @@ +import '@endo/init'; +import { execFileSync } from 'node:child_process'; +import { makeAgd } from './synthetic-chain-excerpt.js'; +import { GOV4ADDR } from './agoric-tools.js'; + +const agd = makeAgd({ execFileSync }).withOpts({ keyringBackend: 'test' }); + +agd.keys.add( + 'gov4', + 'smile unveil sketch gaze length bulb goddess street case exact table fetch robust chronic power choice endorse toward pledge dish access sad illegal dance', +); + +agd.tx( + ['swingset', 'provision-one', 'faucet_provision', GOV4ADDR, 'SMART_WALLET'], + { + chainId: 'agoriclocal', + from: 'validator', + yes: true, + }, +); diff --git a/a3p-integration/proposals/n:upgrade-next/agoric-tools.js b/a3p-integration/proposals/n:upgrade-next/agoric-tools.js new file mode 100644 index 00000000000..205fe47df4f --- /dev/null +++ b/a3p-integration/proposals/n:upgrade-next/agoric-tools.js @@ -0,0 +1,33 @@ +import { queryVstorage } from '@agoric/synthetic-chain'; +import { makeMarshal, Remotable } from '@endo/marshal'; + +export const GOV4ADDR = 'agoric1c9gyu460lu70rtcdp95vummd6032psmpdx7wdy'; + +const slotToRemotable = (_slotId, iface = 'Remotable') => + Remotable(iface, undefined, { + getBoardId: () => _slotId, + }); + +// /** @param {BoardRemote | object} val */ +const boardValToSlot = val => { + if ('getBoardId' in val) { + return val.getBoardId(); + } + throw Error(`unknown obj in boardSlottingMarshaller.valToSlot ${val}`); +}; + +const boardSlottingMarshaller = slotToVal => { + return makeMarshal(boardValToSlot, slotToVal, { + serializeBodyFormat: 'smallcaps', + }); +}; + +export const marshaller = boardSlottingMarshaller(slotToRemotable); + +export const queryVstorageFormatted = async (path, index = -1) => { + const data = await queryVstorage(path); + + const formattedData = JSON.parse(data.value); + const formattedDataAtIndex = JSON.parse(formattedData.values.at(index)); + return marshaller.fromCapData(formattedDataAtIndex); +}; diff --git a/a3p-integration/proposals/n:upgrade-next/package.json b/a3p-integration/proposals/n:upgrade-next/package.json index f88ce460a7e..465160effb6 100644 --- a/a3p-integration/proposals/n:upgrade-next/package.json +++ b/a3p-integration/proposals/n:upgrade-next/package.json @@ -11,7 +11,8 @@ "vats/upgrade-bank.js upgrade-bank", "vats/upgrade-provisionPool.js upgrade-provisionPool", "testing/add-LEMONS.js add-LEMONS", - "testing/add-OLIVES.js add-OLIVES" + "testing/add-OLIVES.js add-OLIVES", + "inter-protocol/replace-electorate-core.js replace-electorate A3P_INTEGRATION" ], "type": "Software Upgrade Proposal" }, @@ -19,6 +20,8 @@ "license": "Apache-2.0", "dependencies": { "@agoric/synthetic-chain": "^0.3.0", + "@endo/init": "^1.1.5", + "@endo/marshal": "^1.5.4", "ava": "^5.3.1", "better-sqlite3": "^9.6.0", "execa": "^9.3.1" diff --git a/a3p-integration/proposals/n:upgrade-next/replaceElectorate.test.js b/a3p-integration/proposals/n:upgrade-next/replaceElectorate.test.js new file mode 100644 index 00000000000..a38faaad217 --- /dev/null +++ b/a3p-integration/proposals/n:upgrade-next/replaceElectorate.test.js @@ -0,0 +1,44 @@ +import test from 'ava'; +import '@endo/init'; +import { GOV1ADDR, GOV2ADDR } from '@agoric/synthetic-chain'; +import { passStyleOf } from '@endo/marshal'; +import { GOV4ADDR, queryVstorageFormatted } from './agoric-tools.js'; + +const governanceAddresses = [GOV4ADDR, GOV2ADDR, GOV1ADDR]; + +test.serial('should be able to view the new accepted invitations', async t => { + const instance = await queryVstorageFormatted( + `published.agoricNames.instance`, + ); + const instances = Object.fromEntries(instance); + + const wallet = await queryVstorageFormatted( + `published.wallet.${GOV1ADDR}.current`, + ); + const usedInvitations = wallet.offerToUsedInvitation.map(v => v[1]); + + const totalCharterInvitations = usedInvitations.filter( + v => v.value[0].description === 'charter member invitation', + ).length; + + t.is(totalCharterInvitations, 2); + + const totalCommitteeInvitations = usedInvitations.filter(v => + v.value[0].description.startsWith('Voter'), + ).length; + t.is(totalCommitteeInvitations, 2); + + const charterInvitation = usedInvitations.find( + v => + v.value[0].instance.getBoardId() === + instances.econCommitteeCharter.getBoardId(), + ); + t.is(passStyleOf(charterInvitation), 'copyRecord'); + + const committeeInvitation = usedInvitations.find( + v => + v.value[0].instance.getBoardId() === + instances.economicCommittee.getBoardId(), + ); + t.is(passStyleOf(committeeInvitation), 'copyRecord'); +}); diff --git a/a3p-integration/proposals/n:upgrade-next/test.sh b/a3p-integration/proposals/n:upgrade-next/test.sh index 215f30f6aaf..56492d366f6 100755 --- a/a3p-integration/proposals/n:upgrade-next/test.sh +++ b/a3p-integration/proposals/n:upgrade-next/test.sh @@ -5,6 +5,8 @@ GLOBIGNORE=initial.test.js +yarn ava ./replaceElectorate.test.js + # test the state right after upgrade yarn ava initial.test.js diff --git a/a3p-integration/proposals/n:upgrade-next/use.sh b/a3p-integration/proposals/n:upgrade-next/use.sh new file mode 100644 index 00000000000..c68eaeafecc --- /dev/null +++ b/a3p-integration/proposals/n:upgrade-next/use.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Exit when any command fails +set -uxeo pipefail + +node ./addGov4 +./acceptInvites.js diff --git a/a3p-integration/proposals/n:upgrade-next/yarn.lock b/a3p-integration/proposals/n:upgrade-next/yarn.lock index 5de3eb1fd07..78dc133a0f9 100644 --- a/a3p-integration/proposals/n:upgrade-next/yarn.lock +++ b/a3p-integration/proposals/n:upgrade-next/yarn.lock @@ -20,6 +20,113 @@ __metadata: languageName: node linkType: hard +"@endo/base64@npm:^1.0.8": + version: 1.0.8 + resolution: "@endo/base64@npm:1.0.8" + checksum: 10c0/3501efbf866acc25b9ad0912ec2383e3b976c890a18dc67b5c6eb128433708db69e8ed1cc57190305266bdcbd132659aa87edfc6d02a9886b711e8b86adc21c0 + languageName: node + linkType: hard + +"@endo/common@npm:^1.2.6": + version: 1.2.6 + resolution: "@endo/common@npm:1.2.6" + dependencies: + "@endo/errors": "npm:^1.2.6" + "@endo/eventual-send": "npm:^1.2.6" + "@endo/promise-kit": "npm:^1.1.6" + checksum: 10c0/7cca372677bd1ab535a0f8fc250eca9bfc202ef7dfd24cac6ff1260003aa4512f8db18419dd632b3e57270a589fec54891496904ff2e8a97047f1e187e3f1401 + languageName: node + linkType: hard + +"@endo/env-options@npm:^1.1.7": + version: 1.1.7 + resolution: "@endo/env-options@npm:1.1.7" + checksum: 10c0/5784bd68790041b08d9ead4f6c29cc7871d2e554c23fc44fff38cb20b6b4e55cdba2f78d844ba5ad4b0818185c32475ff318c1b77890d628690d7c7a6ede9475 + languageName: node + linkType: hard + +"@endo/errors@npm:^1.2.6": + version: 1.2.6 + resolution: "@endo/errors@npm:1.2.6" + dependencies: + ses: "npm:^1.9.0" + checksum: 10c0/cbc541c2d26fbfeb1567dd1cdf0e1f110ee9866430c5a1b91d87270d6f00bc8293cc8512301217c4eda35e60677ce62a941d11eb32c7da1f72d450a011ad2bb5 + languageName: node + linkType: hard + +"@endo/eventual-send@npm:^1.2.6": + version: 1.2.6 + resolution: "@endo/eventual-send@npm:1.2.6" + dependencies: + "@endo/env-options": "npm:^1.1.7" + checksum: 10c0/6983d6b88bf4e99f6c469d4ca037793582b06cc0bfa2da085b5bc7ad67333a1fba6e5e7077b7f279be23ccfba1dff9e06c7f85f9980b09fd002227f89a673c11 + languageName: node + linkType: hard + +"@endo/init@npm:^1.1.5": + version: 1.1.5 + resolution: "@endo/init@npm:1.1.5" + dependencies: + "@endo/base64": "npm:^1.0.8" + "@endo/eventual-send": "npm:^1.2.6" + "@endo/lockdown": "npm:^1.0.11" + "@endo/promise-kit": "npm:^1.1.6" + checksum: 10c0/08abda8a0204450cb39c296270d074189f320cdeb03892e87b27a75f6b98c0b5d9c8471e242c53545843211fe713b01b281eec0eabc1c58ca0760a068b90335c + languageName: node + linkType: hard + +"@endo/lockdown@npm:^1.0.11": + version: 1.0.11 + resolution: "@endo/lockdown@npm:1.0.11" + dependencies: + ses: "npm:^1.9.0" + checksum: 10c0/03bb96f370e7ee69d9d8e26b7b124b6381994d3b28a99dc2bcafe77139283701d7543b40e978226cd49443c149e64610e4dd49c32ada931610ba091809478a11 + languageName: node + linkType: hard + +"@endo/marshal@npm:^1.5.4": + version: 1.5.4 + resolution: "@endo/marshal@npm:1.5.4" + dependencies: + "@endo/common": "npm:^1.2.6" + "@endo/errors": "npm:^1.2.6" + "@endo/eventual-send": "npm:^1.2.6" + "@endo/nat": "npm:^5.0.11" + "@endo/pass-style": "npm:^1.4.4" + "@endo/promise-kit": "npm:^1.1.6" + checksum: 10c0/4668a3678567cfbeefc3a47217912ca3f5d8bdb37e0d5d53339953b4ab83950683505f45c0f5c30b415c989bb9df4fa0859849d23b11f5b1064b0da0a13943ab + languageName: node + linkType: hard + +"@endo/nat@npm:^5.0.11": + version: 5.0.11 + resolution: "@endo/nat@npm:5.0.11" + checksum: 10c0/50cd9033657dd35288f0333a966984f788213f1c836e6772c0d731361d5854d0ce24b8a7d6f351d56618eb40f6b369e130c1ad939c83d5833246c09119b2e777 + languageName: node + linkType: hard + +"@endo/pass-style@npm:^1.4.4": + version: 1.4.4 + resolution: "@endo/pass-style@npm:1.4.4" + dependencies: + "@endo/env-options": "npm:^1.1.7" + "@endo/errors": "npm:^1.2.6" + "@endo/eventual-send": "npm:^1.2.6" + "@endo/promise-kit": "npm:^1.1.6" + "@fast-check/ava": "npm:^1.1.5" + checksum: 10c0/d6c6268b269d4c14541087ce6b2975f9e31847893d01360c0ea92392ae93316d94fbf59cd7299853a0fb5214176ad7ffc4616b9b2581fd720bdb55ef96d0beeb + languageName: node + linkType: hard + +"@endo/promise-kit@npm:^1.1.6": + version: 1.1.6 + resolution: "@endo/promise-kit@npm:1.1.6" + dependencies: + ses: "npm:^1.9.0" + checksum: 10c0/d60de663e58f9de32b6705268c62c63c4721f1874813d3c10cae7846e921e4ea8a60c8622ef8c937741a3387fbc60110076b25304afedf270727af7b0c3fecb3 + languageName: node + linkType: hard + "@endo/zip@npm:^1.0.7": version: 1.0.7 resolution: "@endo/zip@npm:1.0.7" @@ -27,6 +134,17 @@ __metadata: languageName: node linkType: hard +"@fast-check/ava@npm:^1.1.5": + version: 1.2.1 + resolution: "@fast-check/ava@npm:1.2.1" + dependencies: + fast-check: "npm:^3.0.0" + peerDependencies: + ava: ^4 || ^5 || ^6 + checksum: 10c0/3800098fd7e8098102544a2f7a595351d063a7ebaeca18ed4901df5ec2679da2330ba8c0db2c820721d4cbb3e23d817ba22fec6d058957930e229f44fa71a684 + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -119,18 +237,20 @@ __metadata: linkType: hard "acorn-walk@npm:^8.2.0": - version: 8.3.1 - resolution: "acorn-walk@npm:8.3.1" - checksum: 10c0/a23d2f7c6b6cad617f4c77f14dfeb062a239208d61753e9ba808d916c550add92b39535467d2e6028280761ac4f5a904cc9df21530b84d3f834e3edef74ddde5 + version: 8.3.4 + resolution: "acorn-walk@npm:8.3.4" + dependencies: + acorn: "npm:^8.11.0" + checksum: 10c0/76537ac5fb2c37a64560feaf3342023dadc086c46da57da363e64c6148dc21b57d49ace26f949e225063acb6fb441eabffd89f7a3066de5ad37ab3e328927c62 languageName: node linkType: hard -"acorn@npm:^8.8.2": - version: 8.11.2 - resolution: "acorn@npm:8.11.2" +"acorn@npm:^8.11.0, acorn@npm:^8.8.2": + version: 8.13.0 + resolution: "acorn@npm:8.13.0" bin: acorn: bin/acorn - checksum: 10c0/a3ed76c761b75ec54b1ec3068fb7f113a182e95aea7f322f65098c2958d232e3d211cb6dac35ff9c647024b63714bc528a26d54a925d1fef2c25585b4c8e4017 + checksum: 10c0/f35dd53d68177c90699f4c37d0bb205b8abe036d955d0eb011ddb7f14a81e6fd0f18893731c457c1b5bd96754683f4c3d80d9a5585ddecaa53cdf84e0b3d68f7 languageName: node linkType: hard @@ -317,9 +437,9 @@ __metadata: linkType: hard "binary-extensions@npm:^2.0.0": - version: 2.2.0 - resolution: "binary-extensions@npm:2.2.0" - checksum: 10c0/d73d8b897238a2d3ffa5f59c0241870043aa7471335e89ea5e1ff48edb7c2d0bb471517a3e4c5c3f4c043615caa2717b5f80a5e61e07503d51dc85cb848e665d + version: 2.3.0 + resolution: "binary-extensions@npm:2.3.0" + checksum: 10c0/75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5 languageName: node linkType: hard @@ -359,12 +479,12 @@ __metadata: languageName: node linkType: hard -"braces@npm:^3.0.2, braces@npm:~3.0.2": - version: 3.0.2 - resolution: "braces@npm:3.0.2" +"braces@npm:^3.0.3, braces@npm:~3.0.2": + version: 3.0.3 + resolution: "braces@npm:3.0.3" dependencies: - fill-range: "npm:^7.0.1" - checksum: 10c0/321b4d675791479293264019156ca322163f02dc06e3c4cab33bb15cd43d80b51efef69b0930cfde3acd63d126ebca24cd0544fa6f261e093a0fb41ab9dda381 + fill-range: "npm:^7.1.1" + checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 languageName: node linkType: hard @@ -399,9 +519,9 @@ __metadata: linkType: hard "callsites@npm:^4.0.0": - version: 4.1.0 - resolution: "callsites@npm:4.1.0" - checksum: 10c0/91700844127a6dcd4792d231a12dd8e9ec10525eb9962180a8558417d7e3f443e52a4f14746ad2838eaf14f79431ee1539d13bd188da280f720a06a91bd1157a + version: 4.2.0 + resolution: "callsites@npm:4.2.0" + checksum: 10c0/8f7e269ec09fc0946bb22d838a8bc7932e1909ab4a833b964749f4d0e8bdeaa1f253287c4f911f61781f09620b6925ccd19a5ea4897489c4e59442c660c312a3 languageName: node linkType: hard @@ -422,8 +542,8 @@ __metadata: linkType: hard "chokidar@npm:^3.5.3": - version: 3.5.3 - resolution: "chokidar@npm:3.5.3" + version: 3.6.0 + resolution: "chokidar@npm:3.6.0" dependencies: anymatch: "npm:~3.1.2" braces: "npm:~3.0.2" @@ -436,7 +556,7 @@ __metadata: dependenciesMeta: fsevents: optional: true - checksum: 10c0/1076953093e0707c882a92c66c0f56ba6187831aa51bb4de878c1fec59ae611a3bf02898f190efec8e77a086b8df61c2b2a3ea324642a0558bdf8ee6c5dc9ca1 + checksum: 10c0/8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462 languageName: node linkType: hard @@ -662,9 +782,9 @@ __metadata: linkType: hard "emittery@npm:^1.0.1": - version: 1.0.1 - resolution: "emittery@npm:1.0.1" - checksum: 10c0/2587f2f42bb5e004ba1cde61352d2151f4dd4f29eb79ad36f82e200da2faec9742d7bfca1492a024d60396e001e4b07d9b2b9c43be33547ff751ba8ff87c42ce + version: 1.0.3 + resolution: "emittery@npm:1.0.3" + checksum: 10c0/91605d044f3891dd1f8ab731aeb94b520488b21e707f7064dcbcf5303bac3b4e7133dfa23c343ede1fc970340bd78a9b1aed522b805bc15104606bba630dd71e languageName: node linkType: hard @@ -715,9 +835,9 @@ __metadata: linkType: hard "escalade@npm:^3.1.1": - version: 3.1.1 - resolution: "escalade@npm:3.1.1" - checksum: 10c0/afd02e6ca91ffa813e1108b5e7756566173d6bc0d1eb951cb44d6b21702ec17c1cf116cfe75d4a2b02e05acb0b808a7a9387d0d1ca5cf9c04ad03a8445c3e46d + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 languageName: node linkType: hard @@ -786,6 +906,15 @@ __metadata: languageName: node linkType: hard +"fast-check@npm:^3.0.0": + version: 3.22.0 + resolution: "fast-check@npm:3.22.0" + dependencies: + pure-rand: "npm:^6.1.0" + checksum: 10c0/15c70f83df655f9bdcec4f8ed7e3207a933bf6e22c220a4fafc7ea17a28a009bc5d0a85588689f5726e3514796745cb5b35e7295ce469ac286992548fd55cc1d + languageName: node + linkType: hard + "fast-diff@npm:^1.2.0": version: 1.3.0 resolution: "fast-diff@npm:1.3.0" @@ -807,11 +936,11 @@ __metadata: linkType: hard "fastq@npm:^1.6.0": - version: 1.15.0 - resolution: "fastq@npm:1.15.0" + version: 1.17.1 + resolution: "fastq@npm:1.17.1" dependencies: reusify: "npm:^1.0.4" - checksum: 10c0/5ce4f83afa5f88c9379e67906b4d31bc7694a30826d6cc8d0f0473c966929017fda65c2174b0ec89f064ede6ace6c67f8a4fe04cef42119b6a55b0d465554c24 + checksum: 10c0/1095f16cea45fb3beff558bb3afa74ca7a9250f5a670b65db7ed585f92b4b48381445cd328b3d87323da81e43232b5d5978a8201bde84e0cd514310f1ea6da34 languageName: node linkType: hard @@ -841,12 +970,12 @@ __metadata: languageName: node linkType: hard -"fill-range@npm:^7.0.1": - version: 7.0.1 - resolution: "fill-range@npm:7.0.1" +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" dependencies: to-regex-range: "npm:^5.0.1" - checksum: 10c0/7cdad7d426ffbaadf45aeb5d15ec675bbd77f7597ad5399e3d2766987ed20bda24d5fac64b3ee79d93276f5865608bb22344a26b9b1ae6c4d00bd94bf611623f + checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 languageName: node linkType: hard @@ -1040,9 +1169,9 @@ __metadata: linkType: hard "ignore@npm:^5.2.4": - version: 5.3.0 - resolution: "ignore@npm:5.3.0" - checksum: 10c0/dc06bea5c23aae65d0725a957a0638b57e235ae4568dda51ca142053ed2c352de7e3bc93a69b2b32ac31966a1952e9a93c5ef2e2ab7c6b06aef9808f6b55b571 + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 languageName: node linkType: hard @@ -1346,12 +1475,12 @@ __metadata: linkType: hard "micromatch@npm:^4.0.4": - version: 4.0.5 - resolution: "micromatch@npm:4.0.5" + version: 4.0.8 + resolution: "micromatch@npm:4.0.8" dependencies: - braces: "npm:^3.0.2" + braces: "npm:^3.0.3" picomatch: "npm:^2.3.1" - checksum: 10c0/3d6505b20f9fa804af5d8c596cb1c5e475b9b0cd05f652c5b56141cf941bd72adaeb7a436fda344235cef93a7f29b7472efc779fcdb83b478eab0867b95cdeff + checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 languageName: node linkType: hard @@ -1790,6 +1919,13 @@ __metadata: languageName: node linkType: hard +"pure-rand@npm:^6.1.0": + version: 6.1.0 + resolution: "pure-rand@npm:6.1.0" + checksum: 10c0/1abe217897bf74dcb3a0c9aba3555fe975023147b48db540aa2faf507aee91c03bf54f6aef0eb2bf59cc259a16d06b28eca37f0dc426d94f4692aeff02fb0e65 + languageName: node + linkType: hard + "queue-microtask@npm:^1.2.2": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" @@ -1873,6 +2009,8 @@ __metadata: resolution: "root-workspace-0b6124@workspace:." dependencies: "@agoric/synthetic-chain": "npm:^0.3.0" + "@endo/init": "npm:^1.1.5" + "@endo/marshal": "npm:^1.5.4" ava: "npm:^5.3.1" better-sqlite3: "npm:^9.6.0" execa: "npm:^9.3.1" @@ -1902,7 +2040,16 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.2, semver@npm:^7.3.5": +"semver@npm:^7.3.2": + version: 7.6.3 + resolution: "semver@npm:7.6.3" + bin: + semver: bin/semver.js + checksum: 10c0/88f33e148b210c153873cb08cfe1e281d518aaa9a666d4d148add6560db5cd3c582f3a08ccb91f38d5f379ead256da9931234ed122057f40bb5766e65e58adaf + languageName: node + linkType: hard + +"semver@npm:^7.3.5": version: 7.5.4 resolution: "semver@npm:7.5.4" dependencies: @@ -1922,6 +2069,15 @@ __metadata: languageName: node linkType: hard +"ses@npm:^1.9.0": + version: 1.9.0 + resolution: "ses@npm:1.9.0" + dependencies: + "@endo/env-options": "npm:^1.1.7" + checksum: 10c0/356f9601b04a87f33403a15fc627caf0c649d86d8d7ee1f4b3c75b947ab05c31b254c94c0aa26e9904862787c73950d5a60f3435deebe5dba23017e20c40b0cb + languageName: node + linkType: hard + "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -2323,9 +2479,9 @@ __metadata: linkType: hard "yocto-queue@npm:^1.0.0": - version: 1.0.0 - resolution: "yocto-queue@npm:1.0.0" - checksum: 10c0/856117aa15cf5103d2a2fb173f0ab4acb12b4b4d0ed3ab249fdbbf612e55d1cadfd27a6110940e24746fb0a78cf640b522cc8bca76f30a3b00b66e90cf82abe0 + version: 1.1.1 + resolution: "yocto-queue@npm:1.1.1" + checksum: 10c0/cb287fe5e6acfa82690acb43c283de34e945c571a78a939774f6eaba7c285bacdf6c90fbc16ce530060863984c906d2b4c6ceb069c94d1e0a06d5f2b458e2a92 languageName: node linkType: hard From 430be20b5a4a3f88e7247fff8ef6709de0534dab Mon Sep 17 00:00:00 2001 From: Fraz Arshad Date: Mon, 14 Oct 2024 14:00:34 +0500 Subject: [PATCH 02/56] test(a3p): add governance tests to z:acceptance --- .../n:upgrade-next/replaceElectorate.test.js | 49 +++---- .../proposals/z:acceptance/governance.test.js | 92 +++++++++++++ .../proposals/z:acceptance/lib/vaults.mts | 4 +- .../proposals/z:acceptance/package.json | 1 + .../z:acceptance/test-lib/governance.js | 128 ++++++++++++++++++ .../proposals/z:acceptance/test-lib/index.js | 5 + .../proposals/z:acceptance/test.sh | 3 + .../proposals/z:acceptance/yarn.lock | 1 + 8 files changed, 252 insertions(+), 31 deletions(-) create mode 100644 a3p-integration/proposals/z:acceptance/governance.test.js create mode 100644 a3p-integration/proposals/z:acceptance/test-lib/governance.js diff --git a/a3p-integration/proposals/n:upgrade-next/replaceElectorate.test.js b/a3p-integration/proposals/n:upgrade-next/replaceElectorate.test.js index a38faaad217..422287dc8b3 100644 --- a/a3p-integration/proposals/n:upgrade-next/replaceElectorate.test.js +++ b/a3p-integration/proposals/n:upgrade-next/replaceElectorate.test.js @@ -12,33 +12,24 @@ test.serial('should be able to view the new accepted invitations', async t => { ); const instances = Object.fromEntries(instance); - const wallet = await queryVstorageFormatted( - `published.wallet.${GOV1ADDR}.current`, - ); - const usedInvitations = wallet.offerToUsedInvitation.map(v => v[1]); - - const totalCharterInvitations = usedInvitations.filter( - v => v.value[0].description === 'charter member invitation', - ).length; - - t.is(totalCharterInvitations, 2); - - const totalCommitteeInvitations = usedInvitations.filter(v => - v.value[0].description.startsWith('Voter'), - ).length; - t.is(totalCommitteeInvitations, 2); - - const charterInvitation = usedInvitations.find( - v => - v.value[0].instance.getBoardId() === - instances.econCommitteeCharter.getBoardId(), - ); - t.is(passStyleOf(charterInvitation), 'copyRecord'); - - const committeeInvitation = usedInvitations.find( - v => - v.value[0].instance.getBoardId() === - instances.economicCommittee.getBoardId(), - ); - t.is(passStyleOf(committeeInvitation), 'copyRecord'); + for (const address of governanceAddresses) { + const wallet = await queryVstorageFormatted( + `published.wallet.${address}.current`, + ); + const usedInvitations = wallet.offerToUsedInvitation.map(v => v[1]); + + const charterInvitation = usedInvitations.find( + v => + v.value[0].instance.getBoardId() === + instances.econCommitteeCharter.getBoardId(), + ); + t.is(passStyleOf(charterInvitation), 'copyRecord'); + + const committeeInvitation = usedInvitations.find( + v => + v.value[0].instance.getBoardId() === + instances.economicCommittee.getBoardId(), + ); + t.is(passStyleOf(committeeInvitation), 'copyRecord'); + } }); diff --git a/a3p-integration/proposals/z:acceptance/governance.test.js b/a3p-integration/proposals/z:acceptance/governance.test.js new file mode 100644 index 00000000000..a0b61e2b27b --- /dev/null +++ b/a3p-integration/proposals/z:acceptance/governance.test.js @@ -0,0 +1,92 @@ +/* global fetch */ + +import test from 'ava'; +import '@endo/init'; +import { GOV1ADDR, GOV2ADDR } from '@agoric/synthetic-chain'; +import { walletUtils, waitUntil, networkConfig } from './test-lib/index.js'; +import { getLastUpdate } from './test-lib/wallet.js'; +import { makeGovernanceDriver } from './test-lib/governance.js'; + +const GOV4ADDR = 'agoric1c9gyu460lu70rtcdp95vummd6032psmpdx7wdy'; +const governanceAddresses = [GOV4ADDR, GOV2ADDR, GOV1ADDR]; + +test.serial( + 'economic committee can make governance proposal and vote on it', + async t => { + const { readLatestHead } = walletUtils; + const governanceDriver = await makeGovernanceDriver(fetch, networkConfig); + + /** @type {any} */ + const instance = await readLatestHead(`published.agoricNames.instance`); + const instances = Object.fromEntries(instance); + + /** @type {any} */ + const wallet = await readLatestHead( + `published.wallet.${governanceAddresses[0]}.current`, + ); + const usedInvitations = wallet.offerToUsedInvitation; + + const charterInvitation = usedInvitations.find( + v => + v[1].value[0].instance.getBoardId() === + instances.econCommitteeCharter.getBoardId(), + ); + + t.log('proposing on param change'); + const params = { + ChargingPeriod: 400n, + }; + const path = { paramPath: { key: 'governedParams' } }; + + await governanceDriver.proposeVaultDirectorParamChange( + governanceAddresses[0], + params, + path, + charterInvitation[0], + ); + + const questionUpdate = await getLastUpdate(governanceAddresses[0], { + readLatestHead, + }); + t.like(questionUpdate, { + status: { numWantsSatisfied: 1 }, + }); + + t.log('Voting on param change'); + for (const address of governanceAddresses) { + /** @type {any} */ + const voteWallet = await readLatestHead( + `published.wallet.${address}.current`, + ); + + const usedInvitationsForVoter = voteWallet.offerToUsedInvitation; + + const committeeInvitationForVoter = usedInvitationsForVoter.find( + v => + v[1].value[0].instance.getBoardId() === + instances.economicCommittee.getBoardId(), + ); + await governanceDriver.voteOnProposedChanges( + address, + committeeInvitationForVoter[0], + ); + + const voteUpdate = await getLastUpdate(address, { readLatestHead }); + t.like(voteUpdate, { + status: { numWantsSatisfied: 1 }, + }); + } + + /** @type {any} */ + const latestQuestion = await readLatestHead( + 'published.committees.Economic_Committee.latestQuestion', + ); + await waitUntil(latestQuestion.closingRule.deadline); + + t.log('check if latest outcome is correct'); + const latestOutcome = await readLatestHead( + 'published.committees.Economic_Committee.latestOutcome', + ); + t.like(latestOutcome, { outcome: 'win' }); + }, +); diff --git a/a3p-integration/proposals/z:acceptance/lib/vaults.mts b/a3p-integration/proposals/z:acceptance/lib/vaults.mts index 4ce6697f1ee..cec0cfcf062 100644 --- a/a3p-integration/proposals/z:acceptance/lib/vaults.mts +++ b/a3p-integration/proposals/z:acceptance/lib/vaults.mts @@ -11,12 +11,12 @@ import { executeOffer, GOV1ADDR, GOV2ADDR, - GOV3ADDR, provisionSmartWallet, waitForBlock, } from '@agoric/synthetic-chain'; -const govAccounts = [GOV1ADDR, GOV2ADDR, GOV3ADDR]; +const GOV4ADDR = 'agoric1c9gyu460lu70rtcdp95vummd6032psmpdx7wdy'; +const govAccounts = [GOV1ADDR, GOV2ADDR, GOV4ADDR]; export const ISTunit = 1_000_000n; // aka displayInfo: { decimalPlaces: 6 } diff --git a/a3p-integration/proposals/z:acceptance/package.json b/a3p-integration/proposals/z:acceptance/package.json index a5910e5ded6..9004540f415 100644 --- a/a3p-integration/proposals/z:acceptance/package.json +++ b/a3p-integration/proposals/z:acceptance/package.json @@ -14,6 +14,7 @@ "@endo/errors": "^1.2.2", "@endo/far": "^1.1.5", "@endo/init": "^1.1.4", + "@endo/marshal": "^1.5.3", "ava": "^6.1.2", "execa": "^9.3.1", "tsx": "^4.17.0" diff --git a/a3p-integration/proposals/z:acceptance/test-lib/governance.js b/a3p-integration/proposals/z:acceptance/test-lib/governance.js new file mode 100644 index 00000000000..910764d16c2 --- /dev/null +++ b/a3p-integration/proposals/z:acceptance/test-lib/governance.js @@ -0,0 +1,128 @@ +import { agops, agoric, executeOffer } from '@agoric/synthetic-chain'; +import { makeRpcUtils } from './rpc.js'; + +export const makeGovernanceDriver = async (fetch, networkConfig) => { + const { readLatestHead, marshaller } = await makeRpcUtils( + { fetch }, + networkConfig, + ); + + const generateVoteOffer = async previousOfferId => { + const id = `propose-${Date.now()}`; + + /** + * @type {object} + */ + const latestQuestionRecord = await readLatestHead( + 'published.committees.Economic_Committee.latestQuestion', + ); + + const chosenPositions = [latestQuestionRecord.positions[0]]; + const body = { + method: 'executeOffer', + offer: { + id, + invitationSpec: { + invitationMakerName: 'makeVoteInvitation', + previousOffer: previousOfferId, + source: 'continuing', + invitationArgs: harden([ + chosenPositions, + latestQuestionRecord.questionHandle, + ]), + }, + proposal: {}, + }, + }; + + const capData = marshaller.toCapData(harden(body)); + return JSON.stringify(capData); + }; + + const voteOnProposedChanges = async (address, committeeAcceptOfferId) => { + await null; + const offerId = + committeeAcceptOfferId || + (await agops.ec( + 'find-continuing-id', + '--for', + 'Voter0', + '--from', + address, + )); + + return executeOffer(address, generateVoteOffer(offerId)); + }; + + const generateVaultDirectorParamChange = async ( + previousOfferId, + voteDur, + params, + paramsPath, + ) => { + const voteDurSec = BigInt(voteDur); + const toSec = ms => BigInt(Math.round(ms / 1000)); + + const id = `propose-${Date.now()}`; + const deadline = toSec(Date.now()) + voteDurSec; + + const a = await agoric.follow( + '-lF', + ':published.agoricNames.instance', + '-o', + 'text', + ); + const instance = Object.fromEntries(marshaller.fromCapData(JSON.parse(a))); + assert(instance.VaultFactory); + + const body = { + method: 'executeOffer', + offer: { + id, + invitationSpec: { + invitationMakerName: 'VoteOnParamChange', + previousOffer: previousOfferId, + source: 'continuing', + }, + offerArgs: { + deadline, + instance: instance.VaultFactory, + params, + path: paramsPath, + }, + proposal: {}, + }, + }; + + const capData = marshaller.toCapData(harden(body)); + return JSON.stringify(capData); + }; + + const proposeVaultDirectorParamChange = async ( + address, + params, + path, + charterAcceptOfferId, + ) => { + await null; + const offerId = + charterAcceptOfferId || + (await agops.ec( + 'find-continuing-id', + '--for', + `${'charter\\ member\\ invitation'}`, + '--from', + address, + )); + + return executeOffer( + address, + generateVaultDirectorParamChange(offerId, 10, params, path), + ); + }; + + return { + voteOnProposedChanges, + proposeVaultDirectorParamChange, + }; +}; diff --git a/a3p-integration/proposals/z:acceptance/test-lib/index.js b/a3p-integration/proposals/z:acceptance/test-lib/index.js index 2f2e7c02d3c..25716bdd6a7 100644 --- a/a3p-integration/proposals/z:acceptance/test-lib/index.js +++ b/a3p-integration/proposals/z:acceptance/test-lib/index.js @@ -19,3 +19,8 @@ export const walletUtils = await makeWalletUtils( { delay, execFileSync, fetch }, networkConfig, ); + +export const waitUntil = async timestamp => { + const timeDelta = Math.floor(Date.now() / 1000) - Number(timestamp); + await delay(timeDelta); +}; diff --git a/a3p-integration/proposals/z:acceptance/test.sh b/a3p-integration/proposals/z:acceptance/test.sh index f9982b31916..a1f74a431e3 100755 --- a/a3p-integration/proposals/z:acceptance/test.sh +++ b/a3p-integration/proposals/z:acceptance/test.sh @@ -28,3 +28,6 @@ yarn ava wallet.test.js echo ACCEPTANCE TESTING vaults yarn ava vaults.test.js + +echo ACCEPTANCE TESTING governance +yarn ava governance.test.js diff --git a/a3p-integration/proposals/z:acceptance/yarn.lock b/a3p-integration/proposals/z:acceptance/yarn.lock index 72977f2a884..5cd3b8df234 100644 --- a/a3p-integration/proposals/z:acceptance/yarn.lock +++ b/a3p-integration/proposals/z:acceptance/yarn.lock @@ -2516,6 +2516,7 @@ __metadata: "@endo/errors": "npm:^1.2.2" "@endo/far": "npm:^1.1.5" "@endo/init": "npm:^1.1.4" + "@endo/marshal": "npm:^1.5.3" ava: "npm:^6.1.2" execa: "npm:^9.3.1" tsx: "npm:^4.17.0" From 4e88d9f0412fe2b90efda30df0afbb61887bf35f Mon Sep 17 00:00:00 2001 From: Fraz Arshad Date: Fri, 18 Oct 2024 17:03:41 +0500 Subject: [PATCH 03/56] feat: added replace electorate proposal in chain upgrade --- golang/cosmos/app/upgrade.go | 62 +++++++++++++++++++ .../inter-protocol/replace-electorate-core.js | 51 ++++++++------- 2 files changed, 87 insertions(+), 26 deletions(-) diff --git a/golang/cosmos/app/upgrade.go b/golang/cosmos/app/upgrade.go index 4a37e2d2fce..ba60dc6dba5 100644 --- a/golang/cosmos/app/upgrade.go +++ b/golang/cosmos/app/upgrade.go @@ -1,7 +1,10 @@ package gaia import ( + "encoding/json" "fmt" + "strings" + "text/template" "github.com/Agoric/agoric-sdk/golang/cosmos/vm" swingsetkeeper "github.com/Agoric/agoric-sdk/golang/cosmos/x/swingset/keeper" @@ -72,6 +75,59 @@ func isFirstTimeUpgradeOfThisVersion(app *GaiaApp, ctx sdk.Context) bool { return true } +func buildProposalStepWithArgs(moduleName string, entrypoint string, opts map[string]any) (vm.CoreProposalStep, error) { + t := template.Must(template.New("").Parse(`{ + "module": "{{.moduleName}}", + "entrypoint": "{{.entrypoint}}", + "args": [ {{.args}} ] + }`)) + + args, err := json.Marshal(opts) + if err != nil { + return nil, err + } + + var result strings.Builder + err = t.Execute(&result, map[string]any{ + "moduleName": moduleName, + "entrypoint": entrypoint, + "args": string(args), + }) + if err != nil { + return nil, err + } + jsonStr := result.String() + jsonBz := []byte(jsonStr) + if !json.Valid(jsonBz) { + return nil, fmt.Errorf("invalid JSON: %s", jsonStr) + } + proposal := vm.ArbitraryCoreProposal{Json: jsonBz} + return vm.CoreProposalStepForModules(proposal), nil +} + +func replaceElectorateCoreProposalStep(upgradeName string) (vm.CoreProposalStep, error) { + var variant string + + switch validUpgradeName(upgradeName) { + case "UNRELEASED_A3P_INTEGRATION": + variant = "A3P_INTEGRATION" + case "UNRELEASED_main": + variant = "MAINNET" + case "UNRELEASED_devnet": + variant = "DEVNET" + // Noupgrade for this version. + case "UNRELEASED_BASIC": + } + + return buildProposalStepWithArgs( + "@agoric/builders/scripts/inter-protocol/replace-electorate-core.js", + "defaultProposalBuilder", + map[string]any{ + "variant": variant, + }, + ) +} + // unreleasedUpgradeHandler performs standard upgrade actions plus custom actions for the unreleased upgrade. func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Context, upgradetypes.Plan, module.VersionMap) (module.VersionMap, error) { return func(ctx sdk.Context, plan upgradetypes.Plan, fromVm module.VersionMap) (module.VersionMap, error) { @@ -89,9 +145,15 @@ func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Conte return module.VersionMap{}, fmt.Errorf("cannot run %s as first upgrade", plan.Name) } + replaceElectorateStep, err := replaceElectorateCoreProposalStep(targetUpgrade) + if err != nil { + return nil, err + } + // Each CoreProposalStep runs sequentially, and can be constructed from // one or more modules executing in parallel within the step. CoreProposalSteps = []vm.CoreProposalStep{ + replaceElectorateStep, vm.CoreProposalStepForModules( // Upgrade Zoe (no new ZCF needed). "@agoric/builders/scripts/vats/upgrade-zoe.js", diff --git a/packages/builders/scripts/inter-protocol/replace-electorate-core.js b/packages/builders/scripts/inter-protocol/replace-electorate-core.js index 6b2fbbac1f1..7f7bce113b5 100644 --- a/packages/builders/scripts/inter-protocol/replace-electorate-core.js +++ b/packages/builders/scripts/inter-protocol/replace-electorate-core.js @@ -16,25 +16,6 @@ import { makeHelpers } from '@agoric/deploy-script-support'; import { getManifestForReplaceAllElectorates } from '@agoric/inter-protocol/src/proposals/replaceElectorate.js'; -/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ -export const defaultProposalBuilder = async ({ publishRef, install }, opts) => { - return harden({ - sourceSpec: '@agoric/inter-protocol/src/proposals/replaceElectorate.js', - getManifestCall: [ - getManifestForReplaceAllElectorates.name, - { - ...opts, - economicCommitteeRef: publishRef( - install( - '@agoric/governance/src/committee.js', - '../bundles/bundle-committee.js', - ), - ), - }, - ], - }); -}; - const configurations = { MAINNET: { committeeName: 'Economic Committee', @@ -94,21 +75,39 @@ const configurations = { const { keys } = Object; const Usage = `agoric run replace-electorate-core.js ${keys(configurations).join(' | ')}`; +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ +export const defaultProposalBuilder = async ({ publishRef, install }, opts) => { + const config = configurations[opts.variant]; + if (!config) { + console.error(Usage); + process.exit(1); + } + return harden({ + sourceSpec: '@agoric/inter-protocol/src/proposals/replaceElectorate.js', + getManifestCall: [ + getManifestForReplaceAllElectorates.name, + { + ...config, + economicCommitteeRef: publishRef( + install( + '@agoric/governance/src/committee.js', + '../bundles/bundle-committee.js', + ), + ), + }, + ], + }); +}; /** @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ export default async (homeP, endowments) => { const { scriptArgs } = endowments; const variant = scriptArgs?.[0]; - const config = configurations[variant]; - if (!config) { - console.error(Usage); - process.exit(1); - } - console.log('replace-committee', scriptArgs, config); + console.log('replace-committee', scriptArgs, variant); const { writeCoreEval } = await makeHelpers(homeP, endowments); await writeCoreEval(`replace-committee-${variant}`, (utils, opts) => - defaultProposalBuilder(utils, { ...opts, ...config }), + defaultProposalBuilder(utils, { ...opts, variant }), ); }; From 9ace4b9c1c1ae0242e3c543fe398b3331c8bbed7 Mon Sep 17 00:00:00 2001 From: Fraz Arshad Date: Mon, 21 Oct 2024 23:44:05 +0500 Subject: [PATCH 04/56] refactor: util functions should follow ocap discipline --- .../proposals/z:acceptance/governance.test.js | 14 ++++++++---- .../proposals/z:acceptance/test-lib/index.js | 22 ------------------- .../proposals/z:acceptance/test-lib/utils.js | 20 +++++++++++++++++ .../proposals/z:acceptance/test-lib/wallet.js | 4 +++- .../proposals/z:acceptance/valueVow.test.js | 11 +++++++++- 5 files changed, 43 insertions(+), 28 deletions(-) diff --git a/a3p-integration/proposals/z:acceptance/governance.test.js b/a3p-integration/proposals/z:acceptance/governance.test.js index a0b61e2b27b..7e4b23b5418 100644 --- a/a3p-integration/proposals/z:acceptance/governance.test.js +++ b/a3p-integration/proposals/z:acceptance/governance.test.js @@ -1,11 +1,13 @@ -/* global fetch */ +/* global fetch setTimeout */ import test from 'ava'; import '@endo/init'; +import { execFileSync } from 'node:child_process'; import { GOV1ADDR, GOV2ADDR } from '@agoric/synthetic-chain'; -import { walletUtils, waitUntil, networkConfig } from './test-lib/index.js'; -import { getLastUpdate } from './test-lib/wallet.js'; +import { networkConfig } from './test-lib/index.js'; +import { getLastUpdate, makeWalletUtils } from './test-lib/wallet.js'; import { makeGovernanceDriver } from './test-lib/governance.js'; +import { makeTimerUtils } from './test-lib/utils.js'; const GOV4ADDR = 'agoric1c9gyu460lu70rtcdp95vummd6032psmpdx7wdy'; const governanceAddresses = [GOV4ADDR, GOV2ADDR, GOV1ADDR]; @@ -13,7 +15,11 @@ const governanceAddresses = [GOV4ADDR, GOV2ADDR, GOV1ADDR]; test.serial( 'economic committee can make governance proposal and vote on it', async t => { - const { readLatestHead } = walletUtils; + const { waitUntil } = makeTimerUtils({ setTimeout }); + const { readLatestHead } = await makeWalletUtils( + { setTimeout, execFileSync, fetch }, + networkConfig, + ); const governanceDriver = await makeGovernanceDriver(fetch, networkConfig); /** @type {any} */ diff --git a/a3p-integration/proposals/z:acceptance/test-lib/index.js b/a3p-integration/proposals/z:acceptance/test-lib/index.js index 25716bdd6a7..55bbfe8ff20 100644 --- a/a3p-integration/proposals/z:acceptance/test-lib/index.js +++ b/a3p-integration/proposals/z:acceptance/test-lib/index.js @@ -1,26 +1,4 @@ -/* global fetch setTimeout */ -import { execFileSync } from 'child_process'; -import { makeWalletUtils } from './wallet.js'; - export const networkConfig = { rpcAddrs: ['http://0.0.0.0:26657'], chainName: 'agoriclocal', }; - -/** - * Resolve after a delay in milliseconds. - * - * @param {number} ms - * @returns {Promise} - */ -const delay = ms => new Promise(resolve => setTimeout(() => resolve(), ms)); - -export const walletUtils = await makeWalletUtils( - { delay, execFileSync, fetch }, - networkConfig, -); - -export const waitUntil = async timestamp => { - const timeDelta = Math.floor(Date.now() / 1000) - Number(timestamp); - await delay(timeDelta); -}; diff --git a/a3p-integration/proposals/z:acceptance/test-lib/utils.js b/a3p-integration/proposals/z:acceptance/test-lib/utils.js index 4989d4b71e4..2ea35ba4bae 100644 --- a/a3p-integration/proposals/z:acceptance/test-lib/utils.js +++ b/a3p-integration/proposals/z:acceptance/test-lib/utils.js @@ -46,3 +46,23 @@ export const getBalances = async (addresses, targetDenom = undefined) => { }; export const agopsVaults = addr => agops.vaults('list', '--from', addr); + +export const makeTimerUtils = ({ setTimeout }) => { + /** + * Resolve after a delay in milliseconds. + * + * @param {number} ms + * @returns {Promise} + */ + const delay = ms => new Promise(resolve => setTimeout(() => resolve(), ms)); + + const waitUntil = async timestamp => { + const timeDelta = Math.floor(Date.now() / 1000) - Number(timestamp); + await delay(timeDelta); + }; + + return { + delay, + waitUntil, + }; +}; diff --git a/a3p-integration/proposals/z:acceptance/test-lib/wallet.js b/a3p-integration/proposals/z:acceptance/test-lib/wallet.js index 5fbe9b70027..366a988fc8f 100644 --- a/a3p-integration/proposals/z:acceptance/test-lib/wallet.js +++ b/a3p-integration/proposals/z:acceptance/test-lib/wallet.js @@ -10,6 +10,7 @@ import { E, Far } from '@endo/far'; import { inspect } from 'util'; import { execSwingsetTransaction, pollTx } from './chain.js'; import { makeRpcUtils } from './rpc.js'; +import { makeTimerUtils } from './utils.js'; /** @import {CurrentWalletRecord} from '@agoric/smart-wallet/src/smartWallet.js' */ /** @import {AgoricNamesRemotes} from '@agoric/vats/tools/board-utils.js' */ @@ -132,12 +133,13 @@ export const sendAction = async (bridgeAction, opts) => { }; export const makeWalletUtils = async ( - { delay, execFileSync, fetch }, + { setTimeout, execFileSync, fetch }, networkConfig, ) => { const { agoricNames, fromBoard, marshaller, readLatestHead, vstorage } = await makeRpcUtils({ fetch }, networkConfig); + const { delay } = await makeTimerUtils({ setTimeout }); /** * * @param {string} from diff --git a/a3p-integration/proposals/z:acceptance/valueVow.test.js b/a3p-integration/proposals/z:acceptance/valueVow.test.js index faec82e710a..1193740b541 100644 --- a/a3p-integration/proposals/z:acceptance/valueVow.test.js +++ b/a3p-integration/proposals/z:acceptance/valueVow.test.js @@ -1,6 +1,9 @@ +/* global fetch setTimeout */ + // @ts-check import test from 'ava'; import { inspect } from 'node:util'; +import { execFileSync } from 'node:child_process'; import '@endo/init/debug.js'; import { @@ -10,12 +13,18 @@ import { GOV1ADDR as GETTER, // not particular to governance, just a handy wallet GOV2ADDR as SETTER, // not particular to governance, just a handy wallet } from '@agoric/synthetic-chain'; -import { walletUtils } from './test-lib/index.js'; +import { makeWalletUtils } from './test-lib/wallet.js'; +import { networkConfig } from './test-lib/index.js'; const START_VALUEVOW_DIR = 'start-valueVow'; const RESTART_VALUEVOW_DIR = 'restart-valueVow'; test('vow survives restart', async t => { + const walletUtils = await makeWalletUtils( + { setTimeout, execFileSync, fetch }, + networkConfig, + ); + t.log('start valueVow'); await evalBundles(START_VALUEVOW_DIR); t.is(await getIncarnation('valueVow'), 0); From ee39ed9c587b81457850bb7fc1fe11f06dd91b91 Mon Sep 17 00:00:00 2001 From: Fraz Arshad Date: Wed, 23 Oct 2024 14:34:41 +0500 Subject: [PATCH 05/56] chore: added gov4 to replace electorate config for a3p --- .../scripts/inter-protocol/replace-electorate-core.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/builders/scripts/inter-protocol/replace-electorate-core.js b/packages/builders/scripts/inter-protocol/replace-electorate-core.js index 7f7bce113b5..4545cbacfbc 100644 --- a/packages/builders/scripts/inter-protocol/replace-electorate-core.js +++ b/packages/builders/scripts/inter-protocol/replace-electorate-core.js @@ -49,10 +49,12 @@ const configurations = { committeeName: 'Economic Committee', voterAddresses: { gov1: 'agoric1ee9hr0jyrxhy999y755mp862ljgycmwyp4pl7q', + gov2: 'agoric1wrfh296eu2z34p6pah7q04jjuyj3mxu9v98277', + gov4: 'agoric1c9gyu460lu70rtcdp95vummd6032psmpdx7wdy', }, highPrioritySendersConfig: { - addressesToAdd: [], - addressesToRemove: ['agoric1wrfh296eu2z34p6pah7q04jjuyj3mxu9v98277'], + addressesToAdd: ['agoric1c9gyu460lu70rtcdp95vummd6032psmpdx7wdy'], + addressesToRemove: ['agoric1ydzxwh6f893jvpaslmaz6l8j2ulup9a7x8qvvq'], }, }, BOOTSTRAP_TEST: { From f406d258d7de77f8f56e7292e6b0c74ef20226fc Mon Sep 17 00:00:00 2001 From: Fraz Arshad Date: Thu, 24 Oct 2024 16:26:28 +0500 Subject: [PATCH 06/56] fix: using auctioneerKit to get auctioneer data --- .../src/proposals/replaceElectorate.js | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/inter-protocol/src/proposals/replaceElectorate.js b/packages/inter-protocol/src/proposals/replaceElectorate.js index ede08ff8cb4..ac476113d6a 100644 --- a/packages/inter-protocol/src/proposals/replaceElectorate.js +++ b/packages/inter-protocol/src/proposals/replaceElectorate.js @@ -322,7 +322,7 @@ const startNewEconCharter = async ({ * been successfully added to the economic charter */ const addGovernorsToEconCharter = async ( - { consume: { psmKit, governedContractKits } }, + { consume: { psmKit, governedContractKits, auctioneerKit } }, { options: { econCharterKit } }, ) => { const { creatorFacet: ecCreatorFacet } = E.get(econCharterKit); @@ -334,13 +334,27 @@ const addGovernorsToEconCharter = async ( } const governedContractKitMap = await governedContractKits; - + const auctioneerKitObject = await auctioneerKit; for (const { instance, governorCreatorFacet, label, } of governedContractKitMap.values()) { - await E(ecCreatorFacet).addInstance(instance, governorCreatorFacet, label); + // `governedContractKitMap` has an old version of the auctioneer kit + // so using `auctioneerKit` instead + if (label === 'auctioneer') { + await E(ecCreatorFacet).addInstance( + auctioneerKitObject.instance, + auctioneerKitObject.governorCreatorFacet, + label, + ); + } else { + await E(ecCreatorFacet).addInstance( + instance, + governorCreatorFacet, + label, + ); + } } }; @@ -448,6 +462,7 @@ export const getManifestForReplaceAllElectorates = async ( psmKit: true, governedContractKits: true, chainStorage: true, + auctioneerKit: true, highPrioritySendersManager: true, namesByAddressAdmin: true, // Rest of these are designed to be widely shared From df51360eca066ea7046661573077a717513bbb6a Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Wed, 23 Oct 2024 13:40:42 -0700 Subject: [PATCH 07/56] chore: repair KREAd chaaracters since we're upgrading Zoe --- golang/cosmos/app/upgrade.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/golang/cosmos/app/upgrade.go b/golang/cosmos/app/upgrade.go index ba60dc6dba5..e5c2fa97a98 100644 --- a/golang/cosmos/app/upgrade.go +++ b/golang/cosmos/app/upgrade.go @@ -158,6 +158,10 @@ func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Conte // Upgrade Zoe (no new ZCF needed). "@agoric/builders/scripts/vats/upgrade-zoe.js", ), + // Revive KREAd characters + vm.CoreProposalStepForModules( + "@agoric/builders/scripts/vats/revive-kread.js", + ), vm.CoreProposalStepForModules( // Upgrade to new liveslots for repaired vow usage. "@agoric/builders/scripts/vats/upgrade-orch-core.js", From b1a98fab9a4b95589458e6da3a3c836e18005581 Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Mon, 21 Oct 2024 17:58:29 -0700 Subject: [PATCH 08/56] bug: half the committee members casting ballots is not a quorum --- packages/governance/src/committee.js | 5 +- .../test/unitTests/committee.test.js | 173 +++++++++++++++++- 2 files changed, 171 insertions(+), 7 deletions(-) diff --git a/packages/governance/src/committee.js b/packages/governance/src/committee.js index 8eb967d75fb..78441d926ae 100644 --- a/packages/governance/src/committee.js +++ b/packages/governance/src/committee.js @@ -1,6 +1,5 @@ import { makeStoredPublishKit } from '@agoric/notifier'; import { M } from '@agoric/store'; -import { natSafeMath } from '@agoric/zoe/src/contractSupport/index.js'; import { E } from '@endo/eventual-send'; import { StorageNodeShape } from '@agoric/internal'; @@ -20,8 +19,6 @@ import { prepareVoterKit } from './voterKit.js'; * @import {ElectorateCreatorFacet, CommitteeElectoratePublic, QuestionDetails, OutcomeRecord, AddQuestion} from './types.js'; */ -const { ceilDivide } = natSafeMath; - /** * @typedef { ElectorateCreatorFacet & { * getVoterInvitations: () => Promise>[] @@ -139,7 +136,7 @@ export const start = (zcf, privateArgs, baggage) => { const quorumThreshold = quorumRule => { switch (quorumRule) { case QuorumRule.MAJORITY: - return ceilDivide(committeeSize, 2); + return Math.ceil((committeeSize + 1) / 2); case QuorumRule.ALL: return committeeSize; case QuorumRule.NO_QUORUM: diff --git a/packages/governance/test/unitTests/committee.test.js b/packages/governance/test/unitTests/committee.test.js index 5e50c9d300e..39ff48d7832 100644 --- a/packages/governance/test/unitTests/committee.test.js +++ b/packages/governance/test/unitTests/committee.test.js @@ -27,7 +27,9 @@ const dirname = path.dirname(new URL(import.meta.url).pathname); const electorateRoot = `${dirname}/../../src/committee.js`; const counterRoot = `${dirname}/../../src/binaryVoteCounter.js`; -const setupContract = async () => { +const setupContract = async ( + terms = { committeeName: 'illuminati', committeeSize: 13 }, +) => { const zoe = makeZoeForTest(); const mockChainStorageRoot = makeMockChainStorageRoot(); @@ -45,7 +47,6 @@ const setupContract = async () => { E(zoe).install(electorateBundle), E(zoe).install(counterBundle), ]); - const terms = { committeeName: 'illuminati', committeeSize: 13 }; const electorateStartResult = await E(zoe).startInstance( electorateInstallation, {}, @@ -56,7 +57,12 @@ const setupContract = async () => { }, ); - return { counterInstallation, electorateStartResult, mockChainStorageRoot }; + return { + counterInstallation, + electorateStartResult, + mockChainStorageRoot, + zoe, + }; }; test('committee-open no questions', async t => { @@ -233,3 +239,164 @@ test('committee-open question:mixed, with snapshot', async t => { }; await documentStorageSchema(t, mockChainStorageRoot, doc); }); + +const setUpVoterAndVote = async (invitation, zoe, qHandle, choice) => { + const seat = E(zoe).offer(invitation); + const { voter } = E.get(E(seat).getOfferResult()); + return E(voter).castBallotFor(qHandle, [choice]); +}; + +test('committee-tie outcome', async t => { + const { + electorateStartResult: { creatorFacet }, + counterInstallation: counter, + zoe, + } = await setupContract({ committeeName: 'halfDozen', committeeSize: 6 }); + + const timer = buildZoeManualTimer(t.log); + + const positions = [harden({ text: 'guilty' }), harden({ text: 'innocent' })]; + const questionSpec = coerceQuestionSpec( + harden({ + method: ChoiceMethod.UNRANKED, + issue: { text: 'guilt' }, + positions, + electionType: ElectionType.SURVEY, + maxChoices: 1, + maxWinners: 1, + closingRule: { + timer, + deadline: 2n, + }, + quorumRule: QuorumRule.MAJORITY, + tieOutcome: positions[1], + }), + ); + + const qResult = await E(creatorFacet).addQuestion(counter, questionSpec); + + const invites = await E(creatorFacet).getVoterInvitations(); + const votes = []; + for (const i of [...Array(6).keys()]) { + votes.push( + setUpVoterAndVote( + invites[i], + zoe, + qResult.questionHandle, + positions[i % 2], + ), + ); + } + + await Promise.all(votes); + await E(timer).tick(); + await E(timer).tick(); + + // if half vote each way, the tieOutcome prevails + await E.when(E(qResult.publicFacet).getOutcome(), async outcomes => + t.deepEqual(outcomes, { + text: 'innocent', + }), + ); +}); + +test('committee-half vote', async t => { + const { + electorateStartResult: { creatorFacet }, + counterInstallation: counter, + zoe, + } = await setupContract({ committeeName: 'halfDozen', committeeSize: 6 }); + + const timer = buildZoeManualTimer(t.log); + + const positions = [harden({ text: 'guilty' }), harden({ text: 'innocent' })]; + const questionSpec = coerceQuestionSpec( + harden({ + method: ChoiceMethod.UNRANKED, + issue: { text: 'guilt' }, + positions, + electionType: ElectionType.SURVEY, + maxChoices: 1, + maxWinners: 1, + closingRule: { + timer, + deadline: 2n, + }, + quorumRule: QuorumRule.MAJORITY, + tieOutcome: positions[1], + }), + ); + + const qResult = await E(creatorFacet).addQuestion(counter, questionSpec); + + const invites = await E(creatorFacet).getVoterInvitations(); + const votes = []; + for (const i of [...Array(3).keys()]) { + votes.push( + setUpVoterAndVote(invites[i], zoe, qResult.questionHandle, positions[0]), + ); + } + + await Promise.all(votes); + await E(timer).tick(); + await E(timer).tick(); + + // if only half the voters vote, there is no quorum + await E.when( + E(qResult.publicFacet).getOutcome(), + async _outcomes => { + t.fail('expect no quorum'); + }, + e => { + t.is(e, 'No quorum'); + }, + ); +}); + +test('committee-half plus one vote', async t => { + const { + electorateStartResult: { creatorFacet }, + counterInstallation: counter, + zoe, + } = await setupContract({ committeeName: 'halfDozen', committeeSize: 6 }); + + const timer = buildZoeManualTimer(t.log); + + const positions = [harden({ text: 'guilty' }), harden({ text: 'innocent' })]; + const questionSpec = coerceQuestionSpec( + harden({ + method: ChoiceMethod.UNRANKED, + issue: { text: 'guilt' }, + positions, + electionType: ElectionType.SURVEY, + maxChoices: 1, + maxWinners: 1, + closingRule: { + timer, + deadline: 2n, + }, + quorumRule: QuorumRule.MAJORITY, + tieOutcome: positions[1], + }), + ); + + const qResult = await E(creatorFacet).addQuestion(counter, questionSpec); + + const invites = await E(creatorFacet).getVoterInvitations(); + const votes = []; + for (const i of [...Array(4).keys()]) { + votes.push( + setUpVoterAndVote(invites[i], zoe, qResult.questionHandle, positions[0]), + ); + } + + await Promise.all(votes); + await E(timer).tick(); + await E(timer).tick(); + + await E.when(E(qResult.publicFacet).getOutcome(), async outcomes => + t.deepEqual(outcomes, { + text: 'guilty', + }), + ); +}); From a8cac48474ded63bcd4d36faa5daf7750f4d1d7d Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 22 Oct 2024 18:38:56 -0400 Subject: [PATCH 09/56] chore(.github): Update stale references --- .github/actions/restore-node/action.yml | 2 +- .github/workflows/integration.yml | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/actions/restore-node/action.yml b/.github/actions/restore-node/action.yml index 5797145945d..7835b58a573 100644 --- a/.github/actions/restore-node/action.yml +++ b/.github/actions/restore-node/action.yml @@ -85,7 +85,7 @@ runs: if: steps.endo-branch.outputs.result != 'NOPE' uses: actions/checkout@v4 with: - repository: agoric/endo + repository: endojs/endo path: ./replacement-endo ref: ${{ steps.endo-branch.outputs.result }} clean: 'false' diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index a01d25ebad1..f2e4dd92620 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -35,8 +35,10 @@ jobs: pre_check: uses: ./.github/workflows/pre-check-integration.yml - # This job is meant to emulate what developers working with the Agoric platform will experience - # It should be kept in sync with https://agoric.com/documentation/getting-started/ + # This job is meant to emulate what developers working with the Agoric + # platform will experience. + # It should be kept in sync with the "getting started" workflow at + # https://docs.agoric.com/guides/getting-started/ getting-started: needs: pre_check if: needs.pre_check.outputs.should_run == 'true' From d0b75976eb2247ea43cd6ba4b7da3177b2078436 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 22 Oct 2024 18:41:38 -0400 Subject: [PATCH 10/56] chore(.github): Better document CI consumption of override directives in PR descriptions --- .github/actions/restore-node/action.yml | 13 +++++++------ .github/workflows/integration.yml | 15 +++++++++------ .github/workflows/test-documentation.yml | 10 ++++++---- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/.github/actions/restore-node/action.yml b/.github/actions/restore-node/action.yml index 7835b58a573..61ab970341a 100644 --- a/.github/actions/restore-node/action.yml +++ b/.github/actions/restore-node/action.yml @@ -41,10 +41,11 @@ runs: persist-credentials: false path: ${{ inputs.path }} - # Select a branch on Endo to test against by adding text to the body of the - # pull request. For example: #endo-branch: some-pr-branch - # The default is 'NOPE' to indicate not to check out Endo, just use - # the published NPM packages. + # Select an Endo target against which to test, defaulting to 'NOPE' for use + # of internally-referenced NPM packages but allowing overrides in the pull + # request description for referencing a branch of the + # [endo repository](https://github.com/endojs/endo) using lines like + # `#endo-branch: rank-strings-by-codepoint` - name: Get the appropriate Endo branch id: endo-branch uses: actions/github-script@v7 @@ -53,8 +54,8 @@ runs: script: |- let branch = 'NOPE'; if (${{ inputs.ignore-endo-branch }}) { - // Skip endo branch - } else if (context.eventName === 'schedule') { + // Skip any override directive. + } else if (context.eventName === 'schedule') { branch = 'master'; } else if (context.payload.pull_request) { const { body } = context.payload.pull_request; diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index f2e4dd92620..4ae7458e4d3 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -62,9 +62,11 @@ jobs: node-version: 18.18 keep-endo: 'true' - # Select a branch on dapp to test against by adding text to the body of the - # pull request. For example: #getting-started-branch: zoe-release-0.7.0 - # The default is 'main' + # Select a branch of the + # [default dapp repository](https://github.com/Agoric/dapp-offer-up) (cf. + # packages/agoric-cli/src/main.js) against which to test, defaulting to + # 'main' but allowing overrides in the pull request description using + # lines like `#getting-started-branch: zoe-release-0.7.0` - name: Get the appropriate dapp branch id: get-branch uses: actions/github-script@v7 @@ -140,9 +142,10 @@ jobs: # the chances the content of snapshots may deviate between validators xsnap-random-init: '1' - # Select a branch on loadgen to test against by adding text to the body of the - # pull request. For example: #loadgen-branch: user-123-update-foo - # The default is 'main' + # Select a branch of the + # [load generator dapp repository](https://github.com/Agoric/testnet-load-generator) + # to use, defaulting to 'main' but allowing overrides in the pull request + # description using lines like `#loadgen-branch: ceelab` - name: Get the appropriate loadgen branch id: get-loadgen-branch uses: actions/github-script@v7 diff --git a/.github/workflows/test-documentation.yml b/.github/workflows/test-documentation.yml index bade8a4cd19..596bb60a075 100644 --- a/.github/workflows/test-documentation.yml +++ b/.github/workflows/test-documentation.yml @@ -26,9 +26,11 @@ jobs: node-version: ${{ matrix.node-version }} path: ./agoric-sdk - # Select a branch on dapp to test against by adding text to the body of the - # pull request. For example: #dapp-encouragement-branch: zoe-release-0.7.0 - # The default is 'main' + # Select a branch of the + # [documentation repository](https://github.com/Agoric/documentation) + # against which to test, defaulting to 'main' but allowing overrides in + # the pull request description using lines like + # `#documentation-branch: node-22` - name: Get the appropriate dapp branch id: get-branch uses: actions/github-script@v7 @@ -44,7 +46,7 @@ jobs: branch = result[1]; } } - console.log(branch); + console.log('documentation branch: ' + branch); return branch; - name: Check out dapp From efb7cdf327d9fa8837adc74efaa9986e9e5d15fc Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 22 Oct 2024 18:42:57 -0400 Subject: [PATCH 11/56] chore(.github): Put CI override instructions in the PR template --- .github/PULL_REQUEST_TEMPLATE.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index dc37a8ed124..60054b35ff2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -7,6 +7,15 @@ v ✰ Thanks for creating a PR! ✰ closes: #XXXX refs: #XXXX + + ## Description From d69e9652bcd845090d3c47c8724113856c707dae Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Tue, 22 Oct 2024 18:44:41 -0400 Subject: [PATCH 12/56] chore(.github): Remove German Capitalization of "issue" in the PR template --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 60054b35ff2..e0728e65b5d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,7 +2,7 @@ v ✰ Thanks for creating a PR! ✰ ☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > --> - + closes: #XXXX refs: #XXXX From bda22de3bc7c753756429a246a70a02c00124407 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Thu, 24 Oct 2024 13:49:52 -0400 Subject: [PATCH 13/56] test(a3p-integration): Update to agoric-upgrade-17 --- a3p-integration/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/a3p-integration/package.json b/a3p-integration/package.json index a3f253cbf7c..ee9bb0a570a 100644 --- a/a3p-integration/package.json +++ b/a3p-integration/package.json @@ -1,7 +1,7 @@ { "private": true, "agoricSyntheticChain": { - "fromTag": "use-vaults-auctions" + "fromTag": "use-upgrade-17" }, "scripts": { "build": "yarn run build:sdk && yarn run build:submissions && yarn run build:synthetic-chain", From b75e1c272d44a5229d5b0f2fe6b46d149603e6fc Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Fri, 25 Oct 2024 08:25:35 -0400 Subject: [PATCH 14/56] chore(cosmos): Remove upgrade-17 core proposals from upgrade.go --- golang/cosmos/app/upgrade.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/golang/cosmos/app/upgrade.go b/golang/cosmos/app/upgrade.go index e5c2fa97a98..02d38bed7b2 100644 --- a/golang/cosmos/app/upgrade.go +++ b/golang/cosmos/app/upgrade.go @@ -162,18 +162,6 @@ func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Conte vm.CoreProposalStepForModules( "@agoric/builders/scripts/vats/revive-kread.js", ), - vm.CoreProposalStepForModules( - // Upgrade to new liveslots for repaired vow usage. - "@agoric/builders/scripts/vats/upgrade-orch-core.js", - ), - vm.CoreProposalStepForModules( - // Upgrade to new liveslots and support vows. - "@agoric/builders/scripts/smart-wallet/build-wallet-factory2-upgrade.js", - ), - vm.CoreProposalStepForModules( - // Create vat-orchestration. - "@agoric/builders/scripts/vats/init-orchestration.js", - ), } } From 4301629f678cfb2bda3b0e18dad90cc5ae097bf3 Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Tue, 22 Oct 2024 13:34:35 -0700 Subject: [PATCH 15/56] chore: update upgrade.go to include priceFeed coreEvals extracted getVariantFromUpgradeName --- golang/cosmos/app/upgrade.go | 53 ++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/golang/cosmos/app/upgrade.go b/golang/cosmos/app/upgrade.go index 02d38bed7b2..4b5565045c0 100644 --- a/golang/cosmos/app/upgrade.go +++ b/golang/cosmos/app/upgrade.go @@ -105,19 +105,24 @@ func buildProposalStepWithArgs(moduleName string, entrypoint string, opts map[st return vm.CoreProposalStepForModules(proposal), nil } -func replaceElectorateCoreProposalStep(upgradeName string) (vm.CoreProposalStep, error) { - var variant string - - switch validUpgradeName(upgradeName) { - case "UNRELEASED_A3P_INTEGRATION": - variant = "A3P_INTEGRATION" - case "UNRELEASED_main": - variant = "MAINNET" - case "UNRELEASED_devnet": - variant = "DEVNET" +func getVariantFromUpgradeName(upgradeName string) string { + switch upgradeName { + case "UNRELEASED_A3P_INTEGRATION": + return "A3P_INTEGRATION" + case "UNRELEASED_main": + return "MAINNET" + case "UNRELEASED_devnet": + return "DEVNET" // Noupgrade for this version. - case "UNRELEASED_BASIC": - } + case "UNRELEASED_BASIC": + return "" + default: + return "" + } +} + +func replaceElectorateCoreProposalStep(upgradeName string) (vm.CoreProposalStep, error) { + variant := getVariantFromUpgradeName(upgradeName) return buildProposalStepWithArgs( "@agoric/builders/scripts/inter-protocol/replace-electorate-core.js", @@ -128,6 +133,18 @@ func replaceElectorateCoreProposalStep(upgradeName string) (vm.CoreProposalStep, ) } +func replacePriceFeedsCoreProposal(upgradeName string) (vm.CoreProposalStep, error) { + variant := getVariantFromUpgradeName(upgradeName) + + return buildProposalStepWithArgs( + "@agoric/builders/scripts/inter-protocol/updatePriceFeeds.js", + "defaultProposalBuilder", + map[string]any{ + "variant": variant, + }, + ) +} + // unreleasedUpgradeHandler performs standard upgrade actions plus custom actions for the unreleased upgrade. func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Context, upgradetypes.Plan, module.VersionMap) (module.VersionMap, error) { return func(ctx sdk.Context, plan upgradetypes.Plan, fromVm module.VersionMap) (module.VersionMap, error) { @@ -150,10 +167,22 @@ func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Conte return nil, err } + priceFeedUpdate, err := replacePriceFeedsCoreProposal(targetUpgrade) + if err != nil { + return nil, err + } + // Each CoreProposalStep runs sequentially, and can be constructed from // one or more modules executing in parallel within the step. CoreProposalSteps = []vm.CoreProposalStep{ replaceElectorateStep, + priceFeedUpdate, + vm.CoreProposalStepForModules( + "@agoric/builders/scripts/vats/add-auction.js", + ), + vm.CoreProposalStepForModules( + "@agoric/builders/scripts/vats/upgradeVaults.js", + ), vm.CoreProposalStepForModules( // Upgrade Zoe (no new ZCF needed). "@agoric/builders/scripts/vats/upgrade-zoe.js", From 1f7c1daac15f938d3e68c556ee8c8af303d107b8 Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Tue, 22 Oct 2024 13:39:12 -0700 Subject: [PATCH 16/56] test: move priceFeed tests into n:upgrade-next to reflect swUpgrade --- .../proposals/n:upgrade-next/agoric-tools.js | 102 +++++++++++- .../proposals/n:upgrade-next/eval.sh | 15 ++ .../proposals/n:upgrade-next/package.json | 5 +- .../n:upgrade-next/priceFeedUpdate.test.js | 155 ++++++++++++++++++ .../n:upgrade-next/resetChargingPeriod.js | 41 +++++ .../proposals/n:upgrade-next/test.sh | 5 +- 6 files changed, 319 insertions(+), 4 deletions(-) create mode 100755 a3p-integration/proposals/n:upgrade-next/eval.sh create mode 100644 a3p-integration/proposals/n:upgrade-next/priceFeedUpdate.test.js create mode 100755 a3p-integration/proposals/n:upgrade-next/resetChargingPeriod.js diff --git a/a3p-integration/proposals/n:upgrade-next/agoric-tools.js b/a3p-integration/proposals/n:upgrade-next/agoric-tools.js index 205fe47df4f..9e69836862d 100644 --- a/a3p-integration/proposals/n:upgrade-next/agoric-tools.js +++ b/a3p-integration/proposals/n:upgrade-next/agoric-tools.js @@ -1,4 +1,10 @@ -import { queryVstorage } from '@agoric/synthetic-chain'; +import assert from 'node:assert'; +import { + queryVstorage, + agops, + agoric, + executeOffer, +} from '@agoric/synthetic-chain'; import { makeMarshal, Remotable } from '@endo/marshal'; export const GOV4ADDR = 'agoric1c9gyu460lu70rtcdp95vummd6032psmpdx7wdy'; @@ -31,3 +37,97 @@ export const queryVstorageFormatted = async (path, index = -1) => { const formattedDataAtIndex = JSON.parse(formattedData.values.at(index)); return marshaller.fromCapData(formattedDataAtIndex); }; + +export const generateVaultDirectorParamChange = async ( + previousOfferId, + voteDur, + params, + paramsPath, +) => { + const voteDurSec = BigInt(voteDur); + const toSec = ms => BigInt(Math.round(ms / 1000)); + + const id = `propose-${Date.now()}`; + const deadline = toSec(Date.now()) + voteDurSec; + + const zip = (xs, ys) => xs.map((x, i) => [x, ys[i]]); + // KLUDGE: partial deconstruction of smallCaps values + const fromSmallCapsEntries = txt => { + 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 = []; // XXX global mutable state + const smallCaps = { + Nat: n => `+${n}`, + // XXX mutates obj + ref: obj => { + if (obj.ix) return obj.ix; + const ix = slots.length; + slots.push(obj.boardID); + obj.ix = `$${ix}.Alleged: ${obj.iface}`; + return obj.ix; + }, + }; + + await null; + const instance = fromSmallCapsEntries( + await agoric.follow('-lF', ':published.agoricNames.instance', '-o', 'text'), + ); + assert(instance.VaultFactory); + + const body = { + method: 'executeOffer', + offer: { + id, + invitationSpec: { + invitationMakerName: 'VoteOnParamChange', + previousOffer: previousOfferId, + source: 'continuing', + }, + offerArgs: { + deadline: smallCaps.Nat(deadline), + instance: smallCaps.ref(instance.VaultFactory), + params, + path: paramsPath, + }, + proposal: {}, + }, + }; + + const capData = { body: `#${JSON.stringify(body)}`, slots }; + return JSON.stringify(capData); +}; + +export const proposeVaultDirectorParamChange = async ( + address, + params, + path, +) => { + const charterAcceptOfferId = await agops.ec( + 'find-continuing-id', + '--for', + `${'charter\\ member\\ invitation'}`, + '--from', + address, + ); + + return executeOffer( + address, + generateVaultDirectorParamChange(charterAcceptOfferId, 30, params, path), + ); +}; + +export const voteForNewParams = (accounts, position) => { + return Promise.all( + accounts.map(account => + agops.ec('vote', '--forPosition', position, '--send-from', account), + ), + ); +}; diff --git a/a3p-integration/proposals/n:upgrade-next/eval.sh b/a3p-integration/proposals/n:upgrade-next/eval.sh new file mode 100755 index 00000000000..c44d11c5a35 --- /dev/null +++ b/a3p-integration/proposals/n:upgrade-next/eval.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Exit when any command fails +set -uxeo pipefail + +# The upgrade-vaults proposal needs to know the existing vaultDirector +# parameters in order to cleanly upgrade the contract. The governance notifier +# it relies on doesn't give the most recent value if there were no updates to +# the parameters, so we'll do a governance action to reset them to their current +# values so the notifier will work. + +./resetChargingPeriod.js + +cp /usr/src/upgrade-test-scripts/eval_submission.js . +./eval_submission.js diff --git a/a3p-integration/proposals/n:upgrade-next/package.json b/a3p-integration/proposals/n:upgrade-next/package.json index 465160effb6..69134383f62 100644 --- a/a3p-integration/proposals/n:upgrade-next/package.json +++ b/a3p-integration/proposals/n:upgrade-next/package.json @@ -12,7 +12,10 @@ "vats/upgrade-provisionPool.js upgrade-provisionPool", "testing/add-LEMONS.js add-LEMONS", "testing/add-OLIVES.js add-OLIVES", - "inter-protocol/replace-electorate-core.js replace-electorate A3P_INTEGRATION" + "inter-protocol/replace-electorate-core.js replace-electorate A3P_INTEGRATION", + "inter-protocol/updatePriceFeeds.js price-feeds A3P_INTEGRATION", + "vats/add-auction.js price-feeds", + "vats/upgradeVaults.js price-feeds" ], "type": "Software Upgrade Proposal" }, diff --git a/a3p-integration/proposals/n:upgrade-next/priceFeedUpdate.test.js b/a3p-integration/proposals/n:upgrade-next/priceFeedUpdate.test.js new file mode 100644 index 00000000000..5cbacd019ad --- /dev/null +++ b/a3p-integration/proposals/n:upgrade-next/priceFeedUpdate.test.js @@ -0,0 +1,155 @@ +import test from 'ava'; + +import { + agops, + ATOM_DENOM, + bankSend, + createBid, + generateOracleMap, + getDetailsMatchingVats, + getInstanceBoardId, + getISTBalance, + getLiveOffers, + getPriceQuote, + getVaultPrices, + getVatDetails, + openVault, + pushPrices, + registerOraclesForBrand, + USER1ADDR, +} from '@agoric/synthetic-chain'; + +import { BID_OFFER_ID } from './agd-tools.js'; + +export const checkForOracle = async (t, name) => { + const instanceName = `${name}-USD price feed`; + const instance = await getInstanceBoardId(instanceName); + t.truthy(instance); +}; + +const checkPriceFeedVatsUpdated = async t => { + const atomDetails = await getVatDetails('ATOM-USD_price_feed'); + // both the original and the new ATOM vault are incarnation 0 + t.is(atomDetails.incarnation, 0); + const stAtomDetails = await getVatDetails('stATOM'); + t.is(stAtomDetails.incarnation, 0); + await checkForOracle(t, 'ATOM'); + await checkForOracle(t, 'stATOM'); +}; + +console.log('adding oracle for each brand'); +const oraclesByBrand = generateOracleMap('f-priceFeeds', ['ATOM', 'stATOM']); +await registerOraclesForBrand('ATOM', oraclesByBrand); +await registerOraclesForBrand('stATOM', oraclesByBrand); + +let roundId = 1; + +const tryPushPrices = async t => { + // There are no old prices for the other currencies. + // const atomOutPre = await getPriceQuote('ATOM'); + // t.is(atomOutPre, '+12010000'); + // const stAtomOutPre = await getPriceQuote('stATOM'); + // t.is(stAtomOutPre, '+12010000'); + + t.log('pushing new prices'); + await pushPrices(13.4, 'ATOM', oraclesByBrand, roundId); + await pushPrices(13.7, 'stATOM', oraclesByBrand, roundId); + roundId += 1; + + t.log('awaiting new quotes'); + const atomOut = await getPriceQuote('ATOM'); + t.is(atomOut, '+13400000'); + const stAtomOut = await getPriceQuote('stATOM'); + t.is(stAtomOut, '+13700000'); +}; + +const createNewBid = async t => { + await createBid('20', USER1ADDR, BID_OFFER_ID); + const liveOffer = await getLiveOffers(USER1ADDR); + t.true(liveOffer[0].includes(BID_OFFER_ID)); +}; + +const openMarginalVault = async t => { + let user1IST = await getISTBalance(USER1ADDR); + await bankSend(USER1ADDR, `20000000${ATOM_DENOM}`); + const currentVaults = await agops.vaults('list', '--from', USER1ADDR); + + t.log('opening a vault'); + await openVault(USER1ADDR, 5, 10); + user1IST += 5; + const istBalanceAfterVaultOpen = await getISTBalance(USER1ADDR); + t.is(istBalanceAfterVaultOpen, user1IST); + + const activeVaultsAfter = await agops.vaults('list', '--from', USER1ADDR); + t.log(currentVaults, activeVaultsAfter); + t.true( + activeVaultsAfter.length > currentVaults.length, + `vaults count should increase, ${activeVaultsAfter.length}, ${currentVaults.length}`, + ); +}; + +const triggerAuction = async t => { + await pushPrices(5.2, 'ATOM', oraclesByBrand, roundId); + + const atomOut = await getPriceQuote('ATOM'); + t.is(atomOut, '+5200000'); +}; + +const checkNewAuctionVat = async t => { + const details = await getDetailsMatchingVats('auctioneer'); + // This query matches both the auction and its governor, so double the count + t.is(Object.keys(details).length, 3 * 2); +}; + +const countPriceFeedVats = async t => { + // price_feed and governor, old and new for two tokens + const priceFeedDetails = await getDetailsMatchingVats('price_feed'); + t.is(Object.keys(priceFeedDetails).length, 8); + + // Two old SPAs, and two new ones + const details = await getDetailsMatchingVats('scaledPriceAuthority'); + t.is(Object.keys(details).length, 4); + + // ATOM vat name is something like zcf-DEADBEEF-ATOM_USD_price_feed + // initial '-' distinguishes this from stAOM + const atomDetails = await getDetailsMatchingVats('-ATOM-USD_price_feed'); + t.is(Object.keys(atomDetails).length, 4); + + const stAtomDetails = await getVatDetails('stATOM'); + t.is(Object.keys(stAtomDetails).length, 4); + await Promise.all([checkForOracle(t, 'ATOM'), checkForOracle(t, 'stATOM')]); +}; + +const verifyVaultPriceUpdate = async t => { + const ATOMManagerIndex = 0; + const quote = await getVaultPrices(ATOMManagerIndex); + t.true(quote.value[0].amountIn.brand.includes(' ATOM ')); + t.is(quote.value[0].amountOut.value, '+5200000'); +}; + +// test.serial() isn't guaranteed to run tests in order, so we run the intended tests here +test('liquidation post upgrade', async t => { + t.log('starting upgrade vaults test'); + await checkPriceFeedVatsUpdated(t); + + t.log('starting pushPrices'); + await tryPushPrices(t); + + t.log('create a new Bid for the auction'); + await createNewBid(t); + + t.log('open a marginal vault'); + await openMarginalVault(t); + + t.log('trigger Auction'); + await triggerAuction(t); + + t.log('check new auction'); + await checkNewAuctionVat(t); + + t.log('count vats'); + await countPriceFeedVats(t); + + t.log('verify Vault priceUpdate'); + await verifyVaultPriceUpdate(t); +}); diff --git a/a3p-integration/proposals/n:upgrade-next/resetChargingPeriod.js b/a3p-integration/proposals/n:upgrade-next/resetChargingPeriod.js new file mode 100755 index 00000000000..c77494dddc6 --- /dev/null +++ b/a3p-integration/proposals/n:upgrade-next/resetChargingPeriod.js @@ -0,0 +1,41 @@ +#!/usr/bin/env node + +/* global setTimeout */ + +import { + getQuoteBody, + GOV1ADDR, + GOV2ADDR, + GOV3ADDR, +} from '@agoric/synthetic-chain'; +import { + proposeVaultDirectorParamChange, + voteForNewParams, +} from './agoric-tools.js'; + +const GOV_ADDRESSES = [GOV1ADDR, GOV2ADDR, GOV3ADDR]; + +const readChargingPeriod = async () => { + const governanceBody = await getQuoteBody( + 'published.vaultFactory.governance', + ); + const period = + governanceBody.current.ChargingPeriod.value.match(/\+?(\d+)/)[1]; + return `+${period}`; +}; + +const setChargingPeriod = async period => { + const params = { + ChargingPeriod: period, + }; + + const path = { paramPath: { key: 'governedParams' } }; + + await proposeVaultDirectorParamChange(GOV1ADDR, params, path); + await voteForNewParams(GOV_ADDRESSES, 0); + + await new Promise(r => setTimeout(r, 65000)); +}; + +const period = await readChargingPeriod(); +await setChargingPeriod(period); diff --git a/a3p-integration/proposals/n:upgrade-next/test.sh b/a3p-integration/proposals/n:upgrade-next/test.sh index 56492d366f6..9d703ba45c3 100755 --- a/a3p-integration/proposals/n:upgrade-next/test.sh +++ b/a3p-integration/proposals/n:upgrade-next/test.sh @@ -3,12 +3,13 @@ # Place here any test that should be executed using the executed proposal. # The effects of this step are not persisted in further proposal layers. -GLOBIGNORE=initial.test.js +# suppress file names from glob that run earlier +GLOBIGNORE=initial.test.js replaceElectorate.test.js yarn ava ./replaceElectorate.test.js # test the state right after upgrade yarn ava initial.test.js -# test more, in ways that changes system state +# test more, in ways that change system state yarn ava ./*.test.js From 74e3b042ef98639a5de7bf888d425f5531e487a6 Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Tue, 22 Oct 2024 13:52:41 -0700 Subject: [PATCH 17/56] test: drop f:replace-price-feeds/ tests to make swUpgrade --- .../f:replace-price-feeds/.yarnrc.yml | 1 - .../proposals/f:replace-price-feeds/README.md | 10 - .../f:replace-price-feeds/agd-tools.js | 23 - .../f:replace-price-feeds/agoric-tools.js | 96 - .../proposals/f:replace-price-feeds/eval.sh | 15 - .../f:replace-price-feeds/package.json | 34 - .../priceFeedUpdate.test.js | 155 -- .../resetChargingPeriod.js | 41 - .../proposals/f:replace-price-feeds/test.sh | 6 - .../proposals/f:replace-price-feeds/yarn.lock | 2345 ----------------- .../proposals/n:upgrade-next/package.json | 8 +- 11 files changed, 7 insertions(+), 2727 deletions(-) delete mode 100644 a3p-integration/proposals/f:replace-price-feeds/.yarnrc.yml delete mode 100644 a3p-integration/proposals/f:replace-price-feeds/README.md delete mode 100644 a3p-integration/proposals/f:replace-price-feeds/agd-tools.js delete mode 100644 a3p-integration/proposals/f:replace-price-feeds/agoric-tools.js delete mode 100755 a3p-integration/proposals/f:replace-price-feeds/eval.sh delete mode 100644 a3p-integration/proposals/f:replace-price-feeds/package.json delete mode 100644 a3p-integration/proposals/f:replace-price-feeds/priceFeedUpdate.test.js delete mode 100755 a3p-integration/proposals/f:replace-price-feeds/resetChargingPeriod.js delete mode 100755 a3p-integration/proposals/f:replace-price-feeds/test.sh delete mode 100644 a3p-integration/proposals/f:replace-price-feeds/yarn.lock diff --git a/a3p-integration/proposals/f:replace-price-feeds/.yarnrc.yml b/a3p-integration/proposals/f:replace-price-feeds/.yarnrc.yml deleted file mode 100644 index 3186f3f0795..00000000000 --- a/a3p-integration/proposals/f:replace-price-feeds/.yarnrc.yml +++ /dev/null @@ -1 +0,0 @@ -nodeLinker: node-modules diff --git a/a3p-integration/proposals/f:replace-price-feeds/README.md b/a3p-integration/proposals/f:replace-price-feeds/README.md deleted file mode 100644 index 414bb07fe81..00000000000 --- a/a3p-integration/proposals/f:replace-price-feeds/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# CoreEvalProposal to replace existing price_feed and scaledPriceAuthority vats -# with new contracts. Auctions will need to be replaced, and Vaults will need to -# get at least a null upgrade in order to make use of the new prices. Oracle -# operators will need to accept new invitations, and sync to the roundId (0) of -# the new contracts in order to feed the new pipelines. - -The `submission` for this proposal is automatically generated during `yarn build` -in [a3p-integration](../..) using the code in agoric-sdk through -[build-all-submissions.sh](../../scripts/build-all-submissions.sh) and -[build-submission.sh](../../scripts/build-submission.sh). diff --git a/a3p-integration/proposals/f:replace-price-feeds/agd-tools.js b/a3p-integration/proposals/f:replace-price-feeds/agd-tools.js deleted file mode 100644 index c94718bc1ba..00000000000 --- a/a3p-integration/proposals/f:replace-price-feeds/agd-tools.js +++ /dev/null @@ -1,23 +0,0 @@ -import { agd } from '@agoric/synthetic-chain'; - -export const BID_OFFER_ID = 'bid-vaultUpgrade-test3'; - -/** @param {string} path */ -export const queryVstorage = path => - agd.query('vstorage', 'data', '--output', 'json', path); - -export const getOracleInstance = async price => { - const instanceRec = await queryVstorage(`published.agoricNames.instance`); - - const value = JSON.parse(instanceRec.value); - const body = JSON.parse(value.values.at(-1)); - - const feeds = JSON.parse(body.body.substring(1)); - const feedName = `${price}-USD price feed`; - - const key = Object.keys(feeds).find(k => feeds[k][0] === feedName); - if (key) { - return body.slots[key]; - } - return null; -}; diff --git a/a3p-integration/proposals/f:replace-price-feeds/agoric-tools.js b/a3p-integration/proposals/f:replace-price-feeds/agoric-tools.js deleted file mode 100644 index 1c6e10ac8f6..00000000000 --- a/a3p-integration/proposals/f:replace-price-feeds/agoric-tools.js +++ /dev/null @@ -1,96 +0,0 @@ -import assert from 'node:assert'; -import { agops, agoric, executeOffer } from '@agoric/synthetic-chain'; - -export const generateVaultDirectorParamChange = async ( - previousOfferId, - voteDur, - params, - paramsPath, -) => { - const voteDurSec = BigInt(voteDur); - const toSec = ms => BigInt(Math.round(ms / 1000)); - - const id = `propose-${Date.now()}`; - const deadline = toSec(Date.now()) + voteDurSec; - - const zip = (xs, ys) => xs.map((x, i) => [x, ys[i]]); - // KLUDGE: partial deconstruction of smallCaps values - const fromSmallCapsEntries = txt => { - 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 = []; // XXX global mutable state - const smallCaps = { - Nat: n => `+${n}`, - // XXX mutates obj - ref: obj => { - if (obj.ix) return obj.ix; - const ix = slots.length; - slots.push(obj.boardID); - obj.ix = `$${ix}.Alleged: ${obj.iface}`; - return obj.ix; - }, - }; - - await null; - const instance = fromSmallCapsEntries( - await agoric.follow('-lF', ':published.agoricNames.instance', '-o', 'text'), - ); - assert(instance.VaultFactory); - - const body = { - method: 'executeOffer', - offer: { - id, - invitationSpec: { - invitationMakerName: 'VoteOnParamChange', - previousOffer: previousOfferId, - source: 'continuing', - }, - offerArgs: { - deadline: smallCaps.Nat(deadline), - instance: smallCaps.ref(instance.VaultFactory), - params, - path: paramsPath, - }, - proposal: {}, - }, - }; - - const capData = { body: `#${JSON.stringify(body)}`, slots }; - return JSON.stringify(capData); -}; - -export const proposeVaultDirectorParamChange = async ( - address, - params, - path, -) => { - const charterAcceptOfferId = await agops.ec( - 'find-continuing-id', - '--for', - `${'charter\\ member\\ invitation'}`, - '--from', - address, - ); - - return executeOffer( - address, - generateVaultDirectorParamChange(charterAcceptOfferId, 30, params, path), - ); -}; - -export const voteForNewParams = (accounts, position) => { - return Promise.all( - accounts.map(account => - agops.ec('vote', '--forPosition', position, '--send-from', account), - ), - ); -}; diff --git a/a3p-integration/proposals/f:replace-price-feeds/eval.sh b/a3p-integration/proposals/f:replace-price-feeds/eval.sh deleted file mode 100755 index c44d11c5a35..00000000000 --- a/a3p-integration/proposals/f:replace-price-feeds/eval.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -# Exit when any command fails -set -uxeo pipefail - -# The upgrade-vaults proposal needs to know the existing vaultDirector -# parameters in order to cleanly upgrade the contract. The governance notifier -# it relies on doesn't give the most recent value if there were no updates to -# the parameters, so we'll do a governance action to reset them to their current -# values so the notifier will work. - -./resetChargingPeriod.js - -cp /usr/src/upgrade-test-scripts/eval_submission.js . -./eval_submission.js diff --git a/a3p-integration/proposals/f:replace-price-feeds/package.json b/a3p-integration/proposals/f:replace-price-feeds/package.json deleted file mode 100644 index ca4637e9cdb..00000000000 --- a/a3p-integration/proposals/f:replace-price-feeds/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "agoricProposal": { - "source": "subdir", - "sdk-generate": [ - "inter-protocol/updatePriceFeeds.js submission A3P_INTEGRATION", - "vats/add-auction.js submission", - "vats/upgradeVaults.js submission", - "inter-protocol/updatePriceFeeds.js submission/main main", - "vats/add-auction.js submission/main", - "vats/upgradeVaults.js submission/main", - "inter-protocol/updatePriceFeeds.js submission/devnet devnet", - "vats/add-auction.js submission/devnet", - "vats/upgradeVaults.js submission/devnet" - ], - "type": "/agoric.swingset.CoreEvalProposal" - }, - "type": "module", - "license": "Apache-2.0", - "dependencies": { - "@agoric/synthetic-chain": "^0.3.0", - "ava": "^5.3.1" - }, - "ava": { - "concurrency": 1, - "timeout": "2m", - "files": [ - "!submission" - ] - }, - "scripts": { - "agops": "yarn --cwd /usr/src/agoric-sdk/ --silent agops" - }, - "packageManager": "yarn@4.2.2" -} diff --git a/a3p-integration/proposals/f:replace-price-feeds/priceFeedUpdate.test.js b/a3p-integration/proposals/f:replace-price-feeds/priceFeedUpdate.test.js deleted file mode 100644 index 5cbacd019ad..00000000000 --- a/a3p-integration/proposals/f:replace-price-feeds/priceFeedUpdate.test.js +++ /dev/null @@ -1,155 +0,0 @@ -import test from 'ava'; - -import { - agops, - ATOM_DENOM, - bankSend, - createBid, - generateOracleMap, - getDetailsMatchingVats, - getInstanceBoardId, - getISTBalance, - getLiveOffers, - getPriceQuote, - getVaultPrices, - getVatDetails, - openVault, - pushPrices, - registerOraclesForBrand, - USER1ADDR, -} from '@agoric/synthetic-chain'; - -import { BID_OFFER_ID } from './agd-tools.js'; - -export const checkForOracle = async (t, name) => { - const instanceName = `${name}-USD price feed`; - const instance = await getInstanceBoardId(instanceName); - t.truthy(instance); -}; - -const checkPriceFeedVatsUpdated = async t => { - const atomDetails = await getVatDetails('ATOM-USD_price_feed'); - // both the original and the new ATOM vault are incarnation 0 - t.is(atomDetails.incarnation, 0); - const stAtomDetails = await getVatDetails('stATOM'); - t.is(stAtomDetails.incarnation, 0); - await checkForOracle(t, 'ATOM'); - await checkForOracle(t, 'stATOM'); -}; - -console.log('adding oracle for each brand'); -const oraclesByBrand = generateOracleMap('f-priceFeeds', ['ATOM', 'stATOM']); -await registerOraclesForBrand('ATOM', oraclesByBrand); -await registerOraclesForBrand('stATOM', oraclesByBrand); - -let roundId = 1; - -const tryPushPrices = async t => { - // There are no old prices for the other currencies. - // const atomOutPre = await getPriceQuote('ATOM'); - // t.is(atomOutPre, '+12010000'); - // const stAtomOutPre = await getPriceQuote('stATOM'); - // t.is(stAtomOutPre, '+12010000'); - - t.log('pushing new prices'); - await pushPrices(13.4, 'ATOM', oraclesByBrand, roundId); - await pushPrices(13.7, 'stATOM', oraclesByBrand, roundId); - roundId += 1; - - t.log('awaiting new quotes'); - const atomOut = await getPriceQuote('ATOM'); - t.is(atomOut, '+13400000'); - const stAtomOut = await getPriceQuote('stATOM'); - t.is(stAtomOut, '+13700000'); -}; - -const createNewBid = async t => { - await createBid('20', USER1ADDR, BID_OFFER_ID); - const liveOffer = await getLiveOffers(USER1ADDR); - t.true(liveOffer[0].includes(BID_OFFER_ID)); -}; - -const openMarginalVault = async t => { - let user1IST = await getISTBalance(USER1ADDR); - await bankSend(USER1ADDR, `20000000${ATOM_DENOM}`); - const currentVaults = await agops.vaults('list', '--from', USER1ADDR); - - t.log('opening a vault'); - await openVault(USER1ADDR, 5, 10); - user1IST += 5; - const istBalanceAfterVaultOpen = await getISTBalance(USER1ADDR); - t.is(istBalanceAfterVaultOpen, user1IST); - - const activeVaultsAfter = await agops.vaults('list', '--from', USER1ADDR); - t.log(currentVaults, activeVaultsAfter); - t.true( - activeVaultsAfter.length > currentVaults.length, - `vaults count should increase, ${activeVaultsAfter.length}, ${currentVaults.length}`, - ); -}; - -const triggerAuction = async t => { - await pushPrices(5.2, 'ATOM', oraclesByBrand, roundId); - - const atomOut = await getPriceQuote('ATOM'); - t.is(atomOut, '+5200000'); -}; - -const checkNewAuctionVat = async t => { - const details = await getDetailsMatchingVats('auctioneer'); - // This query matches both the auction and its governor, so double the count - t.is(Object.keys(details).length, 3 * 2); -}; - -const countPriceFeedVats = async t => { - // price_feed and governor, old and new for two tokens - const priceFeedDetails = await getDetailsMatchingVats('price_feed'); - t.is(Object.keys(priceFeedDetails).length, 8); - - // Two old SPAs, and two new ones - const details = await getDetailsMatchingVats('scaledPriceAuthority'); - t.is(Object.keys(details).length, 4); - - // ATOM vat name is something like zcf-DEADBEEF-ATOM_USD_price_feed - // initial '-' distinguishes this from stAOM - const atomDetails = await getDetailsMatchingVats('-ATOM-USD_price_feed'); - t.is(Object.keys(atomDetails).length, 4); - - const stAtomDetails = await getVatDetails('stATOM'); - t.is(Object.keys(stAtomDetails).length, 4); - await Promise.all([checkForOracle(t, 'ATOM'), checkForOracle(t, 'stATOM')]); -}; - -const verifyVaultPriceUpdate = async t => { - const ATOMManagerIndex = 0; - const quote = await getVaultPrices(ATOMManagerIndex); - t.true(quote.value[0].amountIn.brand.includes(' ATOM ')); - t.is(quote.value[0].amountOut.value, '+5200000'); -}; - -// test.serial() isn't guaranteed to run tests in order, so we run the intended tests here -test('liquidation post upgrade', async t => { - t.log('starting upgrade vaults test'); - await checkPriceFeedVatsUpdated(t); - - t.log('starting pushPrices'); - await tryPushPrices(t); - - t.log('create a new Bid for the auction'); - await createNewBid(t); - - t.log('open a marginal vault'); - await openMarginalVault(t); - - t.log('trigger Auction'); - await triggerAuction(t); - - t.log('check new auction'); - await checkNewAuctionVat(t); - - t.log('count vats'); - await countPriceFeedVats(t); - - t.log('verify Vault priceUpdate'); - await verifyVaultPriceUpdate(t); -}); diff --git a/a3p-integration/proposals/f:replace-price-feeds/resetChargingPeriod.js b/a3p-integration/proposals/f:replace-price-feeds/resetChargingPeriod.js deleted file mode 100755 index c77494dddc6..00000000000 --- a/a3p-integration/proposals/f:replace-price-feeds/resetChargingPeriod.js +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env node - -/* global setTimeout */ - -import { - getQuoteBody, - GOV1ADDR, - GOV2ADDR, - GOV3ADDR, -} from '@agoric/synthetic-chain'; -import { - proposeVaultDirectorParamChange, - voteForNewParams, -} from './agoric-tools.js'; - -const GOV_ADDRESSES = [GOV1ADDR, GOV2ADDR, GOV3ADDR]; - -const readChargingPeriod = async () => { - const governanceBody = await getQuoteBody( - 'published.vaultFactory.governance', - ); - const period = - governanceBody.current.ChargingPeriod.value.match(/\+?(\d+)/)[1]; - return `+${period}`; -}; - -const setChargingPeriod = async period => { - const params = { - ChargingPeriod: period, - }; - - const path = { paramPath: { key: 'governedParams' } }; - - await proposeVaultDirectorParamChange(GOV1ADDR, params, path); - await voteForNewParams(GOV_ADDRESSES, 0); - - await new Promise(r => setTimeout(r, 65000)); -}; - -const period = await readChargingPeriod(); -await setChargingPeriod(period); diff --git a/a3p-integration/proposals/f:replace-price-feeds/test.sh b/a3p-integration/proposals/f:replace-price-feeds/test.sh deleted file mode 100755 index 23a194f7f79..00000000000 --- a/a3p-integration/proposals/f:replace-price-feeds/test.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -# Place here any test that should be executed using the proposal. -# The effects of this step are not persisted in further layers. - -yarn ava ./*.test.js diff --git a/a3p-integration/proposals/f:replace-price-feeds/yarn.lock b/a3p-integration/proposals/f:replace-price-feeds/yarn.lock deleted file mode 100644 index ee7a03e8e81..00000000000 --- a/a3p-integration/proposals/f:replace-price-feeds/yarn.lock +++ /dev/null @@ -1,2345 +0,0 @@ -# This file is generated by running "yarn install" inside your project. -# Manual changes might be lost - proceed with caution! - -__metadata: - version: 8 - cacheKey: 10c0 - -"@agoric/synthetic-chain@npm:^0.3.0": - version: 0.3.0 - resolution: "@agoric/synthetic-chain@npm:0.3.0" - dependencies: - "@endo/zip": "npm:^1.0.7" - better-sqlite3: "npm:^9.6.0" - chalk: "npm:^5.3.0" - cosmjs-types: "npm:^0.9.0" - execa: "npm:^9.3.1" - bin: - synthetic-chain: dist/cli/cli.js - checksum: 10c0/17c6241bdc48b8a2a7608c9d4d7c0a0c76fb10d4ee44a31a1150104a792bcd1133f4b1a7e8ab26673a07450b3ceabccd9911999117568221b49221b6ee4306a1 - languageName: node - linkType: hard - -"@endo/zip@npm:^1.0.7": - version: 1.0.7 - resolution: "@endo/zip@npm:1.0.7" - checksum: 10c0/a1c0d155448ce877012b34c8fe8cd3a58de9eb807514c81cddeebb802ee8e552b27d8a9a40fab3f3e4c49e0cb7fea6902fa1dd12a23ff6f30b56161fc3edc1f8 - languageName: node - linkType: hard - -"@isaacs/cliui@npm:^8.0.2": - version: 8.0.2 - resolution: "@isaacs/cliui@npm:8.0.2" - dependencies: - string-width: "npm:^5.1.2" - string-width-cjs: "npm:string-width@^4.2.0" - strip-ansi: "npm:^7.0.1" - strip-ansi-cjs: "npm:strip-ansi@^6.0.1" - wrap-ansi: "npm:^8.1.0" - wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" - checksum: 10c0/b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e - languageName: node - linkType: hard - -"@nodelib/fs.scandir@npm:2.1.5": - version: 2.1.5 - resolution: "@nodelib/fs.scandir@npm:2.1.5" - dependencies: - "@nodelib/fs.stat": "npm:2.0.5" - run-parallel: "npm:^1.1.9" - checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb - languageName: node - linkType: hard - -"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": - version: 2.0.5 - resolution: "@nodelib/fs.stat@npm:2.0.5" - checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d - languageName: node - linkType: hard - -"@nodelib/fs.walk@npm:^1.2.3": - version: 1.2.8 - resolution: "@nodelib/fs.walk@npm:1.2.8" - dependencies: - "@nodelib/fs.scandir": "npm:2.1.5" - fastq: "npm:^1.6.0" - checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 - languageName: node - linkType: hard - -"@npmcli/agent@npm:^2.0.0": - version: 2.2.2 - resolution: "@npmcli/agent@npm:2.2.2" - dependencies: - agent-base: "npm:^7.1.0" - http-proxy-agent: "npm:^7.0.0" - https-proxy-agent: "npm:^7.0.1" - lru-cache: "npm:^10.0.1" - socks-proxy-agent: "npm:^8.0.3" - checksum: 10c0/325e0db7b287d4154ecd164c0815c08007abfb07653cc57bceded17bb7fd240998a3cbdbe87d700e30bef494885eccc725ab73b668020811d56623d145b524ae - languageName: node - linkType: hard - -"@npmcli/fs@npm:^3.1.0": - version: 3.1.1 - resolution: "@npmcli/fs@npm:3.1.1" - dependencies: - semver: "npm:^7.3.5" - checksum: 10c0/c37a5b4842bfdece3d14dfdb054f73fe15ed2d3da61b34ff76629fb5b1731647c49166fd2a8bf8b56fcfa51200382385ea8909a3cbecdad612310c114d3f6c99 - languageName: node - linkType: hard - -"@pkgjs/parseargs@npm:^0.11.0": - version: 0.11.0 - resolution: "@pkgjs/parseargs@npm:0.11.0" - checksum: 10c0/5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd - languageName: node - linkType: hard - -"@sec-ant/readable-stream@npm:^0.4.1": - version: 0.4.1 - resolution: "@sec-ant/readable-stream@npm:0.4.1" - checksum: 10c0/64e9e9cf161e848067a5bf60cdc04d18495dc28bb63a8d9f8993e4dd99b91ad34e4b563c85de17d91ffb177ec17a0664991d2e115f6543e73236a906068987af - languageName: node - linkType: hard - -"@sindresorhus/merge-streams@npm:^4.0.0": - version: 4.0.0 - resolution: "@sindresorhus/merge-streams@npm:4.0.0" - checksum: 10c0/482ee543629aa1933b332f811a1ae805a213681ecdd98c042b1c1b89387df63e7812248bb4df3910b02b3cc5589d3d73e4393f30e197c9dde18046ccd471fc6b - languageName: node - linkType: hard - -"abbrev@npm:^2.0.0": - version: 2.0.0 - resolution: "abbrev@npm:2.0.0" - checksum: 10c0/f742a5a107473946f426c691c08daba61a1d15942616f300b5d32fd735be88fef5cba24201757b6c407fd564555fb48c751cfa33519b2605c8a7aadd22baf372 - languageName: node - linkType: hard - -"acorn-walk@npm:^8.2.0": - version: 8.3.4 - resolution: "acorn-walk@npm:8.3.4" - dependencies: - acorn: "npm:^8.11.0" - checksum: 10c0/76537ac5fb2c37a64560feaf3342023dadc086c46da57da363e64c6148dc21b57d49ace26f949e225063acb6fb441eabffd89f7a3066de5ad37ab3e328927c62 - languageName: node - linkType: hard - -"acorn@npm:^8.11.0, acorn@npm:^8.8.2": - version: 8.12.1 - resolution: "acorn@npm:8.12.1" - bin: - acorn: bin/acorn - checksum: 10c0/51fb26cd678f914e13287e886da2d7021f8c2bc0ccc95e03d3e0447ee278dd3b40b9c57dc222acd5881adcf26f3edc40901a4953403232129e3876793cd17386 - languageName: node - linkType: hard - -"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1": - version: 7.1.1 - resolution: "agent-base@npm:7.1.1" - dependencies: - debug: "npm:^4.3.4" - checksum: 10c0/e59ce7bed9c63bf071a30cc471f2933862044c97fd9958967bfe22521d7a0f601ce4ed5a8c011799d0c726ca70312142ae193bbebb60f576b52be19d4a363b50 - languageName: node - linkType: hard - -"aggregate-error@npm:^3.0.0": - version: 3.1.0 - resolution: "aggregate-error@npm:3.1.0" - dependencies: - clean-stack: "npm:^2.0.0" - indent-string: "npm:^4.0.0" - checksum: 10c0/a42f67faa79e3e6687a4923050e7c9807db3848a037076f791d10e092677d65c1d2d863b7848560699f40fc0502c19f40963fb1cd1fb3d338a7423df8e45e039 - languageName: node - linkType: hard - -"aggregate-error@npm:^4.0.0": - version: 4.0.1 - resolution: "aggregate-error@npm:4.0.1" - dependencies: - clean-stack: "npm:^4.0.0" - indent-string: "npm:^5.0.0" - checksum: 10c0/75fd739f5c4c60a667cce35ccaf0edf135e147ef0be9a029cab75de14ac9421779b15339d562e58d25b233ea0ef2bbd4c916f149fdbcb73c2b9a62209e611343 - languageName: node - linkType: hard - -"ansi-regex@npm:^5.0.1": - version: 5.0.1 - resolution: "ansi-regex@npm:5.0.1" - checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 - languageName: node - linkType: hard - -"ansi-regex@npm:^6.0.1": - version: 6.1.0 - resolution: "ansi-regex@npm:6.1.0" - checksum: 10c0/a91daeddd54746338478eef88af3439a7edf30f8e23196e2d6ed182da9add559c601266dbef01c2efa46a958ad6f1f8b176799657616c702b5b02e799e7fd8dc - languageName: node - linkType: hard - -"ansi-styles@npm:^4.0.0": - version: 4.3.0 - resolution: "ansi-styles@npm:4.3.0" - dependencies: - color-convert: "npm:^2.0.1" - checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 - languageName: node - linkType: hard - -"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.1.0, ansi-styles@npm:^6.2.1": - version: 6.2.1 - resolution: "ansi-styles@npm:6.2.1" - checksum: 10c0/5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c - languageName: node - linkType: hard - -"anymatch@npm:~3.1.2": - version: 3.1.3 - resolution: "anymatch@npm:3.1.3" - dependencies: - normalize-path: "npm:^3.0.0" - picomatch: "npm:^2.0.4" - checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac - languageName: node - linkType: hard - -"argparse@npm:^1.0.7": - version: 1.0.10 - resolution: "argparse@npm:1.0.10" - dependencies: - sprintf-js: "npm:~1.0.2" - checksum: 10c0/b2972c5c23c63df66bca144dbc65d180efa74f25f8fd9b7d9a0a6c88ae839db32df3d54770dcb6460cf840d232b60695d1a6b1053f599d84e73f7437087712de - languageName: node - linkType: hard - -"array-find-index@npm:^1.0.1": - version: 1.0.2 - resolution: "array-find-index@npm:1.0.2" - checksum: 10c0/86b9485c74ddd324feab807e10a6de3f9c1683856267236fac4bb4d4667ada6463e106db3f6c540ae6b720e0442b590ec701d13676df4c6af30ebf4da09b4f57 - languageName: node - linkType: hard - -"arrgv@npm:^1.0.2": - version: 1.0.2 - resolution: "arrgv@npm:1.0.2" - checksum: 10c0/7e6e782e6b749923ac7cbc4048ef6fe0844c4a59bfc8932fcd4c44566ba25eed46501f94dd7cf3c7297da88f3f599ca056bfb77d0c2484aebc92f04239f69124 - languageName: node - linkType: hard - -"arrify@npm:^3.0.0": - version: 3.0.0 - resolution: "arrify@npm:3.0.0" - checksum: 10c0/2e26601b8486f29780f1f70f7ac05a226755814c2a3ab42e196748f650af1dc310cd575a11dd4b9841c70fd7460b2dd2b8fe6fb7a3375878e2660706efafa58e - languageName: node - linkType: hard - -"ava@npm:^5.3.1": - version: 5.3.1 - resolution: "ava@npm:5.3.1" - dependencies: - acorn: "npm:^8.8.2" - acorn-walk: "npm:^8.2.0" - ansi-styles: "npm:^6.2.1" - arrgv: "npm:^1.0.2" - arrify: "npm:^3.0.0" - callsites: "npm:^4.0.0" - cbor: "npm:^8.1.0" - chalk: "npm:^5.2.0" - chokidar: "npm:^3.5.3" - chunkd: "npm:^2.0.1" - ci-info: "npm:^3.8.0" - ci-parallel-vars: "npm:^1.0.1" - clean-yaml-object: "npm:^0.1.0" - cli-truncate: "npm:^3.1.0" - code-excerpt: "npm:^4.0.0" - common-path-prefix: "npm:^3.0.0" - concordance: "npm:^5.0.4" - currently-unhandled: "npm:^0.4.1" - debug: "npm:^4.3.4" - emittery: "npm:^1.0.1" - figures: "npm:^5.0.0" - globby: "npm:^13.1.4" - ignore-by-default: "npm:^2.1.0" - indent-string: "npm:^5.0.0" - is-error: "npm:^2.2.2" - is-plain-object: "npm:^5.0.0" - is-promise: "npm:^4.0.0" - matcher: "npm:^5.0.0" - mem: "npm:^9.0.2" - ms: "npm:^2.1.3" - p-event: "npm:^5.0.1" - p-map: "npm:^5.5.0" - picomatch: "npm:^2.3.1" - pkg-conf: "npm:^4.0.0" - plur: "npm:^5.1.0" - pretty-ms: "npm:^8.0.0" - resolve-cwd: "npm:^3.0.0" - stack-utils: "npm:^2.0.6" - strip-ansi: "npm:^7.0.1" - supertap: "npm:^3.0.1" - temp-dir: "npm:^3.0.0" - write-file-atomic: "npm:^5.0.1" - yargs: "npm:^17.7.2" - peerDependencies: - "@ava/typescript": "*" - peerDependenciesMeta: - "@ava/typescript": - optional: true - bin: - ava: entrypoints/cli.mjs - checksum: 10c0/262cbdb9e8c3ce7177be91b92ba521e9d5aef577dcc8095cc591f86baaa291b91c88925928f5d26832c4d1b381a6ae99f2e8804077c592d0d32322c1212605cc - languageName: node - linkType: hard - -"balanced-match@npm:^1.0.0": - version: 1.0.2 - resolution: "balanced-match@npm:1.0.2" - checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee - languageName: node - linkType: hard - -"base64-js@npm:^1.3.1": - version: 1.5.1 - resolution: "base64-js@npm:1.5.1" - checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf - languageName: node - linkType: hard - -"better-sqlite3@npm:^9.6.0": - version: 9.6.0 - resolution: "better-sqlite3@npm:9.6.0" - dependencies: - bindings: "npm:^1.5.0" - node-gyp: "npm:latest" - prebuild-install: "npm:^7.1.1" - checksum: 10c0/8db9b38f414e26a56d4c40fc16e94a253118491dae0e2c054338a9e470f1a883c7eb4cb330f2f5737db30f704d4f2e697c59071ca04e03364ee9fe04375aa9c8 - languageName: node - linkType: hard - -"binary-extensions@npm:^2.0.0": - version: 2.3.0 - resolution: "binary-extensions@npm:2.3.0" - checksum: 10c0/75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5 - languageName: node - linkType: hard - -"bindings@npm:^1.5.0": - version: 1.5.0 - resolution: "bindings@npm:1.5.0" - dependencies: - file-uri-to-path: "npm:1.0.0" - checksum: 10c0/3dab2491b4bb24124252a91e656803eac24292473e56554e35bbfe3cc1875332cfa77600c3bac7564049dc95075bf6fcc63a4609920ff2d64d0fe405fcf0d4ba - languageName: node - linkType: hard - -"bl@npm:^4.0.3": - version: 4.1.0 - resolution: "bl@npm:4.1.0" - dependencies: - buffer: "npm:^5.5.0" - inherits: "npm:^2.0.4" - readable-stream: "npm:^3.4.0" - checksum: 10c0/02847e1d2cb089c9dc6958add42e3cdeaf07d13f575973963335ac0fdece563a50ac770ac4c8fa06492d2dd276f6cc3b7f08c7cd9c7a7ad0f8d388b2a28def5f - languageName: node - linkType: hard - -"blueimp-md5@npm:^2.10.0": - version: 2.19.0 - resolution: "blueimp-md5@npm:2.19.0" - checksum: 10c0/85d04343537dd99a288c62450341dcce7380d3454c81f8e5a971ddd80307d6f9ef51b5b92ad7d48aaaa92fd6d3a1f6b2f4fada068faae646887f7bfabc17a346 - languageName: node - linkType: hard - -"brace-expansion@npm:^2.0.1": - version: 2.0.1 - resolution: "brace-expansion@npm:2.0.1" - dependencies: - balanced-match: "npm:^1.0.0" - checksum: 10c0/b358f2fe060e2d7a87aa015979ecea07f3c37d4018f8d6deb5bd4c229ad3a0384fe6029bb76cd8be63c81e516ee52d1a0673edbe2023d53a5191732ae3c3e49f - languageName: node - linkType: hard - -"braces@npm:^3.0.3, braces@npm:~3.0.2": - version: 3.0.3 - resolution: "braces@npm:3.0.3" - dependencies: - fill-range: "npm:^7.1.1" - checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 - languageName: node - linkType: hard - -"buffer@npm:^5.5.0": - version: 5.7.1 - resolution: "buffer@npm:5.7.1" - dependencies: - base64-js: "npm:^1.3.1" - ieee754: "npm:^1.1.13" - checksum: 10c0/27cac81cff434ed2876058d72e7c4789d11ff1120ef32c9de48f59eab58179b66710c488987d295ae89a228f835fc66d088652dffeb8e3ba8659f80eb091d55e - languageName: node - linkType: hard - -"cacache@npm:^18.0.0": - version: 18.0.4 - resolution: "cacache@npm:18.0.4" - dependencies: - "@npmcli/fs": "npm:^3.1.0" - fs-minipass: "npm:^3.0.0" - glob: "npm:^10.2.2" - lru-cache: "npm:^10.0.1" - minipass: "npm:^7.0.3" - minipass-collect: "npm:^2.0.1" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - p-map: "npm:^4.0.0" - ssri: "npm:^10.0.0" - tar: "npm:^6.1.11" - unique-filename: "npm:^3.0.0" - checksum: 10c0/6c055bafed9de4f3dcc64ac3dc7dd24e863210902b7c470eb9ce55a806309b3efff78033e3d8b4f7dcc5d467f2db43c6a2857aaaf26f0094b8a351d44c42179f - languageName: node - linkType: hard - -"callsites@npm:^4.0.0": - version: 4.2.0 - resolution: "callsites@npm:4.2.0" - checksum: 10c0/8f7e269ec09fc0946bb22d838a8bc7932e1909ab4a833b964749f4d0e8bdeaa1f253287c4f911f61781f09620b6925ccd19a5ea4897489c4e59442c660c312a3 - languageName: node - linkType: hard - -"cbor@npm:^8.1.0": - version: 8.1.0 - resolution: "cbor@npm:8.1.0" - dependencies: - nofilter: "npm:^3.1.0" - checksum: 10c0/a836e2e7ea0efb1b9c4e5a4be906c57113d730cc42293a34072e0164ed110bb8ac035dc7dca2e3ebb641bd4b37e00fdbbf09c951aa864b3d4888a6ed8c6243f7 - languageName: node - linkType: hard - -"chalk@npm:^5.2.0, chalk@npm:^5.3.0": - version: 5.3.0 - resolution: "chalk@npm:5.3.0" - checksum: 10c0/8297d436b2c0f95801103ff2ef67268d362021b8210daf8ddbe349695333eb3610a71122172ff3b0272f1ef2cf7cc2c41fdaa4715f52e49ffe04c56340feed09 - languageName: node - linkType: hard - -"chokidar@npm:^3.5.3": - version: 3.6.0 - resolution: "chokidar@npm:3.6.0" - dependencies: - anymatch: "npm:~3.1.2" - braces: "npm:~3.0.2" - fsevents: "npm:~2.3.2" - glob-parent: "npm:~5.1.2" - is-binary-path: "npm:~2.1.0" - is-glob: "npm:~4.0.1" - normalize-path: "npm:~3.0.0" - readdirp: "npm:~3.6.0" - dependenciesMeta: - fsevents: - optional: true - checksum: 10c0/8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462 - languageName: node - linkType: hard - -"chownr@npm:^1.1.1": - version: 1.1.4 - resolution: "chownr@npm:1.1.4" - checksum: 10c0/ed57952a84cc0c802af900cf7136de643d3aba2eecb59d29344bc2f3f9bf703a301b9d84cdc71f82c3ffc9ccde831b0d92f5b45f91727d6c9da62f23aef9d9db - languageName: node - linkType: hard - -"chownr@npm:^2.0.0": - version: 2.0.0 - resolution: "chownr@npm:2.0.0" - checksum: 10c0/594754e1303672171cc04e50f6c398ae16128eb134a88f801bf5354fd96f205320f23536a045d9abd8b51024a149696e51231565891d4efdab8846021ecf88e6 - languageName: node - linkType: hard - -"chunkd@npm:^2.0.1": - version: 2.0.1 - resolution: "chunkd@npm:2.0.1" - checksum: 10c0/4e0c5aac6048ecedfa4cd0a5f6c4f010c70a7b7645aeca7bfeb47cb0733c3463054f0ced3f2667b2e0e67edd75d68a8e05481b01115ba3f8a952a93026254504 - languageName: node - linkType: hard - -"ci-info@npm:^3.8.0": - version: 3.9.0 - resolution: "ci-info@npm:3.9.0" - checksum: 10c0/6f0109e36e111684291d46123d491bc4e7b7a1934c3a20dea28cba89f1d4a03acd892f5f6a81ed3855c38647e285a150e3c9ba062e38943bef57fee6c1554c3a - languageName: node - linkType: hard - -"ci-parallel-vars@npm:^1.0.1": - version: 1.0.1 - resolution: "ci-parallel-vars@npm:1.0.1" - checksum: 10c0/80952f699cbbc146092b077b4f3e28d085620eb4e6be37f069b4dbb3db0ee70e8eec3beef4ebe70ff60631e9fc743b9d0869678489f167442cac08b260e5ac08 - languageName: node - linkType: hard - -"clean-stack@npm:^2.0.0": - version: 2.2.0 - resolution: "clean-stack@npm:2.2.0" - checksum: 10c0/1f90262d5f6230a17e27d0c190b09d47ebe7efdd76a03b5a1127863f7b3c9aec4c3e6c8bb3a7bbf81d553d56a1fd35728f5a8ef4c63f867ac8d690109742a8c1 - languageName: node - linkType: hard - -"clean-stack@npm:^4.0.0": - version: 4.2.0 - resolution: "clean-stack@npm:4.2.0" - dependencies: - escape-string-regexp: "npm:5.0.0" - checksum: 10c0/2bdf981a0fef0a23c14255df693b30eb9ae27eedf212470d8c400a0c0b6fb82fbf1ff8c5216ccd5721e3670b700389c886b1dce5070776dc9fbcc040957758c0 - languageName: node - linkType: hard - -"clean-yaml-object@npm:^0.1.0": - version: 0.1.0 - resolution: "clean-yaml-object@npm:0.1.0" - checksum: 10c0/a6505310590038afb9f0adc7f17a4c66787719c94d23f8491267ea4d9c405cdd378bd576ae1926169b6d997d4c59a8b86516bf4d16ba228280cf615598c58e05 - languageName: node - linkType: hard - -"cli-truncate@npm:^3.1.0": - version: 3.1.0 - resolution: "cli-truncate@npm:3.1.0" - dependencies: - slice-ansi: "npm:^5.0.0" - string-width: "npm:^5.0.0" - checksum: 10c0/a19088878409ec0e5dc2659a5166929629d93cfba6d68afc9cde2282fd4c751af5b555bf197047e31c87c574396348d011b7aa806fec29c4139ea4f7f00b324c - languageName: node - linkType: hard - -"cliui@npm:^8.0.1": - version: 8.0.1 - resolution: "cliui@npm:8.0.1" - dependencies: - string-width: "npm:^4.2.0" - strip-ansi: "npm:^6.0.1" - wrap-ansi: "npm:^7.0.0" - checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 - languageName: node - linkType: hard - -"code-excerpt@npm:^4.0.0": - version: 4.0.0 - resolution: "code-excerpt@npm:4.0.0" - dependencies: - convert-to-spaces: "npm:^2.0.1" - checksum: 10c0/b6c5a06e039cecd2ab6a0e10ee0831de8362107d1f298ca3558b5f9004cb8e0260b02dd6c07f57b9a0e346c76864d2873311ee1989809fdeb05bd5fbbadde773 - languageName: node - linkType: hard - -"color-convert@npm:^2.0.1": - version: 2.0.1 - resolution: "color-convert@npm:2.0.1" - dependencies: - color-name: "npm:~1.1.4" - checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 - languageName: node - linkType: hard - -"color-name@npm:~1.1.4": - version: 1.1.4 - resolution: "color-name@npm:1.1.4" - checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 - languageName: node - linkType: hard - -"common-path-prefix@npm:^3.0.0": - version: 3.0.0 - resolution: "common-path-prefix@npm:3.0.0" - checksum: 10c0/c4a74294e1b1570f4a8ab435285d185a03976c323caa16359053e749db4fde44e3e6586c29cd051100335e11895767cbbd27ea389108e327d62f38daf4548fdb - languageName: node - linkType: hard - -"concordance@npm:^5.0.4": - version: 5.0.4 - resolution: "concordance@npm:5.0.4" - dependencies: - date-time: "npm:^3.1.0" - esutils: "npm:^2.0.3" - fast-diff: "npm:^1.2.0" - js-string-escape: "npm:^1.0.1" - lodash: "npm:^4.17.15" - md5-hex: "npm:^3.0.1" - semver: "npm:^7.3.2" - well-known-symbols: "npm:^2.0.0" - checksum: 10c0/59b440f330df3a7c9aa148ba588b3e99aed86acab225b4f01ffcea34ace4cf11f817e31153254e8f38ed48508998dad40b9106951a743c334d751f7ab21afb8a - languageName: node - linkType: hard - -"convert-to-spaces@npm:^2.0.1": - version: 2.0.1 - resolution: "convert-to-spaces@npm:2.0.1" - checksum: 10c0/d90aa0e3b6a27f9d5265a8d32def3c5c855b3e823a9db1f26d772f8146d6b91020a2fdfd905ce8048a73fad3aaf836fef8188c67602c374405e2ae8396c4ac46 - languageName: node - linkType: hard - -"cosmjs-types@npm:^0.9.0": - version: 0.9.0 - resolution: "cosmjs-types@npm:0.9.0" - checksum: 10c0/bc20f4293fb34629d7c5f96bafe533987f753df957ff68eb078d0128ae5a418320cb945024441769a07bb9bc5dde9d22b972fd40d485933e5706ea191c43727b - languageName: node - linkType: hard - -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3": - version: 7.0.3 - resolution: "cross-spawn@npm:7.0.3" - dependencies: - path-key: "npm:^3.1.0" - shebang-command: "npm:^2.0.0" - which: "npm:^2.0.1" - checksum: 10c0/5738c312387081c98d69c98e105b6327b069197f864a60593245d64c8089c8a0a744e16349281210d56835bb9274130d825a78b2ad6853ca13cfbeffc0c31750 - languageName: node - linkType: hard - -"currently-unhandled@npm:^0.4.1": - version: 0.4.1 - resolution: "currently-unhandled@npm:0.4.1" - dependencies: - array-find-index: "npm:^1.0.1" - checksum: 10c0/32d197689ec32f035910202c1abb0dc6424dce01d7b51779c685119b380d98535c110ffff67a262fc7e367612a7dfd30d3d3055f9a6634b5a9dd1302de7ef11c - languageName: node - linkType: hard - -"date-time@npm:^3.1.0": - version: 3.1.0 - resolution: "date-time@npm:3.1.0" - dependencies: - time-zone: "npm:^1.0.0" - checksum: 10c0/aa3e2e930d74b0b9e90f69de7a16d3376e30f21f1f4ce9a2311d8fec32d760e776efea752dafad0ce188187265235229013036202be053fc2d7979813bfb6ded - languageName: node - linkType: hard - -"debug@npm:4, debug@npm:^4.3.4": - version: 4.3.7 - resolution: "debug@npm:4.3.7" - dependencies: - ms: "npm:^2.1.3" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10c0/1471db19c3b06d485a622d62f65947a19a23fbd0dd73f7fd3eafb697eec5360cde447fb075919987899b1a2096e85d35d4eb5a4de09a57600ac9cf7e6c8e768b - languageName: node - linkType: hard - -"decompress-response@npm:^6.0.0": - version: 6.0.0 - resolution: "decompress-response@npm:6.0.0" - dependencies: - mimic-response: "npm:^3.1.0" - checksum: 10c0/bd89d23141b96d80577e70c54fb226b2f40e74a6817652b80a116d7befb8758261ad073a8895648a29cc0a5947021ab66705cb542fa9c143c82022b27c5b175e - languageName: node - linkType: hard - -"deep-extend@npm:^0.6.0": - version: 0.6.0 - resolution: "deep-extend@npm:0.6.0" - checksum: 10c0/1c6b0abcdb901e13a44c7d699116d3d4279fdb261983122a3783e7273844d5f2537dc2e1c454a23fcf645917f93fbf8d07101c1d03c015a87faa662755212566 - languageName: node - linkType: hard - -"detect-libc@npm:^2.0.0": - version: 2.0.3 - resolution: "detect-libc@npm:2.0.3" - checksum: 10c0/88095bda8f90220c95f162bf92cad70bd0e424913e655c20578600e35b91edc261af27531cf160a331e185c0ced93944bc7e09939143225f56312d7fd800fdb7 - languageName: node - linkType: hard - -"dir-glob@npm:^3.0.1": - version: 3.0.1 - resolution: "dir-glob@npm:3.0.1" - dependencies: - path-type: "npm:^4.0.0" - checksum: 10c0/dcac00920a4d503e38bb64001acb19df4efc14536ada475725e12f52c16777afdee4db827f55f13a908ee7efc0cb282e2e3dbaeeb98c0993dd93d1802d3bf00c - languageName: node - linkType: hard - -"eastasianwidth@npm:^0.2.0": - version: 0.2.0 - resolution: "eastasianwidth@npm:0.2.0" - checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 - languageName: node - linkType: hard - -"emittery@npm:^1.0.1": - version: 1.0.3 - resolution: "emittery@npm:1.0.3" - checksum: 10c0/91605d044f3891dd1f8ab731aeb94b520488b21e707f7064dcbcf5303bac3b4e7133dfa23c343ede1fc970340bd78a9b1aed522b805bc15104606bba630dd71e - languageName: node - linkType: hard - -"emoji-regex@npm:^8.0.0": - version: 8.0.0 - resolution: "emoji-regex@npm:8.0.0" - checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 - languageName: node - linkType: hard - -"emoji-regex@npm:^9.2.2": - version: 9.2.2 - resolution: "emoji-regex@npm:9.2.2" - checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 - languageName: node - linkType: hard - -"encoding@npm:^0.1.13": - version: 0.1.13 - resolution: "encoding@npm:0.1.13" - dependencies: - iconv-lite: "npm:^0.6.2" - checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 - languageName: node - linkType: hard - -"end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1": - version: 1.4.4 - resolution: "end-of-stream@npm:1.4.4" - dependencies: - once: "npm:^1.4.0" - checksum: 10c0/870b423afb2d54bb8d243c63e07c170409d41e20b47eeef0727547aea5740bd6717aca45597a9f2745525667a6b804c1e7bede41f856818faee5806dd9ff3975 - languageName: node - linkType: hard - -"env-paths@npm:^2.2.0": - version: 2.2.1 - resolution: "env-paths@npm:2.2.1" - checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 - languageName: node - linkType: hard - -"err-code@npm:^2.0.2": - version: 2.0.3 - resolution: "err-code@npm:2.0.3" - checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 - languageName: node - linkType: hard - -"escalade@npm:^3.1.1": - version: 3.2.0 - resolution: "escalade@npm:3.2.0" - checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 - languageName: node - linkType: hard - -"escape-string-regexp@npm:5.0.0, escape-string-regexp@npm:^5.0.0": - version: 5.0.0 - resolution: "escape-string-regexp@npm:5.0.0" - checksum: 10c0/6366f474c6f37a802800a435232395e04e9885919873e382b157ab7e8f0feb8fed71497f84a6f6a81a49aab41815522f5839112bd38026d203aea0c91622df95 - languageName: node - linkType: hard - -"escape-string-regexp@npm:^2.0.0": - version: 2.0.0 - resolution: "escape-string-regexp@npm:2.0.0" - checksum: 10c0/2530479fe8db57eace5e8646c9c2a9c80fa279614986d16dcc6bcaceb63ae77f05a851ba6c43756d816c61d7f4534baf56e3c705e3e0d884818a46808811c507 - languageName: node - linkType: hard - -"esprima@npm:^4.0.0": - version: 4.0.1 - resolution: "esprima@npm:4.0.1" - bin: - esparse: ./bin/esparse.js - esvalidate: ./bin/esvalidate.js - checksum: 10c0/ad4bab9ead0808cf56501750fd9d3fb276f6b105f987707d059005d57e182d18a7c9ec7f3a01794ebddcca676773e42ca48a32d67a250c9d35e009ca613caba3 - languageName: node - linkType: hard - -"esutils@npm:^2.0.3": - version: 2.0.3 - resolution: "esutils@npm:2.0.3" - checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 - languageName: node - linkType: hard - -"execa@npm:^9.3.1": - version: 9.4.0 - resolution: "execa@npm:9.4.0" - dependencies: - "@sindresorhus/merge-streams": "npm:^4.0.0" - cross-spawn: "npm:^7.0.3" - figures: "npm:^6.1.0" - get-stream: "npm:^9.0.0" - human-signals: "npm:^8.0.0" - is-plain-obj: "npm:^4.1.0" - is-stream: "npm:^4.0.1" - npm-run-path: "npm:^6.0.0" - pretty-ms: "npm:^9.0.0" - signal-exit: "npm:^4.1.0" - strip-final-newline: "npm:^4.0.0" - yoctocolors: "npm:^2.0.0" - checksum: 10c0/6ad06c627b5d7bb007bc7b6cc35d7e32b5a3365375ffc8ddbcc12d2423651fa9928ba0c447cc9e60079e505e9b24fbe0a57f80371511d7d20302c04c2d3ce95e - languageName: node - linkType: hard - -"expand-template@npm:^2.0.3": - version: 2.0.3 - resolution: "expand-template@npm:2.0.3" - checksum: 10c0/1c9e7afe9acadf9d373301d27f6a47b34e89b3391b1ef38b7471d381812537ef2457e620ae7f819d2642ce9c43b189b3583813ec395e2938319abe356a9b2f51 - languageName: node - linkType: hard - -"exponential-backoff@npm:^3.1.1": - version: 3.1.1 - resolution: "exponential-backoff@npm:3.1.1" - checksum: 10c0/160456d2d647e6019640bd07111634d8c353038d9fa40176afb7cd49b0548bdae83b56d05e907c2cce2300b81cae35d800ef92fefb9d0208e190fa3b7d6bb579 - languageName: node - linkType: hard - -"fast-diff@npm:^1.2.0": - version: 1.3.0 - resolution: "fast-diff@npm:1.3.0" - checksum: 10c0/5c19af237edb5d5effda008c891a18a585f74bf12953be57923f17a3a4d0979565fc64dbc73b9e20926b9d895f5b690c618cbb969af0cf022e3222471220ad29 - languageName: node - linkType: hard - -"fast-glob@npm:^3.3.0": - version: 3.3.2 - resolution: "fast-glob@npm:3.3.2" - dependencies: - "@nodelib/fs.stat": "npm:^2.0.2" - "@nodelib/fs.walk": "npm:^1.2.3" - glob-parent: "npm:^5.1.2" - merge2: "npm:^1.3.0" - micromatch: "npm:^4.0.4" - checksum: 10c0/42baad7b9cd40b63e42039132bde27ca2cb3a4950d0a0f9abe4639ea1aa9d3e3b40f98b1fe31cbc0cc17b664c9ea7447d911a152fa34ec5b72977b125a6fc845 - languageName: node - linkType: hard - -"fastq@npm:^1.6.0": - version: 1.17.1 - resolution: "fastq@npm:1.17.1" - dependencies: - reusify: "npm:^1.0.4" - checksum: 10c0/1095f16cea45fb3beff558bb3afa74ca7a9250f5a670b65db7ed585f92b4b48381445cd328b3d87323da81e43232b5d5978a8201bde84e0cd514310f1ea6da34 - languageName: node - linkType: hard - -"figures@npm:^5.0.0": - version: 5.0.0 - resolution: "figures@npm:5.0.0" - dependencies: - escape-string-regexp: "npm:^5.0.0" - is-unicode-supported: "npm:^1.2.0" - checksum: 10c0/ce0f17d4ea8b0fc429c5207c343534a2f5284ecfb22aa08607da7dc84ed9e1cf754f5b97760e8dcb98d3c9d1a1e4d3d578fe3b5b99c426f05d0f06c7ba618e16 - languageName: node - linkType: hard - -"figures@npm:^6.1.0": - version: 6.1.0 - resolution: "figures@npm:6.1.0" - dependencies: - is-unicode-supported: "npm:^2.0.0" - checksum: 10c0/9159df4264d62ef447a3931537de92f5012210cf5135c35c010df50a2169377581378149abfe1eb238bd6acbba1c0d547b1f18e0af6eee49e30363cedaffcfe4 - languageName: node - linkType: hard - -"file-uri-to-path@npm:1.0.0": - version: 1.0.0 - resolution: "file-uri-to-path@npm:1.0.0" - checksum: 10c0/3b545e3a341d322d368e880e1c204ef55f1d45cdea65f7efc6c6ce9e0c4d22d802d5629320eb779d006fe59624ac17b0e848d83cc5af7cd101f206cb704f5519 - languageName: node - linkType: hard - -"fill-range@npm:^7.1.1": - version: 7.1.1 - resolution: "fill-range@npm:7.1.1" - dependencies: - to-regex-range: "npm:^5.0.1" - checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 - languageName: node - linkType: hard - -"find-up@npm:^6.0.0": - version: 6.3.0 - resolution: "find-up@npm:6.3.0" - dependencies: - locate-path: "npm:^7.1.0" - path-exists: "npm:^5.0.0" - checksum: 10c0/07e0314362d316b2b13f7f11ea4692d5191e718ca3f7264110127520f3347996349bf9e16805abae3e196805814bc66ef4bff2b8904dc4a6476085fc9b0eba07 - languageName: node - linkType: hard - -"foreground-child@npm:^3.1.0": - version: 3.3.0 - resolution: "foreground-child@npm:3.3.0" - dependencies: - cross-spawn: "npm:^7.0.0" - signal-exit: "npm:^4.0.1" - checksum: 10c0/028f1d41000553fcfa6c4bb5c372963bf3d9bf0b1f25a87d1a6253014343fb69dfb1b42d9625d7cf44c8ba429940f3d0ff718b62105d4d4a4f6ef8ca0a53faa2 - languageName: node - linkType: hard - -"fs-constants@npm:^1.0.0": - version: 1.0.0 - resolution: "fs-constants@npm:1.0.0" - checksum: 10c0/a0cde99085f0872f4d244e83e03a46aa387b74f5a5af750896c6b05e9077fac00e9932fdf5aef84f2f16634cd473c63037d7a512576da7d5c2b9163d1909f3a8 - languageName: node - linkType: hard - -"fs-minipass@npm:^2.0.0": - version: 2.1.0 - resolution: "fs-minipass@npm:2.1.0" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/703d16522b8282d7299337539c3ed6edddd1afe82435e4f5b76e34a79cd74e488a8a0e26a636afc2440e1a23b03878e2122e3a2cfe375a5cf63c37d92b86a004 - languageName: node - linkType: hard - -"fs-minipass@npm:^3.0.0": - version: 3.0.3 - resolution: "fs-minipass@npm:3.0.3" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 - languageName: node - linkType: hard - -"fsevents@npm:~2.3.2": - version: 2.3.3 - resolution: "fsevents@npm:2.3.3" - dependencies: - node-gyp: "npm:latest" - checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 - conditions: os=darwin - languageName: node - linkType: hard - -"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": - version: 2.3.3 - resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" - dependencies: - node-gyp: "npm:latest" - conditions: os=darwin - languageName: node - linkType: hard - -"get-caller-file@npm:^2.0.5": - version: 2.0.5 - resolution: "get-caller-file@npm:2.0.5" - checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde - languageName: node - linkType: hard - -"get-stream@npm:^9.0.0": - version: 9.0.1 - resolution: "get-stream@npm:9.0.1" - dependencies: - "@sec-ant/readable-stream": "npm:^0.4.1" - is-stream: "npm:^4.0.1" - checksum: 10c0/d70e73857f2eea1826ac570c3a912757dcfbe8a718a033fa0c23e12ac8e7d633195b01710e0559af574cbb5af101009b42df7b6f6b29ceec8dbdf7291931b948 - languageName: node - linkType: hard - -"github-from-package@npm:0.0.0": - version: 0.0.0 - resolution: "github-from-package@npm:0.0.0" - checksum: 10c0/737ee3f52d0a27e26332cde85b533c21fcdc0b09fb716c3f8e522cfaa9c600d4a631dec9fcde179ec9d47cca89017b7848ed4d6ae6b6b78f936c06825b1fcc12 - languageName: node - linkType: hard - -"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": - version: 5.1.2 - resolution: "glob-parent@npm:5.1.2" - dependencies: - is-glob: "npm:^4.0.1" - checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee - languageName: node - linkType: hard - -"glob@npm:^10.2.2, glob@npm:^10.3.10": - version: 10.4.5 - resolution: "glob@npm:10.4.5" - dependencies: - foreground-child: "npm:^3.1.0" - jackspeak: "npm:^3.1.2" - minimatch: "npm:^9.0.4" - minipass: "npm:^7.1.2" - package-json-from-dist: "npm:^1.0.0" - path-scurry: "npm:^1.11.1" - bin: - glob: dist/esm/bin.mjs - checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e - languageName: node - linkType: hard - -"globby@npm:^13.1.4": - version: 13.2.2 - resolution: "globby@npm:13.2.2" - dependencies: - dir-glob: "npm:^3.0.1" - fast-glob: "npm:^3.3.0" - ignore: "npm:^5.2.4" - merge2: "npm:^1.4.1" - slash: "npm:^4.0.0" - checksum: 10c0/a8d7cc7cbe5e1b2d0f81d467bbc5bc2eac35f74eaded3a6c85fc26d7acc8e6de22d396159db8a2fc340b8a342e74cac58de8f4aee74146d3d146921a76062664 - languageName: node - linkType: hard - -"graceful-fs@npm:^4.2.6": - version: 4.2.11 - resolution: "graceful-fs@npm:4.2.11" - checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 - languageName: node - linkType: hard - -"http-cache-semantics@npm:^4.1.1": - version: 4.1.1 - resolution: "http-cache-semantics@npm:4.1.1" - checksum: 10c0/ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc - languageName: node - linkType: hard - -"http-proxy-agent@npm:^7.0.0": - version: 7.0.2 - resolution: "http-proxy-agent@npm:7.0.2" - dependencies: - agent-base: "npm:^7.1.0" - debug: "npm:^4.3.4" - checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 - languageName: node - linkType: hard - -"https-proxy-agent@npm:^7.0.1": - version: 7.0.5 - resolution: "https-proxy-agent@npm:7.0.5" - dependencies: - agent-base: "npm:^7.0.2" - debug: "npm:4" - checksum: 10c0/2490e3acec397abeb88807db52cac59102d5ed758feee6df6112ab3ccd8325e8a1ce8bce6f4b66e5470eca102d31e425ace904242e4fa28dbe0c59c4bafa7b2c - languageName: node - linkType: hard - -"human-signals@npm:^8.0.0": - version: 8.0.0 - resolution: "human-signals@npm:8.0.0" - checksum: 10c0/e4dac4f7d3eb791ed04129fc6a85bd454a9102d3e3b76c911d0db7057ebd60b2956b435b5b5712aec18960488ede3c21ef7c56e42cdd70760c0d84d3c05cd92e - languageName: node - linkType: hard - -"iconv-lite@npm:^0.6.2": - version: 0.6.3 - resolution: "iconv-lite@npm:0.6.3" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3.0.0" - checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 - languageName: node - linkType: hard - -"ieee754@npm:^1.1.13": - version: 1.2.1 - resolution: "ieee754@npm:1.2.1" - checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb - languageName: node - linkType: hard - -"ignore-by-default@npm:^2.1.0": - version: 2.1.0 - resolution: "ignore-by-default@npm:2.1.0" - checksum: 10c0/3a6040dac25ed9da39dee73bf1634fdd1e15b0eb7cf52a6bdec81c310565782d8811c104ce40acb3d690d61c5fc38a91c78e6baee830a8a2232424dbc6b66981 - languageName: node - linkType: hard - -"ignore@npm:^5.2.4": - version: 5.3.2 - resolution: "ignore@npm:5.3.2" - checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 - languageName: node - linkType: hard - -"imurmurhash@npm:^0.1.4": - version: 0.1.4 - resolution: "imurmurhash@npm:0.1.4" - checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 - languageName: node - linkType: hard - -"indent-string@npm:^4.0.0": - version: 4.0.0 - resolution: "indent-string@npm:4.0.0" - checksum: 10c0/1e1904ddb0cb3d6cce7cd09e27a90184908b7a5d5c21b92e232c93579d314f0b83c246ffb035493d0504b1e9147ba2c9b21df0030f48673fba0496ecd698161f - languageName: node - linkType: hard - -"indent-string@npm:^5.0.0": - version: 5.0.0 - resolution: "indent-string@npm:5.0.0" - checksum: 10c0/8ee77b57d92e71745e133f6f444d6fa3ed503ad0e1bcd7e80c8da08b42375c07117128d670589725ed07b1978065803fa86318c309ba45415b7fe13e7f170220 - languageName: node - linkType: hard - -"inherits@npm:^2.0.3, inherits@npm:^2.0.4": - version: 2.0.4 - resolution: "inherits@npm:2.0.4" - checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 - languageName: node - linkType: hard - -"ini@npm:~1.3.0": - version: 1.3.8 - resolution: "ini@npm:1.3.8" - checksum: 10c0/ec93838d2328b619532e4f1ff05df7909760b6f66d9c9e2ded11e5c1897d6f2f9980c54dd638f88654b00919ce31e827040631eab0a3969e4d1abefa0719516a - languageName: node - linkType: hard - -"ip-address@npm:^9.0.5": - version: 9.0.5 - resolution: "ip-address@npm:9.0.5" - dependencies: - jsbn: "npm:1.1.0" - sprintf-js: "npm:^1.1.3" - checksum: 10c0/331cd07fafcb3b24100613e4b53e1a2b4feab11e671e655d46dc09ee233da5011284d09ca40c4ecbdfe1d0004f462958675c224a804259f2f78d2465a87824bc - languageName: node - linkType: hard - -"irregular-plurals@npm:^3.3.0": - version: 3.5.0 - resolution: "irregular-plurals@npm:3.5.0" - checksum: 10c0/7c033bbe7325e5a6e0a26949cc6863b6ce273403d4cd5b93bd99b33fecb6605b0884097c4259c23ed0c52c2133bf7d1cdcdd7a0630e8c325161fe269b3447918 - languageName: node - linkType: hard - -"is-binary-path@npm:~2.1.0": - version: 2.1.0 - resolution: "is-binary-path@npm:2.1.0" - dependencies: - binary-extensions: "npm:^2.0.0" - checksum: 10c0/a16eaee59ae2b315ba36fad5c5dcaf8e49c3e27318f8ab8fa3cdb8772bf559c8d1ba750a589c2ccb096113bb64497084361a25960899cb6172a6925ab6123d38 - languageName: node - linkType: hard - -"is-error@npm:^2.2.2": - version: 2.2.2 - resolution: "is-error@npm:2.2.2" - checksum: 10c0/475d3463968bf16e94485555d7cb7a879ed68685e08d365a3370972e626054f1846ebbb3934403091e06682445568601fe919e41646096e5007952d0c1f4fd9b - languageName: node - linkType: hard - -"is-extglob@npm:^2.1.1": - version: 2.1.1 - resolution: "is-extglob@npm:2.1.1" - checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 - languageName: node - linkType: hard - -"is-fullwidth-code-point@npm:^3.0.0": - version: 3.0.0 - resolution: "is-fullwidth-code-point@npm:3.0.0" - checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc - languageName: node - linkType: hard - -"is-fullwidth-code-point@npm:^4.0.0": - version: 4.0.0 - resolution: "is-fullwidth-code-point@npm:4.0.0" - checksum: 10c0/df2a717e813567db0f659c306d61f2f804d480752526886954a2a3e2246c7745fd07a52b5fecf2b68caf0a6c79dcdace6166fdf29cc76ed9975cc334f0a018b8 - languageName: node - linkType: hard - -"is-glob@npm:^4.0.1, is-glob@npm:~4.0.1": - version: 4.0.3 - resolution: "is-glob@npm:4.0.3" - dependencies: - is-extglob: "npm:^2.1.1" - checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a - languageName: node - linkType: hard - -"is-lambda@npm:^1.0.1": - version: 1.0.1 - resolution: "is-lambda@npm:1.0.1" - checksum: 10c0/85fee098ae62ba6f1e24cf22678805473c7afd0fb3978a3aa260e354cb7bcb3a5806cf0a98403188465efedec41ab4348e8e4e79305d409601323855b3839d4d - languageName: node - linkType: hard - -"is-number@npm:^7.0.0": - version: 7.0.0 - resolution: "is-number@npm:7.0.0" - checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 - languageName: node - linkType: hard - -"is-plain-obj@npm:^4.1.0": - version: 4.1.0 - resolution: "is-plain-obj@npm:4.1.0" - checksum: 10c0/32130d651d71d9564dc88ba7e6fda0e91a1010a3694648e9f4f47bb6080438140696d3e3e15c741411d712e47ac9edc1a8a9de1fe76f3487b0d90be06ac9975e - languageName: node - linkType: hard - -"is-plain-object@npm:^5.0.0": - version: 5.0.0 - resolution: "is-plain-object@npm:5.0.0" - checksum: 10c0/893e42bad832aae3511c71fd61c0bf61aa3a6d853061c62a307261842727d0d25f761ce9379f7ba7226d6179db2a3157efa918e7fe26360f3bf0842d9f28942c - languageName: node - linkType: hard - -"is-promise@npm:^4.0.0": - version: 4.0.0 - resolution: "is-promise@npm:4.0.0" - checksum: 10c0/ebd5c672d73db781ab33ccb155fb9969d6028e37414d609b115cc534654c91ccd061821d5b987eefaa97cf4c62f0b909bb2f04db88306de26e91bfe8ddc01503 - languageName: node - linkType: hard - -"is-stream@npm:^4.0.1": - version: 4.0.1 - resolution: "is-stream@npm:4.0.1" - checksum: 10c0/2706c7f19b851327ba374687bc4a3940805e14ca496dc672b9629e744d143b1ad9c6f1b162dece81c7bfbc0f83b32b61ccc19ad2e05aad2dd7af347408f60c7f - languageName: node - linkType: hard - -"is-unicode-supported@npm:^1.2.0": - version: 1.3.0 - resolution: "is-unicode-supported@npm:1.3.0" - checksum: 10c0/b8674ea95d869f6faabddc6a484767207058b91aea0250803cbf1221345cb0c56f466d4ecea375dc77f6633d248d33c47bd296fb8f4cdba0b4edba8917e83d8a - languageName: node - linkType: hard - -"is-unicode-supported@npm:^2.0.0": - version: 2.1.0 - resolution: "is-unicode-supported@npm:2.1.0" - checksum: 10c0/a0f53e9a7c1fdbcf2d2ef6e40d4736fdffff1c9f8944c75e15425118ff3610172c87bf7bc6c34d3903b04be59790bb2212ddbe21ee65b5a97030fc50370545a5 - languageName: node - linkType: hard - -"isexe@npm:^2.0.0": - version: 2.0.0 - resolution: "isexe@npm:2.0.0" - checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d - languageName: node - linkType: hard - -"isexe@npm:^3.1.1": - version: 3.1.1 - resolution: "isexe@npm:3.1.1" - checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 - languageName: node - linkType: hard - -"jackspeak@npm:^3.1.2": - version: 3.4.3 - resolution: "jackspeak@npm:3.4.3" - dependencies: - "@isaacs/cliui": "npm:^8.0.2" - "@pkgjs/parseargs": "npm:^0.11.0" - dependenciesMeta: - "@pkgjs/parseargs": - optional: true - checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 - languageName: node - linkType: hard - -"js-string-escape@npm:^1.0.1": - version: 1.0.1 - resolution: "js-string-escape@npm:1.0.1" - checksum: 10c0/2c33b9ff1ba6b84681c51ca0997e7d5a1639813c95d5b61cb7ad47e55cc28fa4a0b1935c3d218710d8e6bcee5d0cd8c44755231e3a4e45fc604534d9595a3628 - languageName: node - linkType: hard - -"js-yaml@npm:^3.14.1": - version: 3.14.1 - resolution: "js-yaml@npm:3.14.1" - dependencies: - argparse: "npm:^1.0.7" - esprima: "npm:^4.0.0" - bin: - js-yaml: bin/js-yaml.js - checksum: 10c0/6746baaaeac312c4db8e75fa22331d9a04cccb7792d126ed8ce6a0bbcfef0cedaddd0c5098fade53db067c09fe00aa1c957674b4765610a8b06a5a189e46433b - languageName: node - linkType: hard - -"jsbn@npm:1.1.0": - version: 1.1.0 - resolution: "jsbn@npm:1.1.0" - checksum: 10c0/4f907fb78d7b712e11dea8c165fe0921f81a657d3443dde75359ed52eb2b5d33ce6773d97985a089f09a65edd80b11cb75c767b57ba47391fee4c969f7215c96 - languageName: node - linkType: hard - -"load-json-file@npm:^7.0.0": - version: 7.0.1 - resolution: "load-json-file@npm:7.0.1" - checksum: 10c0/7117459608a0b6329c7f78e6e1f541b3162dd901c29dd5af721fec8b270177d2e3d7999c971f344fff04daac368d052732e2c7146014bc84d15e0b636975e19a - languageName: node - linkType: hard - -"locate-path@npm:^7.1.0": - version: 7.2.0 - resolution: "locate-path@npm:7.2.0" - dependencies: - p-locate: "npm:^6.0.0" - checksum: 10c0/139e8a7fe11cfbd7f20db03923cacfa5db9e14fa14887ea121345597472b4a63c1a42a8a5187defeeff6acf98fd568da7382aa39682d38f0af27433953a97751 - languageName: node - linkType: hard - -"lodash@npm:^4.17.15": - version: 4.17.21 - resolution: "lodash@npm:4.17.21" - checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c - languageName: node - linkType: hard - -"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": - version: 10.4.3 - resolution: "lru-cache@npm:10.4.3" - checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb - languageName: node - linkType: hard - -"make-fetch-happen@npm:^13.0.0": - version: 13.0.1 - resolution: "make-fetch-happen@npm:13.0.1" - dependencies: - "@npmcli/agent": "npm:^2.0.0" - cacache: "npm:^18.0.0" - http-cache-semantics: "npm:^4.1.1" - is-lambda: "npm:^1.0.1" - minipass: "npm:^7.0.2" - minipass-fetch: "npm:^3.0.0" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - negotiator: "npm:^0.6.3" - proc-log: "npm:^4.2.0" - promise-retry: "npm:^2.0.1" - ssri: "npm:^10.0.0" - checksum: 10c0/df5f4dbb6d98153b751bccf4dc4cc500de85a96a9331db9805596c46aa9f99d9555983954e6c1266d9f981ae37a9e4647f42b9a4bb5466f867f4012e582c9e7e - languageName: node - linkType: hard - -"map-age-cleaner@npm:^0.1.3": - version: 0.1.3 - resolution: "map-age-cleaner@npm:0.1.3" - dependencies: - p-defer: "npm:^1.0.0" - checksum: 10c0/7495236c7b0950956c144fd8b4bc6399d4e78072a8840a4232fe1c4faccbb5eb5d842e5c0a56a60afc36d723f315c1c672325ca03c1b328650f7fcc478f385fd - languageName: node - linkType: hard - -"matcher@npm:^5.0.0": - version: 5.0.0 - resolution: "matcher@npm:5.0.0" - dependencies: - escape-string-regexp: "npm:^5.0.0" - checksum: 10c0/eda5471fc9d5b7264d63c81727824adc3585ddb5cfdc5fce5a9b7c86f946ff181610735d330b1c37a84811df872d1290bf4e9401d2be2a414204343701144b18 - languageName: node - linkType: hard - -"md5-hex@npm:^3.0.1": - version: 3.0.1 - resolution: "md5-hex@npm:3.0.1" - dependencies: - blueimp-md5: "npm:^2.10.0" - checksum: 10c0/ee2b4d8da16b527b3a3fe4d7a96720f43afd07b46a82d49421208b5a126235fb75cfb30b80d4029514772c8844273f940bddfbf4155c787f968f3be4060d01e4 - languageName: node - linkType: hard - -"mem@npm:^9.0.2": - version: 9.0.2 - resolution: "mem@npm:9.0.2" - dependencies: - map-age-cleaner: "npm:^0.1.3" - mimic-fn: "npm:^4.0.0" - checksum: 10c0/c2c56141399e520d8f0e50186bb7e4b49300b33984dc919682f3f13e53dec0e6608fbd327d5ae99494f45061a3a05a8ee04ccba6dcf795c3c215b5aa906eb41f - languageName: node - linkType: hard - -"merge2@npm:^1.3.0, merge2@npm:^1.4.1": - version: 1.4.1 - resolution: "merge2@npm:1.4.1" - checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb - languageName: node - linkType: hard - -"micromatch@npm:^4.0.4": - version: 4.0.8 - resolution: "micromatch@npm:4.0.8" - dependencies: - braces: "npm:^3.0.3" - picomatch: "npm:^2.3.1" - checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 - languageName: node - linkType: hard - -"mimic-fn@npm:^4.0.0": - version: 4.0.0 - resolution: "mimic-fn@npm:4.0.0" - checksum: 10c0/de9cc32be9996fd941e512248338e43407f63f6d497abe8441fa33447d922e927de54d4cc3c1a3c6d652857acd770389d5a3823f311a744132760ce2be15ccbf - languageName: node - linkType: hard - -"mimic-response@npm:^3.1.0": - version: 3.1.0 - resolution: "mimic-response@npm:3.1.0" - checksum: 10c0/0d6f07ce6e03e9e4445bee655202153bdb8a98d67ee8dc965ac140900d7a2688343e6b4c9a72cfc9ef2f7944dfd76eef4ab2482eb7b293a68b84916bac735362 - languageName: node - linkType: hard - -"minimatch@npm:^9.0.4": - version: 9.0.5 - resolution: "minimatch@npm:9.0.5" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed - languageName: node - linkType: hard - -"minimist@npm:^1.2.0, minimist@npm:^1.2.3": - version: 1.2.8 - resolution: "minimist@npm:1.2.8" - checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 - languageName: node - linkType: hard - -"minipass-collect@npm:^2.0.1": - version: 2.0.1 - resolution: "minipass-collect@npm:2.0.1" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e - languageName: node - linkType: hard - -"minipass-fetch@npm:^3.0.0": - version: 3.0.5 - resolution: "minipass-fetch@npm:3.0.5" - dependencies: - encoding: "npm:^0.1.13" - minipass: "npm:^7.0.3" - minipass-sized: "npm:^1.0.3" - minizlib: "npm:^2.1.2" - dependenciesMeta: - encoding: - optional: true - checksum: 10c0/9d702d57f556274286fdd97e406fc38a2f5c8d15e158b498d7393b1105974b21249289ec571fa2b51e038a4872bfc82710111cf75fae98c662f3d6f95e72152b - languageName: node - linkType: hard - -"minipass-flush@npm:^1.0.5": - version: 1.0.5 - resolution: "minipass-flush@npm:1.0.5" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd - languageName: node - linkType: hard - -"minipass-pipeline@npm:^1.2.4": - version: 1.2.4 - resolution: "minipass-pipeline@npm:1.2.4" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 - languageName: node - linkType: hard - -"minipass-sized@npm:^1.0.3": - version: 1.0.3 - resolution: "minipass-sized@npm:1.0.3" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb - languageName: node - linkType: hard - -"minipass@npm:^3.0.0": - version: 3.3.6 - resolution: "minipass@npm:3.3.6" - dependencies: - yallist: "npm:^4.0.0" - checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c - languageName: node - linkType: hard - -"minipass@npm:^5.0.0": - version: 5.0.0 - resolution: "minipass@npm:5.0.0" - checksum: 10c0/a91d8043f691796a8ac88df039da19933ef0f633e3d7f0d35dcd5373af49131cf2399bfc355f41515dc495e3990369c3858cd319e5c2722b4753c90bf3152462 - languageName: node - linkType: hard - -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.1.2": - version: 7.1.2 - resolution: "minipass@npm:7.1.2" - checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 - languageName: node - linkType: hard - -"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": - version: 2.1.2 - resolution: "minizlib@npm:2.1.2" - dependencies: - minipass: "npm:^3.0.0" - yallist: "npm:^4.0.0" - checksum: 10c0/64fae024e1a7d0346a1102bb670085b17b7f95bf6cfdf5b128772ec8faf9ea211464ea4add406a3a6384a7d87a0cd1a96263692134323477b4fb43659a6cab78 - languageName: node - linkType: hard - -"mkdirp-classic@npm:^0.5.2, mkdirp-classic@npm:^0.5.3": - version: 0.5.3 - resolution: "mkdirp-classic@npm:0.5.3" - checksum: 10c0/95371d831d196960ddc3833cc6907e6b8f67ac5501a6582f47dfae5eb0f092e9f8ce88e0d83afcae95d6e2b61a01741ba03714eeafb6f7a6e9dcc158ac85b168 - languageName: node - linkType: hard - -"mkdirp@npm:^1.0.3": - version: 1.0.4 - resolution: "mkdirp@npm:1.0.4" - bin: - mkdirp: bin/cmd.js - checksum: 10c0/46ea0f3ffa8bc6a5bc0c7081ffc3907777f0ed6516888d40a518c5111f8366d97d2678911ad1a6882bf592fa9de6c784fea32e1687bb94e1f4944170af48a5cf - languageName: node - linkType: hard - -"ms@npm:^2.1.3": - version: 2.1.3 - resolution: "ms@npm:2.1.3" - checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 - languageName: node - linkType: hard - -"napi-build-utils@npm:^1.0.1": - version: 1.0.2 - resolution: "napi-build-utils@npm:1.0.2" - checksum: 10c0/37fd2cd0ff2ad20073ce78d83fd718a740d568b225924e753ae51cb69d68f330c80544d487e5e5bd18e28702ed2ca469c2424ad948becd1862c1b0209542b2e9 - languageName: node - linkType: hard - -"negotiator@npm:^0.6.3": - version: 0.6.3 - resolution: "negotiator@npm:0.6.3" - checksum: 10c0/3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2 - languageName: node - linkType: hard - -"node-abi@npm:^3.3.0": - version: 3.67.0 - resolution: "node-abi@npm:3.67.0" - dependencies: - semver: "npm:^7.3.5" - checksum: 10c0/72ce2edbdfb84745bc201a4e48aa7146fd88a0d2c80046b6b17f28439c9a7683eab846f40f1e819349c31f7d9331ed5c50d1e741208d938dd5f38b29cab2275e - languageName: node - linkType: hard - -"node-gyp@npm:latest": - version: 10.2.0 - resolution: "node-gyp@npm:10.2.0" - dependencies: - env-paths: "npm:^2.2.0" - exponential-backoff: "npm:^3.1.1" - glob: "npm:^10.3.10" - graceful-fs: "npm:^4.2.6" - make-fetch-happen: "npm:^13.0.0" - nopt: "npm:^7.0.0" - proc-log: "npm:^4.1.0" - semver: "npm:^7.3.5" - tar: "npm:^6.2.1" - which: "npm:^4.0.0" - bin: - node-gyp: bin/node-gyp.js - checksum: 10c0/00630d67dbd09a45aee0a5d55c05e3916ca9e6d427ee4f7bc392d2d3dc5fad7449b21fc098dd38260a53d9dcc9c879b36704a1994235d4707e7271af7e9a835b - languageName: node - linkType: hard - -"nofilter@npm:^3.1.0": - version: 3.1.0 - resolution: "nofilter@npm:3.1.0" - checksum: 10c0/92459f3864a067b347032263f0b536223cbfc98153913b5dce350cb39c8470bc1813366e41993f22c33cc6400c0f392aa324a4b51e24c22040635c1cdb046499 - languageName: node - linkType: hard - -"nopt@npm:^7.0.0": - version: 7.2.1 - resolution: "nopt@npm:7.2.1" - dependencies: - abbrev: "npm:^2.0.0" - bin: - nopt: bin/nopt.js - checksum: 10c0/a069c7c736767121242037a22a788863accfa932ab285a1eb569eb8cd534b09d17206f68c37f096ae785647435e0c5a5a0a67b42ec743e481a455e5ae6a6df81 - languageName: node - linkType: hard - -"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": - version: 3.0.0 - resolution: "normalize-path@npm:3.0.0" - checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 - languageName: node - linkType: hard - -"npm-run-path@npm:^6.0.0": - version: 6.0.0 - resolution: "npm-run-path@npm:6.0.0" - dependencies: - path-key: "npm:^4.0.0" - unicorn-magic: "npm:^0.3.0" - checksum: 10c0/b223c8a0dcd608abf95363ea5c3c0ccc3cd877daf0102eaf1b0f2390d6858d8337fbb7c443af2403b067a7d2c116d10691ecd22ab3c5273c44da1ff8d07753bd - languageName: node - linkType: hard - -"once@npm:^1.3.1, once@npm:^1.4.0": - version: 1.4.0 - resolution: "once@npm:1.4.0" - dependencies: - wrappy: "npm:1" - checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 - languageName: node - linkType: hard - -"p-defer@npm:^1.0.0": - version: 1.0.0 - resolution: "p-defer@npm:1.0.0" - checksum: 10c0/ed603c3790e74b061ac2cb07eb6e65802cf58dce0fbee646c113a7b71edb711101329ad38f99e462bd2e343a74f6e9366b496a35f1d766c187084d3109900487 - languageName: node - linkType: hard - -"p-event@npm:^5.0.1": - version: 5.0.1 - resolution: "p-event@npm:5.0.1" - dependencies: - p-timeout: "npm:^5.0.2" - checksum: 10c0/2317171489537f316661fa863f3bb711b2ceb89182937238422cec10223cbb958c432d6c26a238446a622d788187bdd295b1d8ecedbe2e467e045930d60202b0 - languageName: node - linkType: hard - -"p-limit@npm:^4.0.0": - version: 4.0.0 - resolution: "p-limit@npm:4.0.0" - dependencies: - yocto-queue: "npm:^1.0.0" - checksum: 10c0/a56af34a77f8df2ff61ddfb29431044557fcbcb7642d5a3233143ebba805fc7306ac1d448de724352861cb99de934bc9ab74f0d16fe6a5460bdbdf938de875ad - languageName: node - linkType: hard - -"p-locate@npm:^6.0.0": - version: 6.0.0 - resolution: "p-locate@npm:6.0.0" - dependencies: - p-limit: "npm:^4.0.0" - checksum: 10c0/d72fa2f41adce59c198270aa4d3c832536c87a1806e0f69dffb7c1a7ca998fb053915ca833d90f166a8c082d3859eabfed95f01698a3214c20df6bb8de046312 - languageName: node - linkType: hard - -"p-map@npm:^4.0.0": - version: 4.0.0 - resolution: "p-map@npm:4.0.0" - dependencies: - aggregate-error: "npm:^3.0.0" - checksum: 10c0/592c05bd6262c466ce269ff172bb8de7c6975afca9b50c975135b974e9bdaafbfe80e61aaaf5be6d1200ba08b30ead04b88cfa7e25ff1e3b93ab28c9f62a2c75 - languageName: node - linkType: hard - -"p-map@npm:^5.5.0": - version: 5.5.0 - resolution: "p-map@npm:5.5.0" - dependencies: - aggregate-error: "npm:^4.0.0" - checksum: 10c0/410bce846b1e3db6bb2ccab6248372ecf4e635fc2b31331c8f56478e73fec9e146e8b4547585e635703160a3d252a6a65b8f855834aebc2c3408eb5789630cc4 - languageName: node - linkType: hard - -"p-timeout@npm:^5.0.2": - version: 5.1.0 - resolution: "p-timeout@npm:5.1.0" - checksum: 10c0/1b026cf9d5878c64bec4341ca9cda8ec6b8b3aea8a57885ca0fe2b35753a20d767fb6f9d3aa41e1252f42bc95432c05ea33b6b18f271fb10bfb0789591850a41 - languageName: node - linkType: hard - -"package-json-from-dist@npm:^1.0.0": - version: 1.0.0 - resolution: "package-json-from-dist@npm:1.0.0" - checksum: 10c0/e3ffaf6ac1040ab6082a658230c041ad14e72fabe99076a2081bb1d5d41210f11872403fc09082daf4387fc0baa6577f96c9c0e94c90c394fd57794b66aa4033 - languageName: node - linkType: hard - -"parse-ms@npm:^3.0.0": - version: 3.0.0 - resolution: "parse-ms@npm:3.0.0" - checksum: 10c0/056b4a32a9d3749f3f4cfffefb45c45540491deaa8e1d8ad43c2ddde7ba04edd076bd1b298f521238bb5fb084a9b2c4a2ebb78aefa651afbc4c2b0af4232fc54 - languageName: node - linkType: hard - -"parse-ms@npm:^4.0.0": - version: 4.0.0 - resolution: "parse-ms@npm:4.0.0" - checksum: 10c0/a7900f4f1ebac24cbf5e9708c16fb2fd482517fad353aecd7aefb8c2ba2f85ce017913ccb8925d231770404780df46244ea6fec598b3bde6490882358b4d2d16 - languageName: node - linkType: hard - -"path-exists@npm:^5.0.0": - version: 5.0.0 - resolution: "path-exists@npm:5.0.0" - checksum: 10c0/b170f3060b31604cde93eefdb7392b89d832dfbc1bed717c9718cbe0f230c1669b7e75f87e19901da2250b84d092989a0f9e44d2ef41deb09aa3ad28e691a40a - languageName: node - linkType: hard - -"path-key@npm:^3.1.0": - version: 3.1.1 - resolution: "path-key@npm:3.1.1" - checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c - languageName: node - linkType: hard - -"path-key@npm:^4.0.0": - version: 4.0.0 - resolution: "path-key@npm:4.0.0" - checksum: 10c0/794efeef32863a65ac312f3c0b0a99f921f3e827ff63afa5cb09a377e202c262b671f7b3832a4e64731003fa94af0263713962d317b9887bd1e0c48a342efba3 - languageName: node - linkType: hard - -"path-scurry@npm:^1.11.1": - version: 1.11.1 - resolution: "path-scurry@npm:1.11.1" - dependencies: - lru-cache: "npm:^10.2.0" - minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d - languageName: node - linkType: hard - -"path-type@npm:^4.0.0": - version: 4.0.0 - resolution: "path-type@npm:4.0.0" - checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c - languageName: node - linkType: hard - -"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": - version: 2.3.1 - resolution: "picomatch@npm:2.3.1" - checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be - languageName: node - linkType: hard - -"pkg-conf@npm:^4.0.0": - version: 4.0.0 - resolution: "pkg-conf@npm:4.0.0" - dependencies: - find-up: "npm:^6.0.0" - load-json-file: "npm:^7.0.0" - checksum: 10c0/27d027609f27228edcde121f6f707b4ba1f5488e95e98f2e58652ae4e99792081bd1de67d591f4a0f05b02c0b66d745591d49f82041cbc8d41e2238ef5d73eb4 - languageName: node - linkType: hard - -"plur@npm:^5.1.0": - version: 5.1.0 - resolution: "plur@npm:5.1.0" - dependencies: - irregular-plurals: "npm:^3.3.0" - checksum: 10c0/26bb622b8545fcfd47bbf56fbcca66c08693708a232e403fa3589e00003c56c14231ac57c7588ca5db83ef4be1f61383402c4ea954000768f779f8aef6eb6da8 - languageName: node - linkType: hard - -"prebuild-install@npm:^7.1.1": - version: 7.1.2 - resolution: "prebuild-install@npm:7.1.2" - dependencies: - detect-libc: "npm:^2.0.0" - expand-template: "npm:^2.0.3" - github-from-package: "npm:0.0.0" - minimist: "npm:^1.2.3" - mkdirp-classic: "npm:^0.5.3" - napi-build-utils: "npm:^1.0.1" - node-abi: "npm:^3.3.0" - pump: "npm:^3.0.0" - rc: "npm:^1.2.7" - simple-get: "npm:^4.0.0" - tar-fs: "npm:^2.0.0" - tunnel-agent: "npm:^0.6.0" - bin: - prebuild-install: bin.js - checksum: 10c0/e64868ba9ef2068fd7264f5b03e5298a901e02a450acdb1f56258d88c09dea601eefdb3d1dfdff8513fdd230a92961712be0676192626a3b4d01ba154d48bdd3 - languageName: node - linkType: hard - -"pretty-ms@npm:^8.0.0": - version: 8.0.0 - resolution: "pretty-ms@npm:8.0.0" - dependencies: - parse-ms: "npm:^3.0.0" - checksum: 10c0/e960d633ecca45445cf5c6dffc0f5e4bef6744c92449ab0e8c6c704800675ab71e181c5e02ece5265e02137a33e313d3f3e355fbf8ea30b4b5b23de423329f8d - languageName: node - linkType: hard - -"pretty-ms@npm:^9.0.0": - version: 9.1.0 - resolution: "pretty-ms@npm:9.1.0" - dependencies: - parse-ms: "npm:^4.0.0" - checksum: 10c0/fd111aad8800a04dfd654e6016da69bdaa6fc6a4c280f8e727cffd8b5960558e94942f1a94d4aa6e4d179561a0fbb0366a9ebe0ccefbbb0f8ff853b129cdefb9 - languageName: node - linkType: hard - -"proc-log@npm:^4.1.0, proc-log@npm:^4.2.0": - version: 4.2.0 - resolution: "proc-log@npm:4.2.0" - checksum: 10c0/17db4757c2a5c44c1e545170e6c70a26f7de58feb985091fb1763f5081cab3d01b181fb2dd240c9f4a4255a1d9227d163d5771b7e69c9e49a561692db865efb9 - languageName: node - linkType: hard - -"promise-retry@npm:^2.0.1": - version: 2.0.1 - resolution: "promise-retry@npm:2.0.1" - dependencies: - err-code: "npm:^2.0.2" - retry: "npm:^0.12.0" - checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 - languageName: node - linkType: hard - -"pump@npm:^3.0.0": - version: 3.0.2 - resolution: "pump@npm:3.0.2" - dependencies: - end-of-stream: "npm:^1.1.0" - once: "npm:^1.3.1" - checksum: 10c0/5ad655cb2a7738b4bcf6406b24ad0970d680649d996b55ad20d1be8e0c02394034e4c45ff7cd105d87f1e9b96a0e3d06fd28e11fae8875da26e7f7a8e2c9726f - languageName: node - linkType: hard - -"queue-microtask@npm:^1.2.2": - version: 1.2.3 - resolution: "queue-microtask@npm:1.2.3" - checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 - languageName: node - linkType: hard - -"rc@npm:^1.2.7": - version: 1.2.8 - resolution: "rc@npm:1.2.8" - dependencies: - deep-extend: "npm:^0.6.0" - ini: "npm:~1.3.0" - minimist: "npm:^1.2.0" - strip-json-comments: "npm:~2.0.1" - bin: - rc: ./cli.js - checksum: 10c0/24a07653150f0d9ac7168e52943cc3cb4b7a22c0e43c7dff3219977c2fdca5a2760a304a029c20811a0e79d351f57d46c9bde216193a0f73978496afc2b85b15 - languageName: node - linkType: hard - -"readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0": - version: 3.6.2 - resolution: "readable-stream@npm:3.6.2" - dependencies: - inherits: "npm:^2.0.3" - string_decoder: "npm:^1.1.1" - util-deprecate: "npm:^1.0.1" - checksum: 10c0/e37be5c79c376fdd088a45fa31ea2e423e5d48854be7a22a58869b4e84d25047b193f6acb54f1012331e1bcd667ffb569c01b99d36b0bd59658fb33f513511b7 - languageName: node - linkType: hard - -"readdirp@npm:~3.6.0": - version: 3.6.0 - resolution: "readdirp@npm:3.6.0" - dependencies: - picomatch: "npm:^2.2.1" - checksum: 10c0/6fa848cf63d1b82ab4e985f4cf72bd55b7dcfd8e0a376905804e48c3634b7e749170940ba77b32804d5fe93b3cc521aa95a8d7e7d725f830da6d93f3669ce66b - languageName: node - linkType: hard - -"require-directory@npm:^2.1.1": - version: 2.1.1 - resolution: "require-directory@npm:2.1.1" - checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 - languageName: node - linkType: hard - -"resolve-cwd@npm:^3.0.0": - version: 3.0.0 - resolution: "resolve-cwd@npm:3.0.0" - dependencies: - resolve-from: "npm:^5.0.0" - checksum: 10c0/e608a3ebd15356264653c32d7ecbc8fd702f94c6703ea4ac2fb81d9c359180cba0ae2e6b71faa446631ed6145454d5a56b227efc33a2d40638ac13f8beb20ee4 - languageName: node - linkType: hard - -"resolve-from@npm:^5.0.0": - version: 5.0.0 - resolution: "resolve-from@npm:5.0.0" - checksum: 10c0/b21cb7f1fb746de8107b9febab60095187781137fd803e6a59a76d421444b1531b641bba5857f5dc011974d8a5c635d61cec49e6bd3b7fc20e01f0fafc4efbf2 - languageName: node - linkType: hard - -"retry@npm:^0.12.0": - version: 0.12.0 - resolution: "retry@npm:0.12.0" - checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe - languageName: node - linkType: hard - -"reusify@npm:^1.0.4": - version: 1.0.4 - resolution: "reusify@npm:1.0.4" - checksum: 10c0/c19ef26e4e188f408922c46f7ff480d38e8dfc55d448310dfb518736b23ed2c4f547fb64a6ed5bdba92cd7e7ddc889d36ff78f794816d5e71498d645ef476107 - languageName: node - linkType: hard - -"root-workspace-0b6124@workspace:.": - version: 0.0.0-use.local - resolution: "root-workspace-0b6124@workspace:." - dependencies: - "@agoric/synthetic-chain": "npm:^0.3.0" - ava: "npm:^5.3.1" - languageName: unknown - linkType: soft - -"run-parallel@npm:^1.1.9": - version: 1.2.0 - resolution: "run-parallel@npm:1.2.0" - dependencies: - queue-microtask: "npm:^1.2.2" - checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 - languageName: node - linkType: hard - -"safe-buffer@npm:^5.0.1, safe-buffer@npm:~5.2.0": - version: 5.2.1 - resolution: "safe-buffer@npm:5.2.1" - checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 - languageName: node - linkType: hard - -"safer-buffer@npm:>= 2.1.2 < 3.0.0": - version: 2.1.2 - resolution: "safer-buffer@npm:2.1.2" - checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 - languageName: node - linkType: hard - -"semver@npm:^7.3.2, semver@npm:^7.3.5": - version: 7.6.3 - resolution: "semver@npm:7.6.3" - bin: - semver: bin/semver.js - checksum: 10c0/88f33e148b210c153873cb08cfe1e281d518aaa9a666d4d148add6560db5cd3c582f3a08ccb91f38d5f379ead256da9931234ed122057f40bb5766e65e58adaf - languageName: node - linkType: hard - -"serialize-error@npm:^7.0.1": - version: 7.0.1 - resolution: "serialize-error@npm:7.0.1" - dependencies: - type-fest: "npm:^0.13.1" - checksum: 10c0/7982937d578cd901276c8ab3e2c6ed8a4c174137730f1fb0402d005af209a0e84d04acc874e317c936724c7b5b26c7a96ff7e4b8d11a469f4924a4b0ea814c05 - languageName: node - linkType: hard - -"shebang-command@npm:^2.0.0": - version: 2.0.0 - resolution: "shebang-command@npm:2.0.0" - dependencies: - shebang-regex: "npm:^3.0.0" - checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e - languageName: node - linkType: hard - -"shebang-regex@npm:^3.0.0": - version: 3.0.0 - resolution: "shebang-regex@npm:3.0.0" - checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 - languageName: node - linkType: hard - -"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": - version: 4.1.0 - resolution: "signal-exit@npm:4.1.0" - checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 - languageName: node - linkType: hard - -"simple-concat@npm:^1.0.0": - version: 1.0.1 - resolution: "simple-concat@npm:1.0.1" - checksum: 10c0/62f7508e674414008910b5397c1811941d457dfa0db4fd5aa7fa0409eb02c3609608dfcd7508cace75b3a0bf67a2a77990711e32cd213d2c76f4fd12ee86d776 - languageName: node - linkType: hard - -"simple-get@npm:^4.0.0": - version: 4.0.1 - resolution: "simple-get@npm:4.0.1" - dependencies: - decompress-response: "npm:^6.0.0" - once: "npm:^1.3.1" - simple-concat: "npm:^1.0.0" - checksum: 10c0/b0649a581dbca741babb960423248899203165769747142033479a7dc5e77d7b0fced0253c731cd57cf21e31e4d77c9157c3069f4448d558ebc96cf9e1eebcf0 - languageName: node - linkType: hard - -"slash@npm:^4.0.0": - version: 4.0.0 - resolution: "slash@npm:4.0.0" - checksum: 10c0/b522ca75d80d107fd30d29df0549a7b2537c83c4c4ecd12cd7d4ea6c8aaca2ab17ada002e7a1d78a9d736a0261509f26ea5b489082ee443a3a810586ef8eff18 - languageName: node - linkType: hard - -"slice-ansi@npm:^5.0.0": - version: 5.0.0 - resolution: "slice-ansi@npm:5.0.0" - dependencies: - ansi-styles: "npm:^6.0.0" - is-fullwidth-code-point: "npm:^4.0.0" - checksum: 10c0/2d4d40b2a9d5cf4e8caae3f698fe24ae31a4d778701724f578e984dcb485ec8c49f0c04dab59c401821e80fcdfe89cace9c66693b0244e40ec485d72e543914f - languageName: node - linkType: hard - -"smart-buffer@npm:^4.2.0": - version: 4.2.0 - resolution: "smart-buffer@npm:4.2.0" - checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 - languageName: node - linkType: hard - -"socks-proxy-agent@npm:^8.0.3": - version: 8.0.4 - resolution: "socks-proxy-agent@npm:8.0.4" - dependencies: - agent-base: "npm:^7.1.1" - debug: "npm:^4.3.4" - socks: "npm:^2.8.3" - checksum: 10c0/345593bb21b95b0508e63e703c84da11549f0a2657d6b4e3ee3612c312cb3a907eac10e53b23ede3557c6601d63252103494caa306b66560f43af7b98f53957a - languageName: node - linkType: hard - -"socks@npm:^2.8.3": - version: 2.8.3 - resolution: "socks@npm:2.8.3" - dependencies: - ip-address: "npm:^9.0.5" - smart-buffer: "npm:^4.2.0" - checksum: 10c0/d54a52bf9325165770b674a67241143a3d8b4e4c8884560c4e0e078aace2a728dffc7f70150660f51b85797c4e1a3b82f9b7aa25e0a0ceae1a243365da5c51a7 - languageName: node - linkType: hard - -"sprintf-js@npm:^1.1.3": - version: 1.1.3 - resolution: "sprintf-js@npm:1.1.3" - checksum: 10c0/09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec - languageName: node - linkType: hard - -"sprintf-js@npm:~1.0.2": - version: 1.0.3 - resolution: "sprintf-js@npm:1.0.3" - checksum: 10c0/ecadcfe4c771890140da5023d43e190b7566d9cf8b2d238600f31bec0fc653f328da4450eb04bd59a431771a8e9cc0e118f0aa3974b683a4981b4e07abc2a5bb - languageName: node - linkType: hard - -"ssri@npm:^10.0.0": - version: 10.0.6 - resolution: "ssri@npm:10.0.6" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/e5a1e23a4057a86a97971465418f22ea89bd439ac36ade88812dd920e4e61873e8abd6a9b72a03a67ef50faa00a2daf1ab745c5a15b46d03e0544a0296354227 - languageName: node - linkType: hard - -"stack-utils@npm:^2.0.6": - version: 2.0.6 - resolution: "stack-utils@npm:2.0.6" - dependencies: - escape-string-regexp: "npm:^2.0.0" - checksum: 10c0/651c9f87667e077584bbe848acaecc6049bc71979f1e9a46c7b920cad4431c388df0f51b8ad7cfd6eed3db97a2878d0fc8b3122979439ea8bac29c61c95eec8a - languageName: node - linkType: hard - -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": - version: 4.2.3 - resolution: "string-width@npm:4.2.3" - dependencies: - emoji-regex: "npm:^8.0.0" - is-fullwidth-code-point: "npm:^3.0.0" - strip-ansi: "npm:^6.0.1" - checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b - languageName: node - linkType: hard - -"string-width@npm:^5.0.0, string-width@npm:^5.0.1, string-width@npm:^5.1.2": - version: 5.1.2 - resolution: "string-width@npm:5.1.2" - dependencies: - eastasianwidth: "npm:^0.2.0" - emoji-regex: "npm:^9.2.2" - strip-ansi: "npm:^7.0.1" - checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca - languageName: node - linkType: hard - -"string_decoder@npm:^1.1.1": - version: 1.3.0 - resolution: "string_decoder@npm:1.3.0" - dependencies: - safe-buffer: "npm:~5.2.0" - checksum: 10c0/810614ddb030e271cd591935dcd5956b2410dd079d64ff92a1844d6b7588bf992b3e1b69b0f4d34a3e06e0bd73046ac646b5264c1987b20d0601f81ef35d731d - languageName: node - linkType: hard - -"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": - version: 6.0.1 - resolution: "strip-ansi@npm:6.0.1" - dependencies: - ansi-regex: "npm:^5.0.1" - checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 - languageName: node - linkType: hard - -"strip-ansi@npm:^7.0.1": - version: 7.1.0 - resolution: "strip-ansi@npm:7.1.0" - dependencies: - ansi-regex: "npm:^6.0.1" - checksum: 10c0/a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4 - languageName: node - linkType: hard - -"strip-final-newline@npm:^4.0.0": - version: 4.0.0 - resolution: "strip-final-newline@npm:4.0.0" - checksum: 10c0/b0cf2b62d597a1b0e3ebc42b88767f0a0d45601f89fd379a928a1812c8779440c81abba708082c946445af1d6b62d5f16e2a7cf4f30d9d6587b89425fae801ff - languageName: node - linkType: hard - -"strip-json-comments@npm:~2.0.1": - version: 2.0.1 - resolution: "strip-json-comments@npm:2.0.1" - checksum: 10c0/b509231cbdee45064ff4f9fd73609e2bcc4e84a4d508e9dd0f31f70356473fde18abfb5838c17d56fb236f5a06b102ef115438de0600b749e818a35fbbc48c43 - languageName: node - linkType: hard - -"supertap@npm:^3.0.1": - version: 3.0.1 - resolution: "supertap@npm:3.0.1" - dependencies: - indent-string: "npm:^5.0.0" - js-yaml: "npm:^3.14.1" - serialize-error: "npm:^7.0.1" - strip-ansi: "npm:^7.0.1" - checksum: 10c0/8164674f2e280cab875f0fef5bb36c15553c13e29697ff92f4e0d6bc62149f0303a89eee47535413ed145ea72e14a24d065bab233059d48a499ec5ebb4566b0f - languageName: node - linkType: hard - -"tar-fs@npm:^2.0.0": - version: 2.1.1 - resolution: "tar-fs@npm:2.1.1" - dependencies: - chownr: "npm:^1.1.1" - mkdirp-classic: "npm:^0.5.2" - pump: "npm:^3.0.0" - tar-stream: "npm:^2.1.4" - checksum: 10c0/871d26a934bfb7beeae4c4d8a09689f530b565f79bd0cf489823ff0efa3705da01278160da10bb006d1a793fa0425cf316cec029b32a9159eacbeaff4965fb6d - languageName: node - linkType: hard - -"tar-stream@npm:^2.1.4": - version: 2.2.0 - resolution: "tar-stream@npm:2.2.0" - dependencies: - bl: "npm:^4.0.3" - end-of-stream: "npm:^1.4.1" - fs-constants: "npm:^1.0.0" - inherits: "npm:^2.0.3" - readable-stream: "npm:^3.1.1" - checksum: 10c0/2f4c910b3ee7196502e1ff015a7ba321ec6ea837667220d7bcb8d0852d51cb04b87f7ae471008a6fb8f5b1a1b5078f62f3a82d30c706f20ada1238ac797e7692 - languageName: node - linkType: hard - -"tar@npm:^6.1.11, tar@npm:^6.2.1": - version: 6.2.1 - resolution: "tar@npm:6.2.1" - dependencies: - chownr: "npm:^2.0.0" - fs-minipass: "npm:^2.0.0" - minipass: "npm:^5.0.0" - minizlib: "npm:^2.1.1" - mkdirp: "npm:^1.0.3" - yallist: "npm:^4.0.0" - checksum: 10c0/a5eca3eb50bc11552d453488344e6507156b9193efd7635e98e867fab275d527af53d8866e2370cd09dfe74378a18111622ace35af6a608e5223a7d27fe99537 - languageName: node - linkType: hard - -"temp-dir@npm:^3.0.0": - version: 3.0.0 - resolution: "temp-dir@npm:3.0.0" - checksum: 10c0/a86978a400984cd5f315b77ebf3fe53bb58c61f192278cafcb1f3fb32d584a21dc8e08b93171d7874b7cc972234d3455c467306cc1bfc4524b622e5ad3bfd671 - languageName: node - linkType: hard - -"time-zone@npm:^1.0.0": - version: 1.0.0 - resolution: "time-zone@npm:1.0.0" - checksum: 10c0/d00ebd885039109011b6e2423ebbf225160927333c2ade6d833e9cc4676db20759f1f3855fafde00d1bd668c243a6aa68938ce71fe58aab0d514e820d59c1d81 - languageName: node - linkType: hard - -"to-regex-range@npm:^5.0.1": - version: 5.0.1 - resolution: "to-regex-range@npm:5.0.1" - dependencies: - is-number: "npm:^7.0.0" - checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 - languageName: node - linkType: hard - -"tunnel-agent@npm:^0.6.0": - version: 0.6.0 - resolution: "tunnel-agent@npm:0.6.0" - dependencies: - safe-buffer: "npm:^5.0.1" - checksum: 10c0/4c7a1b813e7beae66fdbf567a65ec6d46313643753d0beefb3c7973d66fcec3a1e7f39759f0a0b4465883499c6dc8b0750ab8b287399af2e583823e40410a17a - languageName: node - linkType: hard - -"type-fest@npm:^0.13.1": - version: 0.13.1 - resolution: "type-fest@npm:0.13.1" - checksum: 10c0/0c0fa07ae53d4e776cf4dac30d25ad799443e9eef9226f9fddbb69242db86b08584084a99885cfa5a9dfe4c063ebdc9aa7b69da348e735baede8d43f1aeae93b - languageName: node - linkType: hard - -"unicorn-magic@npm:^0.3.0": - version: 0.3.0 - resolution: "unicorn-magic@npm:0.3.0" - checksum: 10c0/0a32a997d6c15f1c2a077a15b1c4ca6f268d574cf5b8975e778bb98e6f8db4ef4e86dfcae4e158cd4c7e38fb4dd383b93b13eefddc7f178dea13d3ac8a603271 - languageName: node - linkType: hard - -"unique-filename@npm:^3.0.0": - version: 3.0.0 - resolution: "unique-filename@npm:3.0.0" - dependencies: - unique-slug: "npm:^4.0.0" - checksum: 10c0/6363e40b2fa758eb5ec5e21b3c7fb83e5da8dcfbd866cc0c199d5534c42f03b9ea9ab069769cc388e1d7ab93b4eeef28ef506ab5f18d910ef29617715101884f - languageName: node - linkType: hard - -"unique-slug@npm:^4.0.0": - version: 4.0.0 - resolution: "unique-slug@npm:4.0.0" - dependencies: - imurmurhash: "npm:^0.1.4" - checksum: 10c0/cb811d9d54eb5821b81b18205750be84cb015c20a4a44280794e915f5a0a70223ce39066781a354e872df3572e8155c228f43ff0cce94c7cbf4da2cc7cbdd635 - languageName: node - linkType: hard - -"util-deprecate@npm:^1.0.1": - version: 1.0.2 - resolution: "util-deprecate@npm:1.0.2" - checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 - languageName: node - linkType: hard - -"well-known-symbols@npm:^2.0.0": - version: 2.0.0 - resolution: "well-known-symbols@npm:2.0.0" - checksum: 10c0/cb6c12e98877e8952ec28d13ae6f4fdb54ae1cb49b16a728720276dadd76c930e6cb0e174af3a4620054dd2752546f842540122920c6e31410208abd4958ee6b - languageName: node - linkType: hard - -"which@npm:^2.0.1": - version: 2.0.2 - resolution: "which@npm:2.0.2" - dependencies: - isexe: "npm:^2.0.0" - bin: - node-which: ./bin/node-which - checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f - languageName: node - linkType: hard - -"which@npm:^4.0.0": - version: 4.0.0 - resolution: "which@npm:4.0.0" - dependencies: - isexe: "npm:^3.1.1" - bin: - node-which: bin/which.js - checksum: 10c0/449fa5c44ed120ccecfe18c433296a4978a7583bf2391c50abce13f76878d2476defde04d0f79db8165bdf432853c1f8389d0485ca6e8ebce3bbcded513d5e6a - languageName: node - linkType: hard - -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": - version: 7.0.0 - resolution: "wrap-ansi@npm:7.0.0" - dependencies: - ansi-styles: "npm:^4.0.0" - string-width: "npm:^4.1.0" - strip-ansi: "npm:^6.0.0" - checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da - languageName: node - linkType: hard - -"wrap-ansi@npm:^8.1.0": - version: 8.1.0 - resolution: "wrap-ansi@npm:8.1.0" - dependencies: - ansi-styles: "npm:^6.1.0" - string-width: "npm:^5.0.1" - strip-ansi: "npm:^7.0.1" - checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 - languageName: node - linkType: hard - -"wrappy@npm:1": - version: 1.0.2 - resolution: "wrappy@npm:1.0.2" - checksum: 10c0/56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0 - languageName: node - linkType: hard - -"write-file-atomic@npm:^5.0.1": - version: 5.0.1 - resolution: "write-file-atomic@npm:5.0.1" - dependencies: - imurmurhash: "npm:^0.1.4" - signal-exit: "npm:^4.0.1" - checksum: 10c0/e8c850a8e3e74eeadadb8ad23c9d9d63e4e792bd10f4836ed74189ef6e996763959f1249c5650e232f3c77c11169d239cbfc8342fc70f3fe401407d23810505d - languageName: node - linkType: hard - -"y18n@npm:^5.0.5": - version: 5.0.8 - resolution: "y18n@npm:5.0.8" - checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 - languageName: node - linkType: hard - -"yallist@npm:^4.0.0": - version: 4.0.0 - resolution: "yallist@npm:4.0.0" - checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a - languageName: node - linkType: hard - -"yargs-parser@npm:^21.1.1": - version: 21.1.1 - resolution: "yargs-parser@npm:21.1.1" - checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 - languageName: node - linkType: hard - -"yargs@npm:^17.7.2": - version: 17.7.2 - resolution: "yargs@npm:17.7.2" - dependencies: - cliui: "npm:^8.0.1" - escalade: "npm:^3.1.1" - get-caller-file: "npm:^2.0.5" - require-directory: "npm:^2.1.1" - string-width: "npm:^4.2.3" - y18n: "npm:^5.0.5" - yargs-parser: "npm:^21.1.1" - checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 - languageName: node - linkType: hard - -"yocto-queue@npm:^1.0.0": - version: 1.1.1 - resolution: "yocto-queue@npm:1.1.1" - checksum: 10c0/cb287fe5e6acfa82690acb43c283de34e945c571a78a939774f6eaba7c285bacdf6c90fbc16ce530060863984c906d2b4c6ceb069c94d1e0a06d5f2b458e2a92 - languageName: node - linkType: hard - -"yoctocolors@npm:^2.0.0": - version: 2.1.1 - resolution: "yoctocolors@npm:2.1.1" - checksum: 10c0/85903f7fa96f1c70badee94789fade709f9d83dab2ec92753d612d84fcea6d34c772337a9f8914c6bed2f5fc03a428ac5d893e76fab636da5f1236ab725486d0 - languageName: node - linkType: hard diff --git a/a3p-integration/proposals/n:upgrade-next/package.json b/a3p-integration/proposals/n:upgrade-next/package.json index 69134383f62..e2914aa6253 100644 --- a/a3p-integration/proposals/n:upgrade-next/package.json +++ b/a3p-integration/proposals/n:upgrade-next/package.json @@ -15,7 +15,13 @@ "inter-protocol/replace-electorate-core.js replace-electorate A3P_INTEGRATION", "inter-protocol/updatePriceFeeds.js price-feeds A3P_INTEGRATION", "vats/add-auction.js price-feeds", - "vats/upgradeVaults.js price-feeds" + "vats/upgradeVaults.js price-feeds", + "inter-protocol/updatePriceFeeds.js submission/main main", + "vats/add-auction.js submission/main", + "vats/upgradeVaults.js submission/main", + "inter-protocol/updatePriceFeeds.js submission/devnet devnet", + "vats/add-auction.js submission/devnet", + "vats/upgradeVaults.js submission/devnet" ], "type": "Software Upgrade Proposal" }, From 9d92f8ede2c8971a94714d2e0cbbb1940de4be81 Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Tue, 22 Oct 2024 14:44:18 -0700 Subject: [PATCH 18/56] chore: rearrange price-feed proposal and script to match upgrade.go --- .../inter-protocol/updatePriceFeeds.js | 28 ++++++++----------- .../src/proposals/deploy-price-feeds.js | 4 +++ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/packages/builders/scripts/inter-protocol/updatePriceFeeds.js b/packages/builders/scripts/inter-protocol/updatePriceFeeds.js index 866a4865882..c6a96157abf 100644 --- a/packages/builders/scripts/inter-protocol/updatePriceFeeds.js +++ b/packages/builders/scripts/inter-protocol/updatePriceFeeds.js @@ -1,5 +1,3 @@ -/* global process */ - import { makeHelpers } from '@agoric/deploy-script-support'; import { getManifestForPriceFeeds } from '@agoric/inter-protocol/src/proposals/deploy-price-feeds.js'; @@ -38,14 +36,21 @@ const configurations = { }, }; +const { keys } = Object; +const Usage = `agoric run updatePriceFeed.js ${keys(configurations).join(' | ')}`; /** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ export const defaultProposalBuilder = async ({ publishRef, install }, opts) => { + const config = configurations[opts.variant]; + if (!config) { + console.error(Usage); + throw Error(Usage); + } return harden({ sourceSpec: '@agoric/inter-protocol/src/proposals/deploy-price-feeds.js', getManifestCall: [ getManifestForPriceFeeds.name, { - ...opts, + ...config, priceAggregatorRef: publishRef( install( '@agoric/inter-protocol/src/price/fluxAggregatorContract.js', @@ -63,26 +68,15 @@ export const defaultProposalBuilder = async ({ publishRef, install }, opts) => { }); }; -const { keys } = Object; -const Usage = `agoric run updatePriceFeed.js ${keys(configurations).join(' | ')}`; - /** @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ export default async (homeP, endowments) => { const { scriptArgs } = endowments; - assert(scriptArgs, 'expected script args endowment'); - const config = configurations[scriptArgs[0]]; - if (!config) { - console.error(Usage); - process.exit(1); - } - console.log('UPPrices', scriptArgs, config); + const variant = scriptArgs?.[0]; + console.log('updatePriceFeeds', scriptArgs, variant); const { writeCoreEval } = await makeHelpers(homeP, endowments); - const match = scriptArgs[0].match(/UNRELEASED_(.*)/); - const variant = match ? match[1] : scriptArgs; - await writeCoreEval(`gov-price-feeds-${variant}`, (utils, opts) => - defaultProposalBuilder(utils, { ...opts, ...config }), + defaultProposalBuilder(utils, { ...opts, variant }), ); }; diff --git a/packages/inter-protocol/src/proposals/deploy-price-feeds.js b/packages/inter-protocol/src/proposals/deploy-price-feeds.js index a80c50edf43..f98f62223f2 100644 --- a/packages/inter-protocol/src/proposals/deploy-price-feeds.js +++ b/packages/inter-protocol/src/proposals/deploy-price-feeds.js @@ -222,6 +222,9 @@ export const deployPriceFeeds = async (powers, config) => { ); const { priceAuthorityAdmin, priceAuthority } = powers.consume; + + trace({ oracleAddresses }); + trace({ inBrandNames }); for (const inBrandName of inBrandNames) { const AGORIC_INSTANCE_NAME = oracleBrandFeedName(inBrandName, outBrandName); const brandIn = await ensureOracleBrand(powers, { @@ -263,6 +266,7 @@ export const deployPriceFeeds = async (powers, config) => { options: { scaledPARef }, }); + trace('resolving priceAuthority8400'); // cf. #8400 QuotePayments storage leak powers.produce.priceAuthority8400.resolve(priceAuthority); }; From eadaef63cc82728e493e864f37fba477e4978b90 Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Tue, 22 Oct 2024 16:19:02 -0700 Subject: [PATCH 19/56] chore: don't rely on getting old auctioneer instance The vaults upgrade consumed instance.auctioneer and compared it to the new instance, passed from the new auctioneer proposal in a single-use promise, and failed if they weren't different. In this software upgrade, the instance.auctioneer value might be the new value, so this test can fail. Since the single-use promise comes directly from the new auction proposal, if it has a value, it's the new one. --- .../inter-protocol/src/proposals/upgrade-vaults.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/inter-protocol/src/proposals/upgrade-vaults.js b/packages/inter-protocol/src/proposals/upgrade-vaults.js index ab516d4bc37..febaa8cfa35 100644 --- a/packages/inter-protocol/src/proposals/upgrade-vaults.js +++ b/packages/inter-protocol/src/proposals/upgrade-vaults.js @@ -39,9 +39,6 @@ export const upgradeVaults = async ( auctionUpgradeNewInstance: auctionUpgradeNewInstanceProducer, newContractGovBundleId: newContractGovBundleIdErasor, }, - instance: { - consume: { auctioneer: auctioneerInstanceP }, - }, }, { options: { VaultFactoryBundle: vaultBundleRef } }, ) => { @@ -52,12 +49,7 @@ export const upgradeVaults = async ( await priceAuthority8400; - const [auctionOldInstance, auctionNewInstance] = await Promise.all([ - auctioneerInstanceP, - auctionUpgradeNewInstance, - ]); - auctionOldInstance !== auctionNewInstance || - Fail`Auction instance didn't change`; + const auctionNewInstance = await auctionUpgradeNewInstance; auctionUpgradeNewInstanceProducer.reset(); const publicFacet = E(zoe).getPublicFacet(auctionNewInstance); /** @type {import('@agoric/inter-protocol/src/auction/scheduler.js').FullSchedule} */ @@ -202,7 +194,6 @@ export const getManifestForUpgradeVaults = async ( auctionUpgradeNewInstance: uV, newContractGovBundleId: uV, }, - instance: { consume: { auctioneer: uV } }, }, }, installations: { VaultFactory: restoreRef(VaultFactoryRef) }, From 41c461f2869f53822ffe69dacbf24c78322dccee Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Thu, 24 Oct 2024 10:31:20 -0700 Subject: [PATCH 20/56] chore: gitignore price-feeds Why didn't it complain earlier? --- a3p-integration/proposals/n:upgrade-next/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/a3p-integration/proposals/n:upgrade-next/.gitignore b/a3p-integration/proposals/n:upgrade-next/.gitignore index 10c30e2fde7..2f3ed03b169 100644 --- a/a3p-integration/proposals/n:upgrade-next/.gitignore +++ b/a3p-integration/proposals/n:upgrade-next/.gitignore @@ -5,3 +5,4 @@ upgrade-bank/ upgrade-provisionPool/ upgrade-orch-core/ replace-electorate/ +price-feeds/ From 23ba30ac06297e41bd1a3f4bd6b9653048ff186b Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Thu, 24 Oct 2024 15:04:16 -0700 Subject: [PATCH 21/56] refactor: replaceElectorate get new auction via direct promises --- .../src/proposals/add-auction.js | 4 ++ .../src/proposals/replaceElectorate.js | 52 ++++++++++++++----- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/packages/inter-protocol/src/proposals/add-auction.js b/packages/inter-protocol/src/proposals/add-auction.js index 89769e4cb25..4b2e05da99a 100644 --- a/packages/inter-protocol/src/proposals/add-auction.js +++ b/packages/inter-protocol/src/proposals/add-auction.js @@ -9,6 +9,7 @@ const trace = makeTracer('NewAuction', true); /** * @typedef {PromiseSpaceOf<{ * auctionUpgradeNewInstance: Instance; + * auctionUpgradeNewGovCreator: any; * newContractGovBundleId: string; * }>} interlockPowers */ @@ -39,6 +40,7 @@ export const addAuction = async ( produce: { auctioneerKit: produceAuctioneerKit, auctionUpgradeNewInstance, + auctionUpgradeNewGovCreator, newContractGovBundleId, }, instance: { @@ -198,6 +200,7 @@ export const addAuction = async ( ); auctionUpgradeNewInstance.resolve(governedInstance); + auctionUpgradeNewGovCreator.resolve(kit.governorCreatorFacet); newContractGovBundleId.resolve(contractGovernorBundle.bundleID); }; @@ -217,6 +220,7 @@ export const ADD_AUCTION_MANIFEST = harden({ produce: { auctioneerKit: true, auctionUpgradeNewInstance: true, + auctionUpgradeNewGovCreator: true, newContractGovBundleId: true, }, instance: { diff --git a/packages/inter-protocol/src/proposals/replaceElectorate.js b/packages/inter-protocol/src/proposals/replaceElectorate.js index ac476113d6a..de30496b6d4 100644 --- a/packages/inter-protocol/src/proposals/replaceElectorate.js +++ b/packages/inter-protocol/src/proposals/replaceElectorate.js @@ -305,24 +305,42 @@ const startNewEconCharter = async ({ return startResult; }; +/** + * @typedef {PromiseSpaceOf<{ + * auctionUpgradeNewInstance: Instance; + * auctionUpgradeNewGovCreator: any; + * }>} interlockPowers + */ + /** * Adds governors to an existing Economic Committee Charter * - * - @param {EconomyBootstrapPowers} powers - The resources and capabilities - * required to start the committee. + * @param {EconomyBootstrapPowers & interlockPowers} powers + * + * - The resources and capabilities required to start the committee. * * @param {{ * options: { * econCharterKit: EconCharterStartResult; * }; - * }} config + * }} options * - 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, auctioneerKit } }, + { + consume: { + auctionUpgradeNewInstance: auctionUpgradeNewInstanceP, + auctionUpgradeNewGovCreator: auctionUpgradeNewGovCreatorP, + psmKit, + governedContractKits, + }, + produce: { + auctionUpgradeNewGovCreator: auctionUpgradeNewGovCreatorProduce, + }, + }, { options: { econCharterKit } }, ) => { const { creatorFacet: ecCreatorFacet } = E.get(econCharterKit); @@ -334,18 +352,24 @@ const addGovernorsToEconCharter = async ( } const governedContractKitMap = await governedContractKits; - const auctioneerKitObject = await auctioneerKit; for (const { instance, governorCreatorFacet, label, } of governedContractKitMap.values()) { - // `governedContractKitMap` has an old version of the auctioneer kit - // so using `auctioneerKit` instead + // The auctioneer was updated in this same release, getting values directly if (label === 'auctioneer') { + const [auctionUpgradeNewInstance, auctionUpgradeNewGovCreator] = + await Promise.all([ + auctionUpgradeNewInstanceP, + auctionUpgradeNewGovCreatorP, + ]); + // reset after use. auctionUpgradeNewInstance is reset by upgrade-vault.js + auctionUpgradeNewGovCreatorProduce.reset(); + await E(ecCreatorFacet).addInstance( - auctioneerKitObject.instance, - auctioneerKitObject.governorCreatorFacet, + auctionUpgradeNewInstance, + auctionUpgradeNewGovCreator, label, ); } else { @@ -362,9 +386,9 @@ const addGovernorsToEconCharter = async ( * Replaces the electorate for governance contracts by creating a new Economic * Committee and updating contracts with the new electorate's creator facet. * - * @param {EconomyBootstrapPowers} permittedPowers - The resources and - * capabilities needed for operations, including access to governance - * contracts and the PSM kit. + * @param {EconomyBootstrapPowers & interlockPowers} permittedPowers - The + * resources and capabilities needed for operations, including access to + * governance contracts and the PSM kit. * @param {{ * options: { * committeeName: string; @@ -459,10 +483,11 @@ export const getManifestForReplaceAllElectorates = async ( manifest: { [replaceAllElectorates.name]: { consume: { + auctionUpgradeNewGovCreator: true, + auctionUpgradeNewInstance: true, psmKit: true, governedContractKits: true, chainStorage: true, - auctioneerKit: true, highPrioritySendersManager: true, namesByAddressAdmin: true, // Rest of these are designed to be widely shared @@ -473,6 +498,7 @@ export const getManifestForReplaceAllElectorates = async ( econCharterKit: true, economicCommitteeKit: true, economicCommitteeCreatorFacet: true, + auctionUpgradeNewGovCreator: true, }, installation: { consume: { From 31717573ab8654aff5624d1587770825cb54082d Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Thu, 24 Oct 2024 15:04:51 -0700 Subject: [PATCH 22/56] chore: update auctioneer in governedContractKits --- packages/inter-protocol/src/proposals/add-auction.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/inter-protocol/src/proposals/add-auction.js b/packages/inter-protocol/src/proposals/add-auction.js index 4b2e05da99a..abecb842390 100644 --- a/packages/inter-protocol/src/proposals/add-auction.js +++ b/packages/inter-protocol/src/proposals/add-auction.js @@ -34,6 +34,7 @@ export const addAuction = async ( chainTimerService, economicCommitteeCreatorFacet: electorateCreatorFacet, econCharterKit, + governedContractKits: governedContractKitsP, priceAuthority8400, zoe, }, @@ -199,6 +200,8 @@ export const addAuction = async ( governedInstance, ); + const governedContractKits = await governedContractKitsP; + governedContractKits.init(kit.instance, kit); auctionUpgradeNewInstance.resolve(governedInstance); auctionUpgradeNewGovCreator.resolve(kit.governorCreatorFacet); newContractGovBundleId.resolve(contractGovernorBundle.bundleID); @@ -214,6 +217,7 @@ export const ADD_AUCTION_MANIFEST = harden({ chainTimerService: true, econCharterKit: true, economicCommitteeCreatorFacet: true, + governedContractKits: true, priceAuthority8400: true, zoe: true, }, From fc569b90e157bb5277aa6a71822129eb7803341b Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Thu, 24 Oct 2024 15:37:58 -0700 Subject: [PATCH 23/56] test: restore prior state of GLOBIGNORE I added replaceElectorate.js, so it wouldn't be run a second time by yarn ava ./*.test.js but that caused it to not in CI be found for yarn ava ./replaceElectorate.test.js which seems bizarre to me --- a3p-integration/proposals/n:upgrade-next/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/a3p-integration/proposals/n:upgrade-next/test.sh b/a3p-integration/proposals/n:upgrade-next/test.sh index 9d703ba45c3..07c0eb0d035 100755 --- a/a3p-integration/proposals/n:upgrade-next/test.sh +++ b/a3p-integration/proposals/n:upgrade-next/test.sh @@ -4,7 +4,7 @@ # The effects of this step are not persisted in further proposal layers. # suppress file names from glob that run earlier -GLOBIGNORE=initial.test.js replaceElectorate.test.js +GLOBIGNORE=initial.test.js yarn ava ./replaceElectorate.test.js From 8c0299ed1fae55d59aa1ec93f7c7a3bcec66d905 Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Thu, 24 Oct 2024 17:33:54 -0700 Subject: [PATCH 24/56] test: allow EC update bootstap test to run without auction upgrade --- .../bootstrapTests/ec-membership-update.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/boot/test/bootstrapTests/ec-membership-update.test.ts b/packages/boot/test/bootstrapTests/ec-membership-update.test.ts index 164b7f710e8..3865cb87432 100644 --- a/packages/boot/test/bootstrapTests/ec-membership-update.test.ts +++ b/packages/boot/test/bootstrapTests/ec-membership-update.test.ts @@ -59,6 +59,23 @@ export const makeZoeTestContext = async t => { await EV.vat('bootstrap').consumeItem('vaultFactoryKit'); console.timeLog('DefaultTestContext', 'vaultFactoryKit'); + // replaceElectorate relies on these values from the auction upgrade. Insert + // them manually since this bootstrap test doesn't run the auction upgrade. + const governedKits = await EV.vat('bootstrap').consumeItem( + 'governedContractKits', + ); + const auctioneerKit = await EV.vat('bootstrap').consumeItem('auctioneerKit'); + const auctionInstance = await auctioneerKit.instance; + const aKit = await EV(governedKits).get(auctionInstance); + await EV.vat('bootstrap').produceItem( + 'auctionUpgradeNewInstance', + aKit.instance, + ); + await EV.vat('bootstrap').produceItem( + 'auctionUpgradeNewGovCreator', + aKit.governorCreatorFacet, + ); + // has to be late enough for agoricNames data to have been published const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); console.timeLog('DefaultTestContext', 'agoricNamesRemotes'); From a1093950697c04c37bbef727a85b9f2d2d146efa Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Fri, 25 Oct 2024 14:16:10 -0700 Subject: [PATCH 25/56] feat: only add one auctioneer instance to charter --- .../src/proposals/replaceElectorate.js | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/inter-protocol/src/proposals/replaceElectorate.js b/packages/inter-protocol/src/proposals/replaceElectorate.js index de30496b6d4..cb862be5a67 100644 --- a/packages/inter-protocol/src/proposals/replaceElectorate.js +++ b/packages/inter-protocol/src/proposals/replaceElectorate.js @@ -358,21 +358,9 @@ const addGovernorsToEconCharter = async ( label, } of governedContractKitMap.values()) { // The auctioneer was updated in this same release, getting values directly - if (label === 'auctioneer') { - const [auctionUpgradeNewInstance, auctionUpgradeNewGovCreator] = - await Promise.all([ - auctionUpgradeNewInstanceP, - auctionUpgradeNewGovCreatorP, - ]); - // reset after use. auctionUpgradeNewInstance is reset by upgrade-vault.js - auctionUpgradeNewGovCreatorProduce.reset(); - - await E(ecCreatorFacet).addInstance( - auctionUpgradeNewInstance, - auctionUpgradeNewGovCreator, - label, - ); - } else { + // (there might be more than one auctioneer instance, but the others don't + // need to be registered.) + if (label !== 'auctioneer') { await E(ecCreatorFacet).addInstance( instance, governorCreatorFacet, @@ -380,6 +368,20 @@ const addGovernorsToEconCharter = async ( ); } } + + const [auctionUpgradeNewInstance, auctionUpgradeNewGovCreator] = + await Promise.all([ + auctionUpgradeNewInstanceP, + auctionUpgradeNewGovCreatorP, + ]); + // reset after use. auctionUpgradeNewInstance is reset by upgrade-vault.js + auctionUpgradeNewGovCreatorProduce.reset(); + + await E(ecCreatorFacet).addInstance( + auctionUpgradeNewInstance, + auctionUpgradeNewGovCreator, + 'auctioneer', + ); }; /** From 33cd4f265921cdbcfd419dbbbd22e2614dd2c2a3 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Tue, 8 Oct 2024 18:41:28 -0700 Subject: [PATCH 26/56] chore(swingset/misc-tools): add #9039 scanner to count cleanup work Give this tool a swingstore DB, and it will identify all the vat/kpid pairs that need cleanup to remediate the #9039 bug. This also estimates the amount of work the kernel-side remediation tool will need to do the cleanup automatically. The tool needed 11,882 DB queries to find the 3166 KPIDs needing cleanup in run-53, and ran in 0.37s on my laptop --- .../SwingSet/misc-tools/scan-9039-promises.js | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 packages/SwingSet/misc-tools/scan-9039-promises.js diff --git a/packages/SwingSet/misc-tools/scan-9039-promises.js b/packages/SwingSet/misc-tools/scan-9039-promises.js new file mode 100644 index 00000000000..c847c2b55d3 --- /dev/null +++ b/packages/SwingSet/misc-tools/scan-9039-promises.js @@ -0,0 +1,158 @@ +#!/usr/bin/env node +// @ts-nocheck + +// Given a swingstore database, produce a list of vpid/vatID pairs for +// all promises that are resolved but still in the c-list of their +// formerly-deciding vat. This happens when a vat is upgraded and the +// kernel disconnects (rejects) the previous incarnation's outstanding +// promises (https://github.com/Agoric/agoric-sdk/issues/9039). + +import process from 'process'; +import fs from 'fs'; +import sqlite3 from 'better-sqlite3'; +import yargsParser from 'yargs-parser'; +import '@endo/init/debug.js'; + +const main = rawArgv => { + const { _: args, ...options } = yargsParser(rawArgv.slice(2)); + // console.log(args, options); + if (Reflect.ownKeys(options).length > 0 || args.length !== 1) { + console.error( + [ + `Usage: ${rawArgv[1]} /path/to/swingstore.sqlite`, + 'Find leftover promises for bug #9039.', + ].join('\n'), + ); + process.exitCode = 1; + return; + } + + const [ssDBPath] = args; + if (!fs.existsSync(ssDBPath)) { + throw Error(`swingstore DB path (${ssDBPath}) must exist`); + } + const ssDB = sqlite3(/** @type {string} */ (ssDBPath)); + let queries = 0; + + const sqlGet = ssDB.prepare('SELECT value FROM kvStore WHERE key=?').pluck(); + const realRawGet = key => { + queries += 1; + return sqlGet.get(key); + }; + const realGet = key => JSON.parse(realRawGet(key)); + + // fake database for testing + const fake = { + 'vat.names': ['bootstrap'], + 'vat.name.bootstrap': 'v1', + 'vat.dynamicIDs': ['v9'], + runQueue: [10, 10], + 'runQueue.10': { type: 'notify', vatID: 'v9', kpid: 'kp1005304' }, + 'kp1005304.data.body': + '#{"incarnationNumber":0,"name":"vatUpgraded","upgradeMessage":"vat upgraded"}', + 'kp1005304.data.slots': [], + 'kp1005304.refCount': 1, + 'kp1005304.state': 'rejected', + }; + const fakeRawGet = key => { + queries += 1; + return fake[key]; + }; + // eslint-disable-next-line no-unused-vars + const fakeGet = key => fakeRawGet(key); + + // const [get, rawGet] = [fakeGet, fakeRawGet]; + const [get, rawGet] = [realGet, realRawGet]; + + const vatNames = get('vat.names'); + const staticIDs = vatNames.map(name => rawGet(`vat.name.${name}`)); + const dynamicIDs = get('vat.dynamicIDs'); + const allVatIDs = [...staticIDs, ...dynamicIDs]; + // console.log(allVatIDs); + + const sqlRange = ssDB.prepare( + `SELECT * FROM kvStore WHERE key >= ? AND key < ?`, + ); + + // old way took 547472 queries + // const rejectedKPIDs = new Set(); + + const [head, tail] = get('runQueue'); + const notifies = new Map(); // .get(kpid) = [vatIDs..]; + for (let p = head; p < tail; p += 1) { + const rq = get(`runQueue.${p}`); + if (rq.type === 'notify') { + const { vatID, kpid } = rq; + if (!notifies.has(kpid)) { + notifies.set(kpid, []); + } + notifies.get(kpid).push(vatID); + } + } + console.log(`pending notifies:`, notifies); + + const rejectedKPIDs = new Set(); + const nonRejectedKPIDs = new Set(); + + const isRejected = kpid => { + if (nonRejectedKPIDs.has(kpid)) { + return false; + } + if (rejectedKPIDs.has(kpid)) { + return true; + } + const state = rawGet(`${kpid}.state`); + // missing state means the kpid is deleted somehow, shouldn't happen + assert(state, `${kpid}.state is missing`); + if (state === 'rejected') { + rejectedKPIDs.add(kpid); + return true; + } + nonRejectedKPIDs.add(kpid); + return false; + }; + + // Bug 9039 causes the kernel to reject/disconnect a promise on + // behalf of the upgraded vat, but not remove it from the vat's + // c-list (which would normally happen when the vat emitted a + // syscall.resolve). The rejection process erases the `.decider` + // record, so we no longer know which vat to look at. We're looking + // for vpids which are 1: rejected, 2: present in a vat c-list, 3: + // do *not* have a notify scheduled. + + const buggyKPIDs = []; // tuples of [kpid, vatID] + const involvedVats = {}; + + for (const vatID of allVatIDs) { + // TODO: skip vats in vats.terminated, belt-and-suspenders + const prefix = `${vatID}.c.`; + const len = prefix.length; + const k1 = `${prefix}kp`; + const k2 = `${prefix}kq`; + for (const row of sqlRange.iterate(k1, k2)) { + // the kvStore's actual API (getNextKey) requires one query per result + queries += 1; + const kpid = row.key.slice(len); + if (!isRejected(kpid)) { + continue; + } + const n = notifies.get(kpid); + if (!n || !n.includes(vatID)) { + // there is no pending notify + buggyKPIDs.push([kpid, vatID]); + involvedVats[vatID] = 1 + (involvedVats[vatID] || 0); + } + } + } + + // console.log(buggyKPIDs); + console.log(`scan 9039: ${buggyKPIDs.length} kpid/vatID pairs to clean up`); + console.log(`first is:`, buggyKPIDs[0]); + console.log( + `${Reflect.ownKeys(involvedVats).length} vats involved:`, + involvedVats, + ); + console.log(`${queries} DB queries`); +}; + +main(process.argv); From f217ae333c5c605002d6412bdc9e5e057fd9a3d0 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 25 Oct 2024 00:11:47 -0700 Subject: [PATCH 27/56] docs(swingset): start a host-application responsibilities document --- packages/SwingSet/docs/host-app.md | 43 ++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 packages/SwingSet/docs/host-app.md diff --git a/packages/SwingSet/docs/host-app.md b/packages/SwingSet/docs/host-app.md new file mode 100644 index 00000000000..60a3efb0ed0 --- /dev/null +++ b/packages/SwingSet/docs/host-app.md @@ -0,0 +1,43 @@ +# The Host Application + +SwingSet is a library that helps you write a "Host Application" around a kernel, which in turn manages some number of vats and devices. The Host Application does not come with SwingSet: you must write one specific to your particular application. + +## Host Application Responsibilities + +### State Management + +First, the host app is responsible for managing the kernel's state in a "SwingStore", using the `@agoric/swing-store` package, which is a SQLite database in some user-selected base directory, wrapped in useful APIs. A SwingStore is created by the `@agoric/swing-store` package, and consists of both a `hostStorage` facet and a `kernelStorage` facet. The `kernelStorage` facet must be given to `makeSwingsetController()`, the primary API for creating a kernel. All kernel state is kept inside the SwingStore. + +The host app must use `hostStorage.commit()` to commit the SwingStore changes after each group of device inputs and cranks (usually triggered with one or more calls to `controller.run()`). The host must not commit while the run is execution: it must wait for the `controller.run()` return Promise to settle first. + +### Device IO + +The host app is also responsible for all device input and output. The kernel itself cannot talk to the outside world, except through devices. These devices are configured with the kernel config record (along with static vats), but the device *endowments* are provided by the host app via the `deviceEndowments` argument to `makeSwingsetController()`. + +SwingSet provides robust and deterministic computation, even in the face of unexpected reboot, and avoids a failure mode called "hangover inconsistency" by following the lead of the Waterken and E systems. Output messages (in fact all communication with the outside world) must be embargoed until all consequences of an inbound delivery have been durably committed. To maintain this, device endowments must refrain from transmitting their outputs or modifying state outside of the DB until after the host app calls `hostStorage.commit()`, and they must be prepared to re-transmit their outputs or re-apply their effects if they awaken into a world where the durable state says that a message must be transmitted but no record of an acknowledgment is also recorded. See the comms subsystem, especially the "mailbox" device, for more details. + +### Kernel Upgrade + +The life cycle of a SwingSet kernel begins with the one and only call to `initializeSwingset()`, which populates the swingstore DB for the first time. After that, the kernel is presumed to be immortal, but its execution is broken up into a series of reboots. Each reboot (e.g. each time the host application is started), the app must build a new controller with `makeSwingsetController()`, to have something to run. + +From time to time, the host app will be upgraded to use a newer version of the SwingSet kernel code (e.g. a new version of the `@agoric/swingset-vat` package). This newer version might require an upgrade to the kernel's internal state. For example, the way it represents some data about a vat might be made more efficient, and the upgrade process needs to examine and rewrite vat state to switch to the new representation. Or, a bug might be fixed, and the upgrade process needs to locate and remediate any consequences of the bug having been present for earlier execution. + +To make the resulting state changes occur at a predictable time, upgrades do not happen automatically. Instead, each time the host app reboots with a new version of the kernel code, it must call `upgradeSwingset(kernelStorage)`. It must do this *before* calling `makeSwingsetController()`, as that function will throw an error if given a swingstore that has not been upgraded. + +It is safe to call `upgradeSwingset` on reboots that do not change the version of the kernel code: the function is idempotent, and will do nothing if the swingstore is already up-to-date. + +So most host applications will start each reboot with a sequence like this: + +```js +const { hostStorage, kernelStorage } = openSwingStore(baseDirectory); +upgradeSwingset(kernelStorage); +const c = makeSwingsetController(kernelStorage, deviceEndowments); +``` + +### Crank Execution + +For convenience in discussion, we split execution into "blocks". During a block, the host may call one or more device inputs, such as inbound messages, or timer wakeup events. The end of the block is marked by one or more calls to `controller.run()`, followed by a `hostStorage.commit()`, followed by the host-provided device endowments doing whatever kind of outbound IO they need to do. + +In a replicated/blockchain host environment, these are the same blocks that make up the chain. Inbound messages come from the signed transactions that are included in each block. And each block can inform the timer device that time has advanced to whatever consensus time is computed as part of the blockchain voting process. "Outbound IO" is really just recording data in the chain state, where external parties can retrieve it and verify it against the block header and its hash. + +In a singular/solo environment, "block boundaries" are simply points in time when the host app decides it would be useful to perform computation, commit state, and release outbound messages. These "blocks" are triggered by inbound IO requests, or timer wakeup events. The host might choose to trigger a "block" immediately after each such event (to minimize latency), or it might defer execution for a little while to batch them together (for efficiency). From e0f9b092c3d7dcad2ced4e3cdc00cb9e754c56f8 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 10 Oct 2024 09:00:07 -0700 Subject: [PATCH 28/56] chore(swingset): add a proper PromiseRecord type Improve the types we use to represent records in the kernel promise table, and the values we pass around when working with that table. Update the kernel code (mainly kernelKeeper) to satisfy the new variants, by constructing the records with more care. Several assert/Fail lines were changed to satisfy the typechecker. As a side effect, this fixes #9889, because the casual/buggy approach we took before was correctly identified by TSC as not working for all promise states. fixes #9889 --- packages/SwingSet/src/kernel/kernel.js | 10 ++- .../SwingSet/src/kernel/state/kernelKeeper.js | 81 ++++++++++--------- packages/SwingSet/src/kernel/vatTranslator.js | 10 ++- packages/SwingSet/src/types-internal.js | 11 +++ 4 files changed, 70 insertions(+), 42 deletions(-) diff --git a/packages/SwingSet/src/kernel/kernel.js b/packages/SwingSet/src/kernel/kernel.js index b6e11dbd714..7800801fc60 100644 --- a/packages/SwingSet/src/kernel/kernel.js +++ b/packages/SwingSet/src/kernel/kernel.js @@ -597,7 +597,9 @@ export default function buildKernel( const p = kernelKeeper.getKernelPromise(kpid); kernelKeeper.incStat('dispatchNotify'); const vatKeeper = kernelKeeper.provideVatKeeper(vatID); - p.state !== 'unresolved' || Fail`spurious notification ${kpid}`; + if (p.state === 'unresolved') { + throw Fail`spurious notification ${kpid}`; + } /** @type { KernelDeliveryOneNotify[] } */ const resolutions = []; if (!vatKeeper.hasCListEntry(kpid)) { @@ -612,7 +614,9 @@ export default function buildKernel( return NO_DELIVERY_CRANK_RESULTS; } for (const toResolve of targets) { - const { state, data } = kernelKeeper.getKernelPromise(toResolve); + const tp = kernelKeeper.getKernelPromise(toResolve); + assert(tp.state !== 'unresolved'); + const { state, data } = tp; resolutions.push([toResolve, { state, data }]); } /** @type { KernelDeliveryNotify } */ @@ -1186,6 +1190,7 @@ export default function buildKernel( } } default: + // @ts-expect-error throw Fail`unknown promise resolution '${kp.state}'`; } } @@ -2081,6 +2086,7 @@ export default function buildKernel( return p.data; } default: { + // @ts-expect-error throw Fail`invalid state for ${kpid}: ${p.state}`; } } diff --git a/packages/SwingSet/src/kernel/state/kernelKeeper.js b/packages/SwingSet/src/kernel/state/kernelKeeper.js index 1d456160bf8..64adb603b39 100644 --- a/packages/SwingSet/src/kernel/state/kernelKeeper.js +++ b/packages/SwingSet/src/kernel/state/kernelKeeper.js @@ -45,6 +45,7 @@ const enableKernelGC = true; * @typedef { import('../../types-external.js').VatKeeper } VatKeeper * @typedef { import('../../types-internal.js').InternalKernelOptions } InternalKernelOptions * @typedef { import('../../types-internal.js').ReapDirtThreshold } ReapDirtThreshold + * @import {PromiseRecord} from '../../types-internal.js'; * @import {CleanupBudget, CleanupWork, PolicyOutputCleanupBudget} from '../../types-external.js'; * @import {RunQueueEventCleanupTerminatedVat} from '../../types-internal.js'; */ @@ -791,45 +792,41 @@ export default function makeKernelKeeper( return kpid; } + /** + * @param {string} kernelSlot + * @returns {PromiseRecord} + */ function getKernelPromise(kernelSlot) { insistKernelType('promise', kernelSlot); - const p = { state: getRequired(`${kernelSlot}.state`) }; - switch (p.state) { - case undefined: { - throw Fail`unknown kernelPromise '${kernelSlot}'`; - } + const state = getRequired(`${kernelSlot}.state`); + const refCount = Number(kvStore.get(`${kernelSlot}.refCount`)); + switch (state) { case 'unresolved': { - p.refCount = Number(kvStore.get(`${kernelSlot}.refCount`)); - p.decider = kvStore.get(`${kernelSlot}.decider`); - if (p.decider === '') { - p.decider = undefined; - } - p.policy = kvStore.get(`${kernelSlot}.policy`) || 'ignore'; - // @ts-expect-error get() may fail - p.subscribers = commaSplit(kvStore.get(`${kernelSlot}.subscribers`)); - p.queue = Array.from( + const decider = kvStore.get(`${kernelSlot}.decider`) || undefined; + const policy = kvStore.get(`${kernelSlot}.policy`) || 'ignore'; + const subscribers = commaSplit( + kvStore.get(`${kernelSlot}.subscribers`) || '', + ); + const queue = Array.from( getPrefixedValues(kvStore, `${kernelSlot}.queue.`), ).map(s => JSON.parse(s)); - break; + return harden({ state, refCount, decider, policy, subscribers, queue }); } case 'fulfilled': case 'rejected': { - p.refCount = Number(kvStore.get(`${kernelSlot}.refCount`)); - p.data = { - body: kvStore.get(`${kernelSlot}.data.body`), - // @ts-expect-error get() may fail - slots: commaSplit(kvStore.get(`${kernelSlot}.data.slots`)), + const data = { + body: getRequired(`${kernelSlot}.data.body`), + slots: commaSplit(getRequired(`${kernelSlot}.data.slots`)), }; - for (const s of p.data.slots) { + for (const s of data.slots) { parseKernelSlot(s); } - break; + return harden({ state, refCount, data }); } default: { - throw Fail`unknown state for ${kernelSlot}: ${p.state}`; + throw Fail`unknown state for ${kernelSlot}: ${state}`; } } - return harden(p); } function getResolveablePromise(kpid, expectedDecider) { @@ -838,7 +835,9 @@ export default function makeKernelKeeper( insistVatID(expectedDecider); } const p = getKernelPromise(kpid); - p.state === 'unresolved' || Fail`${kpid} was already resolved`; + if (p.state !== 'unresolved') { + throw Fail`${kpid} was already resolved`; + } if (expectedDecider) { p.decider === expectedDecider || Fail`${kpid} is decided by ${p.decider}, not ${expectedDecider}`; @@ -897,6 +896,7 @@ export default function makeKernelKeeper( // up the resolution *now* and set the correct target early. Doing that // might make it easier to remove the Promise Table entry earlier. const p = getKernelPromise(kernelSlot); + assert.equal(p.state, 'unresolved'); for (const msg of p.queue) { const entry = harden({ type: 'send', target: kernelSlot, msg }); enqueue('acceptanceQueue', entry); @@ -1142,18 +1142,22 @@ export default function makeKernelKeeper( function setDecider(kpid, decider) { insistVatID(decider); const p = getKernelPromise(kpid); - p.state === 'unresolved' || Fail`${kpid} was already resolved`; - !p.decider || Fail`${kpid} has decider ${p.decider}, not empty`; + assert.equal(p.state, 'unresolved', `${kpid} was already resolved`); + assert(!p.decider, `${kpid} has decider ${p.decider}, not empty`); kvStore.set(`${kpid}.decider`, decider); } function clearDecider(kpid) { const p = getKernelPromise(kpid); - p.state === 'unresolved' || Fail`${kpid} was already resolved`; - p.decider || Fail`${kpid} does not have a decider`; + assert.equal(p.state, 'unresolved', `${kpid} was already resolved`); + assert(p.decider, `${kpid} does not have a decider`); kvStore.set(`${kpid}.decider`, ''); } + /** + * @param {string} vatID + * @returns {IterableIterator} + */ function* enumeratePromisesByDecider(vatID) { insistVatID(vatID); const promisePrefix = `${vatID}.c.p`; @@ -1167,7 +1171,7 @@ export default function makeKernelKeeper( // whether the vat is the decider or not. If it is, we add the promise // to the list of promises that must be rejected because the dead vat // will never be able to act upon them. - const kpid = kvStore.get(k); + const kpid = getRequired(k); const p = getKernelPromise(kpid); if (p.state === 'unresolved' && p.decider === vatID) { yield kpid; @@ -1180,6 +1184,7 @@ export default function makeKernelKeeper( insistKernelType('promise', kernelSlot); insistVatID(vatID); const p = getKernelPromise(kernelSlot); + assert.equal(p.state, 'unresolved'); const s = new Set(p.subscribers); s.add(vatID); const v = Array.from(s).sort().join(','); @@ -1542,13 +1547,15 @@ export default function makeKernelKeeper( const kp = getKernelPromise(kpid); if (kp.refCount === 0) { let idx = 0; - // TODO (#9889) don't assume promise is settled - for (const slot of kp.data.slots) { - // Note: the following decrement can result in an addition to the - // maybeFreeKrefs set, which we are in the midst of iterating. - // TC39 went to a lot of trouble to ensure that this is kosher. - decrementRefCount(slot, `gc|${kpid}|s${idx}`); - idx += 1; + if (kp.state === 'fulfilled' || kp.state === 'rejected') { + // #9889 don't assume promise is settled + for (const slot of kp.data.slots) { + // Note: the following decrement can result in an addition to the + // maybeFreeKrefs set, which we are in the midst of iterating. + // TC39 went to a lot of trouble to ensure that this is kosher. + decrementRefCount(slot, `gc|${kpid}|s${idx}`); + idx += 1; + } } deleteKernelPromise(kpid); } diff --git a/packages/SwingSet/src/kernel/vatTranslator.js b/packages/SwingSet/src/kernel/vatTranslator.js index d781a41cf38..10a7f9559ae 100644 --- a/packages/SwingSet/src/kernel/vatTranslator.js +++ b/packages/SwingSet/src/kernel/vatTranslator.js @@ -50,6 +50,7 @@ function makeTranslateKernelDeliveryToVatDelivery(vatID, kernelKeeper) { parseVatSlot(targetSlot).allocatedByVat || Fail`deliver() to wrong vat`; } else if (type === 'promise') { const p = kernelKeeper.getKernelPromise(target); + assert(p.state === 'unresolved'); p.decider === vatID || Fail`wrong decider`; } const inputSlots = msg.methargs.slots.map(slot => @@ -59,7 +60,9 @@ function makeTranslateKernelDeliveryToVatDelivery(vatID, kernelKeeper) { if (msg.result) { insistKernelType('promise', msg.result); const p = kernelKeeper.getKernelPromise(msg.result); - p.state === 'unresolved' || Fail`result ${msg.result} already resolved`; + if (p.state !== 'unresolved') { + throw Fail`result ${msg.result} already resolved`; + } !p.decider || Fail`result ${msg.result} already has decider ${p.decider}`; resultSlot = vatKeeper.mapKernelSlotToVatSlot(msg.result); insistVatType('promise', resultSlot); @@ -318,8 +321,9 @@ function makeTranslateVatSyscallToKernelSyscall(vatID, kernelKeeper) { // In the case of non-pipelining vats these checks are redundant since // we're guaranteed to have a promise newly allocated by the vat. const p = kernelKeeper.getKernelPromise(result); - p.state === 'unresolved' || - Fail`send() result ${result} is already resolved`; + if (p.state !== 'unresolved') { + throw Fail`send() result ${result} is already resolved`; + } p.decider === vatID || Fail`send() result ${result} is decided by ${p.decider} not ${vatID}`; kernelKeeper.clearDecider(result); diff --git a/packages/SwingSet/src/types-internal.js b/packages/SwingSet/src/types-internal.js index 1ef68a5735f..7a4b2ef1604 100644 --- a/packages/SwingSet/src/types-internal.js +++ b/packages/SwingSet/src/types-internal.js @@ -92,6 +92,17 @@ export {}; * @property {number} [computrons] */ +/** + * @typedef {{ state: 'unresolved', refCount: number, + * decider: string | undefined, policy: string, + * subscribers: string[], queue: string[], + * }} UnresolvedPromiseRecord + * @typedef {{ state: 'fulfilled' | 'rejected', refCount: number, + * data: SwingSetCapData, + * }} SettledPromiseRecord + * @typedef {UnresolvedPromiseRecord | SettledPromiseRecord} PromiseRecord + */ + /** * @typedef {{ * enablePipelining: boolean, From 53c18b8ce38cfc5b17bc09573de51e0c7e136ab0 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 10 Oct 2024 09:07:23 -0700 Subject: [PATCH 29/56] chore: more efficient kernelKeeper.enumeratePromisesByDecider() API Previously, this function identified a set of promises (unresolved promises decided by a particular vat) and yielded an iterator of their kpids. To perform this filtering, it extracts their promise records along the way, but then throws out that data and only uses the kpids. Some clients will need the full promise record, so this can be more efficient if we return both the record and the kpid, and let the caller discard the parts they don't need. This also improves one client: the code that iterates over dead promises when terminating a vat. Rather than build a list of kpids in RAM, we can simply resolve them one at a time, since we are no longer in danger of the kvStore records being deleted before we resolve them (thanks to slow/deferred deletion). --- packages/SwingSet/src/kernel/kernel.js | 15 ++++++++------- .../SwingSet/src/kernel/state/kernelKeeper.js | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/SwingSet/src/kernel/kernel.js b/packages/SwingSet/src/kernel/kernel.js index 7800801fc60..77462028a33 100644 --- a/packages/SwingSet/src/kernel/kernel.js +++ b/packages/SwingSet/src/kernel/kernel.js @@ -287,15 +287,16 @@ export default function buildKernel( const vatKeeper = kernelKeeper.provideVatKeeper(vatID); critical = vatKeeper.getOptions().critical; - // Reject all promises decided by the vat, making sure to capture the list - // of kpids before that data is deleted. - const deadPromises = [...kernelKeeper.enumeratePromisesByDecider(vatID)]; - // remove vatID from the list of live vats, and mark for deletion + // remove vatID from the list of live vats, and mark for + // deletion (which will happen later, in vat-cleanup events) kernelKeeper.deleteVatID(vatID); kernelKeeper.markVatAsTerminated(vatID); deferred.push(kernelKeeper.removeVatFromSwingStoreExports(vatID)); - for (const kpid of deadPromises) { - resolveToError(kpid, makeError('vat terminated'), vatID); + + // Reject all promises decided by the vat + const errdata = makeError('vat terminated'); + for (const [kpid, _p] of kernelKeeper.enumeratePromisesByDecider(vatID)) { + resolveToError(kpid, errdata, vatID); } } if (critical) { @@ -1000,7 +1001,7 @@ export default function buildKernel( } // reject all promises for which the vat was decider - for (const kpid of kernelKeeper.enumeratePromisesByDecider(vatID)) { + for (const [kpid, _p] of kernelKeeper.enumeratePromisesByDecider(vatID)) { resolveToError(kpid, disconnectionCapData, vatID); } diff --git a/packages/SwingSet/src/kernel/state/kernelKeeper.js b/packages/SwingSet/src/kernel/state/kernelKeeper.js index 64adb603b39..db755abd07b 100644 --- a/packages/SwingSet/src/kernel/state/kernelKeeper.js +++ b/packages/SwingSet/src/kernel/state/kernelKeeper.js @@ -1156,7 +1156,7 @@ export default function makeKernelKeeper( /** * @param {string} vatID - * @returns {IterableIterator} + * @returns {IterableIterator<[kpid: string, p: PromiseRecord]>} */ function* enumeratePromisesByDecider(vatID) { insistVatID(vatID); @@ -1174,7 +1174,7 @@ export default function makeKernelKeeper( const kpid = getRequired(k); const p = getKernelPromise(kpid); if (p.state === 'unresolved' && p.decider === vatID) { - yield kpid; + yield [kpid, p]; } } } From f32d95f37e6cc70ec0edadab6e3d2307b1c2f24c Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 9 Oct 2024 17:14:15 -0700 Subject: [PATCH 30/56] chore: expose kernel.kpRegisterInterest() on controller This method was on the kernel object, but upcoming tests need it exposed on the controller too, to bump the refcount on promises that they want to examine, when those promises would otherwise get GCed when the last vat drops its reference. --- packages/SwingSet/src/controller/controller.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/SwingSet/src/controller/controller.js b/packages/SwingSet/src/controller/controller.js index 3321d1f92a6..05027542e2b 100644 --- a/packages/SwingSet/src/controller/controller.js +++ b/packages/SwingSet/src/controller/controller.js @@ -366,6 +366,10 @@ export async function makeSwingsetController( return kref; }, + kpRegisterInterest(kpid) { + return kernel.kpRegisterInterest(kpid); + }, + kpStatus(kpid) { return kernel.kpStatus(kpid); }, From 79c4af3ba3cdf824ad2edd53c98f86f20ada29ff Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Thu, 10 Oct 2024 17:25:54 -0700 Subject: [PATCH 31/56] refactor: change kernelKeeper to expose helper functions for upgrade Most of the kernelKeeper API methods are defined inside makeKernelKeeper, so they can close over some helper functions, which makes it difficult to use those methods from outside a kernelKeeper (e.g. from upgradeSwingset). This commit rearranges things to export two helper functions: * incrementReferenceCount() * readQueue() The upcoming #9039 remediation work will need access to these. --- .../SwingSet/src/kernel/state/kernelKeeper.js | 140 ++++++++++-------- 1 file changed, 78 insertions(+), 62 deletions(-) diff --git a/packages/SwingSet/src/kernel/state/kernelKeeper.js b/packages/SwingSet/src/kernel/state/kernelKeeper.js index db755abd07b..fad9ecddeeb 100644 --- a/packages/SwingSet/src/kernel/state/kernelKeeper.js +++ b/packages/SwingSet/src/kernel/state/kernelKeeper.js @@ -48,6 +48,7 @@ const enableKernelGC = true; * @import {PromiseRecord} from '../../types-internal.js'; * @import {CleanupBudget, CleanupWork, PolicyOutputCleanupBudget} from '../../types-external.js'; * @import {RunQueueEventCleanupTerminatedVat} from '../../types-internal.js'; + * @import {SwingStoreKernelStorage} from '@agoric/swing-store'; */ export { DEFAULT_REAP_DIRT_THRESHOLD_KEY }; @@ -213,6 +214,77 @@ export const getAllDynamicVats = getRequired => { return JSON.parse(getRequired('vat.dynamicIDs')); }; +const getObjectReferenceCount = (kvStore, kref) => { + const data = kvStore.get(`${kref}.refCount`); + if (!data) { + return { reachable: 0, recognizable: 0 }; + } + const [reachable, recognizable] = commaSplit(data).map(Number); + reachable <= recognizable || + Fail`refmismatch(get) ${kref} ${reachable},${recognizable}`; + return { reachable, recognizable }; +}; + +const setObjectReferenceCount = (kvStore, kref, counts) => { + const { reachable, recognizable } = counts; + assert.typeof(reachable, 'number'); + assert.typeof(recognizable, 'number'); + (reachable >= 0 && recognizable >= 0) || + Fail`${kref} underflow ${reachable},${recognizable}`; + reachable <= recognizable || + Fail`refmismatch(set) ${kref} ${reachable},${recognizable}`; + kvStore.set(`${kref}.refCount`, `${reachable},${recognizable}`); +}; + +/** + * Increment the reference count associated with some kernel object. + * + * We track references to promises and objects, but not devices. Promises + * have only a "reachable" count, whereas objects track both "reachable" + * and "recognizable" counts. + * + * @param { (key: string) => string} getRequired + * @param { import('@agoric/swing-store').KVStore } kvStore + * @param {string} kref The kernel slot whose refcount is to be incremented. + * @param {string?} tag Debugging note with rough source of the reference. + * @param {{ isExport?: boolean, onlyRecognizable?: boolean }} options + * 'isExport' means the reference comes from a clist export, which counts + * for promises but not objects. 'onlyRecognizable' means the reference + * provides only recognition, not reachability + */ +export const incrementReferenceCount = ( + getRequired, + kvStore, + kref, + tag, + options = {}, +) => { + const { isExport = false, onlyRecognizable = false } = options; + kref || Fail`incrementRefCount called with empty kref, tag=${tag}`; + const { type } = parseKernelSlot(kref); + if (type === 'promise') { + const refCount = Number(getRequired(`${kref}.refCount`)) + 1; + // kdebug(`++ ${kref} ${tag} ${refCount}`); + kvStore.set(`${kref}.refCount`, `${refCount}`); + } + if (type === 'object' && !isExport) { + let { reachable, recognizable } = getObjectReferenceCount(kvStore, kref); + if (!onlyRecognizable) { + reachable += 1; + } + recognizable += 1; + // kdebug(`++ ${kref} ${tag} ${reachable},${recognizable}`); + setObjectReferenceCount(kvStore, kref, { reachable, recognizable }); + } +}; + +export function* readQueue(queue, getRequired) { + const [head, tail] = JSON.parse(getRequired(`${queue}`)); + for (let i = head; i < tail; i += 1) { + yield JSON.parse(getRequired(`${queue}.${i}`)); + } +} + // we use different starting index values for the various vNN/koNN/kdNN/kpNN // slots, to reduce confusing overlap when looking at debug messages (e.g. // seeing both kp1 and ko1, which are completely unrelated despite having the @@ -390,14 +462,7 @@ export default function makeKernelKeeper( return tail - head; } - function dumpQueue(queue) { - const [head, tail] = JSON.parse(getRequired(`${queue}`)); - const result = []; - for (let i = head; i < tail; i += 1) { - result.push(JSON.parse(getRequired(`${queue}.${i}`))); - } - return result; - } + const dumpQueue = queue => [...readQueue(queue, getRequired)]; /** * @param {InternalKernelOptions} kernelOptions @@ -621,26 +686,9 @@ export default function makeKernelKeeper( return parseReachableAndVatSlot(kvStore.get(kernelKey)); } - function getObjectRefCount(kernelSlot) { - const data = kvStore.get(`${kernelSlot}.refCount`); - if (!data) { - return { reachable: 0, recognizable: 0 }; - } - const [reachable, recognizable] = commaSplit(data).map(Number); - reachable <= recognizable || - Fail`refmismatch(get) ${kernelSlot} ${reachable},${recognizable}`; - return { reachable, recognizable }; - } - - function setObjectRefCount(kernelSlot, { reachable, recognizable }) { - assert.typeof(reachable, 'number'); - assert.typeof(recognizable, 'number'); - (reachable >= 0 && recognizable >= 0) || - Fail`${kernelSlot} underflow ${reachable},${recognizable}`; - reachable <= recognizable || - Fail`refmismatch(set) ${kernelSlot} ${reachable},${recognizable}`; - kvStore.set(`${kernelSlot}.refCount`, `${reachable},${recognizable}`); - } + const getObjectRefCount = kref => getObjectReferenceCount(kvStore, kref); + const setObjectRefCount = (kref, counts) => + setObjectReferenceCount(kvStore, kref, counts); /** * Iterate over non-durable objects exported by a vat. @@ -1422,40 +1470,8 @@ export default function makeKernelKeeper( maybeFreeKrefs.add(kref); } - /** - * Increment the reference count associated with some kernel object. - * - * We track references to promises and objects, but not devices. Promises - * have only a "reachable" count, whereas objects track both "reachable" - * and "recognizable" counts. - * - * @param {unknown} kernelSlot The kernel slot whose refcount is to be incremented. - * @param {string?} tag Debugging note with rough source of the reference. - * @param {{ isExport?: boolean, onlyRecognizable?: boolean }} options - * 'isExport' means the reference comes from a clist export, which counts - * for promises but not objects. 'onlyRecognizable' means the reference - * provides only recognition, not reachability - */ - function incrementRefCount(kernelSlot, tag, options = {}) { - const { isExport = false, onlyRecognizable = false } = options; - kernelSlot || - Fail`incrementRefCount called with empty kernelSlot, tag=${tag}`; - const { type } = parseKernelSlot(kernelSlot); - if (type === 'promise') { - const refCount = Nat(BigInt(getRequired(`${kernelSlot}.refCount`))) + 1n; - // kdebug(`++ ${kernelSlot} ${tag} ${refCount}`); - kvStore.set(`${kernelSlot}.refCount`, `${refCount}`); - } - if (type === 'object' && !isExport) { - let { reachable, recognizable } = getObjectRefCount(kernelSlot); - if (!onlyRecognizable) { - reachable += 1; - } - recognizable += 1; - // kdebug(`++ ${kernelSlot} ${tag} ${reachable},${recognizable}`); - setObjectRefCount(kernelSlot, { reachable, recognizable }); - } - } + const incrementRefCount = (kref, tag, options = {}) => + incrementReferenceCount(getRequired, kvStore, kref, tag, options); /** * Decrement the reference count associated with some kernel object. From 18c0ca599f7daaf5f0adf9943d70d9814e6b1671 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Wed, 9 Oct 2024 00:27:00 -0700 Subject: [PATCH 32/56] fix(swingset): change processUpgradeVat to delete clist entries The kernel is responsible for "disconnecting" (rejecting) promises decided by a vat being upgraded, because Promise settlement functions (resolve/reject) are ephemeral, so they are lost during the upgrade, so the new incarnation will have no way to ever settle them. When the vat resolves a promise with syscall.resolve(), that vat's c-list entry is removed by the syscall handler. But when the kernel did the rejection as part of upgrade, the c-list entry was left in place, causing the promise to be pinned forever. This changes the kernel to remove the c-list entry as well, except in the case that the vat is also subscribed to its own promise (which happens when a PromiseWatcher is used on a locally-decided promise: unusual but we needed to tolerate that case). Self-subscribed promises are left in the c-list, because the subsequent dispatch.notify() will finish the job and remove the entry. This adds unit tests for the new vat-upgrade -time behavior (including the self-subscription case). It does not yet add remediation code, which will appear in a later commit. Therefore it merely: refs #9039 rather than fixing it entirely. --- packages/SwingSet/src/kernel/kernel.js | 27 +++++++++++++-- .../upgrade/bootstrap-scripted-upgrade.js | 23 +++++++++++-- .../SwingSet/test/upgrade/upgrade.test.js | 33 ++++++++++++++++--- packages/SwingSet/test/upgrade/vat-ulrik-1.js | 22 +++++++++++++ packages/SwingSet/test/upgrade/vat-ulrik-2.js | 13 ++++++++ 5 files changed, 107 insertions(+), 11 deletions(-) diff --git a/packages/SwingSet/src/kernel/kernel.js b/packages/SwingSet/src/kernel/kernel.js index 77462028a33..6f438cf1b5b 100644 --- a/packages/SwingSet/src/kernel/kernel.js +++ b/packages/SwingSet/src/kernel/kernel.js @@ -1000,9 +1000,30 @@ export default function buildKernel( return results; } - // reject all promises for which the vat was decider - for (const [kpid, _p] of kernelKeeper.enumeratePromisesByDecider(vatID)) { - resolveToError(kpid, disconnectionCapData, vatID); + // We are homesick for a future in which most promises are + // durable, and vats do not need to subscribe to their own + // promises to make promise-watchers work. In that world, vats + // somehow mark the non-durable promises, which we must + // reject/disconnect on their behalf during upgrade. + // + // To handle the present reality, without durable promises, we + // pretend that all promises are so marked. + + // reject all ephemeral promises for which the vat was decider + for (const [kpid, p] of kernelKeeper.enumeratePromisesByDecider(vatID)) { + const isEphemeral = true; // future vats will mark these explicitly + const selfSubscribed = + p.state === 'unresolved' && p.subscribers.includes(vatID); + if (isEphemeral) { + resolveToError(kpid, disconnectionCapData, vatID); + if (!selfSubscribed) { + // If the vat was subscribed to its own promise, the + // resolveToError will enqueue a dispatch.notify, whose delivery + // will delete the c-list entry. If it is *not* subscribed, we + // should delete the c-list entry now, because nobody else will. + vatKeeper.deleteCListEntriesForKernelSlots([kpid]); + } + } } // simulate an abandonExports syscall from the vat, diff --git a/packages/SwingSet/test/upgrade/bootstrap-scripted-upgrade.js b/packages/SwingSet/test/upgrade/bootstrap-scripted-upgrade.js index c93cfa6b568..c6bbde12178 100644 --- a/packages/SwingSet/test/upgrade/bootstrap-scripted-upgrade.js +++ b/packages/SwingSet/test/upgrade/bootstrap-scripted-upgrade.js @@ -78,8 +78,16 @@ export const buildRootObject = () => { p1.catch(() => 'hush'); const p2 = E(ulrikRoot).getEternalPromise(); p2.catch(() => 'hush'); + const p3 = E(ulrikRoot).getWatchedDecidedPromise(); - return { version, data, p1, p2, retain, ...parameters }; + // Create our own promises that capture the rejection data in a + // way that the test harness can read, because the originals + // will be GCed when the rejection notifications finish + // delivery. + const p1w = Promise.resolve().then(() => p1); + const p2w = Promise.resolve().then(() => p2); + + return { version, data, p1, p2, p3, p1w, p2w, retain, ...parameters }; }, upgradeV2: async () => { @@ -145,8 +153,17 @@ export const buildRootObject = () => { resolve(`message for your predecessor, don't freak out`); const newDur = await E(ulrikRoot).getNewDurandal(); - - return { version, data, remoerr, newDur, upgradeResult, ...parameters }; + const watcherResult = await E(ulrikRoot).getWatcherResult(); + + return { + version, + data, + remoerr, + newDur, + upgradeResult, + watcherResult, + ...parameters, + }; }, buildV1WithLostKind: async () => { diff --git a/packages/SwingSet/test/upgrade/upgrade.test.js b/packages/SwingSet/test/upgrade/upgrade.test.js index aca1efbbd89..85a1b6de7b5 100644 --- a/packages/SwingSet/test/upgrade/upgrade.test.js +++ b/packages/SwingSet/test/upgrade/upgrade.test.js @@ -363,6 +363,11 @@ const testUpgrade = async (t, defaultManagerType, options = {}) => { // grab the promises that should be rejected const v1p1Kref = verifyPresence(v1result.p1); const v1p2Kref = verifyPresence(v1result.p2); + const v1p1wKref = verifyPresence(v1result.p1w); + const v1p2wKref = verifyPresence(v1result.p2w); + const v1p3Kref = verifyPresence(v1result.p3); + c.kpRegisterInterest(v1p1wKref); + c.kpRegisterInterest(v1p2wKref); // grab krefs for the exported durable/virtual objects to check their abandonment const retainedKrefs = objectMap(v1result.retain, obj => verifyPresence(obj)); const retainedNames = 'dur1 vir2 vir5 vir7 vc1 vc3 dc4 rem1 rem2 rem3'.split( @@ -461,16 +466,34 @@ const testUpgrade = async (t, defaultManagerType, options = {}) => { const newDurKref = verifyPresence(v2result.newDur); t.not(newDurKref, dur1Kref); - // the old version's non-durable promises should be rejected - t.is(c.kpStatus(v1p1Kref), 'rejected'); + // The old version's non-durable promises should be rejected. The + // original kpids will be GCed. Bug #9039 failed to GC them. + t.is(kvStore.get(`${v1p1Kref}.refCount`), undefined); + t.is(kvStore.get(`${v1p2Kref}.refCount`), undefined); + t.is(kvStore.get(`${vatID}.c.${v1p1Kref}`), undefined); + t.is(kvStore.get(`${vatID}.c.${v1p2Kref}`), undefined); + t.is(kvStore.get(`${v1p1Kref}.state`), undefined); + t.is(kvStore.get(`${v1p2Kref}.state`), undefined); + // but vat-bootstrap wraps them (with Promise.all() so we can + // examine their rejection data. + t.is(c.kpStatus(v1p1wKref), 'rejected'); const vatUpgradedError = { name: 'vatUpgraded', upgradeMessage: 'test upgrade', incarnationNumber: 0, }; - t.deepEqual(kunser(c.kpResolution(v1p1Kref)), vatUpgradedError); - t.is(c.kpStatus(v1p2Kref), 'rejected'); - t.deepEqual(kunser(c.kpResolution(v1p2Kref)), vatUpgradedError); + t.deepEqual(kunser(c.kpResolution(v1p1wKref)), vatUpgradedError); + t.is(c.kpStatus(v1p2wKref), 'rejected'); + t.deepEqual(kunser(c.kpResolution(v1p2wKref)), vatUpgradedError); + + // The local-promise watcher should have fired. If vat-upgrade is + // too aggressive about removing c-list entries (i.e. it doesn't + // check for self-subscription first), the kernel-provoked + // dispatch.notify won't be delivered, and the new incarnation won't + // fire the durable watcher. + t.deepEqual(v2result.watcherResult, ['reject', vatUpgradedError]); + // and the promise it was watching should be GCed + t.is(kvStore.get(`${v1p3Kref}.refCount`), undefined); // verify export abandonment/garbage collection/etc. // This used to be MUCH more extensive, but GC was cut to the bone diff --git a/packages/SwingSet/test/upgrade/vat-ulrik-1.js b/packages/SwingSet/test/upgrade/vat-ulrik-1.js index 89c08c286df..71f54b02030 100644 --- a/packages/SwingSet/test/upgrade/vat-ulrik-1.js +++ b/packages/SwingSet/test/upgrade/vat-ulrik-1.js @@ -8,6 +8,7 @@ import { defineKind, makeScalarBigMapStore, makeScalarBigWeakMapStore, + watchPromise, } from '@agoric/vat-data'; // we set up a lot of ephemeral, merely-virtual, and durable objects @@ -29,6 +30,20 @@ const holderMethods = { const makeVir = defineKind('virtual', initHolder, holderMethods); const makeDur = defineDurableKind(durandalHandle, initHolder, holderMethods); +const initWatcher = () => harden({ result: ['unresolved'] }); +const watcherMethods = { + onFulfilled: ({ state }, data) => (state.result = harden(['fulfill', data])), + onRejected: ({ state }, reason) => + (state.result = harden(['reject', reason])), + getResult: ({ state }) => state.result, +}; +const watcherHandle = makeKindHandle('watcher'); +const makeWatcher = defineDurableKind( + watcherHandle, + initWatcher, + watcherMethods, +); + // TODO: explore 'export modRetains' // eslint-disable-next-line no-unused-vars let modRetains; @@ -134,6 +149,12 @@ const buildExports = (baggage, imp) => { export const buildRootObject = (_vatPowers, vatParameters, baggage) => { const { promise: p1 } = makePromiseKit(); const { promise: p2 } = makePromiseKit(); + const { promise: p3 } = makePromiseKit(); + const watcher = makeWatcher(); + watchPromise(p3, watcher); + baggage.init('watcher', watcher); + baggage.init('wh', watcherHandle); + let heldPromise; let counter = 0; @@ -165,6 +186,7 @@ export const buildRootObject = (_vatPowers, vatParameters, baggage) => { }, getEternalPromiseInArray: () => [p1], getEternalPromise: () => p2, + getWatchedDecidedPromise: () => p3, makeLostKind: () => { makeKindHandle('unhandled'); diff --git a/packages/SwingSet/test/upgrade/vat-ulrik-2.js b/packages/SwingSet/test/upgrade/vat-ulrik-2.js index e843e2154e3..51412d8c31e 100644 --- a/packages/SwingSet/test/upgrade/vat-ulrik-2.js +++ b/packages/SwingSet/test/upgrade/vat-ulrik-2.js @@ -61,6 +61,18 @@ export const buildRootObject = (_vatPowers, vatParameters, baggage) => { } } + const watcherHandle = baggage.get('wh'); + const initWatcher = () => harden({ result: ['unresolved'] }); + const watcherMethods = { + onFulfilled: ({ state }, data) => + (state.result = harden(['fulfill', data])), + onRejected: ({ state }, reason) => + (state.result = harden(['reject', reason])), + getResult: ({ state }) => state.result, + }; + defineDurableKind(watcherHandle, initWatcher, watcherMethods); + const watcher = baggage.get('watcher'); + const root = Far('root', { getVersion: () => 'v2', getParameters: () => vatParameters, @@ -89,6 +101,7 @@ export const buildRootObject = (_vatPowers, vatParameters, baggage) => { return E(handler).ping(`ping ${counter}`); }, getNewDurandal: () => newDur, + getWatcherResult: () => watcher.getResult(), }); // buildRootObject() is allowed to return a Promise, as long as it fulfills // promptly (we added this in #5246 to allow ZCF to use the async From e6156c7e171e4d2187af69274ff17213dd246eb3 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 11 Oct 2024 00:44:22 -0700 Subject: [PATCH 33/56] fix(swingset): add remediation of 9039 We use the `upgradeSwingset()` mechanism (when switching to version 3) to identify and fix all the leftover kpids caused by #9039. This performs automatic remediation of the damage, exactly once per database. The v2-to-v3 upgrader will look for all c-list promise entries, then check to see if the kpid is settled. It ignores the ones that have `dispatch.notify` events in either the runQueue or the acceptanceQueue. It then enqueues notify events to the vats, adjusts the refcounts and kernel stats accordinging, and finally and bumps the version to 3. These kpids will be for promises which were decided by an upgraded vat, but not resolved by upgrade time. The kernel "disconnects" (rejects) these promises, but #9039 failed to remove them from the upgraded vat's c-list. On the first run after this remediation, the kernel will deliver those notifies, which will delete the c-list entries and decrement their refcounts, which may trigger the usual deletions and further decrefs. The notifies will be delivered to the vat's new incarnation, which will harmlessly ignore them (as unrecognized vpids). This turned out to be the easiest way to get all the possible cleanups to run. New tests were added to exercise the remediation code. As this implements the last part of the fix, it: fixes #9039 --- .../src/controller/upgradeSwingset.js | 135 +++++++++++ .../SwingSet/src/kernel/state/kernelKeeper.js | 9 +- .../SwingSet/test/snapshots/state.test.js.md | 4 +- .../test/snapshots/state.test.js.snap | Bin 277 -> 279 bytes packages/SwingSet/test/state.test.js | 14 +- .../SwingSet/test/transcript-light.test.js | 2 +- .../SwingSet/test/upgrade-swingset.test.js | 222 +++++++++++++++++- 7 files changed, 368 insertions(+), 18 deletions(-) diff --git a/packages/SwingSet/src/controller/upgradeSwingset.js b/packages/SwingSet/src/controller/upgradeSwingset.js index 87195b8cfae..e9cff415a6e 100644 --- a/packages/SwingSet/src/controller/upgradeSwingset.js +++ b/packages/SwingSet/src/controller/upgradeSwingset.js @@ -1,9 +1,14 @@ +import { Fail } from '@endo/errors'; import { DEFAULT_REAP_DIRT_THRESHOLD_KEY, DEFAULT_GC_KREFS_PER_BOYD, getAllDynamicVats, getAllStaticVats, + incrementReferenceCount, + addToQueue, + readQueue, } from '../kernel/state/kernelKeeper.js'; +import { enumeratePrefixedKeys } from '../kernel/state/storageHelper.js'; const upgradeVatV0toV1 = (kvStore, defaultReapDirtThreshold, vatID) => { // This is called, once per vat, when upgradeSwingset migrates from @@ -204,6 +209,136 @@ export const upgradeSwingset = kernelStorage => { version = 2; } + if (version < 3) { + // v3 means that we've completed remediation for bug #9039 + console.log(`Starting remediation of bug #9039`); + + // find all terminated vats + const terminated = new Set(JSON.parse(getRequired('vats.terminated'))); + + // find all live vats + const allVatIDs = []; + for (const [_name, vatID] of getAllStaticVats(kvStore)) { + if (!terminated.has(vatID)) { + allVatIDs.push(vatID); + } + } + for (const vatID of getAllDynamicVats(getRequired)) { + if (!terminated.has(vatID)) { + allVatIDs.push(vatID); + } + } + + // find all pending notifies + const notifies = new Map(); // .get(kpid) = [vatIDs..]; + for (const name of ['runQueue', 'acceptanceQueue']) { + for (const rq of readQueue(name, getRequired)) { + if (rq.type === 'notify') { + const { vatID, kpid } = rq; + assert(vatID); + assert(kpid); + let vats = notifies.get(kpid); + if (!vats) { + vats = []; + notifies.set(kpid, vats); + } + vats.push(vatID); + } + } + } + console.log(` - pending notifies:`, notifies); + + // cache of known-settled kpids: will grow to num(kpids) + const KPIDStatus = new Map(); + const isSettled = kpid => { + if (KPIDStatus.has(kpid)) { + return KPIDStatus.get(kpid); + } + const state = kvStore.get(`${kpid}.state`); + // missing state means the kpid is deleted somehow, shouldn't happen + state || Fail`${kpid}.state is missing`; + const settled = state !== 'unresolved'; + KPIDStatus.set(kpid, settled); + return settled; + }; + + // walk vNN.c.kpNN for all vats, for each one check the + // kpNN.state, for the settled ones check for a pending notify, + // record the ones without a pending notify + + const buggyKPIDs = []; // [kpid, vatID] + for (const vatID of allVatIDs) { + const prefix = `${vatID}.c.`; + const len = prefix.length; + const ckpPrefix = `${vatID}.c.kp`; + for (const key of enumeratePrefixedKeys(kvStore, ckpPrefix)) { + const kpid = key.slice(len); + if (isSettled(kpid)) { + const n = notifies.get(kpid); + if (!n || !n.includes(vatID)) { + // there is no pending notify + buggyKPIDs.push([kpid, vatID]); + } + } + } + } + console.log(` - found ${buggyKPIDs.length} buggy kpids, enqueueing fixes`); + + // now fix it. The bug means we failed to delete the c-list entry + // and decref it back when the promise was rejected. That decref + // would have pushed the kpid onto maybeFreeKrefs, which would + // have triggered a refcount check at end-of-crank, which might + // have deleted the promise records (if nothing else was + // referencing the promise, like arguments in messages enqueued to + // unresolved promises, or something transient on the + // run-queue). Deleting those promise records might have decreffed + // krefs in the rejection data (although in general 9039 rejects + // those promises with non-slot-bearing DisconnectionObjects). + // + // To avoid duplicating a lot of kernel code inside this upgrade + // handler, we do the simplest possible thing: enqueue a notify to + // the upgraded vat for all these leftover promises. The new vat + // incarnation will ignore it (they don't recognize the vpid), but + // the dispatch.notify() delivery will clear the c-list and decref + // the kpid, and will trigger all the usual GC work. Note that + // these notifies will be delivered before any activity the host + // app might trigger for e.g. a chain upgrade, but they should not + // cause userspace-visible behavior (non-slot-bearing rejection + // data means no other vat will even get a gc-action delivery: + // only the upgraded vat will see anything, and those deliveries + // won't make it past liveslots). + + const kernelStats = JSON.parse(getRequired('kernelStats')); + // copied from kernel/state/stats.js, awkward to factor out + const incStat = (stat, delta = 1) => { + assert.equal(stat, 'acceptanceQueueLength'); + kernelStats[stat] += delta; + const maxStat = `${stat}Max`; + if ( + kernelStats[maxStat] !== undefined && + kernelStats[stat] > kernelStats[maxStat] + ) { + kernelStats[maxStat] = kernelStats[stat]; + } + const upStat = `${stat}Up`; + if (kernelStats[upStat] !== undefined) { + kernelStats[upStat] += delta; + } + }; + + for (const [kpid, vatID] of buggyKPIDs) { + const m = harden({ type: 'notify', vatID, kpid }); + incrementReferenceCount(getRequired, kvStore, kpid, `enq|notify`); + addToQueue('acceptanceQueue', m, getRequired, kvStore, incStat); + } + + kvStore.set('kernelStats', JSON.stringify(kernelStats)); + + console.log(` - #9039 remediation complete`); + modified = true; + version = 3; + } + if (modified) { kvStore.set('version', `${version}`); } diff --git a/packages/SwingSet/src/kernel/state/kernelKeeper.js b/packages/SwingSet/src/kernel/state/kernelKeeper.js index fad9ecddeeb..cf2d1eff3af 100644 --- a/packages/SwingSet/src/kernel/state/kernelKeeper.js +++ b/packages/SwingSet/src/kernel/state/kernelKeeper.js @@ -54,7 +54,7 @@ const enableKernelGC = true; export { DEFAULT_REAP_DIRT_THRESHOLD_KEY }; // most recent DB schema version -export const CURRENT_SCHEMA_VERSION = 2; +export const CURRENT_SCHEMA_VERSION = 3; // Kernel state lives in a key-value store supporting key retrieval by // lexicographic range. All keys and values are strings. @@ -73,9 +73,9 @@ export const CURRENT_SCHEMA_VERSION = 2; // only modified by a call to upgradeSwingset(). See below for // deltas/upgrades from one version to the next. // -// The current ("v2") schema keys/values are: +// The current ("v3") schema keys/values are: // -// version = '2' +// version = '3' // vat.names = JSON([names..]) // vat.dynamicIDs = JSON([vatIDs..]) // vat.name.$NAME = $vatID = v$NN @@ -179,6 +179,9 @@ export const CURRENT_SCHEMA_VERSION = 2; // v2: // * change `version` to `'2'` // * add `vats.terminated` with `[]` as initial value +// v3: +// * change `version` to `'3'` +// * perform remediation for bug #9039 /** @type {(s: string) => string[]} s */ export function commaSplit(s) { diff --git a/packages/SwingSet/test/snapshots/state.test.js.md b/packages/SwingSet/test/snapshots/state.test.js.md index 4c02a66077d..674e2a3c811 100644 --- a/packages/SwingSet/test/snapshots/state.test.js.md +++ b/packages/SwingSet/test/snapshots/state.test.js.md @@ -8,8 +8,8 @@ Generated by [AVA](https://avajs.dev). > initial state - '7b16bffd29f6a2d11bae7b536ef4c230af8cadc29284928b6cc2f7338507a987' + 'af35907384e9d63dd9fc4d4df0440005c0ee81ef88f86089a0e0a280fe3793af' > expected activityhash - '7dbf5a49d4e2b999c431730fcd4927c01c713eaa54fe273626e4201853e38d3b' + '040e27413c25f3ce668d9778add3b3d39547358ded553c0b9fba898004968d1b' diff --git a/packages/SwingSet/test/snapshots/state.test.js.snap b/packages/SwingSet/test/snapshots/state.test.js.snap index 0efcf0fac96c6f997cd9489988b1d3ad22ebcee5..a4a79f56520a3a09b6a39e1270d8a7c6d9b0a07e 100644 GIT binary patch literal 279 zcmV+y0qFigRzVqf00000000ARkg-n0Fc3s@LMLkeARX07o2(tLJHCLLcI{m&*7=eXHs~nf zPq;57Cn_YS9F6p5<~xP5#$)}wYRj$YrG!LLP|a@GQV5KUg!e>9Iu z@u^B-_#cZ100000000AZP`ge8F%Y~6Mb!MkbySgTub&-XKux>eUF)n|?jmeZDB(}Y zmvRvm1tU!}#k{^to>w}y&#Uy>XSqDmD=xg+)nrba;NjiAOQdXeZ+k^ diff --git a/packages/SwingSet/test/state.test.js b/packages/SwingSet/test/state.test.js index c60a01aa79c..8a5076b14a6 100644 --- a/packages/SwingSet/test/state.test.js +++ b/packages/SwingSet/test/state.test.js @@ -183,7 +183,7 @@ test('kernel state', async t => { k.emitCrankHashes(); checkState(t, store.dump, [ - ['version', '2'], + ['version', '3'], ['crankNumber', '0'], ['gcActions', '[]'], ['runQueue', '[1,1]'], @@ -223,7 +223,7 @@ test('kernelKeeper vat names', async t => { k.emitCrankHashes(); checkState(t, store.dump, [ - ['version', '2'], + ['version', '3'], ['crankNumber', '0'], ['gcActions', '[]'], ['runQueue', '[1,1]'], @@ -279,7 +279,7 @@ test('kernelKeeper device names', async t => { k.emitCrankHashes(); checkState(t, store.dump, [ - ['version', '2'], + ['version', '3'], ['crankNumber', '0'], ['gcActions', '[]'], ['runQueue', '[1,1]'], @@ -462,7 +462,7 @@ test('kernelKeeper promises', async t => { k.emitCrankHashes(); checkState(t, store.dump, [ - ['version', '2'], + ['version', '3'], ['crankNumber', '0'], ['device.nextID', '7'], ['vat.nextID', '1'], @@ -1078,7 +1078,7 @@ test('dirt upgrade', async t => { // * v3.reapCountdown: 'never' // * v3.reapInterval: 'never' - t.is(k.kvStore.get('version'), '2'); + t.is(k.kvStore.get('version'), '3'); k.kvStore.delete(`kernel.defaultReapDirtThreshold`); k.kvStore.set(`kernel.defaultReapInterval`, '1000'); @@ -1168,7 +1168,7 @@ test('v2 upgrade', async t => { k.saveStats(); // roll back to v1 - t.is(k.kvStore.get('version'), '2'); + t.is(k.kvStore.get('version'), '3'); k.kvStore.delete(`vats.terminated`); k.kvStore.set('version', '1'); @@ -1187,5 +1187,5 @@ test('v2 upgrade', async t => { t.true(k2.kvStore.has(`vats.terminated`)); t.deepEqual(JSON.parse(k2.kvStore.get(`vats.terminated`)), []); - t.is(k2.kvStore.get(`version`), '2'); + t.is(k2.kvStore.get(`version`), '3'); }); diff --git a/packages/SwingSet/test/transcript-light.test.js b/packages/SwingSet/test/transcript-light.test.js index 9049eb57845..e911a4e3451 100644 --- a/packages/SwingSet/test/transcript-light.test.js +++ b/packages/SwingSet/test/transcript-light.test.js @@ -17,7 +17,7 @@ test('transcript-light load', async t => { t.teardown(c.shutdown); const serialized0 = debug.serialize(); const kvstate0 = debug.dump().kvEntries; - t.is(kvstate0.version, '2'); + t.is(kvstate0.version, '3'); t.is(kvstate0.runQueue, '[1,1]'); t.not(kvstate0.acceptanceQueue, '[]'); diff --git a/packages/SwingSet/test/upgrade-swingset.test.js b/packages/SwingSet/test/upgrade-swingset.test.js index a18b2699dd8..a14e409b83e 100644 --- a/packages/SwingSet/test/upgrade-swingset.test.js +++ b/packages/SwingSet/test/upgrade-swingset.test.js @@ -1,9 +1,12 @@ /* eslint-disable no-underscore-dangle */ // @ts-nocheck -import { initSwingStore } from '@agoric/swing-store'; +// eslint-disable-next-line import/order import { test } from '../tools/prepare-test-env-ava.js'; +import { initSwingStore } from '@agoric/swing-store'; +import { kser } from '@agoric/kmarshal'; + import { initializeSwingset, makeSwingsetController, @@ -28,7 +31,7 @@ test('kernel refuses to run with out-of-date DB - v0', async t => { // kernelkeeper v0 schema, just deleting the version key and adding // 'initialized' - t.is(kvStore.get('version'), '2'); + t.is(kvStore.get('version'), '3'); kvStore.delete(`version`); kvStore.set('initialized', 'true'); await commit(); @@ -51,7 +54,7 @@ test('kernel refuses to run with out-of-date DB - v1', async t => { // kernelkeeper v1 schema, by reducing the version key and removing // vats.terminated - t.is(kvStore.get('version'), '2'); + t.is(kvStore.get('version'), '3'); kvStore.set(`version`, '1'); kvStore.delete('vats.terminated'); await commit(); @@ -62,6 +65,28 @@ test('kernel refuses to run with out-of-date DB - v1', async t => { }); }); +test('kernel refuses to run with out-of-date DB - v2', async t => { + const { hostStorage, kernelStorage } = initSwingStore(); + const { commit } = hostStorage; + const { kvStore } = kernelStorage; + const config = {}; + await initializeSwingset(config, [], kernelStorage, t.context.data); + await commit(); + + // now doctor the initial state to make it look like the + // kernelkeeper v1 schema, by reducing the version key and removing + // vats.terminated + + t.is(kvStore.get('version'), '3'); + kvStore.set(`version`, '2'); + await commit(); + + // Now build a controller around this modified state, which should fail. + await t.throwsAsync(() => makeSwingsetController(kernelStorage), { + message: /kernel DB is too old/, + }); +}); + test('upgrade kernel state', async t => { const { hostStorage, kernelStorage } = initSwingStore(); const { commit } = hostStorage; @@ -96,7 +121,7 @@ test('upgrade kernel state', async t => { t.true(kvStore.has('kernel.defaultReapDirtThreshold')); - t.is(kvStore.get('version'), '2'); + t.is(kvStore.get('version'), '3'); kvStore.delete('version'); // i.e. revert to v0 kvStore.set('initialized', 'true'); kvStore.delete('vats.terminated'); @@ -186,7 +211,7 @@ test('upgrade non-reaping kernel state', async t => { t.true(kvStore.has('kernel.defaultReapDirtThreshold')); - t.is(kvStore.get('version'), '2'); + t.is(kvStore.get('version'), '3'); kvStore.delete('version'); // i.e. revert to v0 kvStore.set('initialized', 'true'); kvStore.delete('vats.terminated'); @@ -229,3 +254,190 @@ test('upgrade non-reaping kernel state', async t => { gcKrefs: 'never', }); }); + +test('v3 upgrade', async t => { + // exercise the remediation code for bug #9039 + const { hostStorage, kernelStorage, debug } = initSwingStore(); + const { commit } = hostStorage; + const { kvStore } = kernelStorage; + const config = {}; + await initializeSwingset(config, [], kernelStorage, t.context.data); + await commit(); + + // doctor the initial state to inject #9039 problems, then check + // that upgrade applies the expected fixes. We pretend that + // v1-vatAdmin was upgraded and left some promises lying around. + + const vatID = kvStore.get('vat.name.vatAdmin'); + t.truthy(vatID); + + const disconnectionObject = { + name: 'vatUpgraded', + upgradeMessage: 'test upgrade', + incarnationNumber: 0, + }; + const dccd = kser(disconnectionObject); + + t.is(kvStore.get('version'), '3'); + kvStore.set('version', '2'); // revert to v2 + const runQueue = []; + const acceptanceQueue = []; + const nextID = Number(kvStore.get('kp.nextID')); + const p1 = `kp${nextID}`; + const p2 = `kp${nextID + 1}`; + const p3 = `kp${nextID + 2}`; + const p4 = `kp${nextID + 3}`; + const p5 = `kp${nextID + 4}`; + const p6 = `kp${nextID + 5}`; + kvStore.set('kp.nextID', `${nextID + 6}`); + + // first promise "was" known only to the upgraded vat, but not + // self-subscribed, so no notify was sent: remediated + kvStore.set(`${p1}.state`, 'rejected'); + kvStore.set(`${p1}.data.body`, dccd.body); + kvStore.set(`${p1}.data.slots`, ''); + kvStore.set(`${p1}.refCount`, '1'); + kvStore.set(`${vatID}.c.${p1}`, 'R p+90'); + kvStore.set(`${vatID}.c.p+90`, p1); + + // second promise was also only known to upgraded vat, but we + // pretend it was self-subscribed, and the notify is still sitting + // in the run-queue: ignored + kvStore.set(`${p2}.state`, 'rejected'); + kvStore.set(`${p2}.data.body`, dccd.body); + kvStore.set(`${p2}.data.slots`, ''); + kvStore.set(`${p2}.refCount`, '2'); // c-list, runQueue + kvStore.set(`${vatID}.c.${p2}`, 'R p+91'); + kvStore.set(`${vatID}.c.p+91`, p2); + runQueue.push({ type: 'notify', vatID, kpid: p2 }); + + // third promise is only known to upgraded vat, but self-subscribed, + // and the notify is still sitting in the acceptance queue: ignored + kvStore.set(`${p3}.state`, 'rejected'); + kvStore.set(`${p3}.data.body`, dccd.body); + kvStore.set(`${p3}.data.slots`, ''); + kvStore.set(`${p3}.refCount`, '2'); // c-list, acceptanceQueue + kvStore.set(`${vatID}.c.${p3}`, 'R p+92'); + kvStore.set(`${vatID}.c.p+92`, p3); + acceptanceQueue.push({ type: 'notify', vatID, kpid: p3 }); + + // fourth promise has additional references, still remediated + kvStore.set(`${p4}.state`, 'rejected'); + kvStore.set(`${p4}.data.body`, dccd.body); + kvStore.set(`${p4}.data.slots`, ''); + // note: we aren't being specific about *where* the other reference + // is coming from. A plausible source is an argument of a message + // queued to some other unresolved promise. A non-plausible one is + // in the c-list of some other vat (as a settled promise that one + // should have gotten a notify too, assuming they were subscribed, + // and they shouldn't be not subscribed). If the refcounts were + // stored in a DB with more runtime checking, we'd be creating an + // illegal situation here, but it's not. + kvStore.set(`${p4}.refCount`, '2'); // c-list, other + kvStore.set(`${vatID}.c.${p4}`, 'R p+93'); + kvStore.set(`${vatID}.c.p+93`, p4); + + // fifth promise is fulfilled, not rejected, without a notify: + // remediated (even though strictly speaking 9039 is about rejected + // promises) + kvStore.set(`${p5}.state`, 'fulfilled'); + kvStore.set(`${p5}.data.body`, '#{}'); + kvStore.set(`${p5}.data.slots`, ''); + kvStore.set(`${p5}.refCount`, '1'); + kvStore.set(`${vatID}.c.${p5}`, 'R p+95'); + kvStore.set(`${vatID}.c.p+95`, p5); + + // sixth promise is unresolved: ignored + kvStore.set(`${p6}.state`, 'unresolved'); + kvStore.set(`${p6}.subscribers`, ''); + kvStore.set(`${p6}.queue.nextID`, `0`); + kvStore.set(`${p6}.refCount`, `1`); + kvStore.set(`${p6}.decider`, vatID); + kvStore.set(`${vatID}.c.${p6}`, 'R p+96'); + kvStore.set(`${vatID}.c.p+96`, p6); + + // now update queues + + // eslint-disable-next-line prefer-const + let [runHead, runTail] = JSON.parse(kvStore.get('runQueue')); + for (const m of runQueue) { + kvStore.set(`runQueue.${runTail}`, JSON.stringify(m)); + runTail += 1; + } + kvStore.set('runQueue', JSON.stringify([runHead, runTail])); + + // eslint-disable-next-line prefer-const + let [accHead, accTail] = JSON.parse(kvStore.get('acceptanceQueue')); + for (const m of acceptanceQueue) { + kvStore.set(`acceptanceQueue.${accTail}`, JSON.stringify(m)); + accTail += 1; + } + kvStore.set('acceptanceQueue', JSON.stringify([accHead, accTail])); + + let stats = JSON.parse(kvStore.get('kernelStats')); + stats.runQueueLength += runQueue.length; + stats.runQueueLengthUp += runQueue.length; + stats.runQueueLengthMax = runQueue.length; + stats.acceptanceQueueLength += acceptanceQueue.length; + stats.acceptanceQueueLengthUp += acceptanceQueue.length; + stats.acceptanceQueueLengthMax = acceptanceQueue.length; + kvStore.set('kernelStats', JSON.stringify(stats)); + + await commit(); + + const data = { ...debug.dump().kvEntries }; + + // confirm that this state is too old for the kernel to use + await t.throwsAsync(() => makeSwingsetController(kernelStorage), { + message: /kernel DB is too old/, + }); + + // upgrade it + upgradeSwingset(kernelStorage); + // now we should be good to go + const _controller = await makeSwingsetController(kernelStorage); + + // check state by mutating our dumped copy and then comparing + // against a new dump + + t.deepEqual({ a: 1, b: 2 }, { b: 2, a: 1 }); + // expect notifies for p1/p4/p5 in acceptance queue + const [head, tail] = JSON.parse(kvStore.get('acceptanceQueue')); + t.is(head, accHead); + t.is(tail, accTail + 3); + data.acceptanceQueue = JSON.stringify([accHead, accTail + 3]); + // note: we aren't JSON-parsing the entries, so this depends upon + // the properties being assigned in this exact order + const np1 = JSON.stringify({ type: 'notify', vatID, kpid: p1 }); + data[`acceptanceQueue.${tail - 3}`] = np1; + const np4 = JSON.stringify({ type: 'notify', vatID, kpid: p4 }); + data[`acceptanceQueue.${tail - 2}`] = np4; + const np5 = JSON.stringify({ type: 'notify', vatID, kpid: p5 }); + data[`acceptanceQueue.${tail - 1}`] = np5; + + // stats are updated with the queue changes + stats = JSON.parse(data.kernelStats); + stats.acceptanceQueueLength += 3; + stats.acceptanceQueueLengthUp += 3; + stats.acceptanceQueueLengthMax = stats.acceptanceQueueLength; + data.kernelStats = JSON.stringify(stats); + + // the refcounts should now be one larger, because of the queued + // notifies + data[`${p1}.refCount`] = '2'; + data[`${p2}.refCount`] = '2'; + data[`${p3}.refCount`] = '2'; + data[`${p4}.refCount`] = '3'; + data[`${p5}.refCount`] = '2'; + data[`${p6}.refCount`] = '1'; + + // the version is bumped, indicating we don't need to perform this + // remediation again (because the bug is fixed and we won't be + // creating similar corruption in the future) + data.version = '3'; + + // no other state changes are expected + + const newData = { ...debug.dump().kvEntries }; + t.deepEqual(data, newData); +}); From a2ab0bc9b731e0f5b2df5d4cb6b2f43df38fb185 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 25 Oct 2024 12:41:44 -0700 Subject: [PATCH 34/56] feat(swingset): add "upgrade events" management Host applications are responsible for calling `upgradeSwingset(kernelStorage)` before creating the controller, at least when the kernel package version has been changed, to perform any necessary upgrades to the kernel state. Previously, if the upgrades needed to inject messages into the run-queue (e.g. the `dispatch.notify` messages used to remediate leftover problems from #9039), `upgradeSwingset` would inject them automatically, which could intermingle their cranks with anything still in the run-queue when the upgrade occurs (like if the previous block had leftover work to do). With this commit, these messages are instead held internally by the kernel, in a new kvStore key (`upgradeEvents`), so the host can choose when to inject them, for instance *after* it has drained the run-queue of any leftover work. This introduces a new host-app responsibility: when `upgradeEvents()` indicates that modifications have been made, the app must call `controller.injectQueuedUpgradeEvents()` some time before the next `hostStorage.commit()`. That will add the remediation messages to the acceptance queue, so the next `controller.run()` will execute them. --- packages/SwingSet/docs/host-app.md | 28 +++++++++-- .../SwingSet/src/controller/controller.js | 2 + .../src/controller/upgradeSwingset.js | 47 ++++++++--------- packages/SwingSet/src/kernel/kernel.js | 5 ++ .../SwingSet/src/kernel/state/kernelKeeper.js | 26 ++++++++++ packages/SwingSet/test/kernel.test.js | 43 ++++++++++++++++ packages/SwingSet/test/state.test.js | 22 +++++++- .../SwingSet/test/upgrade-swingset.test.js | 50 +++++++++++++------ 8 files changed, 176 insertions(+), 47 deletions(-) diff --git a/packages/SwingSet/docs/host-app.md b/packages/SwingSet/docs/host-app.md index 60a3efb0ed0..581406c3907 100644 --- a/packages/SwingSet/docs/host-app.md +++ b/packages/SwingSet/docs/host-app.md @@ -18,22 +18,40 @@ SwingSet provides robust and deterministic computation, even in the face of unex ### Kernel Upgrade -The life cycle of a SwingSet kernel begins with the one and only call to `initializeSwingset()`, which populates the swingstore DB for the first time. After that, the kernel is presumed to be immortal, but its execution is broken up into a series of reboots. Each reboot (e.g. each time the host application is started), the app must build a new controller with `makeSwingsetController()`, to have something to run. +The life cycle of a SwingSet kernel begins with the one and only call to `initializeSwingset()`, which populates the SwingStore DB for the first time. After that, the kernel is presumed to be immortal, but its execution is broken up into a series of reboots. Each reboot (e.g. each time the host application is started), the app must build a new controller with `makeSwingsetController()`, to have something to run. -From time to time, the host app will be upgraded to use a newer version of the SwingSet kernel code (e.g. a new version of the `@agoric/swingset-vat` package). This newer version might require an upgrade to the kernel's internal state. For example, the way it represents some data about a vat might be made more efficient, and the upgrade process needs to examine and rewrite vat state to switch to the new representation. Or, a bug might be fixed, and the upgrade process needs to locate and remediate any consequences of the bug having been present for earlier execution. +From time to time, the host app will be upgraded to use a newer version of the SwingSet kernel code (e.g. a new version of this `@agoric/swingset-vat` package). The newer version might require an upgrade to the kernel's persisted/durable state. For example, the way it represents some vat metadata might be made more efficient, and the upgrade process needs to examine and rewrite vat state to use the new representation. Or, a bug might be fixed, and the upgrade process needs to locate and remediate any consequences of the bug having been present during earlier execution. -To make the resulting state changes occur at a predictable time, upgrades do not happen automatically. Instead, each time the host app reboots with a new version of the kernel code, it must call `upgradeSwingset(kernelStorage)`. It must do this *before* calling `makeSwingsetController()`, as that function will throw an error if given a swingstore that has not been upgraded. +To make the resulting state changes occur deterministically, upgrades are not automatic. Instead, each time the host app reboots with a new version of the kernel code, it must call `upgradeSwingset(kernelStorage)`. It must do this *before* calling `makeSwingsetController()`, as that function will throw an error if given a SwingStore that has not been upgraded. -It is safe to call `upgradeSwingset` on reboots that do not change the version of the kernel code: the function is idempotent, and will do nothing if the swingstore is already up-to-date. +It is safe to call `upgradeSwingset` on reboots that do not change the version of the kernel code: the function is idempotent, and will do nothing if the SwingStore is already up-to-date. + +Some upgrades (in particular bug remediations) need to add events to the kernel's run-queue. To avoid having these events be intermingled with work that might already be on the run-queue at reboot time (e.g. work leftover from previous runs), these events are not automatically injected at that time. Instead, the kernel remembers what needs to be done, and waits for the host to invoke `controller.injectQueuedUpgradeEvents()` at a time of their choosing. This should be done before the next `commit()`, to avoid the risk of them being lost by a reboot. So most host applications will start each reboot with a sequence like this: ```js const { hostStorage, kernelStorage } = openSwingStore(baseDirectory); upgradeSwingset(kernelStorage); -const c = makeSwingsetController(kernelStorage, deviceEndowments); +const controller = makeSwingsetController(kernelStorage, deviceEndowments); +controller.injectQueuedUpgradeEvents(); +``` + +followed by later code to execute runs. Inputs can be fed all-at-once, or each processed in their own run, but at the end of the block, the host app must `commit()` the DB: + +```js +async function doBlock(deviceInputs) { + for (const input of deviceInputs) { + injectDeviceInput(input); + await controller.run(); + } + hostStorage.commit(); + emitDeviceOutputs(); +} ``` +The actual signature of `upgradeSwingset` is `const { modified } = upgradeSwingset(kernelStorage)`, and the `modified` flag indicates whether anything actually got upgraded. Host applications which have other means to keep track of software upgrades may wish to assert that `modified === false` in reboots that are not associated with a change to the kernel package version. They can also safely skip the `injectQueuedUpgradeEvents` call if nothing was modified. + ### Crank Execution For convenience in discussion, we split execution into "blocks". During a block, the host may call one or more device inputs, such as inbound messages, or timer wakeup events. The end of the block is marked by one or more calls to `controller.run()`, followed by a `hostStorage.commit()`, followed by the host-provided device endowments doing whatever kind of outbound IO they need to do. diff --git a/packages/SwingSet/src/controller/controller.js b/packages/SwingSet/src/controller/controller.js index 05027542e2b..c3091989814 100644 --- a/packages/SwingSet/src/controller/controller.js +++ b/packages/SwingSet/src/controller/controller.js @@ -388,6 +388,8 @@ export async function makeSwingsetController( return kernel.deviceNameToID(deviceName); }, + injectQueuedUpgradeEvents: () => kernel.injectQueuedUpgradeEvents(), + /** * Queue a method call into the named vat * diff --git a/packages/SwingSet/src/controller/upgradeSwingset.js b/packages/SwingSet/src/controller/upgradeSwingset.js index e9cff415a6e..3c12c97f0b6 100644 --- a/packages/SwingSet/src/controller/upgradeSwingset.js +++ b/packages/SwingSet/src/controller/upgradeSwingset.js @@ -5,11 +5,14 @@ import { getAllDynamicVats, getAllStaticVats, incrementReferenceCount, - addToQueue, readQueue, } from '../kernel/state/kernelKeeper.js'; import { enumeratePrefixedKeys } from '../kernel/state/storageHelper.js'; +/** + * @import {RunQueueEvent} from '../types-internal.js'; + */ + const upgradeVatV0toV1 = (kvStore, defaultReapDirtThreshold, vatID) => { // This is called, once per vat, when upgradeSwingset migrates from // v0 to v1 @@ -97,11 +100,13 @@ const upgradeVatV0toV1 = (kvStore, defaultReapDirtThreshold, vatID) => { * `hostStorage.commit()` afterwards. * * @param {SwingStoreKernelStorage} kernelStorage - * @returns {boolean} true if any changes were made + * @returns {{ modified: boolean }} */ export const upgradeSwingset = kernelStorage => { const { kvStore } = kernelStorage; let modified = false; + /** @type {RunQueueEvent[]} */ + const upgradeEvents = []; let vstring = kvStore.get('version'); if (vstring === undefined) { vstring = '0'; @@ -308,40 +313,30 @@ export const upgradeSwingset = kernelStorage => { // only the upgraded vat will see anything, and those deliveries // won't make it past liveslots). - const kernelStats = JSON.parse(getRequired('kernelStats')); - // copied from kernel/state/stats.js, awkward to factor out - const incStat = (stat, delta = 1) => { - assert.equal(stat, 'acceptanceQueueLength'); - kernelStats[stat] += delta; - const maxStat = `${stat}Max`; - if ( - kernelStats[maxStat] !== undefined && - kernelStats[stat] > kernelStats[maxStat] - ) { - kernelStats[maxStat] = kernelStats[stat]; - } - const upStat = `${stat}Up`; - if (kernelStats[upStat] !== undefined) { - kernelStats[upStat] += delta; - } - }; - + let count = 0; for (const [kpid, vatID] of buggyKPIDs) { - const m = harden({ type: 'notify', vatID, kpid }); + // account for the reference to this kpid in upgradeEvents incrementReferenceCount(getRequired, kvStore, kpid, `enq|notify`); - addToQueue('acceptanceQueue', m, getRequired, kvStore, incStat); + upgradeEvents.push({ type: 'notify', vatID, kpid }); + count += 1; } - kvStore.set('kernelStats', JSON.stringify(kernelStats)); - - console.log(` - #9039 remediation complete`); + console.log(` - #9039 remediation complete, ${count} notifies to inject`); modified = true; version = 3; } + if (upgradeEvents.length) { + assert(modified); + // stash until host calls controller.injectQueuedUpgradeEvents() + const oldEvents = JSON.parse(kvStore.get('upgradeEvents') || '[]'); + const events = [...oldEvents, ...upgradeEvents]; + kvStore.set('upgradeEvents', JSON.stringify(events)); + } + if (modified) { kvStore.set('version', `${version}`); } - return modified; + return harden({ modified }); }; harden(upgradeSwingset); diff --git a/packages/SwingSet/src/kernel/kernel.js b/packages/SwingSet/src/kernel/kernel.js index 6f438cf1b5b..5b21dbe0894 100644 --- a/packages/SwingSet/src/kernel/kernel.js +++ b/packages/SwingSet/src/kernel/kernel.js @@ -215,6 +215,10 @@ export default function buildKernel( return deviceID; } + function injectQueuedUpgradeEvents() { + kernelKeeper.injectQueuedUpgradeEvents(); + } + function addImport(forVatID, what) { if (!started) { throw Error('must do kernel.start() before addImport()'); @@ -2207,6 +2211,7 @@ export default function buildKernel( pinObject, vatNameToID, deviceNameToID, + injectQueuedUpgradeEvents, queueToKref, kpRegisterInterest, kpStatus, diff --git a/packages/SwingSet/src/kernel/state/kernelKeeper.js b/packages/SwingSet/src/kernel/state/kernelKeeper.js index cf2d1eff3af..aab0d1e66b5 100644 --- a/packages/SwingSet/src/kernel/state/kernelKeeper.js +++ b/packages/SwingSet/src/kernel/state/kernelKeeper.js @@ -143,6 +143,7 @@ export const CURRENT_SCHEMA_VERSION = 3; // gcActions = JSON(gcActions) // reapQueue = JSON([vatIDs...]) // pinnedObjects = ko$NN[,ko$NN..] +// upgradeEvents = JSON([events..]) // ko.nextID = $NN // ko$NN.owner = $vatID @@ -182,6 +183,8 @@ export const CURRENT_SCHEMA_VERSION = 3; // v3: // * change `version` to `'3'` // * perform remediation for bug #9039 +// (after v3, does not get its own version) +// * `upgradeEvents` recognized, but omitted if empty /** @type {(s: string) => string[]} s */ export function commaSplit(s) { @@ -1266,6 +1269,27 @@ export default function makeKernelKeeper( return dequeue('acceptanceQueue'); } + function injectQueuedUpgradeEvents() { + // refcounts: Any krefs in `upgradeEvents` must have a refcount to + // represent the list's hold on those objects. When + // upgradeSwingset() creates these events, it must also + // incref(kref), otherwise we run the risk of dropping the kref by + // the time injectQueuedUpgradeEvents() is called. We're nominally + // removing each event from upgradeEvents (decref), then pushing + // it onto the run-queue (incref), but since those two cancel each + // other out, we don't actually need to modify any reference + // counts from within this function. Note that + // addToAcceptanceQueue does not increment refcounts, just kernel + // queue-length stats. + + const events = JSON.parse(kvStore.get('upgradeEvents') || '[]'); + kvStore.delete('upgradeEvents'); + for (const e of events) { + assert(e.type, `not an event`); + addToAcceptanceQueue(e); + } + } + function allocateMeter(remaining, threshold) { if (remaining !== 'unlimited') { assert.typeof(remaining, 'bigint'); @@ -1965,6 +1989,8 @@ export default function makeKernelKeeper( getAcceptanceQueueLength, getNextAcceptanceQueueMsg, + injectQueuedUpgradeEvents, + allocateMeter, addMeterRemaining, setMeterThreshold, diff --git a/packages/SwingSet/test/kernel.test.js b/packages/SwingSet/test/kernel.test.js index f18a0720ff3..ef17e333a34 100644 --- a/packages/SwingSet/test/kernel.test.js +++ b/packages/SwingSet/test/kernel.test.js @@ -1817,3 +1817,46 @@ test('reap gc-krefs 12', async t => { test('reap gc-krefs overrideNever', async t => { await reapGCKrefsTest(t, 12, true); }); + +test('injectQueuedUpgradeEvents', async t => { + const endowments = makeKernelEndowments(); + const { kernelStorage } = endowments; + const { kvStore } = kernelStorage; + await initializeKernel({}, kernelStorage); + const kernel = buildKernel(endowments, {}, {}); + await kernel.start(); + // TODO: extract `queueLength`/`dumpQueue` from kernelKeeper.js? + { + const [start, end] = JSON.parse(kvStore.get('acceptanceQueue')); + t.deepEqual([start, end], [1, 1]); + } + + // These events would fail if they actually got processed: the vatID + // and all the krefs are fake, the methargs are not valid capdata, + // and we haven't updated refcounts. But all we care about is that + // they make it onto the acceptance queue. + const vatID = 'v1'; + const e1 = harden({ type: 'notify', vatID, kpid: 'kp25' }); + // kernelKeeper.incrementRefCount(kpid, `enq|notify`); + const target = 'ko11'; + const methargs = ''; + const msg = harden({ methargs, result: 'kp33' }); + const e2 = harden({ type: 'send', target, msg }); + kvStore.set('upgradeEvents', JSON.stringify([e1, e2])); + + kernel.injectQueuedUpgradeEvents([e1, e2]); + + { + const [start, end] = JSON.parse(kvStore.get('acceptanceQueue')); + t.deepEqual([start, end], [1, 3]); + t.deepEqual(JSON.parse(kvStore.get('acceptanceQueue.1')), e1); + t.deepEqual(JSON.parse(kvStore.get('acceptanceQueue.2')), e2); + } + + // It'd be nice to also check that the stats were incremented: + // stats.acceptanceQueueLength shoule be 2, acceptanceQueueLengthUp + // should be 2, and acceptanceQueueLengthMax should be 2. But the + // stats are held in RAM until a crank is executed, and we're not + // doing that here. And the kernel hides the kernelKeeper so we + // can't call getStats() to check. +}); diff --git a/packages/SwingSet/test/state.test.js b/packages/SwingSet/test/state.test.js index 8a5076b14a6..16450ab7b17 100644 --- a/packages/SwingSet/test/state.test.js +++ b/packages/SwingSet/test/state.test.js @@ -1109,12 +1109,15 @@ test('dirt upgrade', async t => { // it requires a manual upgrade let k2; + let ks2; { const serialized = store.serialize(); const { kernelStorage } = initSwingStore(null, { serialized }); - upgradeSwingset(kernelStorage); + const { modified } = upgradeSwingset(kernelStorage); + t.true(modified); k2 = makeKernelKeeper(kernelStorage, CURRENT_SCHEMA_VERSION); // works this time k2.loadStats(); + ks2 = kernelStorage; } t.true(k2.kvStore.has(`kernel.defaultReapDirtThreshold`)); @@ -1157,6 +1160,12 @@ test('dirt upgrade', async t => { t.deepEqual(JSON.parse(k2.kvStore.get(`${v3}.options`)).reapDirtThreshold, { never: true, }); + + { + // upgrade is idempotent + const { modified } = upgradeSwingset(ks2); + t.false(modified); + } }); test('v2 upgrade', async t => { @@ -1177,15 +1186,24 @@ test('v2 upgrade', async t => { // it requires a manual upgrade let k2; + let ks2; { const serialized = store.serialize(); const { kernelStorage } = initSwingStore(null, { serialized }); - upgradeSwingset(kernelStorage); + const { modified } = upgradeSwingset(kernelStorage); + t.true(modified); k2 = makeKernelKeeper(kernelStorage, CURRENT_SCHEMA_VERSION); // works this time k2.loadStats(); + ks2 = kernelStorage; } t.true(k2.kvStore.has(`vats.terminated`)); t.deepEqual(JSON.parse(k2.kvStore.get(`vats.terminated`)), []); t.is(k2.kvStore.get(`version`), '3'); + + { + // upgrade is idempotent + const { modified } = upgradeSwingset(ks2); + t.false(modified); + } }); diff --git a/packages/SwingSet/test/upgrade-swingset.test.js b/packages/SwingSet/test/upgrade-swingset.test.js index a14e409b83e..ca88649d3bb 100644 --- a/packages/SwingSet/test/upgrade-swingset.test.js +++ b/packages/SwingSet/test/upgrade-swingset.test.js @@ -1,4 +1,3 @@ -/* eslint-disable no-underscore-dangle */ // @ts-nocheck // eslint-disable-next-line import/order @@ -74,7 +73,7 @@ test('kernel refuses to run with out-of-date DB - v2', async t => { await commit(); // now doctor the initial state to make it look like the - // kernelkeeper v1 schema, by reducing the version key and removing + // kernelkeeper v2 schema, by reducing the version key and removing // vats.terminated t.is(kvStore.get('version'), '3'); @@ -162,7 +161,8 @@ test('upgrade kernel state', async t => { upgradeSwingset(kernelStorage); // now we should be good to go - const _controller = await makeSwingsetController(kernelStorage); + const controller = await makeSwingsetController(kernelStorage); + controller.injectQueuedUpgradeEvents(); t.true(kvStore.has('kernel.defaultReapDirtThreshold')); // the kernel-wide threshold gets a .gcKrefs (to meet our upcoming @@ -245,7 +245,8 @@ test('upgrade non-reaping kernel state', async t => { upgradeSwingset(kernelStorage); // now we should be good to go - const _controller = await makeSwingsetController(kernelStorage); + const controller = await makeSwingsetController(kernelStorage); + controller.injectQueuedUpgradeEvents(); t.true(kvStore.has('kernel.defaultReapDirtThreshold')); t.deepEqual(JSON.parse(kvStore.get('kernel.defaultReapDirtThreshold')), { @@ -374,7 +375,7 @@ test('v3 upgrade', async t => { } kvStore.set('acceptanceQueue', JSON.stringify([accHead, accTail])); - let stats = JSON.parse(kvStore.get('kernelStats')); + const stats = JSON.parse(kvStore.get('kernelStats')); stats.runQueueLength += runQueue.length; stats.runQueueLengthUp += runQueue.length; stats.runQueueLengthMax = runQueue.length; @@ -393,9 +394,26 @@ test('v3 upgrade', async t => { }); // upgrade it - upgradeSwingset(kernelStorage); - // now we should be good to go - const _controller = await makeSwingsetController(kernelStorage); + const { modified } = upgradeSwingset(kernelStorage); + t.true(modified); + + // there should be `upgradeEvents` in the kvStore + const expected = [ + { type: 'notify', vatID, kpid: p1 }, + { type: 'notify', vatID, kpid: p4 }, + { type: 'notify', vatID, kpid: p5 }, + ]; + t.deepEqual(JSON.parse(kvStore.get('upgradeEvents')), expected); + + // DB updated, now we should be ready to build the controller + const controller = await makeSwingsetController(kernelStorage); + + // but the events remain until the controller is told to inject them + t.deepEqual(JSON.parse(kvStore.get('upgradeEvents')), expected); + + // and then they're removed + controller.injectQueuedUpgradeEvents(); + t.is(kvStore.get('upgradeEvents'), undefined); // check state by mutating our dumped copy and then comparing // against a new dump @@ -415,12 +433,16 @@ test('v3 upgrade', async t => { const np5 = JSON.stringify({ type: 'notify', vatID, kpid: p5 }); data[`acceptanceQueue.${tail - 1}`] = np5; - // stats are updated with the queue changes - stats = JSON.parse(data.kernelStats); - stats.acceptanceQueueLength += 3; - stats.acceptanceQueueLengthUp += 3; - stats.acceptanceQueueLengthMax = stats.acceptanceQueueLength; - data.kernelStats = JSON.stringify(stats); + // note: the in-RAM copy of kernelStats will have the elevated + // acceptance-queue counters, but these are not written to the + // kvStore until a crank is executed, so the data we're comparing + // won't see them + // + // stats = JSON.parse(data.kernelStats); + // stats.acceptanceQueueLength += 3; + // stats.acceptanceQueueLengthUp += 3; + // stats.acceptanceQueueLengthMax = stats.acceptanceQueueLength; + // data.kernelStats = JSON.stringify(stats); // the refcounts should now be one larger, because of the queued // notifies From 5789fb68d316643906bc30506059a0a8c8874154 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 25 Oct 2024 12:57:46 -0700 Subject: [PATCH 35/56] fix(cosmic-swingset): inject kernel upgrade events at a safe time We use a new `Action_Type.KERNEL_UPGRADE_EVENTS` to call `controller.injectQueuedUpgradeEvents()` at a safe time. The first run after a chain-halting software upgrade will drain the run-queue of any leftover work, then the second run will process the kernel upgrade events. Any embedded core-proposals will be processed in runs after those finish. --- packages/cosmic-swingset/src/launch-chain.js | 33 +++++++++++++++++++- packages/internal/src/action-types.js | 1 + 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/cosmic-swingset/src/launch-chain.js b/packages/cosmic-swingset/src/launch-chain.js index c18f5fa5d7c..c8b113d5528 100644 --- a/packages/cosmic-swingset/src/launch-chain.js +++ b/packages/cosmic-swingset/src/launch-chain.js @@ -215,7 +215,7 @@ export async function buildSwingset( } const pendingCoreProposals = await ensureSwingsetInitialized(); - upgradeSwingset(kernelStorage); + const { modified } = upgradeSwingset(kernelStorage); const controller = await makeSwingsetController( kernelStorage, deviceEndowments, @@ -236,6 +236,7 @@ export async function buildSwingset( return { coreProposals: pendingCoreProposals, controller, + kernelHasUpgradeEvents: modified, mb: mailboxDevice, bridgeInbound: bridgeDevice.deliverInbound, timer: timerDevice, @@ -408,6 +409,7 @@ export async function launch({ const { coreProposals: bootstrapCoreProposals, controller, + kernelHasUpgradeEvents, mb, bridgeInbound, timer, @@ -510,6 +512,14 @@ export async function launch({ await commit(); } + async function doKernelUpgradeEvents(inboundNum) { + controller.writeSlogObject({ + type: 'cosmic-swingset-inject-kernel-upgrade-events', + inboundNum, + }); + controller.injectQueuedUpgradeEvents(); + } + async function deliverInbound(sender, messages, ack, inboundNum) { Array.isArray(messages) || Fail`inbound given non-Array: ${messages}`; controller.writeSlogObject({ @@ -629,6 +639,11 @@ export async function launch({ break; } + case ActionType.KERNEL_UPGRADE_EVENTS: { + p = doKernelUpgradeEvents(inboundNum); + break; + } + case ActionType.INSTALL_BUNDLE: { p = installBundle(action.bundle); break; @@ -934,6 +949,22 @@ export async function launch({ await doBootstrap(action); } + // The reboot-time upgradeSwingset() may have generated some + // remediation events that need to be injected at the right + // time (after catchup, before proposals). Push them onto + // runThisBlock before anything else goes there. + if (kernelHasUpgradeEvents) { + isBootstrap || + upgradeDetails || + Fail`Unexpected kernel upgrade events outside of consensus start`; + const txHash = 'x/kernel-upgrade-events'; + const context = { blockHeight, txHash, msgIdx: 0 }; + runThisBlock.push({ + action: { type: ActionType.KERNEL_UPGRADE_EVENTS }, + context, + }); + } + // Concatenate together any pending core proposals from chain bootstrap // with any from this inbound init action, then execute them all. const coreProposals = mergeCoreProposals( diff --git a/packages/internal/src/action-types.js b/packages/internal/src/action-types.js index f07a3fd5592..30e363f5117 100644 --- a/packages/internal/src/action-types.js +++ b/packages/internal/src/action-types.js @@ -16,3 +16,4 @@ export const WALLET_ACTION = 'WALLET_ACTION'; export const WALLET_SPEND_ACTION = 'WALLET_SPEND_ACTION'; export const INSTALL_BUNDLE = 'INSTALL_BUNDLE'; export const VTRANSFER_IBC_EVENT = 'VTRANSFER_IBC_EVENT'; +export const KERNEL_UPGRADE_EVENTS = 'KERNEL_UPGRADE_EVENTS'; From ab406f788e0e6823f2e24aa499ffdcdabb8ef048 Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 11 Oct 2024 18:08:51 -0700 Subject: [PATCH 36/56] chore: update termination tests to watch for promise cleanup Two tests are updated to exercise the cleanup of promise c-list entries during vat termination. `terminate.test.js` adds some promises to the c-list and then checks their refcounts after termination, to demonstrate that bug #10261 is leaking a refcount when it deletes the dead vat's c-list entry without also decrementing the refcount. `slow-termination.test.js` adds a number of promises to the c-list, and expects the budget-limited cleanup to spend a phase on promises. Both tests are marked as failing until the code fix is landed in the next commit. --- .../bootstrap-slow-terminate.js | 14 ++- .../slow-termination/slow-termination.test.js | 112 +++++++++++------- .../slow-termination/vat-slow-terminate.js | 2 + .../terminate/bootstrap-die-cleanly.js | 12 +- .../vat-admin/terminate/terminate.test.js | 29 ++++- .../vat-admin/terminate/vat-dude-terminate.js | 6 + 6 files changed, 130 insertions(+), 45 deletions(-) diff --git a/packages/SwingSet/test/vat-admin/slow-termination/bootstrap-slow-terminate.js b/packages/SwingSet/test/vat-admin/slow-termination/bootstrap-slow-terminate.js index 135124209ef..cff9c0ad02d 100644 --- a/packages/SwingSet/test/vat-admin/slow-termination/bootstrap-slow-terminate.js +++ b/packages/SwingSet/test/vat-admin/slow-termination/bootstrap-slow-terminate.js @@ -1,4 +1,5 @@ import { Far, E } from '@endo/far'; +import { makePromiseKit } from '@endo/promise-kit'; export function buildRootObject(_vatPowers) { let root; @@ -21,11 +22,22 @@ export function buildRootObject(_vatPowers) { } // set up 20 "dude exports, bootstrap imports" c-list entries - for (let i = 0; i < 20; i += 1) { myImports.push(await E(root).sendExport()); } + // also 10 imported promises + for (let i = 0; i < 10; i += 1) { + await E(root).acceptImports(makePromiseKit().promise); + } + + // and 10 exported promises + for (let i = 0; i < 10; i += 1) { + const p = E(root).forever(); + myImports.push(p); + p.catch(_err => 0); // hush + } + // ask dude to creates 20 vatstore entries (in addition to the // built-in liveslots stuff) await E(root).makeVatstore(20); diff --git a/packages/SwingSet/test/vat-admin/slow-termination/slow-termination.test.js b/packages/SwingSet/test/vat-admin/slow-termination/slow-termination.test.js index 336176024c6..38a3818992e 100644 --- a/packages/SwingSet/test/vat-admin/slow-termination/slow-termination.test.js +++ b/packages/SwingSet/test/vat-admin/slow-termination/slow-termination.test.js @@ -63,7 +63,7 @@ async function doSlowTerminate(t, mode) { defaultManagerType: 'xsnap', defaultReapInterval: 'never', snapshotInitial: 2, // same as the default - snapshotInterval: 10, // ensure multiple spans+snapshots + snapshotInterval: 11, // ensure multiple spans+snapshots bootstrap: 'bootstrap', bundles: { dude: { @@ -117,13 +117,16 @@ async function doSlowTerminate(t, mode) { t.is(countCleanups, 0); // bootstrap adds a fair amount of vat-dude state: - // * we have c-list entries for 20 imports and 20 exports, each of - // which need two kvStore entries, so 80 kvStore total + // * we have c-list entries for 20 object imports and 20 object + // exports, each of which need two kvStore entries, so 80 kvStore + // total + // * c-list entries for 10 promise imports and 10 promise exports, + // * so 40 kvStore total // * the vat has created 20 baggage entries, all of which go into // the vatstore, adding 20 kvStore // * an empty vat has about 29 kvStore entries just to track // counters, the built-in collection types, baggage itself, etc - // * by sending 40-plus deliveries into an xsnap vat with + // * by sending 60-plus deliveries into an xsnap vat with // snapInterval=5, we get 8-ish transcript spans (7 old, 1 // current), and each old span generates a heap snapshot record // Slow vat termination means deleting these entries slowly. @@ -148,18 +151,28 @@ async function doSlowTerminate(t, mode) { .pluck() .get(vatID); - // 20*2 for imports, 21*2 for exports, 20*1 for vatstore = 102. - // Plus 21 for the usual liveslots stuff, and 6 for kernel stuff - // like vNN.source/options + // 20*2 for imports, 21*2 for exports, 20*2 for promises, 20*1 for + // vatstore = 142. Plus 21 for the usual liveslots stuff, and 6 for + // kernel stuff like vNN.source/options + const initialKVCount = 169; - t.is(remainingKV().length, 129); + t.is(remainingKV().length, initialKVCount); t.false(JSON.parse(kvStore.get('vats.terminated')).includes(vatID)); + // we get one span for snapshotInitial (=2), then a span every - // snapshotInterval (=10). Each non-current span creates a + // snapshotInterval (=11). Each non-current span creates a // snapshot. - t.is(remainingTranscriptSpans(), 6); - t.is(remainingTranscriptItems(), 59); - t.is(remainingSnapshots(), 5); + let expectedRemainingItems = 85; + let expectedRemainingSpans = 8; + let expectedRemainingSnapshots = 7; + + const checkTS = () => { + t.is(remainingTranscriptSpans(), expectedRemainingSpans); + t.is(remainingTranscriptItems(), expectedRemainingItems); + t.is(remainingSnapshots(), expectedRemainingSnapshots); + }; + checkTS(); + const remaining = () => remainingKV().length + remainingSnapshots() + @@ -167,11 +180,19 @@ async function doSlowTerminate(t, mode) { remainingTranscriptSpans(); // note: mode=dieHappy means we send one extra message to the vat, - // which adds a single transcript item (but this doesn't happen to trigger an extra span) + // but we've tuned snapshotInterval to avoid this triggering a BOYD + // and save/load snapshot, so it increases our expected transcript + // items, but leaves the spans/snapshots alone + if (mode === 'dieHappy') { + expectedRemainingItems += 1; + } const kpid = controller.queueToVatRoot('bootstrap', 'kill', [mode]); await controller.run(noCleanupPolicy); await commit(); + + checkTS(); + t.is(controller.kpStatus(kpid), 'fulfilled'); t.deepEqual( controller.kpResolution(kpid), @@ -182,7 +203,7 @@ async function doSlowTerminate(t, mode) { t.true(JSON.parse(kvStore.get('vats.terminated')).includes(vatID)); // no cleanups were allowed, so nothing should be removed yet t.truthy(kernelStorage.kvStore.get(`${vatID}.options`)); - t.is(remainingKV().length, 129); + t.is(remainingKV().length, initialKVCount); // now do a series of cleanup runs, each with budget=5 const clean = async (budget = 5) => { @@ -223,8 +244,16 @@ async function doSlowTerminate(t, mode) { // the non-clist kvstore keys should still be present t.truthy(kernelStorage.kvStore.get(`${vatID}.options`)); - // there are no imports, so this clean(budget.default=5) will delete - // the first five of our 47 other kv entries (20 vatstore plus 27 + // there are no remaining imports, so this clean(budget.default=5) + // will delete the first five of our promise c-list entries, each + // with two kv entries + await cleanKV(10, 5); // 5 c-list promises + await cleanKV(10, 5); // 5 c-list promises + await cleanKV(10, 5); // 5 c-list promises + await cleanKV(10, 5); // 5 c-list promises + + // that finishes the promises, so the next clean will delete the + // first five of our 47 other kv entries (20 vatstore plus 27 // liveslots overhead await cleanKV(5, 5); // 5 other kv @@ -241,54 +270,55 @@ async function doSlowTerminate(t, mode) { t.is(remainingKV().length, 22); await cleanKV(5, 5); // 5 other kv t.is(remainingKV().length, 17); - t.is(remainingSnapshots(), 5); + checkTS(); await cleanKV(5, 5); // 5 other kv t.is(remainingKV().length, 12); await cleanKV(5, 5); // 5 other kv t.is(remainingKV().length, 7); await cleanKV(5, 5); // 5 other kv t.is(remainingKV().length, 2); - t.is(remainingSnapshots(), 5); - // there are two kv left, so this clean will delete those, then all 5 snapshots + checkTS(); + + // there are two kv left, so this clean will delete those, then 5 of + // the 7 snapshots await cleanKV(2, 7); // 2 final kv, and 5 snapshots t.deepEqual(remainingKV(), []); t.is(kernelStorage.kvStore.get(`${vatID}.options`), undefined); - t.is(remainingSnapshots(), 0); - - t.is(remainingTranscriptSpans(), 6); - let ts = 59; - if (mode === 'dieHappy') { - ts = 60; - } - t.is(remainingTranscriptItems(), ts); - - // the next clean (with the default budget of 5) will delete the - // five most recent transcript spans, starting with the isCurrent=1 - // one (which had 9 or 10 items), leaving just the earliest (which - // had 4, due to `snapshotInitial`) + expectedRemainingSnapshots -= 5; + checkTS(); + + // the next clean gets the 2 remaining snapshots, and the five most + // recent transcript spans, starting with the isCurrent=1 one (which + // had 9 or 10 items), leaving the earliest (which had 4, due to + // `snapshotInitial`) and the next two (with 13 each, due to + // snapshotInterval plus 2 for BOYD/snapshot overhead ). let cleanups = await clean(); - t.is(cleanups, 5); - t.is(remainingTranscriptSpans(), 1); - t.is(remainingTranscriptItems(), 4); + + t.is(cleanups, expectedRemainingSnapshots + 5); + expectedRemainingSnapshots = 0; + expectedRemainingItems = 4 + 13 + 13; + expectedRemainingSpans = 3; + checkTS(); // not quite done t.true(JSON.parse(kvStore.get('vats.terminated')).includes(vatID)); - // the final clean deletes the remaining span, and finishes by + // the final clean deletes the remaining spans, and finishes by // removing the "still being deleted" bookkeeping, and the .options cleanups = await clean(); - t.is(cleanups, 1); - t.is(remainingTranscriptSpans(), 0); - t.is(remainingTranscriptItems(), 0); + t.is(cleanups, 3); + expectedRemainingItems = 0; + expectedRemainingSpans = 0; + checkTS(); t.is(remaining(), 0); t.false(JSON.parse(kvStore.get('vats.terminated')).includes(vatID)); } -test.serial('slow terminate (kill)', async t => { +test.serial.failing('slow terminate (kill)', async t => { await doSlowTerminate(t, 'kill'); }); -test.serial('slow terminate (die happy)', async t => { +test.serial.failing('slow terminate (die happy)', async t => { await doSlowTerminate(t, 'dieHappy'); }); diff --git a/packages/SwingSet/test/vat-admin/slow-termination/vat-slow-terminate.js b/packages/SwingSet/test/vat-admin/slow-termination/vat-slow-terminate.js index 78ec9a74e47..0ad2e1f5b4e 100644 --- a/packages/SwingSet/test/vat-admin/slow-termination/vat-slow-terminate.js +++ b/packages/SwingSet/test/vat-admin/slow-termination/vat-slow-terminate.js @@ -1,4 +1,5 @@ import { Far } from '@endo/far'; +import { makePromiseKit } from '@endo/promise-kit'; export function buildRootObject(vatPowers, _vatParameters, baggage) { const hold = []; @@ -7,6 +8,7 @@ export function buildRootObject(vatPowers, _vatParameters, baggage) { dieHappy: completion => vatPowers.exitVat(completion), sendExport: () => Far('dude export', {}), acceptImports: imports => hold.push(imports), + forever: () => makePromiseKit().promise, makeVatstore: count => { for (let i = 0; i < count; i += 1) { baggage.init(`key-${i}`, i); diff --git a/packages/SwingSet/test/vat-admin/terminate/bootstrap-die-cleanly.js b/packages/SwingSet/test/vat-admin/terminate/bootstrap-die-cleanly.js index 8b67c11b9a1..9be8f7af7e1 100644 --- a/packages/SwingSet/test/vat-admin/terminate/bootstrap-die-cleanly.js +++ b/packages/SwingSet/test/vat-admin/terminate/bootstrap-die-cleanly.js @@ -1,16 +1,24 @@ import { Far, E } from '@endo/far'; +import { makePromiseKit } from '@endo/promise-kit'; export function buildRootObject() { let dude; + const hold = []; const self = Far('root', { async bootstrap(vats, devices) { const vatMaker = E(vats.vatAdmin).createVatAdminService(devices.vatAdmin); - // create a dynamic vat, send it a message and let it respond, to make - // sure everything is working + // Create a dynamic vat, send it a message and let it respond, + // to make sure everything is working. Give them a promise to + // follow, to check that its refcount is cleaned up. dude = await E(vatMaker).createVatByName('dude'); await E(dude.root).foo(1); + const p = makePromiseKit().promise; + await E(dude.root).holdPromise(p); + const p2 = E(dude.root).never(); + p2.catch(_err => 'hush'); + hold.push(p2); return 'bootstrap done'; }, async phase2() { diff --git a/packages/SwingSet/test/vat-admin/terminate/terminate.test.js b/packages/SwingSet/test/vat-admin/terminate/terminate.test.js index a4759943e47..8ee36da9662 100644 --- a/packages/SwingSet/test/vat-admin/terminate/terminate.test.js +++ b/packages/SwingSet/test/vat-admin/terminate/terminate.test.js @@ -449,7 +449,7 @@ test.serial('invalid criticalVatKey causes vat creation to fail', async t => { }); }); -test.serial('dead vat state removed', async t => { +test.serial.failing('dead vat state removed', async t => { const configPath = new URL('swingset-die-cleanly.json', import.meta.url) .pathname; const config = await loadSwingsetConfigFile(configPath); @@ -471,6 +471,23 @@ test.serial('dead vat state removed', async t => { t.is(kvStore.get('vat.dynamicIDs'), '["v6"]'); t.is(kvStore.get('ko26.owner'), 'v6'); t.is(Array.from(enumeratePrefixedKeys(kvStore, 'v6.')).length > 10, true); + // find the two promises in the new vat's c-list, they should both + // have refCount=2 (one for bootstrap, one for the dynamic vat) + let decidedKPID; + let subscribedKPID; + for (const key of enumeratePrefixedKeys(kvStore, 'v6.')) { + if (key.startsWith('v6.c.kp')) { + const kpid = key.slice('v6.c.'.length); + const refCount = Number(kvStore.get(`${kpid}.refCount`)); + const decider = kvStore.get(`${kpid}.decider`); + if (decider === 'v6') { + decidedKPID = kpid; + } else { + subscribedKPID = kpid; + } + t.is(refCount, 2, `${kpid}.refCount=${refCount}, not 2`); + } + } const beforeDump = debug.dump(true); t.truthy(beforeDump.transcripts.v6); t.truthy(beforeDump.snapshots.v6); @@ -483,6 +500,16 @@ test.serial('dead vat state removed', async t => { const afterDump = debug.dump(true); t.falsy(afterDump.transcripts.v6); t.falsy(afterDump.snapshots.v6); + // the promise that v6 was deciding will be removed from v6's + // c-list, rejected by the kernel, and the reject notification to + // vat-bootstrap will remove it from that c-list, so the end state + // should be no references, and a completely deleted kernel promise + // table entry + t.is(kvStore.get(`${decidedKPID}.refCount`), undefined); + // the promise that v6 received from vat-bootstrap should be removed + // from v6's c-list, but it is still being exported by + // vat-bootstrap, so the refcount should finish at 1 + t.is(kvStore.get(`${subscribedKPID}.refCount`), '1'); }); test.serial('terminate with presence', async t => { diff --git a/packages/SwingSet/test/vat-admin/terminate/vat-dude-terminate.js b/packages/SwingSet/test/vat-admin/terminate/vat-dude-terminate.js index 42da13a3cfb..af731fb682b 100644 --- a/packages/SwingSet/test/vat-admin/terminate/vat-dude-terminate.js +++ b/packages/SwingSet/test/vat-admin/terminate/vat-dude-terminate.js @@ -6,12 +6,18 @@ export function buildRootObject(vatPowers) { // to be cut off const { testLog } = vatPowers; + const hold = []; + return Far('root', { foo(arg) { testLog(`FOO ${arg}`); return `FOO SAYS ${arg}`; }, + holdPromise(p) { + hold.push(p); + }, + never() { return makePromiseKit().promise; // never fires }, From 08e5dc9a83e50af4fce0309cdf9c16a06aa7804d Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Fri, 11 Oct 2024 17:01:43 -0700 Subject: [PATCH 37/56] fix(swingset): clean up promise c-list entries during vat deletion Previously, when a vat was terminated, and we delete the promise c-list entries from its old state, the cleanup code was failing to decrement the kpid's refcount properly. This resulted in a leak: those promises could never be retired. This commit updates the vat cleanup code to add a new phase, named `promises`. This executes after `exports` and `imports`, but before `kv`, and is responsible for both deleting the c-list entries and also decrementing the refcounts of the corresponding promises. We do this slowly, like we do exports and imports, because we don't know how many there might be, and because those promise records might hold references to other objects (in the resolution data), which could trigger additional work. However, this work is unlikely to be significant: the run-queue is usually empty, so these outstanding promises are probably unresolved, and thus cannot beholding resolution data. All promises *decided* by the dead vat are rejected by the kernel immediately during vat termination, because those rejections are visible to userspace in other vats. In contrast, freeing the promise records is *not* visible to userspace, just like how freeing imports or exports are not visible to userspace, so this cleanup is safe to do at a leisurely pace, rate-limited by `runPolicy.allowCleanup`. The docs are updated to reflect the new `runPolicy` API: * `budget.promises` is new, and respected by slow cleanup * `work.promises` is reported to `runPolicy.didCleanup()` The 'test.failing' marker was removed from the previously updated tests. I don't intend to add any remediation code: it requires a full refcount audit to find such promises, and the mainnet kernel has only ever terminated one vat so far, so I believe there cannot be very many leaked promises, if any. Once this fix is applied, no new leaks will occur. fixes #10261 --- packages/SwingSet/docs/run-policy.md | 11 ++++++---- packages/SwingSet/src/kernel/kernel.js | 2 ++ .../SwingSet/src/kernel/state/kernelKeeper.js | 20 +++++++++++++++++-- packages/SwingSet/src/types-external.js | 1 + .../slow-termination/slow-termination.test.js | 4 ++-- .../vat-admin/terminate/terminate.test.js | 2 +- 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/packages/SwingSet/docs/run-policy.md b/packages/SwingSet/docs/run-policy.md index 7624e9be0ff..33ba7376a2c 100644 --- a/packages/SwingSet/docs/run-policy.md +++ b/packages/SwingSet/docs/run-policy.md @@ -68,16 +68,19 @@ Some vats may grow very large (i.e. large c-lists with lots of imported/exported To protect the system against these bursts, the run policy can be configured to terminate vats slowly. Instead of doing all the cleanup work immediately, the policy allows the kernel to do a little bit of work each time `controller.run()` is called (e.g. once per block, for kernels hosted inside a blockchain). Internally, before servicing the run-queue, the kernel checks to see if any vats are in the "terminated but not fully deleted" state, and executes a "vat-cleanup crank", to delete some state. Depending upon what the run-policy allows, it may do multiple vat-cleanup cranks in a single `controller.run()`, or just one, or none at all. And depending upon the budget provided to each one, it may only take one vat-cleanup crank to finish the job, or millions. If the policy limits the number of cranks in a single block, and limits the budget of the crank, then the cleanup process will be spread over multiple blocks. -For each terminated vat, cleanup proceeds through five phases: +For each terminated vat, cleanup proceeds through six phases: -* `exports`: delete c-list entries for objects/promises *exported* by the vat -* `imports`: same but for objects/promises *imported* by the vat +* `exports`: delete c-list entries for objects *exported* by the vat +* `imports`: same but for objects *imported* by the vat +* `promises`: same but promises referenced by the vat * `kv`: delete all other kv entries for this vat, mostly vatstore records * `snapshots`: delete xsnap heap snapshots, typically one per 200 deliveries (`snapshotInterval`) * `transcripts`: delete transcript spans, and their associated transcript items The first two phases, `exports` and `imports`, cause the most activity in other vats. Deleting `exports` can cause objects to be retired, which will deliver `dispatch.retireImports` GC messages to other vats which have imported those objects and used them as keys in a WeakMapStore. Deleting `imports` can cause refcounts to drop to zero, delivering `dispatch.dropImports` into vats which were exporting those objects. Both of these will add `gcKref` "dirt" to the other vat, eventually triggering a BringOutYourDead, which will cause more DB activity. These are generally the phases we must rate-limit to avoid overwhelming the system. +The `promises` phase may cause kernel promise table entries to be deleted. This is unlikely to trigger activity in other vats, because only settled promises can have references to other objects, and the notification of settlement is enqueued to all subscribers as soon as the promise is fulfilled or rejected. However, if some of these notifications were still in the run-queue when the vat got deleted, the GC work might be accelerated slightly. + The other phases cause DB activity (deleting rows), but do not interact with other vats, so it is easier to accomodate more cleanup steps. The budget can be tuned to allow more kv/snapshots/transcripts than exports/imports in a single cleanup run. There are two RunPolicy methods which control this. The first is `runPolicy.allowCleanup()`. This will be invoked many times during `controller.run()`, each time the kernel tries to decide what to do next (once per step). The return value will enable (or not) a fixed amount of cleanup work. The second is `runPolicy.didCleanup({ cleanups })`, which is called later, to inform the policy of how much cleanup work was actually done. The policy can count the cleanups and switch `allowCleanup()` to return `false` when it reaches a threshold. (We need the pre-check `allowCleanup` method because the simple act of looking for cleanup work is itself a cost that we might not be willing to pay). @@ -92,7 +95,7 @@ The limit can be set to `Infinity` to allow unlimited deletion of that particula Each budget record must include a `{ default }` property, which is used as the default for any phase that is not explicitly mentioned in the budget. This also provides forwards-compatibility for any phases that might be added in the future. So `budget = { default: 5 }` would provide a conservative budget for cleanup, `budget = { default: 5, kv: 50 }` would enable faster deletion of the non-c-list kvstore entries, and `budget = { default: Infinity }` allows unlimited cleanup for all phases. -Note that the cleanup crank ends when no more work is left to be done (which finally allows the vat to be forgotten entirely), or when an individual phase's budget is exceeded. That means multiple phases might see deletion work in a single crank, if the earlier phase finishes its work without exhausting its budget. For example, if the budget is `{ default: 5 }`, but the vat had 4 exports, 4 imports, 4 other kv entries, 4 snapshots, and 4 transcript spans, then all that work would be done in a single crank, because no individual phase would exhaust its budget. The only case that is even worth mentioning is when the end of the `exports` phase overlaps with the start of the `imports` phase, where we might do four more cleanups than usual. +Note that the cleanup crank ends when no more work is left to be done (which finally allows the vat to be forgotten entirely), or when an individual phase's budget is exceeded. That means multiple phases might see deletion work in a single crank, if the earlier phase finishes its work without exhausting its budget. For example, if the budget is `{ default: 5 }`, but the vat had 4 exports, 4 imports, 4 promise entries, 4 other kv entries, 4 snapshots, and 4 transcript spans, then all that work would be done in a single crank, because no individual phase would exhaust its budget. The only case that is even worth mentioning is when the end of the `exports` phase overlaps with the start of the `imports` phase, where we might do four more cleanups than usual. A `true` return value from `allowCleanup()` is equivalent to `{ default: Infinity }`, which allows unlimited cleanup work. This also happens if `allowCleanup()` is missing entirely, which maintains the old behavior for host applications that haven't been updated to make new policy objects. Note that cleanup is higher priority than any delivery, and is second only to acceptance queue routing. diff --git a/packages/SwingSet/src/kernel/kernel.js b/packages/SwingSet/src/kernel/kernel.js index 5b21dbe0894..fcf07955118 100644 --- a/packages/SwingSet/src/kernel/kernel.js +++ b/packages/SwingSet/src/kernel/kernel.js @@ -710,6 +710,7 @@ export default function buildKernel( total: work.exports + work.imports + + work.promises + work.kv + work.snapshots + work.transcripts, @@ -1851,6 +1852,7 @@ export default function buildKernel( { exports: M.number(), imports: M.number(), + promises: M.number(), kv: M.number(), snapshots: M.number(), transcripts: M.number(), diff --git a/packages/SwingSet/src/kernel/state/kernelKeeper.js b/packages/SwingSet/src/kernel/state/kernelKeeper.js index aab0d1e66b5..b8a6d6060d7 100644 --- a/packages/SwingSet/src/kernel/state/kernelKeeper.js +++ b/packages/SwingSet/src/kernel/state/kernelKeeper.js @@ -1021,6 +1021,7 @@ export default function makeKernelKeeper( const work = { exports: 0, imports: 0, + promises: 0, kv: 0, snapshots: 0, transcripts: 0, @@ -1045,6 +1046,7 @@ export default function makeKernelKeeper( const clistPrefix = `${vatID}.c.`; const exportPrefix = `${clistPrefix}o+`; const importPrefix = `${clistPrefix}o-`; + const promisePrefix = `${clistPrefix}p`; // Note: ASCII order is "+,-./", and we rely upon this to split the // keyspace into the various o+NN/o-NN/etc spaces. If we were using a @@ -1097,8 +1099,22 @@ export default function makeKernelKeeper( } } - // the caller used enumeratePromisesByDecider() before calling us, - // so they already know the orphaned promises to reject + // The caller used enumeratePromisesByDecider() before calling us, + // so they have already rejected the orphan promises, but those + // kpids are still present in the dead vat's c-list. Clean those + // up now. + remaining = budget.promises ?? budget.default; + for (const k of enumeratePrefixedKeys(kvStore, promisePrefix)) { + const kref = kvStore.get(k) || Fail`getNextKey ensures get`; + const vref = stripPrefix(clistPrefix, k); + vatKeeper.deleteCListEntry(kref, vref); + // that will also delete both db keys + work.promises += 1; + remaining -= 1; + if (remaining <= 0) { + return { done: false, work }; + } + } // now loop back through everything and delete it all remaining = budget.kv ?? budget.default; diff --git a/packages/SwingSet/src/types-external.js b/packages/SwingSet/src/types-external.js index 9550363c5fd..392324c1300 100644 --- a/packages/SwingSet/src/types-external.js +++ b/packages/SwingSet/src/types-external.js @@ -227,6 +227,7 @@ export {}; * * @typedef { { exports: number, * imports: number, + * promises: number, * kv: number, * snapshots: number, * transcripts: number, diff --git a/packages/SwingSet/test/vat-admin/slow-termination/slow-termination.test.js b/packages/SwingSet/test/vat-admin/slow-termination/slow-termination.test.js index 38a3818992e..bdf050d75a9 100644 --- a/packages/SwingSet/test/vat-admin/slow-termination/slow-termination.test.js +++ b/packages/SwingSet/test/vat-admin/slow-termination/slow-termination.test.js @@ -315,10 +315,10 @@ async function doSlowTerminate(t, mode) { t.false(JSON.parse(kvStore.get('vats.terminated')).includes(vatID)); } -test.serial.failing('slow terminate (kill)', async t => { +test.serial('slow terminate (kill)', async t => { await doSlowTerminate(t, 'kill'); }); -test.serial.failing('slow terminate (die happy)', async t => { +test.serial('slow terminate (die happy)', async t => { await doSlowTerminate(t, 'dieHappy'); }); diff --git a/packages/SwingSet/test/vat-admin/terminate/terminate.test.js b/packages/SwingSet/test/vat-admin/terminate/terminate.test.js index 8ee36da9662..f764441aac4 100644 --- a/packages/SwingSet/test/vat-admin/terminate/terminate.test.js +++ b/packages/SwingSet/test/vat-admin/terminate/terminate.test.js @@ -449,7 +449,7 @@ test.serial('invalid criticalVatKey causes vat creation to fail', async t => { }); }); -test.serial.failing('dead vat state removed', async t => { +test.serial('dead vat state removed', async t => { const configPath = new URL('swingset-die-cleanly.json', import.meta.url) .pathname; const config = await loadSwingsetConfigFile(configPath); From c23ae84c0bb1c79ffb2f9caf39d3e34f9c2c8b8b Mon Sep 17 00:00:00 2001 From: Brian Warner Date: Sat, 12 Oct 2024 18:45:29 -0700 Subject: [PATCH 38/56] fix(swingset): retain vatParameters for vat creation and upgrade All vats have `vatParameters`, a mostly-arbitrary data record, delivered as the second argument to their `buildRootObject()` function. Dynamic vats get their vatParameters from the options bag given to `E(vatAdminSvc).createVat()`. Static vats get them from the kernel config record. When a vat is upgraded, the new incarnation gets new vatParameters; these come from the options bag on `E(adminNode).upgrade()`. When received via `createVat()` or `upgrade()`, the vatParameters can contain object and device references. VatParameters cannot include promises. Previously, the kernel delivered vatParameters to the vat, but did not keep a copy. With this commit, the kernel retains a copy of vatParameters (including a refcount on any kernel objects therein). Internally, `vatKeeper.getVatParameters()` can be used to retrieve this copy. Only vats created or upgraded after this commit lands will get retained vatParameters: for older vats this will return `undefined`. Retained vat parameters should make it easier to implement "upgrade all vats", where the kernel perform a unilateral `upgrade()` on all vats without userspace asking for it. When this is implemented, the new incarnations will receive the same vatParameters as their predecessors. The slow-termination test was updated: it counts kvStore entries precisely as we delete them all, so it requires an update each time we add one. fixes #8947 --- packages/SwingSet/src/kernel/kernel.js | 3 + .../SwingSet/src/kernel/state/kernelKeeper.js | 2 + .../SwingSet/src/kernel/state/vatKeeper.js | 32 +++++++++ packages/SwingSet/test/state.test.js | 38 +++++++++++ .../upgrade/bootstrap-scripted-upgrade.js | 23 +++++++ .../SwingSet/test/upgrade/upgrade.test.js | 66 +++++++++++++++++++ .../test/vat-admin/create-vat.test.js | 51 ++++++++------ .../slow-termination/slow-termination.test.js | 34 +++++----- 8 files changed, 212 insertions(+), 37 deletions(-) diff --git a/packages/SwingSet/src/kernel/kernel.js b/packages/SwingSet/src/kernel/kernel.js index fcf07955118..b68d6836b95 100644 --- a/packages/SwingSet/src/kernel/kernel.js +++ b/packages/SwingSet/src/kernel/kernel.js @@ -752,6 +752,8 @@ export default function buildKernel( kdebug(`vat ${vatID} terminated before startVat delivered`); return NO_DELIVERY_CRANK_RESULTS; } + const vatKeeper = kernelKeeper.provideVatKeeper(vatID); + vatKeeper.setVatParameters(vatParameters); const { meterID } = vatInfo; /** @type { KernelDeliveryStartVat } */ const kd = harden(['startVat', vatParameters]); @@ -1052,6 +1054,7 @@ export default function buildKernel( }); const vatOptions = harden({ ...origOptions, workerOptions }); vatKeeper.setSourceAndOptions(source, vatOptions); + vatKeeper.setVatParameters(vatParameters); // TODO: decref the bundleID once setSourceAndOptions increfs it // pause, take a deep breath, appreciate this moment of silence diff --git a/packages/SwingSet/src/kernel/state/kernelKeeper.js b/packages/SwingSet/src/kernel/state/kernelKeeper.js index b8a6d6060d7..1d64b225f1c 100644 --- a/packages/SwingSet/src/kernel/state/kernelKeeper.js +++ b/packages/SwingSet/src/kernel/state/kernelKeeper.js @@ -119,6 +119,8 @@ export const CURRENT_SCHEMA_VERSION = 3; // old (v0): v$NN.reapCountdown = $NN or 'never' // v$NN.reapDirt = JSON({ deliveries, gcKrefs, computrons }) // missing keys treated as zero // (leave room for v$NN.snapshotDirt and options.snapshotDirtThreshold for #6786) +// v$NN.vatParameters = JSON(capdata) // missing for vats created/upgraded before #8947 +// // exclude from consensus // local.* diff --git a/packages/SwingSet/src/kernel/state/vatKeeper.js b/packages/SwingSet/src/kernel/state/vatKeeper.js index 7322f34ee37..1d4d6b1f12f 100644 --- a/packages/SwingSet/src/kernel/state/vatKeeper.js +++ b/packages/SwingSet/src/kernel/state/vatKeeper.js @@ -7,6 +7,7 @@ import { isObject } from '@endo/marshal'; import { parseKernelSlot } from '../parseKernelSlots.js'; import { makeVatSlot, parseVatSlot } from '../../lib/parseVatSlots.js'; import { insistVatID } from '../../lib/id.js'; +import { insistCapData } from '../../lib/capdata.js'; import { kdebug } from '../../lib/kdebug.js'; import { parseReachableAndVatSlot, @@ -173,6 +174,35 @@ export function makeVatKeeper( return harden(options); } + /** + * @param {SwingSetCapData} newVPCD + */ + function setVatParameters(newVPCD) { + insistCapData(newVPCD); + const key = `${vatID}.vatParameters`; + // increment-before-decrement to minimize spurious rc=0 checks + for (const kref of newVPCD.slots) { + incrementRefCount(kref, `${vatID}.vatParameters`); + } + const old = kvStore.get(key) || '{"slots":[]}'; + for (const kref of JSON.parse(old).slots) { + decrementRefCount(kref, `${vatID}.vatParameters`); + } + kvStore.set(key, JSON.stringify(newVPCD)); + } + + /** + * @returns {SwingSetCapData | undefined} vpcd + */ + function getVatParameters() { + const key = `${vatID}.vatParameters`; + const old = kvStore.get(key); + if (old) { + return JSON.parse(old); + } + return undefined; + } + // This is named "addDirt" because it should increment all dirt // counters (both for reap/BOYD and for heap snapshotting). We don't // have `heapSnapshotDirt` yet, but when we do, it should get @@ -768,6 +798,8 @@ export function makeVatKeeper( setSourceAndOptions, getSourceAndOptions, getOptions, + setVatParameters, + getVatParameters, addDirt, getReapDirt, clearReapDirt, diff --git a/packages/SwingSet/test/state.test.js b/packages/SwingSet/test/state.test.js index 16450ab7b17..b16c3d17d61 100644 --- a/packages/SwingSet/test/state.test.js +++ b/packages/SwingSet/test/state.test.js @@ -592,6 +592,44 @@ test('vatKeeper.getOptions', async t => { t.is(name, 'fred'); }); +test('vatKeeper.setVatParameters', async t => { + const store = buildKeeperStorageInMemory(); + const k = makeKernelKeeper(store, 'uninitialized'); + k.createStartingKernelState({ defaultManagerType: 'local' }); + k.setInitialized(); + const v1 = k.allocateVatIDForNameIfNeeded('name1'); + const bundleID = + 'b1-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; + const source = { bundleID }; + const workerOptions = { type: 'local' }; + const options = { workerOptions, name: 'fred', reapDirtThreshold: {} }; + k.createVatState(v1, source, options); + + const vk = k.provideVatKeeper(v1); + const ko1 = kslot('ko1'); + const ko2 = kslot('ko2'); + const vp1 = kser({ foo: 1, bar: ko1, baz: ko1 }); + vk.setVatParameters(vp1); + t.deepEqual(JSON.parse(store.kvStore.get('v1.vatParameters')), vp1); + t.is(store.kvStore.get('ko1.refCount'), '1,1'); + t.deepEqual(vk.getVatParameters(), vp1); + + const vp2 = kser({ foo: 1, bar: ko1, baz: ko2 }); + vk.setVatParameters(vp2); + t.deepEqual(JSON.parse(store.kvStore.get('v1.vatParameters')), vp2); + t.is(store.kvStore.get('ko1.refCount'), '1,1'); + t.is(store.kvStore.get('ko2.refCount'), '1,1'); + t.deepEqual(vk.getVatParameters(), vp2); + + const vp3 = kser({ foo: 1, baz: ko2 }); + vk.setVatParameters(vp3); + t.deepEqual(JSON.parse(store.kvStore.get('v1.vatParameters')), vp3); + // this test doesn't do processRefcounts, which would remove the 0,0 + t.is(store.kvStore.get('ko1.refCount'), '0,0'); + t.is(store.kvStore.get('ko2.refCount'), '1,1'); + t.deepEqual(vk.getVatParameters(), vp3); +}); + test('XS vatKeeper defaultManagerType', async t => { const store = buildKeeperStorageInMemory(); const k = makeKernelKeeper(store, 'uninitialized'); diff --git a/packages/SwingSet/test/upgrade/bootstrap-scripted-upgrade.js b/packages/SwingSet/test/upgrade/bootstrap-scripted-upgrade.js index c6bbde12178..8e4ee211ab9 100644 --- a/packages/SwingSet/test/upgrade/bootstrap-scripted-upgrade.js +++ b/packages/SwingSet/test/upgrade/bootstrap-scripted-upgrade.js @@ -43,6 +43,8 @@ export const buildRootObject = () => { vatAdmin = await E(vats.vatAdmin).createVatAdminService(devices.vatAdmin); }, + nop: () => 0, + getMarker: () => marker, getImportSensors: () => importSensors, @@ -282,5 +284,26 @@ export const buildRootObject = () => { return [paramA, paramB]; }, + + buildV1WithVatParameters: async () => { + const bcap1 = await E(vatAdmin).getNamedBundleCap('ulrik1'); + const vp1 = { number: 1, marker }; + const options1 = { vatParameters: vp1 }; + const res = await E(vatAdmin).createVat(bcap1, options1); + ulrikAdmin = res.adminNode; + ulrikRoot = res.root; + const param1 = await E(ulrikRoot).getParameters(); + return param1; + }, + + upgradeV2WithVatParameters: async () => { + const bcap2 = await E(vatAdmin).getNamedBundleCap('ulrik2'); + const vp2 = { number: 2, marker }; + const options2 = { vatParameters: vp2 }; + await E(ulrikAdmin).upgrade(bcap2, options2); + const param2 = await E(ulrikRoot).getParameters(); + + return param2; + }, }); }; diff --git a/packages/SwingSet/test/upgrade/upgrade.test.js b/packages/SwingSet/test/upgrade/upgrade.test.js index 85a1b6de7b5..564453a375c 100644 --- a/packages/SwingSet/test/upgrade/upgrade.test.js +++ b/packages/SwingSet/test/upgrade/upgrade.test.js @@ -953,3 +953,69 @@ test('failed vatAdmin upgrade - bad replacement code', async t => { // Just a taste to verify that the create went right; other tests check the rest t.deepEqual(v1result.data, ['some', 'data']); }); + +test('vat upgrade retains vatParameters', async t => { + const config = makeConfigFromPaths('bootstrap-scripted-upgrade.js', { + defaultManagerType: 'xs-worker', + defaultReapInterval: 'never', + bundlePaths: { + ulrik1: 'vat-ulrik-1.js', + ulrik2: 'vat-ulrik-2.js', + }, + }); + const kft = await initKernelForTest(t, t.context.data, config); + const { controller: c, kvStore } = kft; + + // create initial version + const kpid1 = c.queueToVatRoot('bootstrap', 'buildV1WithVatParameters', []); + await c.run(); + // incref=false to keep it from adding a refcount to the marker + const params1 = kunser(c.kpResolution(kpid1, { incref: false })); + // Free kpid1, else it will hold an extra refcount on the + // marker. The c.kpResolution() decremented the kpid1 refcount to 0, + // but we need to provoke a call to processRefcounts(), which only + // happens at end-of-crank + c.queueToVatRoot('bootstrap', 'nop', []); + // BOYD to get vat-admin to drop it's copy of the marker + c.reapAllVats(); + await c.run(); + + t.is(params1.number, 1); + const marker = params1.marker; + t.deepEqual(params1, { number: 1, marker }); + const kref = marker.getKref(); + + // confirm the vatParameters were recorded in kvStore + const vatID = JSON.parse(kvStore.get('vat.dynamicIDs'))[0]; + const vp1cd = kvStore.get(`${vatID}.vatParameters`); + const vp1 = kunser(JSON.parse(vp1cd)); + t.is(vp1.number, 1); + t.is(vp1.marker.getKref(), kref); + + // Ideally, the refcount should be 2: one for the importing vat's + // c-list, and a second for the retained vNN.vatParameters. But it + // will also get a refcount from device-vat-admin, as a conduit for + // the upgrade() call, because devices don't do GC. + + t.is(kvStore.get(`${kref}.refCount`), '3,3'); + + // upgrade + const kpid2 = c.queueToVatRoot('bootstrap', 'upgradeV2WithVatParameters', []); + await c.run(); + const params2 = kunser(c.kpResolution(kpid2, { incref: false })); + c.queueToVatRoot('bootstrap', 'nop', []); + c.reapAllVats(); + await c.run(); + + t.is(params2.number, 2); + t.is(params2.marker.getKref(), kref); + // kvStore should now hold the new vatParameters + const vp2cd = kvStore.get(`${vatID}.vatParameters`); + const vp2 = kunser(JSON.parse(vp2cd)); + t.is(vp2.number, 2); + t.is(vp2.marker.getKref(), kref); + + // same refcount: the old retained vatParameters should be swapped + // out + t.is(kvStore.get(`${kref}.refCount`), '3,3'); +}); diff --git a/packages/SwingSet/test/vat-admin/create-vat.test.js b/packages/SwingSet/test/vat-admin/create-vat.test.js index 40b3325cc58..0fe7547bd2d 100644 --- a/packages/SwingSet/test/vat-admin/create-vat.test.js +++ b/packages/SwingSet/test/vat-admin/create-vat.test.js @@ -276,13 +276,20 @@ test('createVat holds refcount', async t => { const { c, idRC, kernelStorage } = await doTestSetup(t, false, printSlog); const { kvStore } = kernelStorage; + // we typicaly get: v1-bootstrap, v2-vatAdmin, v6-export-held + // for (const name of JSON.parse(kvStore.get('vat.names'))) { + // console.log(`${kvStore.get(`vat.name.${name}`)}: ${name}`); + // } + // and d7-vatAdmin + // The bootstrap vat starts by fetching 'held' from vat-export-held, during // doTestSetup(), and retains it throughout the entire test. When we send // it refcount(), it will send VatAdminService.getBundleCap(), wait for the // response, then send VatAdminService.createVat(held). VAS will tell // device-vat-admin to push a create-vat event (including 'held') on the // run-queue. Some time later, the create-vat event reaches the front, and - // the new vat is created, receiving 'held' in vatParametesr. + // the new vat is created, receiving 'held' in vatParameters. The kernel + // retains a copy of those vatParameters for any future unilateral upgrade. // We want to check the refcounts during this sequence, to confirm that the // create-vat event holds a reference. Otherwise, 'held' might get GCed @@ -292,7 +299,7 @@ test('createVat holds refcount', async t => { // nothing ever decrements it: devices hold eternal refcounts on their // c-list entries, and nothing ever removes a device c-list entry. But some // day when we fix that, we'll rely upon the create-vat event's refcount to - // keep these things alive. + // keep these things alive.) // We happen to know that 'held' will be allocated ko27, but we use // `getHeld()` to obtain the real value in case e.g. a new built-in vat is @@ -365,9 +372,11 @@ test('createVat holds refcount', async t => { expectedRefcount -= 1; // kpid1 deleted, drops ref to 'held', now 2,2 // it also does syscall.send(createVat), which holds a new reference expectedRefcount += 1; // arg to 'createVat' - // now we should see 3,3: v1-bootstrap, the kpResolution pin, and the - // send(createVat) arguments. Two of these are c-lists. + // now we should see 3,3: v1-bootstrap, the kpResolution pin, and + // the send(createVat) arguments. For c-lists, it is present in v1.c + // (import) and v6.c (export) const r1 = findRefs(kvStore, held); + // console.log(`r1:`, JSON.stringify(r1)); t.deepEqual(r1.refcount, [expectedRefcount, expectedRefcount]); t.is(r1.refs.length, expectedCLists); // console.log(`---`); @@ -384,13 +393,13 @@ test('createVat holds refcount', async t => { await stepUntil(seeCreateVat); // console.log(`---`); - // We should see 5,5: v2-bootstrap, the kpResolution pin, vat-vat-admin, - // device-vat-admin, and the create-vat run-queue event. Three of these are - // c-lists. - expectedRefcount += 1; // vat-vat-admin c-list - expectedCLists += 1; // vat-vat-admin c-list - expectedRefcount += 1; // device-vat-admin c-list - expectedCLists += 1; // device-vat-admin c-list + // We should see 5,5: v1-bootstrap, the kpResolution pin, + // v2-vatAdmin, d7-vatAdmin, and the create-vat run-queue + // event. c-lists: v1/v2/d7 imports, v6 export + expectedRefcount += 1; // v2-vatAdmin c-list + expectedCLists += 1; // v2-vatAdmin c-list + expectedRefcount += 1; // d7-vatAdmin c-list + expectedCLists += 1; // d7-vatAdmin c-list const r2 = findRefs(kvStore, held); // console.log(`r2:`, JSON.stringify(r2)); @@ -400,8 +409,8 @@ test('createVat holds refcount', async t => { // Allow the vat-admin bringOutYourDead to be delivered, which // allows it to drop its reference to 'held'. - expectedRefcount -= 1; // vat-vat-admin retires - expectedCLists -= 1; // vat-vat-admin retires + expectedRefcount -= 1; // v2-vatAdmin retires + expectedCLists -= 1; // v2-vatAdmin retires // In addition, device-vat-admin does not yet participate in GC, and holds // its references forever. So this -=1 remains commented out until we @@ -415,25 +424,27 @@ test('createVat holds refcount', async t => { t.deepEqual(c.dump().reapQueue, []); // console.log(`---`); - // At this point we expected to see 5,5: v2-bootstrap, kpResolution pin, - // vat-vat-admin (because of the non-dropping bug), device-vat-admin - // (because of unimplemented GC), and the create-vat run-queue event. Two - // are c-lists. + // At this point we expected to see 4,4: v1-bootstrap, kpResolution + // pin, d7-vatAdmin (because of unimplemented GC), and the + // create-vat run-queue event. c-lists: v1/d7 imports, v6 export const r3 = findRefs(kvStore, held); // console.log(`r3:`, JSON.stringify(r3)); t.deepEqual(r3.refcount, [expectedRefcount, expectedRefcount]); t.is(r3.refs.length, expectedCLists); - // Allow create-vat to be processed, removing the create-vat reference and - // adding a reference from the new vat's c-list + // Allow create-vat to be processed, removing the create-vat + // reference and adding a reference from the new vat's c-list, plus + // a second reference from the retained vatParameters await c.step(); expectedRefcount -= 1; // remove send(createVat) argument expectedRefcount += 1; // new-vat c-list + expectedRefcount += 1; // retained vatParameters expectedCLists += 1; // new-vat c-list // console.log(`---`); - // v2-bootstrap, kpResolution pin, device-vat-admin, new-vat + // v1-bootstrap, kpResolution pin, d7-vatAdmin, new-vat, retained + // vatParameters const r4 = findRefs(kvStore, held); // console.log(`r4:`, JSON.stringify(r4)); t.deepEqual(r4.refcount, [expectedRefcount, expectedRefcount]); diff --git a/packages/SwingSet/test/vat-admin/slow-termination/slow-termination.test.js b/packages/SwingSet/test/vat-admin/slow-termination/slow-termination.test.js index bdf050d75a9..2e5b4909fb7 100644 --- a/packages/SwingSet/test/vat-admin/slow-termination/slow-termination.test.js +++ b/packages/SwingSet/test/vat-admin/slow-termination/slow-termination.test.js @@ -152,9 +152,9 @@ async function doSlowTerminate(t, mode) { .get(vatID); // 20*2 for imports, 21*2 for exports, 20*2 for promises, 20*1 for - // vatstore = 142. Plus 21 for the usual liveslots stuff, and 6 for + // vatstore = 142. Plus 21 for the usual liveslots stuff, and 7 for // kernel stuff like vNN.source/options - const initialKVCount = 169; + const initialKVCount = 170; t.is(remainingKV().length, initialKVCount); t.false(JSON.parse(kvStore.get('vats.terminated')).includes(vatID)); @@ -253,36 +253,36 @@ async function doSlowTerminate(t, mode) { await cleanKV(10, 5); // 5 c-list promises // that finishes the promises, so the next clean will delete the - // first five of our 47 other kv entries (20 vatstore plus 27 - // liveslots overhead + // first five of our 48 other kv entries (20 vatstore plus 28 + // overhead) await cleanKV(5, 5); // 5 other kv - // now there are 42 other kv entries left - t.is(remainingKV().length, 42); + // now there are 43 other kv entries left + t.is(remainingKV().length, 43); await cleanKV(5, 5); // 5 other kv - t.is(remainingKV().length, 37); + t.is(remainingKV().length, 38); await cleanKV(5, 5); // 5 other kv - t.is(remainingKV().length, 32); + t.is(remainingKV().length, 33); await cleanKV(5, 5); // 5 other kv - t.is(remainingKV().length, 27); + t.is(remainingKV().length, 28); await cleanKV(5, 5); // 5 other kv - t.is(remainingKV().length, 22); + t.is(remainingKV().length, 23); await cleanKV(5, 5); // 5 other kv - t.is(remainingKV().length, 17); + t.is(remainingKV().length, 18); checkTS(); await cleanKV(5, 5); // 5 other kv - t.is(remainingKV().length, 12); + t.is(remainingKV().length, 13); await cleanKV(5, 5); // 5 other kv - t.is(remainingKV().length, 7); + t.is(remainingKV().length, 8); await cleanKV(5, 5); // 5 other kv - t.is(remainingKV().length, 2); + t.is(remainingKV().length, 3); checkTS(); - // there are two kv left, so this clean will delete those, then 5 of - // the 7 snapshots - await cleanKV(2, 7); // 2 final kv, and 5 snapshots + // there are three kv left, so this clean will delete those, then 5 + // of the 7 snapshots + await cleanKV(3, 8); // 3 final kv, and 5 snapshots t.deepEqual(remainingKV(), []); t.is(kernelStorage.kvStore.get(`${vatID}.options`), undefined); expectedRemainingSnapshots -= 5; From b632a6ffbd76510d6446dae4e2a147801c430bcb Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Mon, 14 Oct 2024 20:11:07 +0300 Subject: [PATCH 39/56] chore(acceptance): add tests for PSM to `z:acceptance` Refs: https://github.com/Agoric/BytePitchPartnerEng/issues/23 fix(acceptance-psm): address change requests --- .../proposals/z:acceptance/package.json | 3 +- .../proposals/z:acceptance/psm.test.js | 274 ++++++ .../z:acceptance/test-lib/psm-lib.js | 513 +++++++++++ .../z:acceptance/test-lib/sync-tools.js | 2 +- .../proposals/z:acceptance/test.sh | 3 + .../proposals/z:acceptance/yarn.lock | 821 ++++++++++-------- 6 files changed, 1252 insertions(+), 364 deletions(-) create mode 100644 a3p-integration/proposals/z:acceptance/psm.test.js create mode 100644 a3p-integration/proposals/z:acceptance/test-lib/psm-lib.js diff --git a/a3p-integration/proposals/z:acceptance/package.json b/a3p-integration/proposals/z:acceptance/package.json index 9004540f415..a0fcaa82e22 100644 --- a/a3p-integration/proposals/z:acceptance/package.json +++ b/a3p-integration/proposals/z:acceptance/package.json @@ -9,7 +9,8 @@ "type": "module", "license": "Apache-2.0", "dependencies": { - "@agoric/internal": "0.3.3-dev-5676146.0", + "@agoric/ertp": "dev", + "@agoric/internal": "dev", "@agoric/synthetic-chain": "^0.3.0", "@endo/errors": "^1.2.2", "@endo/far": "^1.1.5", diff --git a/a3p-integration/proposals/z:acceptance/psm.test.js b/a3p-integration/proposals/z:acceptance/psm.test.js new file mode 100644 index 00000000000..8a5504a659a --- /dev/null +++ b/a3p-integration/proposals/z:acceptance/psm.test.js @@ -0,0 +1,274 @@ +/* eslint-env node */ +/** + * @file The goal of this file is to implement a set of tests to make sure PSM works properly + * + * Here are the steps we want to take; + * 1 - Change swap fees and mint limit according to "psmTestSpecs" below + * 2 - Create a new user using agd.keys + * 3 - Fund new user with a stable coin from the VALIDATOR + * - Do not provision manually + * 4 - Make sure new user is able to mint IST from PSM (fees are applied) + * 5 - Make sure new user can pay their debt and get their anchor (fees are applied) + * 6 - Make sure mint limit is adhered + */ + +import test from 'ava'; +import { + getUser, + GOV1ADDR, + GOV2ADDR, + GOV3ADDR, + waitForBlock, +} from '@agoric/synthetic-chain'; +import { + adjustBalancesIfNotProvisioned, + agopsPsm, + bankSend, + checkGovParams, + checkSwapExceedMintLimit, + checkSwapSucceeded, + checkUserInitializedSuccessfully, + getPsmMetrics, + implementPsmGovParamChange, + initializeNewUser, + maxMintBelowLimit, +} from './test-lib/psm-lib.js'; +import { getBalances } from './test-lib/utils.js'; + +// Export these from synthetic-chain? +export const USDC_DENOM = process.env.USDC_DENOM + ? process.env.USDC_DENOM + : 'no-denom'; +export const PSM_PAIR = process.env.PSM_PAIR?.replace('.', '-'); + +const psmTestSpecs = { + govParams: { + giveMintedFeeVal: 10n, // in % + wantMintedFeeVal: 10n, // in % + mintLimit: 500n * 1_000_000n, // in IST + deadline: 1, // in minutes + }, + psmInstance: `psm-${PSM_PAIR}`, + // @ts-expect-error we assume PSM_PAIR is set because of synthetic-chain environment + anchor: PSM_PAIR.split('-')[1], + newUser: { + name: 'new-psm-trader', + fund: { + denom: USDC_DENOM, + value: '300000000', // 300 USDC_axl + }, + }, + otherUser: { + name: 'gov1', + fund: { + denom: USDC_DENOM, + value: '1000000000', // 1000 USDC_axl + }, + toIst: { + value: 500, // in IST + }, + }, + toIst: { + value: 50, // in IST + }, + fromIst: { + value: 50, // in USDC_axl + }, +}; + +test.serial('change gov params', async t => { + await implementPsmGovParamChange( + { + address: GOV1ADDR, + instanceName: psmTestSpecs.psmInstance, + newParams: psmTestSpecs.govParams, + deadline: psmTestSpecs.govParams.deadline, + }, + { committeeAddrs: [GOV1ADDR, GOV2ADDR, GOV3ADDR], position: 0 }, + ); + + // Replace when https://github.com/Agoric/agoric-sdk/pull/10171 is in + await waitForBlock(3); + await checkGovParams( + t, + { + GiveMintedFee: { + type: 'ratio', + value: { + numerator: { value: psmTestSpecs.govParams.giveMintedFeeVal * 100n }, // convert to bps + }, + }, + WantMintedFee: { + type: 'ratio', + value: { + numerator: { value: psmTestSpecs.govParams.wantMintedFeeVal * 100n }, // convert to bps + }, + }, + MintLimit: { + type: 'amount', + value: { + value: psmTestSpecs.govParams.mintLimit, + }, + }, + }, + psmTestSpecs.psmInstance.split('-')[2], + ); +}); + +test.serial('initialize new user', async t => { + const { + newUser: { name, fund }, + } = psmTestSpecs; + + await initializeNewUser(name, fund); + // Replace when https://github.com/Agoric/agoric-sdk/pull/10171 is in + await waitForBlock(3); + + await checkUserInitializedSuccessfully(name, fund); + t.pass(); +}); + +test.serial('swap into IST', async t => { + const { + newUser: { name }, + anchor, + toIst, + govParams: { wantMintedFeeVal }, + } = psmTestSpecs; + + const psmTrader = await getUser(name); + + const [metricsBefore, balances] = await Promise.all([ + getPsmMetrics(anchor), + getBalances([psmTrader]), + ]); + + const balancesBefore = await adjustBalancesIfNotProvisioned( + balances, + psmTrader, + ); + t.log('METRICS', metricsBefore); + t.log('BALANCES', balancesBefore); + + await agopsPsm(psmTrader, [ + 'swap', + '--pair', + process.env.PSM_PAIR, + '--wantMinted', + toIst.value, + '--feePct', + wantMintedFeeVal, + ]); + await waitForBlock(5); + + await checkSwapSucceeded(t, metricsBefore, balancesBefore, { + wantMinted: toIst.value, + trader: psmTrader, + fee: Number(wantMintedFeeVal) / 100, // fee has to be between 0 and 1 + anchor, + }); +}); + +test.serial('swap out of IST', async t => { + const { + newUser: { name }, + anchor, + fromIst, + govParams: { giveMintedFeeVal }, + } = psmTestSpecs; + + const psmTrader = await getUser(name); + + const [metricsBefore, balancesBefore] = await Promise.all([ + getPsmMetrics(anchor), + getBalances([psmTrader]), + ]); + + t.log('METRICS', metricsBefore); + t.log('BALANCES', balancesBefore); + + await agopsPsm(psmTrader, [ + 'swap', + '--pair', + process.env.PSM_PAIR, + '--giveMinted', + fromIst.value, + '--feePct', + giveMintedFeeVal, + ]); + await waitForBlock(5); + + await checkSwapSucceeded(t, metricsBefore, balancesBefore, { + giveMinted: fromIst.value, + trader: psmTrader, + fee: Number(giveMintedFeeVal) / 100, // fee has to be between 0 and 1 + anchor, + }); +}); + +test.serial('mint limit is adhered', async t => { + const { + otherUser: { + fund: { denom, value }, + name, + }, + govParams, + anchor, + } = psmTestSpecs; + + // Fund other user + const otherAddr = await getUser(name); + await bankSend(otherAddr, `${value}${denom}`); + + // Replace when https://github.com/Agoric/agoric-sdk/pull/10171 is in + await waitForBlock(3); + + const [metricsBefore, balancesBefore] = await Promise.all([ + getPsmMetrics(anchor), + getBalances([otherAddr]), + ]); + + t.log('METRICS', metricsBefore); + t.log('BALANCES', balancesBefore); + + const { maxMintableValue, wantFeeValue } = await maxMintBelowLimit(anchor); + const maxMintFeesAccounted = Math.floor( + maxMintableValue * (1 - wantFeeValue), + ); + t.log({ maxMintableValue, wantFeeValue, maxMintFeesAccounted }); + + // Send a swap, should fail because mint limit is exceeded + await agopsPsm(otherAddr, [ + 'swap', + '--pair', + process.env.PSM_PAIR, + '--wantMinted', + maxMintFeesAccounted / 1000000 + 2, // Make sure we exceed the limit + '--feePct', + govParams.wantMintedFeeVal, + ]); + await waitForBlock(5); + + // Now check if failed with correct error message + await checkSwapExceedMintLimit(t, otherAddr, metricsBefore); + + // Send another swap offer, this time should succeed + await agopsPsm(otherAddr, [ + 'swap', + '--pair', + process.env.PSM_PAIR, + '--wantMinted', + maxMintFeesAccounted / 1000000, + '--feePct', + govParams.wantMintedFeeVal, + ]); + await waitForBlock(5); + + // Make sure swap succeeded + await checkSwapSucceeded(t, metricsBefore, balancesBefore, { + wantMinted: maxMintFeesAccounted / 1000000, + trader: otherAddr, + fee: Number(govParams.wantMintedFeeVal) / 100, // fee has to be between 0 and 1 + anchor, + }); +}); diff --git a/a3p-integration/proposals/z:acceptance/test-lib/psm-lib.js b/a3p-integration/proposals/z:acceptance/test-lib/psm-lib.js new file mode 100644 index 00000000000..a50dabcf7ee --- /dev/null +++ b/a3p-integration/proposals/z:acceptance/test-lib/psm-lib.js @@ -0,0 +1,513 @@ +/* eslint-env node */ + +import '@endo/init'; +import { + addUser, + agd, + agops, + agoric, + executeOffer, + getUser, + agopsLocation, + executeCommand, + CHAINID, + VALIDATORADDR, + GOV1ADDR, + mkTemp, +} from '@agoric/synthetic-chain'; +import { AmountMath } from '@agoric/ertp'; +import fsp from 'node:fs/promises'; +import { boardSlottingMarshaller, makeFromBoard } from './rpc.js'; +import { getBalances } from './utils.js'; +import { sleep } from './sync-tools.js'; + +// Export these from synthetic-chain? +export const USDC_DENOM = process.env.USDC_DENOM + ? process.env.USDC_DENOM + : 'no-denom'; +export const PSM_PAIR = process.env.PSM_PAIR?.replace('.', '-'); + +/** + * @typedef {object} PsmMetrics + * @property {import('@agoric/ertp').Amount<'nat'>} anchorPoolBalance + * @property {import('@agoric/ertp').Amount<'nat'>} feePoolBalance + * @property {import('@agoric/ertp').Amount<'nat'>} mintedPoolBalance + * @property {import('@agoric/ertp').Amount<'nat'>} totalAnchorProvided + * @property {import('@agoric/ertp').Amount<'nat'>} totalMintedProvided + * + * @typedef {Array<{ denom: string; amount: string; }>} CosmosBalances + */ + +const fromBoard = makeFromBoard(); +const marshaller = boardSlottingMarshaller(fromBoard.convertSlotToVal); + +const VOTING_WAIT_MS = 65 * 1000; + +/** + * Import from synthetic-chain once it is updated + * + * @param {string} addr + * @param {string} wanted + * @param {string} [from] + */ +export const bankSend = (addr, wanted, from = VALIDATORADDR) => { + const chain = ['--chain-id', CHAINID]; + const fromArg = ['--from', from]; + const testKeyring = ['--keyring-backend', 'test']; + const noise = [...fromArg, ...chain, ...testKeyring, '--yes']; + + return agd.tx('bank', 'send', from, addr, wanted, ...noise); +}; + +/** + * + * @param {{ + * address: string + * instanceName: string + * newParams: Params + * deadline: number + * offerId?: string + * }} QuestionDetails + */ +export const buildProposePSMParamChangeOffer = async ({ + address, + instanceName, + newParams, + deadline, + offerId = Date.now().toString(), +}) => { + const charterAcceptOfferId = await agops.ec( + 'find-continuing-id', + '--for', + `${'charter\\ member\\ invitation'}`, + '--from', + address, + ); + console.log('charterAcceptOfferId', charterAcceptOfferId); + const [brands, instances] = await Promise.all([ + agoric + .follow('-lF', ':published.agoricNames.brand', '-o', 'text') + .then(brandsRaw => + Object.fromEntries(marshaller.fromCapData(JSON.parse(brandsRaw))), + ), + agoric + .follow('-lF', ':published.agoricNames.instance', '-o', 'text') + .then(instancesRaw => + Object.fromEntries(marshaller.fromCapData(JSON.parse(instancesRaw))), + ), + ]); + + console.log('charterAcceptOfferId', charterAcceptOfferId); + console.log('BRANDS', brands); + console.log('INSTANCE', instances); + + /** + * @param {bigint} numValInPercent + */ + const toRatio = numValInPercent => { + const commonDenominator = AmountMath.make(brands.IST, 10_000n); + const numerator = AmountMath.make(brands.IST, numValInPercent * 100n); // Convert to bps + + return { + numerator, + denominator: commonDenominator, + }; + }; + + const params = {}; + if (newParams.giveMintedFeeVal) { + params.GiveMintedFee = toRatio(newParams.giveMintedFeeVal); + } + + if (newParams.wantMintedFeeVal) { + params.WantMintedFee = toRatio(newParams.wantMintedFeeVal); + } + + if (newParams.mintLimit) { + params.MintLimit = AmountMath.make(brands.IST, newParams.mintLimit); + } + + const offerSpec = { + id: offerId, + invitationSpec: { + source: 'continuing', + previousOffer: charterAcceptOfferId, + invitationMakerName: 'VoteOnParamChange', + }, + proposal: {}, + offerArgs: { + instance: instances[instanceName], + params, + deadline: BigInt(deadline * 60 + Math.round(Date.now() / 1000)), + }, + }; + + /** @type {string | object} */ + const spendAction = { + method: 'executeOffer', + offer: offerSpec, + }; + + const offer = JSON.stringify(marshaller.toCapData(harden(spendAction))); + console.log(offerSpec); + console.log(offer); + + return executeOffer(address, offer); +}; + +/** + * + * @param {{ + * committeeAddrs: Array + * position: number | string + * }} VotingDetails + */ +export const voteForNewParams = ({ committeeAddrs, position }) => { + console.log('ACTIONS voting for position', position, 'using', committeeAddrs); + return Promise.all( + committeeAddrs.map(account => + // @ts-expect-error Casting + agops.ec('vote', '--forPosition', position, '--send-from', account), + ), + ); +}; + +/** + * @typedef {{ + * giveMintedFeeVal: bigint; + * wantMintedFeeVal: bigint; + * mintLimit: bigint; + * }} Params + * + * + * @param {{ + * address: string + * instanceName: string + * newParams: Params + * deadline: number + * offerId?: string + * }} question + * + * @param {{ + * committeeAddrs: Array + * position: number + * }} voting + */ +export const implementPsmGovParamChange = async (question, voting) => { + await buildProposePSMParamChangeOffer(question); + await voteForNewParams(voting); + console.log('ACTIONS wait for the vote deadline to pass'); + await sleep(VOTING_WAIT_MS, { log: console.log }); +}; + +/** + * @param {string} anchor + */ +export const getPsmGovernance = async anchor => { + const governanceRaw = await agoric.follow( + '-lF', + `:published.psm.IST.${anchor}.governance`, + '-o', + 'text', + ); + const { current } = marshaller.fromCapData(JSON.parse(governanceRaw)); + return current; +}; + +/** + * @param {string} anchor + */ +export const getPsmMetrics = async anchor => { + const metricsRaw = await agoric.follow( + '-lF', + `:published.psm.IST.${anchor}.metrics`, + '-o', + 'text', + ); + + return marshaller.fromCapData(JSON.parse(metricsRaw)); +}; + +export const checkGovParams = async (t, expected, psmName) => { + const current = await getPsmGovernance(psmName); + + t.log({ + give: current.WantMintedFee.value, + want: current.GiveMintedFee.value, + mintLimit: current.MintLimit, + }); + + t.like(current, expected); +}; + +/** + * + * @param {string} name + * @param {{ + * denom: string, + * value: string + * }} fund + */ +export const initializeNewUser = async (name, fund) => { + const psmTrader = await addUser(name); + await Promise.all([ + bankSend(psmTrader, `20000000ubld,${fund.value}${fund.denom}`), + bankSend(psmTrader, `1000000uist`, GOV1ADDR), + ]); +}; + +/** + * + * @param {string} userName + * @param {{ + * denom: string, + * value: string + * }} expectedAnchorFunds + */ +export const checkUserInitializedSuccessfully = async ( + userName, + expectedAnchorFunds, +) => { + const userAddress = await getUser(userName); + + const balance = await getBalances([userAddress], expectedAnchorFunds.denom); + assert(balance >= BigInt(expectedAnchorFunds.value)); +}; + +/** + * Similar to https://github.com/Agoric/agoric-3-proposals/blob/422b163fecfcf025d53431caebf6d476778b5db3/packages/synthetic-chain/src/lib/commonUpgradeHelpers.ts#L123-L139 + * However, in the case where "address" is not provisioned "agoric wallet send" is needed because + * "agops perf satisfaction" tries to follow ":published.wallet.${address}" which blocks the execution because no such path exists in + * vstorage. In situations like this "agoric wallet send" seems a better choice as it doesn't depend on following user's vstorage wallet path + * + * @param {string} address + * @param {Promise} offerPromise + */ +export const sendOfferAgoric = async (address, offerPromise) => { + const offerPath = await mkTemp('agops.XXX'); + const offer = await offerPromise; + await fsp.writeFile(offerPath, offer); + + await agoric.wallet( + '--keyring-backend=test', + 'send', + '--offer', + offerPath, + '--from', + address, + ); +}; + +/** + * @param {string} address + * @param {Array} params + */ +export const agopsPsm = (address, params) => { + const newParams = ['psm', ...params]; + const offerPromise = executeCommand(agopsLocation, newParams); + return sendOfferAgoric(address, offerPromise); +}; + +/** + * + * @param {number} base + * @param {number} fee + * @returns + */ +const giveAnchor = (base, fee) => Math.ceil(base / (1 - fee)); + +/** + * + * @param {number} base + * @param {number} fee + * @returns + */ +const receiveAnchor = (base, fee) => Math.ceil(base * (1 - fee)); + +/** + * + * @param {CosmosBalances} balances + * @param {string} targetDenom + * @returns + */ +const extractBalance = (balances, targetDenom) => { + const balance = balances.find(({ denom }) => denom === targetDenom); + if (!balance) return 0; + return Number(balance.amount); +}; + +/** + * + * @param {import('ava').ExecutionContext} t + * @param {PsmMetrics} metricsBefore + * @param {CosmosBalances} balancesBefore + * @param {{trader: string; fee: number; anchor: string;} & ( + * | {wantMinted: number} + * | {giveMinted: number} + * )} tradeInfo + */ +export const checkSwapSucceeded = async ( + t, + metricsBefore, + balancesBefore, + tradeInfo, +) => { + const [metricsAfter, balancesAfter] = await Promise.all([ + getPsmMetrics(tradeInfo.anchor), + getBalances([tradeInfo.trader]), + ]); + + t.log('METRICS_AFTER', metricsAfter); + t.log('BALANCES_AFTER', balancesAfter); + + if ('wantMinted' in tradeInfo) { + const anchorPaid = giveAnchor( + tradeInfo.wantMinted * 1000000, + tradeInfo.fee, + ); + const mintedReceived = tradeInfo.wantMinted * 1000000; + const feePaid = anchorPaid - mintedReceived; + + t.deepEqual( + extractBalance(balancesAfter, USDC_DENOM), + extractBalance(balancesBefore, USDC_DENOM) - anchorPaid, + ); + + t.deepEqual( + extractBalance(balancesAfter, 'uist'), + extractBalance(balancesBefore, 'uist') + mintedReceived, + ); + + t.like(metricsAfter, { + anchorPoolBalance: { + value: metricsBefore.anchorPoolBalance.value + BigInt(anchorPaid), + }, + feePoolBalance: { + value: metricsBefore.feePoolBalance.value + BigInt(feePaid), + }, + mintedPoolBalance: { + value: metricsBefore.mintedPoolBalance.value + BigInt(anchorPaid), + }, + totalAnchorProvided: { + value: metricsBefore.totalAnchorProvided.value, + }, + totalMintedProvided: { + value: metricsBefore.totalMintedProvided.value + BigInt(anchorPaid), + }, + }); + } else if ('giveMinted' in tradeInfo) { + const mintedPaid = tradeInfo.giveMinted * 1000000; + const anchorReceived = receiveAnchor( + tradeInfo.giveMinted * 1000000, + tradeInfo.fee, + ); + const feePaid = mintedPaid - anchorReceived; + + t.deepEqual( + extractBalance(balancesAfter, USDC_DENOM), + extractBalance(balancesBefore, USDC_DENOM) + anchorReceived, + ); + + t.deepEqual( + extractBalance(balancesAfter, 'uist'), + extractBalance(balancesBefore, 'uist') - mintedPaid, + ); + + t.like(metricsAfter, { + anchorPoolBalance: { + value: metricsBefore.anchorPoolBalance.value - BigInt(anchorReceived), + }, + feePoolBalance: { + value: metricsBefore.feePoolBalance.value + BigInt(feePaid), + }, + mintedPoolBalance: { + value: metricsBefore.mintedPoolBalance.value - BigInt(anchorReceived), + }, + totalAnchorProvided: { + value: metricsBefore.totalAnchorProvided.value + BigInt(anchorReceived), + }, + totalMintedProvided: { + value: metricsBefore.totalMintedProvided.value, + }, + }); + } +}; + +/** + * + * @param {Array<{ denom: string; amount: string }>} balances + * @param {string} address + */ +export const adjustBalancesIfNotProvisioned = async (balances, address) => { + const { children } = await agd.query( + 'vstorage', + 'children', + 'published.wallet', + '-o', + 'json', + ); + const addressProvisioned = children.includes(address); + + if (addressProvisioned === true) return balances; + + const balancesAdjusted = []; + + balances.forEach(({ denom, amount }) => { + if (denom === 'uist') { + amount = (parseInt(amount, 10) + 250000 - 1_000_000).toString(); // provision sends 250000uist to new accounts and 1 IST is charged + balancesAdjusted.push({ denom, amount }); + } else { + balancesAdjusted.push({ denom, amount }); + } + }); + + return balancesAdjusted; +}; + +/** + * + * @param {any} t + * @param {string} address + * @param {Record} metricsBefore + */ +export const checkSwapExceedMintLimit = async (t, address, metricsBefore) => { + const [offerResult, metricsAfter] = await Promise.all([ + agoric.follow('-lF', `:published.wallet.${address}`), + // @ts-expect-error we assume PSM_PAIR is set because of synthetic-chain environment + getPsmMetrics(PSM_PAIR.split('-')[1]), + ]); + const { status, updated } = offerResult; + + t.is(updated, 'offerStatus'); + t.is(status.error, 'Error: Request would exceed mint limit'); + t.like(metricsBefore, { + mintedPoolBalance: { value: metricsAfter.mintedPoolBalance.value }, + }); +}; + +/** + * @param {string} anchor + * @returns {Promise<{ maxMintableValue: number; wantFeeValue: number; giveFeeValue: number; }>} + */ +export const maxMintBelowLimit = async anchor => { + const [governance, metrics] = await Promise.all([ + getPsmGovernance(anchor), + getPsmMetrics(anchor), + ]); + + const mintLimitVal = Number(governance.MintLimit.value.value); + const mintedPoolBalanceVal = Number(metrics.mintedPoolBalance.value); + const maxMintableValue = mintLimitVal - mintedPoolBalanceVal - 1; + + const wantFeeRatio = governance.WantMintedFee.value; + const giveFeeRatio = governance.GiveMintedFee.value; + + const wantFeeValue = + Number(wantFeeRatio.numerator.value) / + Number(wantFeeRatio.denominator.value); + const giveFeeValue = + Number(giveFeeRatio.numerator.value) / + Number(giveFeeRatio.denominator.value); + + return { maxMintableValue, wantFeeValue, giveFeeValue }; +}; diff --git a/a3p-integration/proposals/z:acceptance/test-lib/sync-tools.js b/a3p-integration/proposals/z:acceptance/test-lib/sync-tools.js index f0daeddabde..c445ea8c957 100644 --- a/a3p-integration/proposals/z:acceptance/test-lib/sync-tools.js +++ b/a3p-integration/proposals/z:acceptance/test-lib/sync-tools.js @@ -36,7 +36,7 @@ const ambientSetTimeout = global.setTimeout; * @param {number} ms * @param {*} sleepOptions */ -const sleep = (ms, { log = () => {}, setTimeout = ambientSetTimeout }) => +export const sleep = (ms, { log = () => {}, setTimeout = ambientSetTimeout }) => new Promise(resolve => { log(`Sleeping for ${ms}ms...`); setTimeout(resolve, ms); diff --git a/a3p-integration/proposals/z:acceptance/test.sh b/a3p-integration/proposals/z:acceptance/test.sh index a1f74a431e3..ee4e473496d 100755 --- a/a3p-integration/proposals/z:acceptance/test.sh +++ b/a3p-integration/proposals/z:acceptance/test.sh @@ -29,5 +29,8 @@ yarn ava wallet.test.js echo ACCEPTANCE TESTING vaults yarn ava vaults.test.js +echo ACCEPTANCE TESTING psm +yarn ava psm.test.js + echo ACCEPTANCE TESTING governance yarn ava governance.test.js diff --git a/a3p-integration/proposals/z:acceptance/yarn.lock b/a3p-integration/proposals/z:acceptance/yarn.lock index 5cd3b8df234..6a5183c4ccc 100644 --- a/a3p-integration/proposals/z:acceptance/yarn.lock +++ b/a3p-integration/proposals/z:acceptance/yarn.lock @@ -5,55 +5,106 @@ __metadata: version: 8 cacheKey: 10c0 -"@agoric/assert@npm:0.6.1-dev-5676146.0+5676146": - version: 0.6.1-dev-5676146.0 - resolution: "@agoric/assert@npm:0.6.1-dev-5676146.0" - checksum: 10c0/3391d53d64f4ca74ae5a87b623dc7489a20d91f45716723c33e393cfe6536fb1553344b72d24ac966f49b83d56906140c263778968ff513dfcdbb30e1be68091 +"@agoric/base-zone@npm:0.1.1-dev-2ca7ea6.0+2ca7ea6": + version: 0.1.1-dev-2ca7ea6.0 + resolution: "@agoric/base-zone@npm:0.1.1-dev-2ca7ea6.0" + dependencies: + "@agoric/store": "npm:0.9.3-dev-2ca7ea6.0+2ca7ea6" + "@endo/common": "npm:^1.2.6" + "@endo/errors": "npm:^1.2.6" + "@endo/exo": "npm:^1.5.4" + "@endo/far": "npm:^1.1.6" + "@endo/pass-style": "npm:^1.4.4" + "@endo/patterns": "npm:^1.4.4" + checksum: 10c0/16593b729aa2d1daabfe9539f2d70e76a6da3f7473a6401320a3b012cd998b03f51f7c820e98aebbfb89188d4ecfaed33f9cabd490a11bc576d28d7269c0ff13 + languageName: node + linkType: hard + +"@agoric/ertp@npm:dev": + version: 0.16.3-dev-2ca7ea6.0 + resolution: "@agoric/ertp@npm:0.16.3-dev-2ca7ea6.0" + dependencies: + "@agoric/notifier": "npm:0.6.3-dev-2ca7ea6.0+2ca7ea6" + "@agoric/store": "npm:0.9.3-dev-2ca7ea6.0+2ca7ea6" + "@agoric/vat-data": "npm:0.5.3-dev-2ca7ea6.0+2ca7ea6" + "@agoric/zone": "npm:0.2.3-dev-2ca7ea6.0+2ca7ea6" + "@endo/errors": "npm:^1.2.6" + "@endo/eventual-send": "npm:^1.2.6" + "@endo/far": "npm:^1.1.6" + "@endo/marshal": "npm:^1.5.4" + "@endo/nat": "npm:^5.0.11" + "@endo/patterns": "npm:^1.4.4" + "@endo/promise-kit": "npm:^1.1.6" + checksum: 10c0/413ab9390245201ea26c1e9aa38c75668caeb91dc4a6ec6d6b6a1ff245d8c2b02f67227c4765b098e019e6c9a21e34cb4a2dcbf7e27b136127a6caa4aa08f15e + languageName: node + linkType: hard + +"@agoric/internal@npm:0.3.3-dev-2ca7ea6.0+2ca7ea6, @agoric/internal@npm:dev": + version: 0.3.3-dev-2ca7ea6.0 + resolution: "@agoric/internal@npm:0.3.3-dev-2ca7ea6.0" + dependencies: + "@agoric/base-zone": "npm:0.1.1-dev-2ca7ea6.0+2ca7ea6" + "@endo/common": "npm:^1.2.6" + "@endo/errors": "npm:^1.2.6" + "@endo/far": "npm:^1.1.6" + "@endo/init": "npm:^1.1.5" + "@endo/marshal": "npm:^1.5.4" + "@endo/pass-style": "npm:^1.4.4" + "@endo/patterns": "npm:^1.4.4" + "@endo/promise-kit": "npm:^1.1.6" + "@endo/stream": "npm:^1.2.6" + anylogger: "npm:^0.21.0" + jessie.js: "npm:^0.3.4" + checksum: 10c0/86d5be268c8813fb3c5138486e80ed0c0aab8df2a1cc870a4e78f1e4b6b27e244bb9140ea148a6a1571d055ee4842c2d10aade6b248aae488c438d67af663daf languageName: node linkType: hard -"@agoric/base-zone@npm:0.1.1-dev-5676146.0+5676146": - version: 0.1.1-dev-5676146.0 - resolution: "@agoric/base-zone@npm:0.1.1-dev-5676146.0" +"@agoric/notifier@npm:0.6.3-dev-2ca7ea6.0+2ca7ea6": + version: 0.6.3-dev-2ca7ea6.0 + resolution: "@agoric/notifier@npm:0.6.3-dev-2ca7ea6.0" dependencies: - "@agoric/store": "npm:0.9.3-dev-5676146.0+5676146" - "@endo/common": "npm:^1.1.0" - "@endo/exo": "npm:^1.2.1" - "@endo/far": "npm:^1.0.4" - "@endo/pass-style": "npm:^1.2.0" - "@endo/patterns": "npm:^1.2.0" - checksum: 10c0/b3dfa47af7cf4a686244e2a31243394b44756f127a7612f5d50c77b1a91f777514386c305866705b46219d2d1e0df2b8d5bff9e08f82275043bb0c198c0601e4 + "@agoric/internal": "npm:0.3.3-dev-2ca7ea6.0+2ca7ea6" + "@agoric/vat-data": "npm:0.5.3-dev-2ca7ea6.0+2ca7ea6" + "@endo/errors": "npm:^1.2.6" + "@endo/far": "npm:^1.1.6" + "@endo/marshal": "npm:^1.5.4" + "@endo/patterns": "npm:^1.4.4" + "@endo/promise-kit": "npm:^1.1.6" + checksum: 10c0/276a61bbec8930eb69ab0d7b2c66c735d1c725ba282be3be9ca04980f06b08c54d38eb17a328e8180d56c4b8aa6e0feac5b3b7983918b6318f4bef5f5cb4b2e9 languageName: node linkType: hard -"@agoric/internal@npm:0.3.3-dev-5676146.0": - version: 0.3.3-dev-5676146.0 - resolution: "@agoric/internal@npm:0.3.3-dev-5676146.0" +"@agoric/store@npm:0.9.3-dev-2ca7ea6.0+2ca7ea6": + version: 0.9.3-dev-2ca7ea6.0 + resolution: "@agoric/store@npm:0.9.3-dev-2ca7ea6.0" dependencies: - "@agoric/assert": "npm:0.6.1-dev-5676146.0+5676146" - "@agoric/base-zone": "npm:0.1.1-dev-5676146.0+5676146" - "@endo/common": "npm:^1.1.0" - "@endo/far": "npm:^1.0.4" - "@endo/init": "npm:^1.0.4" - "@endo/marshal": "npm:^1.3.0" - "@endo/patterns": "npm:^1.2.0" - "@endo/promise-kit": "npm:^1.0.4" - "@endo/stream": "npm:^1.1.0" - anylogger: "npm:^0.21.0" - jessie.js: "npm:^0.3.2" - checksum: 10c0/cd2a81ff39790a1b333621b3815f0791b70d0822f201d491175e46602697c80814f1fb87a610167e541a9ad431a771cd7348afe24517a15c45d1591d3d494bc2 + "@endo/errors": "npm:^1.2.6" + "@endo/exo": "npm:^1.5.4" + "@endo/marshal": "npm:^1.5.4" + "@endo/pass-style": "npm:^1.4.4" + "@endo/patterns": "npm:^1.4.4" + checksum: 10c0/6b02d913afa69b6c82b67277b5e5102ecc082f0348285440af8a040d77030c45d8b6c5cd4b05e3e707b8ad3bef778935fba1d982fbe93629466678efb71e139f languageName: node linkType: hard -"@agoric/store@npm:0.9.3-dev-5676146.0+5676146": - version: 0.9.3-dev-5676146.0 - resolution: "@agoric/store@npm:0.9.3-dev-5676146.0" +"@agoric/swingset-liveslots@npm:0.10.3-dev-2ca7ea6.0+2ca7ea6": + version: 0.10.3-dev-2ca7ea6.0 + resolution: "@agoric/swingset-liveslots@npm:0.10.3-dev-2ca7ea6.0" dependencies: - "@endo/exo": "npm:^1.2.1" - "@endo/marshal": "npm:^1.3.0" - "@endo/pass-style": "npm:^1.2.0" - "@endo/patterns": "npm:^1.2.0" - checksum: 10c0/675e73bbcac024c456b658583ec3fd14a50f69fea5fc07aadf30e593978e5cadbc82d365b13976967b5509614a7adf0adad4e84712f7e0b6c13f2a2a93c9ea63 + "@agoric/internal": "npm:0.3.3-dev-2ca7ea6.0+2ca7ea6" + "@agoric/store": "npm:0.9.3-dev-2ca7ea6.0+2ca7ea6" + "@endo/env-options": "npm:^1.1.7" + "@endo/errors": "npm:^1.2.6" + "@endo/eventual-send": "npm:^1.2.6" + "@endo/exo": "npm:^1.5.4" + "@endo/far": "npm:^1.1.6" + "@endo/init": "npm:^1.1.5" + "@endo/marshal": "npm:^1.5.4" + "@endo/nat": "npm:^5.0.11" + "@endo/pass-style": "npm:^1.4.4" + "@endo/patterns": "npm:^1.4.4" + "@endo/promise-kit": "npm:^1.1.6" + checksum: 10c0/ce6285a5e65b1a9a7ed819c96de337234cba0a24e57dd60737716ff0222889fe98cc53a4ff644950d0aab4ccda8c22abe91fbae861659a7da521b659c2c3bd39 languageName: node linkType: hard @@ -72,167 +123,194 @@ __metadata: languageName: node linkType: hard -"@endo/base64@npm:^1.0.7": - version: 1.0.7 - resolution: "@endo/base64@npm:1.0.7" - checksum: 10c0/aab10f433f8ea588ebd1786188b6660c0be3a743c119ef2df52ee23afd4ce3844b1d954028952569a747f6657287aeced875afe8e136ea8bff4ba146a60578bd +"@agoric/vat-data@npm:0.5.3-dev-2ca7ea6.0+2ca7ea6": + version: 0.5.3-dev-2ca7ea6.0 + resolution: "@agoric/vat-data@npm:0.5.3-dev-2ca7ea6.0" + dependencies: + "@agoric/base-zone": "npm:0.1.1-dev-2ca7ea6.0+2ca7ea6" + "@agoric/store": "npm:0.9.3-dev-2ca7ea6.0+2ca7ea6" + "@agoric/swingset-liveslots": "npm:0.10.3-dev-2ca7ea6.0+2ca7ea6" + "@endo/errors": "npm:^1.2.6" + "@endo/exo": "npm:^1.5.4" + "@endo/patterns": "npm:^1.4.4" + checksum: 10c0/c5d24f9715e9c6621f9dab6fd4626d18ec3d7642e9a4ed425a95181afe706bebb378023f00b39221bef012ba1e804d2c4ca1339510b0cc714c835d9cc85b28a7 languageName: node linkType: hard -"@endo/common@npm:^1.1.0, @endo/common@npm:^1.2.5": - version: 1.2.5 - resolution: "@endo/common@npm:1.2.5" +"@agoric/zone@npm:0.2.3-dev-2ca7ea6.0+2ca7ea6": + version: 0.2.3-dev-2ca7ea6.0 + resolution: "@agoric/zone@npm:0.2.3-dev-2ca7ea6.0" dependencies: - "@endo/errors": "npm:^1.2.5" - "@endo/eventual-send": "npm:^1.2.5" - "@endo/promise-kit": "npm:^1.1.5" - checksum: 10c0/104ca2febd87d05b97a77037cb0f281157082b722a39f3fbfca94e36984ad8bc8622e900aeba861d7ed6e6b5d103971599ec2b804eb236537576d498f9ab1fe5 + "@agoric/base-zone": "npm:0.1.1-dev-2ca7ea6.0+2ca7ea6" + "@agoric/vat-data": "npm:0.5.3-dev-2ca7ea6.0+2ca7ea6" + "@endo/errors": "npm:^1.2.6" + "@endo/far": "npm:^1.1.6" + "@endo/pass-style": "npm:^1.4.4" + checksum: 10c0/2d8c3798bd4394ee89f333f1788cefe4f967cc8a4a3feceb7d5de51ae7f83012ef11a448c385918ecfe75fcde17aeb12a55889555a346d73fd1f61d26ad7c95c languageName: node linkType: hard -"@endo/env-options@npm:^1.1.6": - version: 1.1.6 - resolution: "@endo/env-options@npm:1.1.6" - checksum: 10c0/0001b1cba6954cccfa40104f819378f2f5c8babc89103213a8a5da4f8f94248c8389bfa06ec37cecae81edabe570428558399313d649c64ad7c90743f563dea2 +"@endo/base64@npm:^1.0.8": + version: 1.0.8 + resolution: "@endo/base64@npm:1.0.8" + checksum: 10c0/3501efbf866acc25b9ad0912ec2383e3b976c890a18dc67b5c6eb128433708db69e8ed1cc57190305266bdcbd132659aa87edfc6d02a9886b711e8b86adc21c0 languageName: node linkType: hard -"@endo/errors@npm:^1.2.2, @endo/errors@npm:^1.2.5": - version: 1.2.5 - resolution: "@endo/errors@npm:1.2.5" +"@endo/common@npm:^1.2.6": + version: 1.2.6 + resolution: "@endo/common@npm:1.2.6" dependencies: - ses: "npm:^1.8.0" - checksum: 10c0/32eac3b332139ddec8a85a0013645482541e4f3cc0c484073dde430087f27bb683cde8b0a6e399c5b7f07af007c3b6aa589cf31935a8b8d69e5f869bf71a1662 + "@endo/errors": "npm:^1.2.6" + "@endo/eventual-send": "npm:^1.2.6" + "@endo/promise-kit": "npm:^1.1.6" + checksum: 10c0/7cca372677bd1ab535a0f8fc250eca9bfc202ef7dfd24cac6ff1260003aa4512f8db18419dd632b3e57270a589fec54891496904ff2e8a97047f1e187e3f1401 + languageName: node + linkType: hard + +"@endo/env-options@npm:^1.1.7": + version: 1.1.7 + resolution: "@endo/env-options@npm:1.1.7" + checksum: 10c0/5784bd68790041b08d9ead4f6c29cc7871d2e554c23fc44fff38cb20b6b4e55cdba2f78d844ba5ad4b0818185c32475ff318c1b77890d628690d7c7a6ede9475 languageName: node linkType: hard -"@endo/eventual-send@npm:^1.2.5": - version: 1.2.5 - resolution: "@endo/eventual-send@npm:1.2.5" +"@endo/errors@npm:^1.2.2, @endo/errors@npm:^1.2.6": + version: 1.2.6 + resolution: "@endo/errors@npm:1.2.6" dependencies: - "@endo/env-options": "npm:^1.1.6" - checksum: 10c0/7eaa30628582f768920659e4894b871c1056da4252b82b8ad70ed49a24c4559efb8d1655a6845984a0eae83d328179e4272b0917007a2f147dc8b15ecb0ecc52 + ses: "npm:^1.9.0" + checksum: 10c0/cbc541c2d26fbfeb1567dd1cdf0e1f110ee9866430c5a1b91d87270d6f00bc8293cc8512301217c4eda35e60677ce62a941d11eb32c7da1f72d450a011ad2bb5 languageName: node linkType: hard -"@endo/exo@npm:^1.2.1": - version: 1.5.3 - resolution: "@endo/exo@npm:1.5.3" +"@endo/eventual-send@npm:^1.2.6": + version: 1.2.6 + resolution: "@endo/eventual-send@npm:1.2.6" dependencies: - "@endo/common": "npm:^1.2.5" - "@endo/env-options": "npm:^1.1.6" - "@endo/errors": "npm:^1.2.5" - "@endo/eventual-send": "npm:^1.2.5" - "@endo/far": "npm:^1.1.5" - "@endo/pass-style": "npm:^1.4.3" - "@endo/patterns": "npm:^1.4.3" - checksum: 10c0/5510bc442730910ce2470c6ebdb6c04208c033e452cd60c8684e1769039453ad5f47de31b00629be3c6bf4183bee4a421bace142144e92740b606c591a839c72 + "@endo/env-options": "npm:^1.1.7" + checksum: 10c0/6983d6b88bf4e99f6c469d4ca037793582b06cc0bfa2da085b5bc7ad67333a1fba6e5e7077b7f279be23ccfba1dff9e06c7f85f9980b09fd002227f89a673c11 languageName: node linkType: hard -"@endo/far@npm:^1.0.0, @endo/far@npm:^1.0.4, @endo/far@npm:^1.1.5": - version: 1.1.5 - resolution: "@endo/far@npm:1.1.5" +"@endo/exo@npm:^1.5.4": + version: 1.5.4 + resolution: "@endo/exo@npm:1.5.4" dependencies: - "@endo/errors": "npm:^1.2.5" - "@endo/eventual-send": "npm:^1.2.5" - "@endo/pass-style": "npm:^1.4.3" - checksum: 10c0/8c50a28323ab1078d0cb6fce1d7fc6da4884247d76585f37f960a2a7134fc7f293075effaae34b41801b7508a1f75d32304c19db0597709727853c4a87eb4999 + "@endo/common": "npm:^1.2.6" + "@endo/env-options": "npm:^1.1.7" + "@endo/errors": "npm:^1.2.6" + "@endo/eventual-send": "npm:^1.2.6" + "@endo/far": "npm:^1.1.6" + "@endo/pass-style": "npm:^1.4.4" + "@endo/patterns": "npm:^1.4.4" + checksum: 10c0/674d2bfc50f8e7abdf7eb38ba3bb11dac9e84395bdccc3b06eef1076fcab3fd8fe38b9559bfef69f9d78287cd6c2d77533cfec68a57526812d833f1d57151425 languageName: node linkType: hard -"@endo/init@npm:^1.0.4, @endo/init@npm:^1.1.4": - version: 1.1.4 - resolution: "@endo/init@npm:1.1.4" +"@endo/far@npm:^1.0.0, @endo/far@npm:^1.1.5, @endo/far@npm:^1.1.6": + version: 1.1.6 + resolution: "@endo/far@npm:1.1.6" dependencies: - "@endo/base64": "npm:^1.0.7" - "@endo/eventual-send": "npm:^1.2.5" - "@endo/lockdown": "npm:^1.0.10" - "@endo/promise-kit": "npm:^1.1.5" - checksum: 10c0/9e915b3b888b7a9f1d563d532ad180dea987253d71e79eda1fcda8d287391611bcca369f2d9b89c59b9f24b3adc548816954e8eaefa4f7402c68585245a686a5 + "@endo/errors": "npm:^1.2.6" + "@endo/eventual-send": "npm:^1.2.6" + "@endo/pass-style": "npm:^1.4.4" + checksum: 10c0/0d001686f717601fd2a4609161e37235603bc2721fe1cc967247019f4d9bb583d6eafc56940a7d95d252ae2064a663f552d36b7fd15528ce207c3cd748f820ec languageName: node linkType: hard -"@endo/lockdown@npm:^1.0.10": - version: 1.0.10 - resolution: "@endo/lockdown@npm:1.0.10" +"@endo/init@npm:^1.1.4, @endo/init@npm:^1.1.5": + version: 1.1.5 + resolution: "@endo/init@npm:1.1.5" + dependencies: + "@endo/base64": "npm:^1.0.8" + "@endo/eventual-send": "npm:^1.2.6" + "@endo/lockdown": "npm:^1.0.11" + "@endo/promise-kit": "npm:^1.1.6" + checksum: 10c0/08abda8a0204450cb39c296270d074189f320cdeb03892e87b27a75f6b98c0b5d9c8471e242c53545843211fe713b01b281eec0eabc1c58ca0760a068b90335c + languageName: node + linkType: hard + +"@endo/lockdown@npm:^1.0.11": + version: 1.0.11 + resolution: "@endo/lockdown@npm:1.0.11" dependencies: - ses: "npm:^1.8.0" - checksum: 10c0/94be0c1b14cacb2d8088dcc17998e901159a028c51170d78a8cc6a820ae76cabc7d2694f1a1956cb4eab70a8c9a0c8254d88ea4c3f3d9725b739aacf6db83d5b + ses: "npm:^1.9.0" + checksum: 10c0/03bb96f370e7ee69d9d8e26b7b124b6381994d3b28a99dc2bcafe77139283701d7543b40e978226cd49443c149e64610e4dd49c32ada931610ba091809478a11 languageName: node linkType: hard -"@endo/marshal@npm:^1.3.0, @endo/marshal@npm:^1.5.3": - version: 1.5.3 - resolution: "@endo/marshal@npm:1.5.3" +"@endo/marshal@npm:^1.5.4": + version: 1.5.4 + resolution: "@endo/marshal@npm:1.5.4" dependencies: - "@endo/common": "npm:^1.2.5" - "@endo/errors": "npm:^1.2.5" - "@endo/eventual-send": "npm:^1.2.5" - "@endo/nat": "npm:^5.0.10" - "@endo/pass-style": "npm:^1.4.3" - "@endo/promise-kit": "npm:^1.1.5" - checksum: 10c0/05f4fceef7727971d3d2a1b8d87f4d9c6651772f9d231e2daa36c3ed0b0e13c3b8d26cb4828ecaadf4329bf77792a293507eadcff7a61df292d4e390936993d1 + "@endo/common": "npm:^1.2.6" + "@endo/errors": "npm:^1.2.6" + "@endo/eventual-send": "npm:^1.2.6" + "@endo/nat": "npm:^5.0.11" + "@endo/pass-style": "npm:^1.4.4" + "@endo/promise-kit": "npm:^1.1.6" + checksum: 10c0/4668a3678567cfbeefc3a47217912ca3f5d8bdb37e0d5d53339953b4ab83950683505f45c0f5c30b415c989bb9df4fa0859849d23b11f5b1064b0da0a13943ab languageName: node linkType: hard -"@endo/nat@npm:^5.0.10": - version: 5.0.10 - resolution: "@endo/nat@npm:5.0.10" - checksum: 10c0/7ad2aa2d216d517409c771aebb465aceb6ea8b88ec808c2dc030d7ffc7fe7d601d8401572f3866384a63ff2aa74209a22f29e1561e773d91d7ad2d81fa13fc7e +"@endo/nat@npm:^5.0.11": + version: 5.0.11 + resolution: "@endo/nat@npm:5.0.11" + checksum: 10c0/50cd9033657dd35288f0333a966984f788213f1c836e6772c0d731361d5854d0ce24b8a7d6f351d56618eb40f6b369e130c1ad939c83d5833246c09119b2e777 languageName: node linkType: hard -"@endo/pass-style@npm:^1.2.0, @endo/pass-style@npm:^1.4.3": - version: 1.4.3 - resolution: "@endo/pass-style@npm:1.4.3" +"@endo/pass-style@npm:^1.4.4": + version: 1.4.4 + resolution: "@endo/pass-style@npm:1.4.4" dependencies: - "@endo/env-options": "npm:^1.1.6" - "@endo/errors": "npm:^1.2.5" - "@endo/eventual-send": "npm:^1.2.5" - "@endo/promise-kit": "npm:^1.1.5" + "@endo/env-options": "npm:^1.1.7" + "@endo/errors": "npm:^1.2.6" + "@endo/eventual-send": "npm:^1.2.6" + "@endo/promise-kit": "npm:^1.1.6" "@fast-check/ava": "npm:^1.1.5" - checksum: 10c0/f24c528b1219f5aa122f9a04e80459dec3e9664e7849019b172ad8354870b849b643c8dfc79104857827457d66b2bb09bade9b2c6ea717a97e613ecf6d53c1f9 + checksum: 10c0/d6c6268b269d4c14541087ce6b2975f9e31847893d01360c0ea92392ae93316d94fbf59cd7299853a0fb5214176ad7ffc4616b9b2581fd720bdb55ef96d0beeb languageName: node linkType: hard -"@endo/patterns@npm:^1.2.0, @endo/patterns@npm:^1.4.3": - version: 1.4.3 - resolution: "@endo/patterns@npm:1.4.3" +"@endo/patterns@npm:^1.4.4": + version: 1.4.4 + resolution: "@endo/patterns@npm:1.4.4" dependencies: - "@endo/common": "npm:^1.2.5" - "@endo/errors": "npm:^1.2.5" - "@endo/eventual-send": "npm:^1.2.5" - "@endo/marshal": "npm:^1.5.3" - "@endo/promise-kit": "npm:^1.1.5" - checksum: 10c0/10aabc6459d1b5d26e8946ab1b88db23eda80231aa6a0b6c9835568eee1daf745d23c29fa7f202bf11859a7ae77d5f51071c3d863d34e259a62c382ec797bb03 + "@endo/common": "npm:^1.2.6" + "@endo/errors": "npm:^1.2.6" + "@endo/eventual-send": "npm:^1.2.6" + "@endo/marshal": "npm:^1.5.4" + "@endo/promise-kit": "npm:^1.1.6" + checksum: 10c0/3e4c43b916134201b98cb3b17480f01aa15c2ce7a447b574f6ad8b8d1796fd929f2a21e7c541c50ad83afab51044cf03719bef9e48b455a0bec89d940287487b languageName: node linkType: hard -"@endo/promise-kit@npm:^1.0.4, @endo/promise-kit@npm:^1.1.5": - version: 1.1.5 - resolution: "@endo/promise-kit@npm:1.1.5" +"@endo/promise-kit@npm:^1.1.6": + version: 1.1.6 + resolution: "@endo/promise-kit@npm:1.1.6" dependencies: - ses: "npm:^1.8.0" - checksum: 10c0/3a9fb59546507dbbb8c83ada4de664ca4f6085ffcb56c9e3e07789e002e717454b1ee5ae1273549935a7e77ac42be7ae8ddca94ff6d4f16914210d31159ce1a4 + ses: "npm:^1.9.0" + checksum: 10c0/d60de663e58f9de32b6705268c62c63c4721f1874813d3c10cae7846e921e4ea8a60c8622ef8c937741a3387fbc60110076b25304afedf270727af7b0c3fecb3 languageName: node linkType: hard -"@endo/stream@npm:^1.1.0": - version: 1.2.5 - resolution: "@endo/stream@npm:1.2.5" +"@endo/stream@npm:^1.2.6": + version: 1.2.6 + resolution: "@endo/stream@npm:1.2.6" dependencies: - "@endo/eventual-send": "npm:^1.2.5" - "@endo/promise-kit": "npm:^1.1.5" - ses: "npm:^1.8.0" - checksum: 10c0/625fd9b8b485149c269a01673b76b33fadd0702d76eb37f136c9f7558252e3d51cc9602b2880f1b43971a00f41e5d3e3d2b3a6ebef6f0253bb314d9a144a2cf5 + "@endo/eventual-send": "npm:^1.2.6" + "@endo/promise-kit": "npm:^1.1.6" + ses: "npm:^1.9.0" + checksum: 10c0/76b686aae1614c23aff63ba1b5d5c57a25d331aba98becf4c02ca374e76cb13d455112841d84b1c6274e518f6d855cbcb8556efcb0ac09f8ccec2aa8c393f743 languageName: node linkType: hard "@endo/zip@npm:^1.0.7": - version: 1.0.7 - resolution: "@endo/zip@npm:1.0.7" - checksum: 10c0/a1c0d155448ce877012b34c8fe8cd3a58de9eb807514c81cddeebb802ee8e552b27d8a9a40fab3f3e4c49e0cb7fea6902fa1dd12a23ff6f30b56161fc3edc1f8 + version: 1.0.8 + resolution: "@endo/zip@npm:1.0.8" + checksum: 10c0/bb74862121932cd27eef59326325c4b61671ce0962033a2ad18e6d6978a9e94bfe604dbfa8c0b039a38fa7eefc495e6a69c583c0e75929cd267979b2c65b775b languageName: node linkType: hard @@ -476,24 +554,24 @@ __metadata: linkType: hard "@npmcli/agent@npm:^2.0.0": - version: 2.2.0 - resolution: "@npmcli/agent@npm:2.2.0" + version: 2.2.2 + resolution: "@npmcli/agent@npm:2.2.2" dependencies: agent-base: "npm:^7.1.0" http-proxy-agent: "npm:^7.0.0" https-proxy-agent: "npm:^7.0.1" lru-cache: "npm:^10.0.1" - socks-proxy-agent: "npm:^8.0.1" - checksum: 10c0/7b89590598476dda88e79c473766b67c682aae6e0ab0213491daa6083dcc0c171f86b3868f5506f22c09aa5ea69ad7dfb78f4bf39a8dca375d89a42f408645b3 + socks-proxy-agent: "npm:^8.0.3" + checksum: 10c0/325e0db7b287d4154ecd164c0815c08007abfb07653cc57bceded17bb7fd240998a3cbdbe87d700e30bef494885eccc725ab73b668020811d56623d145b524ae languageName: node linkType: hard "@npmcli/fs@npm:^3.1.0": - version: 3.1.0 - resolution: "@npmcli/fs@npm:3.1.0" + version: 3.1.1 + resolution: "@npmcli/fs@npm:3.1.1" dependencies: semver: "npm:^7.3.5" - checksum: 10c0/162b4a0b8705cd6f5c2470b851d1dc6cd228c86d2170e1769d738c1fbb69a87160901411c3c035331e9e99db72f1f1099a8b734bf1637cc32b9a5be1660e4e1e + checksum: 10c0/c37a5b4842bfdece3d14dfdb054f73fe15ed2d3da61b34ff76629fb5b1731647c49166fd2a8bf8b56fcfa51200382385ea8909a3cbecdad612310c114d3f6c99 languageName: node linkType: hard @@ -536,8 +614,8 @@ __metadata: linkType: hard "@vercel/nft@npm:^0.26.2": - version: 0.26.4 - resolution: "@vercel/nft@npm:0.26.4" + version: 0.26.5 + resolution: "@vercel/nft@npm:0.26.5" dependencies: "@mapbox/node-pre-gyp": "npm:^1.0.5" "@rollup/pluginutils": "npm:^4.0.0" @@ -553,7 +631,7 @@ __metadata: resolve-from: "npm:^5.0.0" bin: nft: out/cli.js - checksum: 10c0/d347fcd7f5371a83362732d0b1b80b9471a2ed3917d6324cc6037392099d6bdc8eae69f0db61bafc87ba2d62af03ef21efe62a7eb52c8eb20341ebcb58903f0d + checksum: 10c0/b7034b2f851384f26316c856a731c0973a99bd02f6bb349916a750328a4919944ed6fd12c321b38ec6535d29dfb627d7fa8ab0f1e8c1c3cabd71e3350bd77548 languageName: node linkType: hard @@ -581,18 +659,20 @@ __metadata: linkType: hard "acorn-walk@npm:^8.3.2": - version: 8.3.2 - resolution: "acorn-walk@npm:8.3.2" - checksum: 10c0/7e2a8dad5480df7f872569b9dccff2f3da7e65f5353686b1d6032ab9f4ddf6e3a2cb83a9b52cf50b1497fd522154dda92f0abf7153290cc79cd14721ff121e52 + version: 8.3.4 + resolution: "acorn-walk@npm:8.3.4" + dependencies: + acorn: "npm:^8.11.0" + checksum: 10c0/76537ac5fb2c37a64560feaf3342023dadc086c46da57da363e64c6148dc21b57d49ace26f949e225063acb6fb441eabffd89f7a3066de5ad37ab3e328927c62 languageName: node linkType: hard -"acorn@npm:^8.11.3, acorn@npm:^8.6.0": - version: 8.11.3 - resolution: "acorn@npm:8.11.3" +"acorn@npm:^8.11.0, acorn@npm:^8.11.3, acorn@npm:^8.6.0": + version: 8.13.0 + resolution: "acorn@npm:8.13.0" bin: acorn: bin/acorn - checksum: 10c0/3ff155f8812e4a746fee8ecff1f227d527c4c45655bb1fad6347c3cb58e46190598217551b1500f18542d2bbe5c87120cb6927f5a074a59166fbdd9468f0a299 + checksum: 10c0/f35dd53d68177c90699f4c37d0bb205b8abe036d955d0eb011ddb7f14a81e6fd0f18893731c457c1b5bd96754683f4c3d80d9a5585ddecaa53cdf84e0b3d68f7 languageName: node linkType: hard @@ -605,12 +685,12 @@ __metadata: languageName: node linkType: hard -"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0": - version: 7.1.0 - resolution: "agent-base@npm:7.1.0" +"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1": + version: 7.1.1 + resolution: "agent-base@npm:7.1.1" dependencies: debug: "npm:^4.3.4" - checksum: 10c0/fc974ab57ffdd8421a2bc339644d312a9cca320c20c3393c9d8b1fd91731b9bbabdb985df5fc860f5b79d81c3e350daa3fcb31c5c07c0bb385aafc817df004ce + checksum: 10c0/e59ce7bed9c63bf071a30cc471f2933862044c97fd9958967bfe22521d7a0f601ce4ed5a8c011799d0c726ca70312142ae193bbebb60f576b52be19d4a363b50 languageName: node linkType: hard @@ -632,9 +712,9 @@ __metadata: linkType: hard "ansi-regex@npm:^6.0.1": - version: 6.0.1 - resolution: "ansi-regex@npm:6.0.1" - checksum: 10c0/cbe16dbd2c6b2735d1df7976a7070dd277326434f0212f43abf6d87674095d247968209babdaad31bb00882fa68807256ba9be340eec2f1004de14ca75f52a08 + version: 6.1.0 + resolution: "ansi-regex@npm:6.1.0" + checksum: 10c0/a91daeddd54746338478eef88af3439a7edf30f8e23196e2d6ed182da9add559c601266dbef01c2efa46a958ad6f1f8b176799657616c702b5b02e799e7fd8dc languageName: node linkType: hard @@ -716,8 +796,8 @@ __metadata: linkType: hard "ava@npm:^6.1.2": - version: 6.1.2 - resolution: "ava@npm:6.1.2" + version: 6.1.3 + resolution: "ava@npm:6.1.3" dependencies: "@vercel/nft": "npm:^0.26.2" acorn: "npm:^8.11.3" @@ -766,7 +846,7 @@ __metadata: optional: true bin: ava: entrypoints/cli.mjs - checksum: 10c0/f35cb1f9bc716714e7c78a601985745774096e4ecd34f9310b858d5779307afa2245ad24274e2d55dd1022c226f4fbdb41947476300977f0b653be0e627adaa4 + checksum: 10c0/108b28aceb0dfdb077bcf4c96109d736667999c2ce9f564489c4747482ea0e0c455d4d96fdfaad610b1125b74129b7b1d2ce570ec4903e767f6950d7f722f2cd languageName: node linkType: hard @@ -841,12 +921,12 @@ __metadata: languageName: node linkType: hard -"braces@npm:^3.0.2": - version: 3.0.2 - resolution: "braces@npm:3.0.2" +"braces@npm:^3.0.3": + version: 3.0.3 + resolution: "braces@npm:3.0.3" dependencies: - fill-range: "npm:^7.0.1" - checksum: 10c0/321b4d675791479293264019156ca322163f02dc06e3c4cab33bb15cd43d80b51efef69b0930cfde3acd63d126ebca24cd0544fa6f261e093a0fb41ab9dda381 + fill-range: "npm:^7.1.1" + checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 languageName: node linkType: hard @@ -861,8 +941,8 @@ __metadata: linkType: hard "cacache@npm:^18.0.0": - version: 18.0.1 - resolution: "cacache@npm:18.0.1" + version: 18.0.4 + resolution: "cacache@npm:18.0.4" dependencies: "@npmcli/fs": "npm:^3.1.0" fs-minipass: "npm:^3.0.0" @@ -876,14 +956,14 @@ __metadata: ssri: "npm:^10.0.0" tar: "npm:^6.1.11" unique-filename: "npm:^3.0.0" - checksum: 10c0/a31666805a80a8b16ad3f85faf66750275a9175a3480896f4f6d31b5d53ef190484fabd71bdb6d2ea5603c717fbef09f4af03d6a65b525c8ef0afaa44c361866 + checksum: 10c0/6c055bafed9de4f3dcc64ac3dc7dd24e863210902b7c470eb9ce55a806309b3efff78033e3d8b4f7dcc5d467f2db43c6a2857aaaf26f0094b8a351d44c42179f languageName: node linkType: hard "callsites@npm:^4.1.0": - version: 4.1.0 - resolution: "callsites@npm:4.1.0" - checksum: 10c0/91700844127a6dcd4792d231a12dd8e9ec10525eb9962180a8558417d7e3f443e52a4f14746ad2838eaf14f79431ee1539d13bd188da280f720a06a91bd1157a + version: 4.2.0 + resolution: "callsites@npm:4.2.0" + checksum: 10c0/8f7e269ec09fc0946bb22d838a8bc7932e1909ab4a833b964749f4d0e8bdeaa1f253287c4f911f61781f09620b6925ccd19a5ea4897489c4e59442c660c312a3 languageName: node linkType: hard @@ -1081,14 +1161,14 @@ __metadata: linkType: hard "debug@npm:4, debug@npm:^4.3.4": - version: 4.3.4 - resolution: "debug@npm:4.3.4" + version: 4.3.7 + resolution: "debug@npm:4.3.7" dependencies: - ms: "npm:2.1.2" + ms: "npm:^2.1.3" peerDependenciesMeta: supports-color: optional: true - checksum: 10c0/cedbec45298dd5c501d01b92b119cd3faebe5438c3917ff11ae1bff86a6c722930ac9c8659792824013168ba6db7c4668225d845c633fbdafbbf902a6389f736 + checksum: 10c0/1471db19c3b06d485a622d62f65947a19a23fbd0dd73f7fd3eafb697eec5360cde447fb075919987899b1a2096e85d35d4eb5a4de09a57600ac9cf7e6c8e768b languageName: node linkType: hard @@ -1116,9 +1196,9 @@ __metadata: linkType: hard "detect-libc@npm:^2.0.0": - version: 2.0.2 - resolution: "detect-libc@npm:2.0.2" - checksum: 10c0/a9f4ffcd2701525c589617d98afe5a5d0676c8ea82bcc4ed6f3747241b79f781d36437c59a5e855254c864d36a3e9f8276568b6b531c28d6e53b093a15703f11 + version: 2.0.3 + resolution: "detect-libc@npm:2.0.3" + checksum: 10c0/88095bda8f90220c95f162bf92cad70bd0e424913e655c20578600e35b91edc261af27531cf160a331e185c0ced93944bc7e09939143225f56312d7fd800fdb7 languageName: node linkType: hard @@ -1130,16 +1210,16 @@ __metadata: linkType: hard "emittery@npm:^1.0.1": - version: 1.0.1 - resolution: "emittery@npm:1.0.1" - checksum: 10c0/2587f2f42bb5e004ba1cde61352d2151f4dd4f29eb79ad36f82e200da2faec9742d7bfca1492a024d60396e001e4b07d9b2b9c43be33547ff751ba8ff87c42ce + version: 1.0.3 + resolution: "emittery@npm:1.0.3" + checksum: 10c0/91605d044f3891dd1f8ab731aeb94b520488b21e707f7064dcbcf5303bac3b4e7133dfa23c343ede1fc970340bd78a9b1aed522b805bc15104606bba630dd71e languageName: node linkType: hard "emoji-regex@npm:^10.3.0": - version: 10.3.0 - resolution: "emoji-regex@npm:10.3.0" - checksum: 10c0/b4838e8dcdceb44cf47f59abe352c25ff4fe7857acaf5fb51097c427f6f75b44d052eb907a7a3b86f86bc4eae3a93f5c2b7460abe79c407307e6212d65c91163 + version: 10.4.0 + resolution: "emoji-regex@npm:10.4.0" + checksum: 10c0/a3fcedfc58bfcce21a05a5f36a529d81e88d602100145fcca3dc6f795e3c8acc4fc18fe773fbf9b6d6e9371205edb3afa2668ec3473fa2aa7fd47d2a9d46482d languageName: node linkType: hard @@ -1273,9 +1353,9 @@ __metadata: linkType: hard "escalade@npm:^3.1.1": - version: 3.1.1 - resolution: "escalade@npm:3.1.1" - checksum: 10c0/afd02e6ca91ffa813e1108b5e7756566173d6bc0d1eb951cb44d6b21702ec17c1cf116cfe75d4a2b02e05acb0b808a7a9387d0d1ca5cf9c04ad03a8445c3e46d + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 languageName: node linkType: hard @@ -1318,8 +1398,8 @@ __metadata: linkType: hard "execa@npm:^9.3.1": - version: 9.3.1 - resolution: "execa@npm:9.3.1" + version: 9.4.1 + resolution: "execa@npm:9.4.1" dependencies: "@sindresorhus/merge-streams": "npm:^4.0.0" cross-spawn: "npm:^7.0.3" @@ -1328,12 +1408,12 @@ __metadata: human-signals: "npm:^8.0.0" is-plain-obj: "npm:^4.1.0" is-stream: "npm:^4.0.1" - npm-run-path: "npm:^5.2.0" + npm-run-path: "npm:^6.0.0" pretty-ms: "npm:^9.0.0" signal-exit: "npm:^4.1.0" strip-final-newline: "npm:^4.0.0" yoctocolors: "npm:^2.0.0" - checksum: 10c0/113979ff56575f6cb69fd021eb3894a674fb59b264f5e8c2b9b30e301629abc4f44cee881e680f9fb3b7d4956645df76a2d8c0006869dea985f96ec65f07b226 + checksum: 10c0/2166de02c8c940312481e480ef47f54636725b24a402d25bdbaeca97f6eeb82b21def1279e00378872fbe6a0c675d6b5cd144be4d52c1485a7a3895e611ac03e languageName: node linkType: hard @@ -1381,11 +1461,11 @@ __metadata: linkType: hard "fastq@npm:^1.6.0": - version: 1.15.0 - resolution: "fastq@npm:1.15.0" + version: 1.17.1 + resolution: "fastq@npm:1.17.1" dependencies: reusify: "npm:^1.0.4" - checksum: 10c0/5ce4f83afa5f88c9379e67906b4d31bc7694a30826d6cc8d0f0473c966929017fda65c2174b0ec89f064ede6ace6c67f8a4fe04cef42119b6a55b0d465554c24 + checksum: 10c0/1095f16cea45fb3beff558bb3afa74ca7a9250f5a670b65db7ed585f92b4b48381445cd328b3d87323da81e43232b5d5978a8201bde84e0cd514310f1ea6da34 languageName: node linkType: hard @@ -1405,12 +1485,12 @@ __metadata: languageName: node linkType: hard -"fill-range@npm:^7.0.1": - version: 7.0.1 - resolution: "fill-range@npm:7.0.1" +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" dependencies: to-regex-range: "npm:^5.0.1" - checksum: 10c0/7cdad7d426ffbaadf45aeb5d15ec675bbd77f7597ad5399e3d2766987ed20bda24d5fac64b3ee79d93276f5865608bb22344a26b9b1ae6c4d00bd94bf611623f + checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 languageName: node linkType: hard @@ -1422,12 +1502,12 @@ __metadata: linkType: hard "foreground-child@npm:^3.1.0": - version: 3.1.1 - resolution: "foreground-child@npm:3.1.1" + version: 3.3.0 + resolution: "foreground-child@npm:3.3.0" dependencies: cross-spawn: "npm:^7.0.0" signal-exit: "npm:^4.0.1" - checksum: 10c0/9700a0285628abaeb37007c9a4d92bd49f67210f09067638774338e146c8e9c825c5c877f072b2f75f41dc6a2d0be8664f79ffc03f6576649f54a84fb9b47de0 + checksum: 10c0/028f1d41000553fcfa6c4bb5c372963bf3d9bf0b1f25a87d1a6253014343fb69dfb1b42d9625d7cf44c8ba429940f3d0ff718b62105d4d4a4f6ef8ca0a53faa2 languageName: node linkType: hard @@ -1507,9 +1587,9 @@ __metadata: linkType: hard "get-east-asian-width@npm:^1.0.0": - version: 1.2.0 - resolution: "get-east-asian-width@npm:1.2.0" - checksum: 10c0/914b1e217cf38436c24b4c60b4c45289e39a45bf9e65ef9fd343c2815a1a02b8a0215aeec8bf9c07c516089004b6e3826332481f40a09529fcadbf6e579f286b + version: 1.3.0 + resolution: "get-east-asian-width@npm:1.3.0" + checksum: 10c0/1a049ba697e0f9a4d5514c4623781c5246982bdb61082da6b5ae6c33d838e52ce6726407df285cdbb27ec1908b333cf2820989bd3e986e37bb20979437fdf34b languageName: node linkType: hard @@ -1524,11 +1604,11 @@ __metadata: linkType: hard "get-tsconfig@npm:^4.7.5": - version: 4.7.6 - resolution: "get-tsconfig@npm:4.7.6" + version: 4.8.1 + resolution: "get-tsconfig@npm:4.8.1" dependencies: resolve-pkg-maps: "npm:^1.0.0" - checksum: 10c0/2240e1b13e996dfbb947d177f422f83d09d1f93c9ce16959ebb3c2bdf8bdf4f04f98eba043859172da1685f9c7071091f0acfa964ebbe4780394d83b7dc3f58a + checksum: 10c0/536ee85d202f604f4b5fb6be81bcd6e6d9a96846811e83e9acc6de4a04fb49506edea0e1b8cf1d5ee7af33e469916ec2809d4c5445ab8ae015a7a51fbd1572f9 languageName: node linkType: hard @@ -1549,17 +1629,18 @@ __metadata: linkType: hard "glob@npm:^10.2.2, glob@npm:^10.3.10": - version: 10.3.10 - resolution: "glob@npm:10.3.10" + version: 10.4.5 + resolution: "glob@npm:10.4.5" dependencies: foreground-child: "npm:^3.1.0" - jackspeak: "npm:^2.3.5" - minimatch: "npm:^9.0.1" - minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - path-scurry: "npm:^1.10.1" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^1.11.1" bin: glob: dist/esm/bin.mjs - checksum: 10c0/13d8a1feb7eac7945f8c8480e11cd4a44b24d26503d99a8d8ac8d5aefbf3e9802a2b6087318a829fad04cb4e829f25c5f4f1110c68966c498720dd261c7e344d + checksum: 10c0/19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e languageName: node linkType: hard @@ -1578,8 +1659,8 @@ __metadata: linkType: hard "globby@npm:^14.0.0": - version: 14.0.1 - resolution: "globby@npm:14.0.1" + version: 14.0.2 + resolution: "globby@npm:14.0.2" dependencies: "@sindresorhus/merge-streams": "npm:^2.1.0" fast-glob: "npm:^3.3.2" @@ -1587,7 +1668,7 @@ __metadata: path-type: "npm:^5.0.0" slash: "npm:^5.1.0" unicorn-magic: "npm:^0.1.0" - checksum: 10c0/749a6be91cf455c161ebb5c9130df3991cb9fd7568425db850a8279a6cf45acd031c5069395beb7aeb4dd606b64f0d6ff8116c93726178d8e6182fee58c2736d + checksum: 10c0/3f771cd683b8794db1e7ebc8b6b888d43496d93a82aad4e9d974620f578581210b6c5a6e75ea29573ed16a1345222fab6e9b877a8d1ed56eeb147e09f69c6f78 languageName: node linkType: hard @@ -1613,12 +1694,12 @@ __metadata: linkType: hard "http-proxy-agent@npm:^7.0.0": - version: 7.0.0 - resolution: "http-proxy-agent@npm:7.0.0" + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" dependencies: agent-base: "npm:^7.1.0" debug: "npm:^4.3.4" - checksum: 10c0/a11574ff39436cee3c7bc67f259444097b09474605846ddd8edf0bf4ad8644be8533db1aa463426e376865047d05dc22755e638632819317c0c2f1b2196657c8 + checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 languageName: node linkType: hard @@ -1633,12 +1714,12 @@ __metadata: linkType: hard "https-proxy-agent@npm:^7.0.1": - version: 7.0.2 - resolution: "https-proxy-agent@npm:7.0.2" + version: 7.0.5 + resolution: "https-proxy-agent@npm:7.0.5" dependencies: agent-base: "npm:^7.0.2" debug: "npm:4" - checksum: 10c0/7735eb90073db087e7e79312e3d97c8c04baf7ea7ca7b013382b6a45abbaa61b281041a98f4e13c8c80d88f843785bcc84ba189165b4b4087b1e3496ba656d77 + checksum: 10c0/2490e3acec397abeb88807db52cac59102d5ed758feee6df6112ab3ccd8325e8a1ce8bce6f4b66e5470eca102d31e425ace904242e4fa28dbe0c59c4bafa7b2c languageName: node linkType: hard @@ -1673,9 +1754,9 @@ __metadata: linkType: hard "ignore@npm:^5.2.4": - version: 5.3.0 - resolution: "ignore@npm:5.3.0" - checksum: 10c0/dc06bea5c23aae65d0725a957a0638b57e235ae4568dda51ca142053ed2c352de7e3bc93a69b2b32ac31966a1952e9a93c5ef2e2ab7c6b06aef9808f6b55b571 + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 languageName: node linkType: hard @@ -1724,10 +1805,13 @@ __metadata: languageName: node linkType: hard -"ip@npm:^2.0.0": - version: 2.0.0 - resolution: "ip@npm:2.0.0" - checksum: 10c0/8d186cc5585f57372847ae29b6eba258c68862055e18a75cc4933327232cb5c107f89800ce29715d542eef2c254fbb68b382e780a7414f9ee7caf60b7a473958 +"ip-address@npm:^9.0.5": + version: 9.0.5 + resolution: "ip-address@npm:9.0.5" + dependencies: + jsbn: "npm:1.1.0" + sprintf-js: "npm:^1.1.3" + checksum: 10c0/331cd07fafcb3b24100613e4b53e1a2b4feab11e671e655d46dc09ee233da5011284d09ca40c4ecbdfe1d0004f462958675c224a804259f2f78d2465a87824bc languageName: node linkType: hard @@ -1811,9 +1895,9 @@ __metadata: linkType: hard "is-unicode-supported@npm:^2.0.0": - version: 2.0.0 - resolution: "is-unicode-supported@npm:2.0.0" - checksum: 10c0/3013dfb8265fe9f9a0d1e9433fc4e766595631a8d85d60876c457b4bedc066768dab1477c553d02e2f626d88a4e019162706e04263c94d74994ef636a33b5f94 + version: 2.1.0 + resolution: "is-unicode-supported@npm:2.1.0" + checksum: 10c0/a0f53e9a7c1fdbcf2d2ef6e40d4736fdffff1c9f8944c75e15425118ff3610172c87bf7bc6c34d3903b04be59790bb2212ddbe21ee65b5a97030fc50370545a5 languageName: node linkType: hard @@ -1831,20 +1915,20 @@ __metadata: languageName: node linkType: hard -"jackspeak@npm:^2.3.5": - version: 2.3.6 - resolution: "jackspeak@npm:2.3.6" +"jackspeak@npm:^3.1.2": + version: 3.4.3 + resolution: "jackspeak@npm:3.4.3" dependencies: "@isaacs/cliui": "npm:^8.0.2" "@pkgjs/parseargs": "npm:^0.11.0" dependenciesMeta: "@pkgjs/parseargs": optional: true - checksum: 10c0/f01d8f972d894cd7638bc338e9ef5ddb86f7b208ce177a36d718eac96ec86638a6efa17d0221b10073e64b45edc2ce15340db9380b1f5d5c5d000cbc517dc111 + checksum: 10c0/6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 languageName: node linkType: hard -"jessie.js@npm:^0.3.2": +"jessie.js@npm:^0.3.4": version: 0.3.4 resolution: "jessie.js@npm:0.3.4" dependencies: @@ -1872,6 +1956,13 @@ __metadata: languageName: node linkType: hard +"jsbn@npm:1.1.0": + version: 1.1.0 + resolution: "jsbn@npm:1.1.0" + checksum: 10c0/4f907fb78d7b712e11dea8c165fe0921f81a657d3443dde75359ed52eb2b5d33ce6773d97985a089f09a65edd80b11cb75c767b57ba47391fee4c969f7215c96 + languageName: node + linkType: hard + "load-json-file@npm:^7.0.1": version: 7.0.1 resolution: "load-json-file@npm:7.0.1" @@ -1886,19 +1977,10 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^10.0.1, lru-cache@npm:^9.1.1 || ^10.0.0": - version: 10.1.0 - resolution: "lru-cache@npm:10.1.0" - checksum: 10c0/778bc8b2626daccd75f24c4b4d10632496e21ba064b126f526c626fbdbc5b28c472013fccd45d7646b9e1ef052444824854aed617b59cd570d01a8b7d651fc1e - languageName: node - linkType: hard - -"lru-cache@npm:^6.0.0": - version: 6.0.0 - resolution: "lru-cache@npm:6.0.0" - dependencies: - yallist: "npm:^4.0.0" - checksum: 10c0/cb53e582785c48187d7a188d3379c181b5ca2a9c78d2bce3e7dee36f32761d1c42983da3fe12b55cb74e1779fa94cdc2e5367c028a9b35317184ede0c07a30a9 +"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": + version: 10.4.3 + resolution: "lru-cache@npm:10.4.3" + checksum: 10c0/ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb languageName: node linkType: hard @@ -1912,8 +1994,8 @@ __metadata: linkType: hard "make-fetch-happen@npm:^13.0.0": - version: 13.0.0 - resolution: "make-fetch-happen@npm:13.0.0" + version: 13.0.1 + resolution: "make-fetch-happen@npm:13.0.1" dependencies: "@npmcli/agent": "npm:^2.0.0" cacache: "npm:^18.0.0" @@ -1924,9 +2006,10 @@ __metadata: minipass-flush: "npm:^1.0.5" minipass-pipeline: "npm:^1.2.4" negotiator: "npm:^0.6.3" + proc-log: "npm:^4.2.0" promise-retry: "npm:^2.0.1" ssri: "npm:^10.0.0" - checksum: 10c0/43b9f6dcbc6fe8b8604cb6396957c3698857a15ba4dbc38284f7f0e61f248300585ef1eb8cc62df54e9c724af977e45b5cdfd88320ef7f53e45070ed3488da55 + checksum: 10c0/df5f4dbb6d98153b751bccf4dc4cc500de85a96a9331db9805596c46aa9f99d9555983954e6c1266d9f981ae37a9e4647f42b9a4bb5466f867f4012e582c9e7e languageName: node linkType: hard @@ -1965,12 +2048,12 @@ __metadata: linkType: hard "micromatch@npm:^4.0.2, micromatch@npm:^4.0.4": - version: 4.0.5 - resolution: "micromatch@npm:4.0.5" + version: 4.0.8 + resolution: "micromatch@npm:4.0.8" dependencies: - braces: "npm:^3.0.2" + braces: "npm:^3.0.3" picomatch: "npm:^2.3.1" - checksum: 10c0/3d6505b20f9fa804af5d8c596cb1c5e475b9b0cd05f652c5b56141cf941bd72adaeb7a436fda344235cef93a7f29b7472efc779fcdb83b478eab0867b95cdeff + checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 languageName: node linkType: hard @@ -1997,12 +2080,12 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^9.0.1": - version: 9.0.3 - resolution: "minimatch@npm:9.0.3" +"minimatch@npm:^9.0.4": + version: 9.0.5 + resolution: "minimatch@npm:9.0.5" dependencies: brace-expansion: "npm:^2.0.1" - checksum: 10c0/85f407dcd38ac3e180f425e86553911d101455ca3ad5544d6a7cec16286657e4f8a9aa6695803025c55e31e35a91a2252b5dc8e7d527211278b8b65b4dbd5eac + checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed languageName: node linkType: hard @@ -2023,8 +2106,8 @@ __metadata: linkType: hard "minipass-fetch@npm:^3.0.0": - version: 3.0.4 - resolution: "minipass-fetch@npm:3.0.4" + version: 3.0.5 + resolution: "minipass-fetch@npm:3.0.5" dependencies: encoding: "npm:^0.1.13" minipass: "npm:^7.0.3" @@ -2033,7 +2116,7 @@ __metadata: dependenciesMeta: encoding: optional: true - checksum: 10c0/1b63c1f3313e88eeac4689f1b71c9f086598db9a189400e3ee960c32ed89e06737fa23976c9305c2d57464fb3fcdc12749d3378805c9d6176f5569b0d0ee8a75 + checksum: 10c0/9d702d57f556274286fdd97e406fc38a2f5c8d15e158b498d7393b1105974b21249289ec571fa2b51e038a4872bfc82710111cf75fae98c662f3d6f95e72152b languageName: node linkType: hard @@ -2080,10 +2163,10 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3": - version: 7.0.4 - resolution: "minipass@npm:7.0.4" - checksum: 10c0/6c7370a6dfd257bf18222da581ba89a5eaedca10e158781232a8b5542a90547540b4b9b7e7f490e4cda43acfbd12e086f0453728ecf8c19e0ef6921bc5958ac5 +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 languageName: node linkType: hard @@ -2113,13 +2196,6 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.2": - version: 2.1.2 - resolution: "ms@npm:2.1.2" - checksum: 10c0/a437714e2f90dbf881b5191d35a6db792efbca5badf112f87b9e1c712aace4b4b9b742dd6537f3edf90fd6f684de897cec230abde57e87883766712ddda297cc - languageName: node - linkType: hard - "ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" @@ -2135,18 +2211,18 @@ __metadata: linkType: hard "negotiator@npm:^0.6.3": - version: 0.6.3 - resolution: "negotiator@npm:0.6.3" - checksum: 10c0/3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2 + version: 0.6.4 + resolution: "negotiator@npm:0.6.4" + checksum: 10c0/3e677139c7fb7628a6f36335bf11a885a62c21d5390204590a1a214a5631fcbe5ea74ef6a610b60afe84b4d975cbe0566a23f20ee17c77c73e74b80032108dea languageName: node linkType: hard "node-abi@npm:^3.3.0": - version: 3.54.0 - resolution: "node-abi@npm:3.54.0" + version: 3.71.0 + resolution: "node-abi@npm:3.71.0" dependencies: semver: "npm:^7.3.5" - checksum: 10c0/9ebbb21e6951aa51e831549ed62b68dc56bcc10f6b21ffd04195a16a6abf5ddfc48b6ae5e3334720fe4459cafde5ec8103025902efff5599d0539f8656fc694e + checksum: 10c0/dbd0792ea729329cd9d099f28a5681ff9e8a6db48cf64e1437bf6a7fd669009d1e758a784619a1c4cc8bfd1ed17162f042c787654edf19a1f64b5018457c9c1f languageName: node linkType: hard @@ -2165,19 +2241,19 @@ __metadata: linkType: hard "node-gyp-build@npm:^4.2.2": - version: 4.8.0 - resolution: "node-gyp-build@npm:4.8.0" + version: 4.8.2 + resolution: "node-gyp-build@npm:4.8.2" bin: node-gyp-build: bin.js node-gyp-build-optional: optional.js node-gyp-build-test: build-test.js - checksum: 10c0/85324be16f81f0235cbbc42e3eceaeb1b5ab94c8d8f5236755e1435b4908338c65a4e75f66ee343cbcb44ddf9b52a428755bec16dcd983295be4458d95c8e1ad + checksum: 10c0/d816b43974d31d6257b6e87d843f2626c72389a285208394bc57a7766b210454d2642860a5e5b5c333d8ecabaeabad3b31b94f58cf8ca1aabdef0c320d02baaa languageName: node linkType: hard "node-gyp@npm:latest": - version: 10.0.1 - resolution: "node-gyp@npm:10.0.1" + version: 10.2.0 + resolution: "node-gyp@npm:10.2.0" dependencies: env-paths: "npm:^2.2.0" exponential-backoff: "npm:^3.1.1" @@ -2185,13 +2261,13 @@ __metadata: graceful-fs: "npm:^4.2.6" make-fetch-happen: "npm:^13.0.0" nopt: "npm:^7.0.0" - proc-log: "npm:^3.0.0" + proc-log: "npm:^4.1.0" semver: "npm:^7.3.5" - tar: "npm:^6.1.2" + tar: "npm:^6.2.1" which: "npm:^4.0.0" bin: node-gyp: bin/node-gyp.js - checksum: 10c0/abddfff7d873312e4ed4a5fb75ce893a5c4fb69e7fcb1dfa71c28a6b92a7f1ef6b62790dffb39181b5a82728ba8f2f32d229cf8cbe66769fe02cea7db4a555aa + checksum: 10c0/00630d67dbd09a45aee0a5d55c05e3916ca9e6d427ee4f7bc392d2d3dc5fad7449b21fc098dd38260a53d9dcc9c879b36704a1994235d4707e7271af7e9a835b languageName: node linkType: hard @@ -2214,22 +2290,23 @@ __metadata: linkType: hard "nopt@npm:^7.0.0": - version: 7.2.0 - resolution: "nopt@npm:7.2.0" + version: 7.2.1 + resolution: "nopt@npm:7.2.1" dependencies: abbrev: "npm:^2.0.0" bin: nopt: bin/nopt.js - checksum: 10c0/9bd7198df6f16eb29ff16892c77bcf7f0cc41f9fb5c26280ac0def2cf8cf319f3b821b3af83eba0e74c85807cc430a16efe0db58fe6ae1f41e69519f585b6aff + checksum: 10c0/a069c7c736767121242037a22a788863accfa932ab285a1eb569eb8cd534b09d17206f68c37f096ae785647435e0c5a5a0a67b42ec743e481a455e5ae6a6df81 languageName: node linkType: hard -"npm-run-path@npm:^5.2.0": - version: 5.3.0 - resolution: "npm-run-path@npm:5.3.0" +"npm-run-path@npm:^6.0.0": + version: 6.0.0 + resolution: "npm-run-path@npm:6.0.0" dependencies: path-key: "npm:^4.0.0" - checksum: 10c0/124df74820c40c2eb9a8612a254ea1d557ddfab1581c3e751f825e3e366d9f00b0d76a3c94ecd8398e7f3eee193018622677e95816e8491f0797b21e30b2deba + unicorn-magic: "npm:^0.3.0" + checksum: 10c0/b223c8a0dcd608abf95363ea5c3c0ccc3cd877daf0102eaf1b0f2390d6858d8337fbb7c443af2403b067a7d2c116d10691ecd22ab3c5273c44da1ff8d07753bd languageName: node linkType: hard @@ -2287,6 +2364,13 @@ __metadata: languageName: node linkType: hard +"package-json-from-dist@npm:^1.0.0": + version: 1.0.1 + resolution: "package-json-from-dist@npm:1.0.1" + checksum: 10c0/62ba2785eb655fec084a257af34dbe24292ab74516d6aecef97ef72d4897310bc6898f6c85b5cd22770eaa1ce60d55a0230e150fb6a966e3ecd6c511e23d164b + languageName: node + linkType: hard + "parse-ms@npm:^4.0.0": version: 4.0.0 resolution: "parse-ms@npm:4.0.0" @@ -2315,13 +2399,13 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.10.1": - version: 1.10.1 - resolution: "path-scurry@npm:1.10.1" +"path-scurry@npm:^1.11.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" dependencies: - lru-cache: "npm:^9.1.1 || ^10.0.0" + lru-cache: "npm:^10.2.0" minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" - checksum: 10c0/e5dc78a7348d25eec61ab166317e9e9c7b46818aa2c2b9006c507a6ff48c672d011292d9662527213e558f5652ce0afcc788663a061d8b59ab495681840c0c1e + checksum: 10c0/32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d languageName: node linkType: hard @@ -2356,8 +2440,8 @@ __metadata: linkType: hard "prebuild-install@npm:^7.1.1": - version: 7.1.1 - resolution: "prebuild-install@npm:7.1.1" + version: 7.1.2 + resolution: "prebuild-install@npm:7.1.2" dependencies: detect-libc: "npm:^2.0.0" expand-template: "npm:^2.0.3" @@ -2373,23 +2457,23 @@ __metadata: tunnel-agent: "npm:^0.6.0" bin: prebuild-install: bin.js - checksum: 10c0/6dc70f36b0f4adcb2fe0ed38d874ab28b571fb1a9725d769e8ba3f64a15831e58462de09f3e6e64569bcc4a3e03b9328b56faa0d45fe10ae1574478814536c76 + checksum: 10c0/e64868ba9ef2068fd7264f5b03e5298a901e02a450acdb1f56258d88c09dea601eefdb3d1dfdff8513fdd230a92961712be0676192626a3b4d01ba154d48bdd3 languageName: node linkType: hard "pretty-ms@npm:^9.0.0": - version: 9.0.0 - resolution: "pretty-ms@npm:9.0.0" + version: 9.1.0 + resolution: "pretty-ms@npm:9.1.0" dependencies: parse-ms: "npm:^4.0.0" - checksum: 10c0/ba4a2acd1fe92a1c629e5cdeb555d7fa344ae9920e20fa00e8ac1db61b8d3dff8638ffc70c7569f681e375df68c9f31291c2c1912cefd02ef1b1bdd0861a4aed + checksum: 10c0/fd111aad8800a04dfd654e6016da69bdaa6fc6a4c280f8e727cffd8b5960558e94942f1a94d4aa6e4d179561a0fbb0366a9ebe0ccefbbb0f8ff853b129cdefb9 languageName: node linkType: hard -"proc-log@npm:^3.0.0": - version: 3.0.0 - resolution: "proc-log@npm:3.0.0" - checksum: 10c0/f66430e4ff947dbb996058f6fd22de2c66612ae1a89b097744e17fb18a4e8e7a86db99eda52ccf15e53f00b63f4ec0b0911581ff2aac0355b625c8eac509b0dc +"proc-log@npm:^4.1.0, proc-log@npm:^4.2.0": + version: 4.2.0 + resolution: "proc-log@npm:4.2.0" + checksum: 10c0/17db4757c2a5c44c1e545170e6c70a26f7de58feb985091fb1763f5081cab3d01b181fb2dd240c9f4a4255a1d9227d163d5771b7e69c9e49a561692db865efb9 languageName: node linkType: hard @@ -2404,12 +2488,12 @@ __metadata: linkType: hard "pump@npm:^3.0.0": - version: 3.0.0 - resolution: "pump@npm:3.0.0" + version: 3.0.2 + resolution: "pump@npm:3.0.2" dependencies: end-of-stream: "npm:^1.1.0" once: "npm:^1.3.1" - checksum: 10c0/bbdeda4f747cdf47db97428f3a135728669e56a0ae5f354a9ac5b74556556f5446a46f720a8f14ca2ece5be9b4d5d23c346db02b555f46739934cc6c093a5478 + checksum: 10c0/5ad655cb2a7738b4bcf6406b24ad0970d680649d996b55ad20d1be8e0c02394034e4c45ff7cd105d87f1e9b96a0e3d06fd28e11fae8875da26e7f7a8e2c9726f languageName: node linkType: hard @@ -2511,7 +2595,8 @@ __metadata: version: 0.0.0-use.local resolution: "root-workspace-0b6124@workspace:." dependencies: - "@agoric/internal": "npm:0.3.3-dev-5676146.0" + "@agoric/ertp": "npm:dev" + "@agoric/internal": "npm:dev" "@agoric/synthetic-chain": "npm:^0.3.0" "@endo/errors": "npm:^1.2.2" "@endo/far": "npm:^1.1.5" @@ -2557,13 +2642,11 @@ __metadata: linkType: hard "semver@npm:^7.3.2, semver@npm:^7.3.5": - version: 7.5.4 - resolution: "semver@npm:7.5.4" - dependencies: - lru-cache: "npm:^6.0.0" + version: 7.6.3 + resolution: "semver@npm:7.6.3" bin: semver: bin/semver.js - checksum: 10c0/5160b06975a38b11c1ab55950cb5b8a23db78df88275d3d8a42ccf1f29e55112ac995b3a26a522c36e3b5f76b0445f1eef70d696b8c7862a2b4303d7b0e7609e + checksum: 10c0/88f33e148b210c153873cb08cfe1e281d518aaa9a666d4d148add6560db5cd3c582f3a08ccb91f38d5f379ead256da9931234ed122057f40bb5766e65e58adaf languageName: node linkType: hard @@ -2576,12 +2659,12 @@ __metadata: languageName: node linkType: hard -"ses@npm:^1.8.0": - version: 1.8.0 - resolution: "ses@npm:1.8.0" +"ses@npm:^1.9.0": + version: 1.9.0 + resolution: "ses@npm:1.9.0" dependencies: - "@endo/env-options": "npm:^1.1.6" - checksum: 10c0/4b2114e586a547dd2a71477e0a42e8ea5d0ea9c3ff135d0dbfb63569eeda19c7152db76b82bcad12a2969d3f5fb09e5fa52e921b5a2831560e6876ca1f9ba207 + "@endo/env-options": "npm:^1.1.7" + checksum: 10c0/356f9601b04a87f33403a15fc627caf0c649d86d8d7ee1f4b3c75b947ab05c31b254c94c0aa26e9904862787c73950d5a60f3435deebe5dba23017e20c40b0cb languageName: node linkType: hard @@ -2664,24 +2747,31 @@ __metadata: languageName: node linkType: hard -"socks-proxy-agent@npm:^8.0.1": - version: 8.0.2 - resolution: "socks-proxy-agent@npm:8.0.2" +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.4 + resolution: "socks-proxy-agent@npm:8.0.4" dependencies: - agent-base: "npm:^7.0.2" + agent-base: "npm:^7.1.1" debug: "npm:^4.3.4" - socks: "npm:^2.7.1" - checksum: 10c0/a842402fc9b8848a31367f2811ca3cd14c4106588b39a0901cd7a69029998adfc6456b0203617c18ed090542ad0c24ee4e9d4c75a0c4b75071e214227c177eb7 + socks: "npm:^2.8.3" + checksum: 10c0/345593bb21b95b0508e63e703c84da11549f0a2657d6b4e3ee3612c312cb3a907eac10e53b23ede3557c6601d63252103494caa306b66560f43af7b98f53957a languageName: node linkType: hard -"socks@npm:^2.7.1": - version: 2.7.1 - resolution: "socks@npm:2.7.1" +"socks@npm:^2.8.3": + version: 2.8.3 + resolution: "socks@npm:2.8.3" dependencies: - ip: "npm:^2.0.0" + ip-address: "npm:^9.0.5" smart-buffer: "npm:^4.2.0" - checksum: 10c0/43f69dbc9f34fc8220bc51c6eea1c39715ab3cfdb115d6e3285f6c7d1a603c5c75655668a5bbc11e3c7e2c99d60321fb8d7ab6f38cda6a215fadd0d6d0b52130 + checksum: 10c0/d54a52bf9325165770b674a67241143a3d8b4e4c8884560c4e0e078aace2a728dffc7f70150660f51b85797c4e1a3b82f9b7aa25e0a0ceae1a243365da5c51a7 + languageName: node + linkType: hard + +"sprintf-js@npm:^1.1.3": + version: 1.1.3 + resolution: "sprintf-js@npm:1.1.3" + checksum: 10c0/09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec languageName: node linkType: hard @@ -2693,11 +2783,11 @@ __metadata: linkType: hard "ssri@npm:^10.0.0": - version: 10.0.5 - resolution: "ssri@npm:10.0.5" + version: 10.0.6 + resolution: "ssri@npm:10.0.6" dependencies: minipass: "npm:^7.0.3" - checksum: 10c0/b091f2ae92474183c7ac5ed3f9811457e1df23df7a7e70c9476eaa9a0c4a0c8fc190fb45acefbf023ca9ee864dd6754237a697dc52a0fb182afe65d8e77443d8 + checksum: 10c0/e5a1e23a4057a86a97971465418f22ea89bd439ac36ade88812dd920e4e61873e8abd6a9b72a03a67ef50faa00a2daf1ab745c5a15b46d03e0544a0296354227 languageName: node linkType: hard @@ -2733,13 +2823,13 @@ __metadata: linkType: hard "string-width@npm:^7.0.0": - version: 7.1.0 - resolution: "string-width@npm:7.1.0" + version: 7.2.0 + resolution: "string-width@npm:7.2.0" dependencies: emoji-regex: "npm:^10.3.0" get-east-asian-width: "npm:^1.0.0" strip-ansi: "npm:^7.1.0" - checksum: 10c0/68a99fbc3bd3d8eb42886ff38dce819767dee55f606f74dfa4687a07dfd21262745d9683df0aa53bf81a5dd47c13da921a501925b974bec66a7ddd634fef0634 + checksum: 10c0/eb0430dd43f3199c7a46dcbf7a0b34539c76fe3aa62763d0b0655acdcbdf360b3f66f3d58ca25ba0205f42ea3491fa00f09426d3b7d3040e506878fc7664c9b9 languageName: node linkType: hard @@ -2821,9 +2911,9 @@ __metadata: languageName: node linkType: hard -"tar@npm:^6.1.11, tar@npm:^6.1.2": - version: 6.2.0 - resolution: "tar@npm:6.2.0" +"tar@npm:^6.1.11, tar@npm:^6.2.1": + version: 6.2.1 + resolution: "tar@npm:6.2.1" dependencies: chownr: "npm:^2.0.0" fs-minipass: "npm:^2.0.0" @@ -2831,7 +2921,7 @@ __metadata: minizlib: "npm:^2.1.1" mkdirp: "npm:^1.0.3" yallist: "npm:^4.0.0" - checksum: 10c0/02ca064a1a6b4521fef88c07d389ac0936730091f8c02d30ea60d472e0378768e870769ab9e986d87807bfee5654359cf29ff4372746cc65e30cbddc352660d8 + checksum: 10c0/a5eca3eb50bc11552d453488344e6507156b9193efd7635e98e867fab275d527af53d8866e2370cd09dfe74378a18111622ace35af6a608e5223a7d27fe99537 languageName: node linkType: hard @@ -2866,8 +2956,8 @@ __metadata: linkType: hard "tsx@npm:^4.17.0": - version: 4.17.0 - resolution: "tsx@npm:4.17.0" + version: 4.19.1 + resolution: "tsx@npm:4.19.1" dependencies: esbuild: "npm:~0.23.0" fsevents: "npm:~2.3.3" @@ -2877,7 +2967,7 @@ __metadata: optional: true bin: tsx: dist/cli.mjs - checksum: 10c0/ad720b81d6447c7695d24c27947fa1a2b6db9d2ef03216389edd6fa0006aa479bc0d8348a1ac9975a08edef4ce791ff5629a24d8dccbb0987f42e5407785cfa4 + checksum: 10c0/cbea9baf57e7406fa0ecc2c03b9bb2501ee740dc28c938f949180a646a28e5d65e7cccbfba340508923bfd45e90320ef9eef7f815cae4515b6ef2ee429edc7ee languageName: node linkType: hard @@ -2898,22 +2988,22 @@ __metadata: linkType: hard "typescript@npm:^5.5.4": - version: 5.5.4 - resolution: "typescript@npm:5.5.4" + version: 5.6.3 + resolution: "typescript@npm:5.6.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/422be60f89e661eab29ac488c974b6cc0a660fb2228003b297c3d10c32c90f3bcffc1009b43876a082515a3c376b1eefcce823d6e78982e6878408b9a923199c + checksum: 10c0/44f61d3fb15c35359bc60399cb8127c30bae554cd555b8e2b46d68fa79d680354b83320ad419ff1b81a0bdf324197b29affe6cc28988cd6a74d4ac60c94f9799 languageName: node linkType: hard "typescript@patch:typescript@npm%3A^5.5.4#optional!builtin": - version: 5.5.4 - resolution: "typescript@patch:typescript@npm%3A5.5.4#optional!builtin::version=5.5.4&hash=b45daf" + version: 5.6.3 + resolution: "typescript@patch:typescript@npm%3A5.6.3#optional!builtin::version=5.6.3&hash=b45daf" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10c0/10dd9881baba22763de859e8050d6cb6e2db854197495c6f1929b08d1eb2b2b00d0b5d9b0bcee8472f1c3f4a7ef6a5d7ebe0cfd703f853aa5ae465b8404bc1ba + checksum: 10c0/ac8307bb06bbfd08ae7137da740769b7d8c3ee5943188743bb622c621f8ad61d244767480f90fbd840277fbf152d8932aa20c33f867dea1bb5e79b187ca1a92f languageName: node linkType: hard @@ -2924,6 +3014,13 @@ __metadata: languageName: node linkType: hard +"unicorn-magic@npm:^0.3.0": + version: 0.3.0 + resolution: "unicorn-magic@npm:0.3.0" + checksum: 10c0/0a32a997d6c15f1c2a077a15b1c4ca6f268d574cf5b8975e778bb98e6f8db4ef4e86dfcae4e158cd4c7e38fb4dd383b93b13eefddc7f178dea13d3ac8a603271 + languageName: node + linkType: hard + "unique-filename@npm:^3.0.0": version: 3.0.0 resolution: "unique-filename@npm:3.0.0" From 501c138e79e16d7da2508ca21297b7d97bac22fb Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Mon, 28 Oct 2024 15:42:16 +0300 Subject: [PATCH 40/56] chore(sync-tools): improve types --- .../z:acceptance/test-lib/sync-tools.js | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/a3p-integration/proposals/z:acceptance/test-lib/sync-tools.js b/a3p-integration/proposals/z:acceptance/test-lib/sync-tools.js index c445ea8c957..dac2ba7e04f 100644 --- a/a3p-integration/proposals/z:acceptance/test-lib/sync-tools.js +++ b/a3p-integration/proposals/z:acceptance/test-lib/sync-tools.js @@ -16,12 +16,12 @@ /** * @typedef {object} RetryOptions - * @property {number} maxRetries - * @property {number} retryIntervalMs - * @property {(...arg0: string[]) => void} log - * @property {(object) => void} [setTimeout] - * @property {string} [errorMessage=Error] + * @property {number} [maxRetries] + * @property {number} [retryIntervalMs] + * @property {(...arg0: string[]) => void} [log] + * @property {(callback: Function, delay: number) => void} [setTimeout] * + * @typedef {RetryOptions & {errorMessage: string}} WaitUntilOptions * * @typedef {object} CosmosBalanceThreshold * @property {string} denom @@ -54,7 +54,7 @@ export const retryUntilCondition = async ( operation, condition, message, - { maxRetries = 6, retryIntervalMs = 3500, log, setTimeout }, + { maxRetries = 6, retryIntervalMs = 3500, log = console.log, setTimeout }, ) => { console.log({ maxRetries, retryIntervalMs, message }); let retries = 0; @@ -85,7 +85,7 @@ export const retryUntilCondition = async ( }; /** - * @param {RetryOptions} options + * @param {WaitUntilOptions} options */ const overrideDefaultOptions = options => { const defaultValues = { @@ -113,7 +113,7 @@ const makeGetInstances = follow => async () => { * * @param {string} contractName * @param {{follow: () => object, setTimeout: (object) => void}} ambientAuthority - * @param {RetryOptions} options + * @param {WaitUntilOptions} options */ export const waitUntilContractDeployed = ( contractName, @@ -155,7 +155,7 @@ const checkCosmosBalance = (balances, threshold) => { * @param {string} destAcct * @param {{query: () => Promise, setTimeout: (object) => void}} ambientAuthority * @param {{denom: string, value: number}} threshold - * @param {RetryOptions} options + * @param {WaitUntilOptions} options */ export const waitUntilAccountFunded = ( destAcct, @@ -197,7 +197,7 @@ const checkOfferState = (offerStatus, waitForPayouts, offerId) => { if (!status) return false; if (status.id !== offerId) return false; if (!status.numWantsSatisfied || status.numWantsSatisfied !== 1) return false; - if (waitForPayouts && status.result && status.payouts) return true; + if (waitForPayouts && status.payouts) return true; if (!waitForPayouts && status.result) return true; return false; @@ -208,8 +208,8 @@ const checkOfferState = (offerStatus, waitForPayouts, offerId) => { * @param {string} addr * @param {string} offerId * @param {boolean} waitForPayouts - * @param {{follow: () => object, setTimeout: (object) => void}} ambientAuthority - * @param {RetryOptions} options + * @param {{follow: () => object, setTimeout: (callback: Function, delay: number) => void}} ambientAuthority + * @param {WaitUntilOptions} options */ export const waitUntilOfferResult = ( addr, @@ -251,7 +251,7 @@ const checkForInvitation = update => { * * @param {string} addr * @param {{follow: () => object, setTimeout: (object) => void}} ambientAuthority - * @param {RetryOptions} options + * @param {WaitUntilOptions} options */ export const waitUntilInvitationReceived = ( addr, From 11b336bb2c2fe58adc25a7a779ec6996d23e7d10 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Mon, 28 Oct 2024 16:02:03 +0300 Subject: [PATCH 41/56] chore(psm-acceptance): address change requests and apply sync tools Refs: https://github.com/Agoric/BytePitchPartnerEng/issues/23 Refs: https://github.com/Agoric/BytePitchPartnerEng/issues/10 --- .../proposals/z:acceptance/psm.test.js | 147 +++--- .../proposals/z:acceptance/test-lib/errors.js | 20 + .../z:acceptance/test-lib/psm-lib.js | 198 +++++-- .../proposals/z:acceptance/yarn.lock | 482 +++++++++--------- 4 files changed, 498 insertions(+), 349 deletions(-) create mode 100644 a3p-integration/proposals/z:acceptance/test-lib/errors.js diff --git a/a3p-integration/proposals/z:acceptance/psm.test.js b/a3p-integration/proposals/z:acceptance/psm.test.js index 8a5504a659a..bd64b3c5229 100644 --- a/a3p-integration/proposals/z:acceptance/psm.test.js +++ b/a3p-integration/proposals/z:acceptance/psm.test.js @@ -14,43 +14,48 @@ import test from 'ava'; import { + agd, + agoric, getUser, GOV1ADDR, GOV2ADDR, - GOV3ADDR, - waitForBlock, } from '@agoric/synthetic-chain'; import { adjustBalancesIfNotProvisioned, - agopsPsm, + psmSwap, bankSend, checkGovParams, checkSwapExceedMintLimit, checkSwapSucceeded, - checkUserInitializedSuccessfully, getPsmMetrics, implementPsmGovParamChange, initializeNewUser, maxMintBelowLimit, } from './test-lib/psm-lib.js'; import { getBalances } from './test-lib/utils.js'; +import { NonNullish } from './test-lib/errors.js'; +import { waitUntilAccountFunded } from './test-lib/sync-tools.js'; // Export these from synthetic-chain? -export const USDC_DENOM = process.env.USDC_DENOM - ? process.env.USDC_DENOM - : 'no-denom'; -export const PSM_PAIR = process.env.PSM_PAIR?.replace('.', '-'); +const USDC_DENOM = NonNullish(process.env.USDC_DENOM); +const PSM_PAIR = NonNullish(process.env.PSM_PAIR); +const PSM_INSTANCE = `psm-${PSM_PAIR.replace('.', '-')}`; + +const psmSwapIo = { + now: Date.now, + follow: agoric.follow, + setTimeout, +}; const psmTestSpecs = { govParams: { giveMintedFeeVal: 10n, // in % wantMintedFeeVal: 10n, // in % mintLimit: 500n * 1_000_000n, // in IST - deadline: 1, // in minutes + votingDuration: 1, // in minutes }, - psmInstance: `psm-${PSM_PAIR}`, - // @ts-expect-error we assume PSM_PAIR is set because of synthetic-chain environment - anchor: PSM_PAIR.split('-')[1], + psmInstance: PSM_INSTANCE, + anchor: PSM_INSTANCE.split('-')[2], newUser: { name: 'new-psm-trader', fund: { @@ -82,13 +87,12 @@ test.serial('change gov params', async t => { address: GOV1ADDR, instanceName: psmTestSpecs.psmInstance, newParams: psmTestSpecs.govParams, - deadline: psmTestSpecs.govParams.deadline, + votingDuration: psmTestSpecs.govParams.votingDuration, }, - { committeeAddrs: [GOV1ADDR, GOV2ADDR, GOV3ADDR], position: 0 }, + { committeeAddrs: [GOV1ADDR, GOV2ADDR], position: 0 }, + { now: Date.now, follow: agoric.follow }, ); - // Replace when https://github.com/Agoric/agoric-sdk/pull/10171 is in - await waitForBlock(3); await checkGovParams( t, { @@ -120,11 +124,7 @@ test.serial('initialize new user', async t => { newUser: { name, fund }, } = psmTestSpecs; - await initializeNewUser(name, fund); - // Replace when https://github.com/Agoric/agoric-sdk/pull/10171 is in - await waitForBlock(3); - - await checkUserInitializedSuccessfully(name, fund); + await initializeNewUser(name, fund, { query: agd.query, setTimeout }); t.pass(); }); @@ -150,16 +150,19 @@ test.serial('swap into IST', async t => { t.log('METRICS', metricsBefore); t.log('BALANCES', balancesBefore); - await agopsPsm(psmTrader, [ - 'swap', - '--pair', - process.env.PSM_PAIR, - '--wantMinted', - toIst.value, - '--feePct', - wantMintedFeeVal, - ]); - await waitForBlock(5); + await psmSwap( + psmTrader, + [ + 'swap', + '--pair', + PSM_PAIR, + '--wantMinted', + toIst.value, + '--feePct', + wantMintedFeeVal, + ], + psmSwapIo, + ); await checkSwapSucceeded(t, metricsBefore, balancesBefore, { wantMinted: toIst.value, @@ -187,16 +190,19 @@ test.serial('swap out of IST', async t => { t.log('METRICS', metricsBefore); t.log('BALANCES', balancesBefore); - await agopsPsm(psmTrader, [ - 'swap', - '--pair', - process.env.PSM_PAIR, - '--giveMinted', - fromIst.value, - '--feePct', - giveMintedFeeVal, - ]); - await waitForBlock(5); + await psmSwap( + psmTrader, + [ + 'swap', + '--pair', + PSM_PAIR, + '--giveMinted', + fromIst.value, + '--feePct', + giveMintedFeeVal, + ], + psmSwapIo, + ); await checkSwapSucceeded(t, metricsBefore, balancesBefore, { giveMinted: fromIst.value, @@ -219,9 +225,12 @@ test.serial('mint limit is adhered', async t => { // Fund other user const otherAddr = await getUser(name); await bankSend(otherAddr, `${value}${denom}`); - - // Replace when https://github.com/Agoric/agoric-sdk/pull/10171 is in - await waitForBlock(3); + await waitUntilAccountFunded( + otherAddr, + { query: agd.query, setTimeout }, + { denom, value: parseInt(value, 10) }, + { errorMessage: `${otherAddr} could not be funded with ${value}${denom}` }, + ); const [metricsBefore, balancesBefore] = await Promise.all([ getPsmMetrics(anchor), @@ -238,31 +247,41 @@ test.serial('mint limit is adhered', async t => { t.log({ maxMintableValue, wantFeeValue, maxMintFeesAccounted }); // Send a swap, should fail because mint limit is exceeded - await agopsPsm(otherAddr, [ - 'swap', - '--pair', - process.env.PSM_PAIR, - '--wantMinted', - maxMintFeesAccounted / 1000000 + 2, // Make sure we exceed the limit - '--feePct', - govParams.wantMintedFeeVal, - ]); - await waitForBlock(5); + await t.throwsAsync( + () => + psmSwap( + otherAddr, + [ + 'swap', + '--pair', + PSM_PAIR, + '--wantMinted', + maxMintFeesAccounted / 1000000 + 2, // Make sure we exceed the limit + '--feePct', + govParams.wantMintedFeeVal, + ], + psmSwapIo, + ), + { message: /not succeeded/ }, + ); // Now check if failed with correct error message await checkSwapExceedMintLimit(t, otherAddr, metricsBefore); // Send another swap offer, this time should succeed - await agopsPsm(otherAddr, [ - 'swap', - '--pair', - process.env.PSM_PAIR, - '--wantMinted', - maxMintFeesAccounted / 1000000, - '--feePct', - govParams.wantMintedFeeVal, - ]); - await waitForBlock(5); + await psmSwap( + otherAddr, + [ + 'swap', + '--pair', + PSM_PAIR, + '--wantMinted', + maxMintFeesAccounted / 1000000, + '--feePct', + govParams.wantMintedFeeVal, + ], + psmSwapIo, + ); // Make sure swap succeeded await checkSwapSucceeded(t, metricsBefore, balancesBefore, { diff --git a/a3p-integration/proposals/z:acceptance/test-lib/errors.js b/a3p-integration/proposals/z:acceptance/test-lib/errors.js new file mode 100644 index 00000000000..57dc771e6a5 --- /dev/null +++ b/a3p-integration/proposals/z:acceptance/test-lib/errors.js @@ -0,0 +1,20 @@ +/** + * @file Copied from "@agoric/internal" + */ + +import { q } from '@endo/errors'; + +/** + * @template T + * @param {T | null | undefined} val + * @param {string} [optDetails] + * @returns {T} + */ +export const NonNullish = (val, optDetails = `unexpected ${q(val)}`) => { + if (val != null) { + // This `!= null` idiom checks that `val` is neither `null` nor `undefined`. + return val; + } + assert.fail(optDetails); +}; +harden(NonNullish); diff --git a/a3p-integration/proposals/z:acceptance/test-lib/psm-lib.js b/a3p-integration/proposals/z:acceptance/test-lib/psm-lib.js index a50dabcf7ee..442a947500b 100644 --- a/a3p-integration/proposals/z:acceptance/test-lib/psm-lib.js +++ b/a3p-integration/proposals/z:acceptance/test-lib/psm-lib.js @@ -19,13 +19,16 @@ import { AmountMath } from '@agoric/ertp'; import fsp from 'node:fs/promises'; import { boardSlottingMarshaller, makeFromBoard } from './rpc.js'; import { getBalances } from './utils.js'; -import { sleep } from './sync-tools.js'; +import { + retryUntilCondition, + waitUntilAccountFunded, + waitUntilOfferResult, +} from './sync-tools.js'; +import { NonNullish } from './errors.js'; // Export these from synthetic-chain? -export const USDC_DENOM = process.env.USDC_DENOM - ? process.env.USDC_DENOM - : 'no-denom'; -export const PSM_PAIR = process.env.PSM_PAIR?.replace('.', '-'); +const USDC_DENOM = NonNullish(process.env.USDC_DENOM); +const PSM_PAIR = NonNullish(process.env.PSM_PAIR).replace('.', '-'); /** * @typedef {object} PsmMetrics @@ -41,8 +44,6 @@ export const PSM_PAIR = process.env.PSM_PAIR?.replace('.', '-'); const fromBoard = makeFromBoard(); const marshaller = boardSlottingMarshaller(fromBoard.convertSlotToVal); -const VOTING_WAIT_MS = 65 * 1000; - /** * Import from synthetic-chain once it is updated * @@ -65,8 +66,8 @@ export const bankSend = (addr, wanted, from = VALIDATORADDR) => { * address: string * instanceName: string * newParams: Params - * deadline: number - * offerId?: string + * deadline: bigint + * offerId: string * }} QuestionDetails */ export const buildProposePSMParamChangeOffer = async ({ @@ -74,7 +75,7 @@ export const buildProposePSMParamChangeOffer = async ({ instanceName, newParams, deadline, - offerId = Date.now().toString(), + offerId, }) => { const charterAcceptOfferId = await agops.ec( 'find-continuing-id', @@ -138,7 +139,7 @@ export const buildProposePSMParamChangeOffer = async ({ offerArgs: { instance: instances[instanceName], params, - deadline: BigInt(deadline * 60 + Math.round(Date.now() / 1000)), + deadline, }, }; @@ -172,6 +173,45 @@ export const voteForNewParams = ({ committeeAddrs, position }) => { ); }; +/** + * @param {{follow: (...params: string[]) => Promise }} io + */ +export const fetchLatestEcQuestion = async io => { + const { follow } = io; + const pathOutcome = ':published.committees.Economic_Committee.latestOutcome'; + const pathQuestion = + ':published.committees.Economic_Committee.latestQuestion'; + + const [latestOutcome, latestQuestion] = await Promise.all([ + follow('-lF', pathOutcome, '-o', 'text').then(outcomeRaw => + marshaller.fromCapData(JSON.parse(outcomeRaw)), + ), + follow('-lF', pathQuestion, '-o', 'text').then(questionRaw => + marshaller.fromCapData(JSON.parse(questionRaw)), + ), + ]); + + return { latestOutcome, latestQuestion }; +}; + +const checkCommitteeElectionResult = (electionResult, expectedResult) => { + const { + latestOutcome: { outcome, question }, + latestQuestion: { + closingRule: { deadline }, + questionHandle, + }, + } = electionResult; + const { outcome: expectedOutcome, deadline: expectedDeadline } = + expectedResult; + + return ( + expectedOutcome === outcome && + deadline === expectedDeadline && + question === questionHandle + ); +}; + /** * @typedef {{ * giveMintedFeeVal: bigint; @@ -184,7 +224,7 @@ export const voteForNewParams = ({ committeeAddrs, position }) => { * address: string * instanceName: string * newParams: Params - * deadline: number + * votingDuration: number * offerId?: string * }} question * @@ -192,12 +232,34 @@ export const voteForNewParams = ({ committeeAddrs, position }) => { * committeeAddrs: Array * position: number * }} voting + * + * @param {{ now: () => number; follow: (...params: string[]) => Promise}} io */ -export const implementPsmGovParamChange = async (question, voting) => { - await buildProposePSMParamChangeOffer(question); +export const implementPsmGovParamChange = async (question, voting, io) => { + const { now: getNow, follow } = io; + const now = getNow(); + const deadline = BigInt( + question.votingDuration * 60 + Math.round(now / 1000), + ); + + await buildProposePSMParamChangeOffer({ + ...question, + deadline, + offerId: now.toString(), + }); await voteForNewParams(voting); + console.log('ACTIONS wait for the vote deadline to pass'); - await sleep(VOTING_WAIT_MS, { log: console.log }); + await retryUntilCondition( + () => fetchLatestEcQuestion({ follow }), + electionResult => + checkCommitteeElectionResult(electionResult, { + outcome: 'win', + deadline, + }), + 'PSM param change election failed', + { setTimeout, retryIntervalMs: 5000, maxRetries: 15 }, + ); }; /** @@ -240,22 +302,6 @@ export const checkGovParams = async (t, expected, psmName) => { t.like(current, expected); }; -/** - * - * @param {string} name - * @param {{ - * denom: string, - * value: string - * }} fund - */ -export const initializeNewUser = async (name, fund) => { - const psmTrader = await addUser(name); - await Promise.all([ - bankSend(psmTrader, `20000000ubld,${fund.value}${fund.denom}`), - bankSend(psmTrader, `1000000uist`, GOV1ADDR), - ]); -}; - /** * * @param {string} userName @@ -274,6 +320,32 @@ export const checkUserInitializedSuccessfully = async ( assert(balance >= BigInt(expectedAnchorFunds.value)); }; +/** + * + * @param {string} name + * @param {{ + * denom: string, + * value: string + * }} fund + * @param io + */ +export const initializeNewUser = async (name, fund, io) => { + const psmTrader = await addUser(name); + await Promise.all([ + bankSend(psmTrader, `20000000ubld,${fund.value}${fund.denom}`), + bankSend(psmTrader, `1000000uist`, GOV1ADDR), + ]); + + await waitUntilAccountFunded( + psmTrader, + io, + { denom: fund.denom, value: parseInt(fund.value, 10) }, + { errorMessage: `${psmTrader} not funded` }, + ); + + await checkUserInitializedSuccessfully(name, fund); +}; + /** * Similar to https://github.com/Agoric/agoric-3-proposals/blob/422b163fecfcf025d53431caebf6d476778b5db3/packages/synthetic-chain/src/lib/commonUpgradeHelpers.ts#L123-L139 * However, in the case where "address" is not provisioned "agoric wallet send" is needed because @@ -301,18 +373,28 @@ export const sendOfferAgoric = async (address, offerPromise) => { /** * @param {string} address * @param {Array} params + * @param {{ + * follow: (...params: string[]) => Promise; + * setTimeout: (callback: Function, delay: number) => void; + * now: () => number + * }} io */ -export const agopsPsm = (address, params) => { - const newParams = ['psm', ...params]; +export const psmSwap = async (address, params, io) => { + const now = io.now(); + const offerId = `${address}-psm-swap-${now}`; + const newParams = ['psm', ...params, '--offerId', offerId]; const offerPromise = executeCommand(agopsLocation, newParams); - return sendOfferAgoric(address, offerPromise); + await sendOfferAgoric(address, offerPromise); + + await waitUntilOfferResult(address, offerId, true, io, { + errorMessage: `${offerId} not succeeded`, + }); }; /** * * @param {number} base * @param {number} fee - * @returns */ const giveAnchor = (base, fee) => Math.ceil(base / (1 - fee)); @@ -320,7 +402,6 @@ const giveAnchor = (base, fee) => Math.ceil(base / (1 - fee)); * * @param {number} base * @param {number} fee - * @returns */ const receiveAnchor = (base, fee) => Math.ceil(base * (1 - fee)); @@ -328,7 +409,6 @@ const receiveAnchor = (base, fee) => Math.ceil(base * (1 - fee)); * * @param {CosmosBalances} balances * @param {string} targetDenom - * @returns */ const extractBalance = (balances, targetDenom) => { const balance = balances.find(({ denom }) => denom === targetDenom); @@ -336,6 +416,31 @@ const extractBalance = (balances, targetDenom) => { return Number(balance.amount); }; +/** + * Checking IST balances can be tricky because of the execution fee mentioned in + * https://github.com/Agoric/agoric-sdk/issues/6525. Here we first check with + * whatever is passed in. If the first attempt fails we try again to see if + * there was an execution fee charged. If still fails, we throw. + * + * @param {import('ava').ExecutionContext} t + * @param {number} actualBalance + * @param {number} expectedBalance + */ +const tryISTBalances = async (t, actualBalance, expectedBalance) => { + const firstTry = await t.try( + (tt, actual, expected) => { + tt.deepEqual(actual, expected); + }, + actualBalance, + expectedBalance, + ); + + if (!firstTry.passed) { + firstTry.discard(); + t.deepEqual(actualBalance + 200000, expectedBalance); + } else firstTry.commit(); +}; + /** * * @param {import('ava').ExecutionContext} t @@ -373,7 +478,8 @@ export const checkSwapSucceeded = async ( extractBalance(balancesBefore, USDC_DENOM) - anchorPaid, ); - t.deepEqual( + await tryISTBalances( + t, extractBalance(balancesAfter, 'uist'), extractBalance(balancesBefore, 'uist') + mintedReceived, ); @@ -408,7 +514,8 @@ export const checkSwapSucceeded = async ( extractBalance(balancesBefore, USDC_DENOM) + anchorReceived, ); - t.deepEqual( + await tryISTBalances( + t, extractBalance(balancesAfter, 'uist'), extractBalance(balancesBefore, 'uist') - mintedPaid, ); @@ -452,14 +559,18 @@ export const adjustBalancesIfNotProvisioned = async (balances, address) => { const balancesAdjusted = []; - balances.forEach(({ denom, amount }) => { + for (const { denom, amount } of balances) { if (denom === 'uist') { - amount = (parseInt(amount, 10) + 250000 - 1_000_000).toString(); // provision sends 250000uist to new accounts and 1 IST is charged - balancesAdjusted.push({ denom, amount }); + const startingAmount = ( + parseInt(amount, 10) + + 250000 - + 1_000_000 + ).toString(); // provision sends 250000uist to new accounts and 1 IST is charged + balancesAdjusted.push({ denom, amount: startingAmount }); } else { balancesAdjusted.push({ denom, amount }); } - }); + } return balancesAdjusted; }; @@ -473,7 +584,6 @@ export const adjustBalancesIfNotProvisioned = async (balances, address) => { export const checkSwapExceedMintLimit = async (t, address, metricsBefore) => { const [offerResult, metricsAfter] = await Promise.all([ agoric.follow('-lF', `:published.wallet.${address}`), - // @ts-expect-error we assume PSM_PAIR is set because of synthetic-chain environment getPsmMetrics(PSM_PAIR.split('-')[1]), ]); const { status, updated } = offerResult; diff --git a/a3p-integration/proposals/z:acceptance/yarn.lock b/a3p-integration/proposals/z:acceptance/yarn.lock index 6a5183c4ccc..9ce83592e4c 100644 --- a/a3p-integration/proposals/z:acceptance/yarn.lock +++ b/a3p-integration/proposals/z:acceptance/yarn.lock @@ -5,106 +5,106 @@ __metadata: version: 8 cacheKey: 10c0 -"@agoric/base-zone@npm:0.1.1-dev-2ca7ea6.0+2ca7ea6": - version: 0.1.1-dev-2ca7ea6.0 - resolution: "@agoric/base-zone@npm:0.1.1-dev-2ca7ea6.0" +"@agoric/base-zone@npm:0.1.1-dev-9c73ae4.0+9c73ae4": + version: 0.1.1-dev-9c73ae4.0 + resolution: "@agoric/base-zone@npm:0.1.1-dev-9c73ae4.0" dependencies: - "@agoric/store": "npm:0.9.3-dev-2ca7ea6.0+2ca7ea6" - "@endo/common": "npm:^1.2.6" - "@endo/errors": "npm:^1.2.6" - "@endo/exo": "npm:^1.5.4" - "@endo/far": "npm:^1.1.6" - "@endo/pass-style": "npm:^1.4.4" - "@endo/patterns": "npm:^1.4.4" - checksum: 10c0/16593b729aa2d1daabfe9539f2d70e76a6da3f7473a6401320a3b012cd998b03f51f7c820e98aebbfb89188d4ecfaed33f9cabd490a11bc576d28d7269c0ff13 + "@agoric/store": "npm:0.9.3-dev-9c73ae4.0+9c73ae4" + "@endo/common": "npm:^1.2.7" + "@endo/errors": "npm:^1.2.7" + "@endo/exo": "npm:^1.5.6" + "@endo/far": "npm:^1.1.8" + "@endo/pass-style": "npm:^1.4.6" + "@endo/patterns": "npm:^1.4.6" + checksum: 10c0/a924c8572c17d1e94a711e46a83dff409d37b8a00cd7e0899f091a64401ca981f31ce51cfa60ed00e5bee94d7b96dd5523fd88a1419f542c73d95e418a10c3b8 languageName: node linkType: hard "@agoric/ertp@npm:dev": - version: 0.16.3-dev-2ca7ea6.0 - resolution: "@agoric/ertp@npm:0.16.3-dev-2ca7ea6.0" - dependencies: - "@agoric/notifier": "npm:0.6.3-dev-2ca7ea6.0+2ca7ea6" - "@agoric/store": "npm:0.9.3-dev-2ca7ea6.0+2ca7ea6" - "@agoric/vat-data": "npm:0.5.3-dev-2ca7ea6.0+2ca7ea6" - "@agoric/zone": "npm:0.2.3-dev-2ca7ea6.0+2ca7ea6" - "@endo/errors": "npm:^1.2.6" - "@endo/eventual-send": "npm:^1.2.6" - "@endo/far": "npm:^1.1.6" - "@endo/marshal": "npm:^1.5.4" - "@endo/nat": "npm:^5.0.11" - "@endo/patterns": "npm:^1.4.4" - "@endo/promise-kit": "npm:^1.1.6" - checksum: 10c0/413ab9390245201ea26c1e9aa38c75668caeb91dc4a6ec6d6b6a1ff245d8c2b02f67227c4765b098e019e6c9a21e34cb4a2dcbf7e27b136127a6caa4aa08f15e - languageName: node - linkType: hard - -"@agoric/internal@npm:0.3.3-dev-2ca7ea6.0+2ca7ea6, @agoric/internal@npm:dev": - version: 0.3.3-dev-2ca7ea6.0 - resolution: "@agoric/internal@npm:0.3.3-dev-2ca7ea6.0" - dependencies: - "@agoric/base-zone": "npm:0.1.1-dev-2ca7ea6.0+2ca7ea6" - "@endo/common": "npm:^1.2.6" - "@endo/errors": "npm:^1.2.6" - "@endo/far": "npm:^1.1.6" - "@endo/init": "npm:^1.1.5" - "@endo/marshal": "npm:^1.5.4" - "@endo/pass-style": "npm:^1.4.4" - "@endo/patterns": "npm:^1.4.4" - "@endo/promise-kit": "npm:^1.1.6" - "@endo/stream": "npm:^1.2.6" + version: 0.16.3-dev-9c73ae4.0 + resolution: "@agoric/ertp@npm:0.16.3-dev-9c73ae4.0" + dependencies: + "@agoric/notifier": "npm:0.6.3-dev-9c73ae4.0+9c73ae4" + "@agoric/store": "npm:0.9.3-dev-9c73ae4.0+9c73ae4" + "@agoric/vat-data": "npm:0.5.3-dev-9c73ae4.0+9c73ae4" + "@agoric/zone": "npm:0.2.3-dev-9c73ae4.0+9c73ae4" + "@endo/errors": "npm:^1.2.7" + "@endo/eventual-send": "npm:^1.2.7" + "@endo/far": "npm:^1.1.8" + "@endo/marshal": "npm:^1.6.1" + "@endo/nat": "npm:^5.0.12" + "@endo/patterns": "npm:^1.4.6" + "@endo/promise-kit": "npm:^1.1.7" + checksum: 10c0/0012ed9cc17d6338196cfdef0198fa16bba784e535a7845758db8f0d0ecb766bbdbb22efc5cedc3971a910d65956305f3d08c77a47c79268037cc6e53a6656b8 + languageName: node + linkType: hard + +"@agoric/internal@npm:0.3.3-dev-9c73ae4.0+9c73ae4, @agoric/internal@npm:dev": + version: 0.3.3-dev-9c73ae4.0 + resolution: "@agoric/internal@npm:0.3.3-dev-9c73ae4.0" + dependencies: + "@agoric/base-zone": "npm:0.1.1-dev-9c73ae4.0+9c73ae4" + "@endo/common": "npm:^1.2.7" + "@endo/errors": "npm:^1.2.7" + "@endo/far": "npm:^1.1.8" + "@endo/init": "npm:^1.1.6" + "@endo/marshal": "npm:^1.6.1" + "@endo/pass-style": "npm:^1.4.6" + "@endo/patterns": "npm:^1.4.6" + "@endo/promise-kit": "npm:^1.1.7" + "@endo/stream": "npm:^1.2.7" anylogger: "npm:^0.21.0" jessie.js: "npm:^0.3.4" - checksum: 10c0/86d5be268c8813fb3c5138486e80ed0c0aab8df2a1cc870a4e78f1e4b6b27e244bb9140ea148a6a1571d055ee4842c2d10aade6b248aae488c438d67af663daf + checksum: 10c0/cebcf81f503e72cdb431e56d13af438275eaab407ea69512f94ca6ee8117ece71b291c2604e05264f61c31eac59fbd35bbced9227b14a4a37c5ee92f862044f6 languageName: node linkType: hard -"@agoric/notifier@npm:0.6.3-dev-2ca7ea6.0+2ca7ea6": - version: 0.6.3-dev-2ca7ea6.0 - resolution: "@agoric/notifier@npm:0.6.3-dev-2ca7ea6.0" +"@agoric/notifier@npm:0.6.3-dev-9c73ae4.0+9c73ae4": + version: 0.6.3-dev-9c73ae4.0 + resolution: "@agoric/notifier@npm:0.6.3-dev-9c73ae4.0" dependencies: - "@agoric/internal": "npm:0.3.3-dev-2ca7ea6.0+2ca7ea6" - "@agoric/vat-data": "npm:0.5.3-dev-2ca7ea6.0+2ca7ea6" - "@endo/errors": "npm:^1.2.6" - "@endo/far": "npm:^1.1.6" - "@endo/marshal": "npm:^1.5.4" - "@endo/patterns": "npm:^1.4.4" - "@endo/promise-kit": "npm:^1.1.6" - checksum: 10c0/276a61bbec8930eb69ab0d7b2c66c735d1c725ba282be3be9ca04980f06b08c54d38eb17a328e8180d56c4b8aa6e0feac5b3b7983918b6318f4bef5f5cb4b2e9 + "@agoric/internal": "npm:0.3.3-dev-9c73ae4.0+9c73ae4" + "@agoric/vat-data": "npm:0.5.3-dev-9c73ae4.0+9c73ae4" + "@endo/errors": "npm:^1.2.7" + "@endo/far": "npm:^1.1.8" + "@endo/marshal": "npm:^1.6.1" + "@endo/patterns": "npm:^1.4.6" + "@endo/promise-kit": "npm:^1.1.7" + checksum: 10c0/5c98617465aac5a32e69f18579e5650c8290cb3bb8643acb80a2c84d5d0cf4012df98de54ecc8c8c6cc09e4cae825818955fa61cd64d4ae2bbf6772f86c3c6d9 languageName: node linkType: hard -"@agoric/store@npm:0.9.3-dev-2ca7ea6.0+2ca7ea6": - version: 0.9.3-dev-2ca7ea6.0 - resolution: "@agoric/store@npm:0.9.3-dev-2ca7ea6.0" +"@agoric/store@npm:0.9.3-dev-9c73ae4.0+9c73ae4": + version: 0.9.3-dev-9c73ae4.0 + resolution: "@agoric/store@npm:0.9.3-dev-9c73ae4.0" dependencies: - "@endo/errors": "npm:^1.2.6" - "@endo/exo": "npm:^1.5.4" - "@endo/marshal": "npm:^1.5.4" - "@endo/pass-style": "npm:^1.4.4" - "@endo/patterns": "npm:^1.4.4" - checksum: 10c0/6b02d913afa69b6c82b67277b5e5102ecc082f0348285440af8a040d77030c45d8b6c5cd4b05e3e707b8ad3bef778935fba1d982fbe93629466678efb71e139f + "@endo/errors": "npm:^1.2.7" + "@endo/exo": "npm:^1.5.6" + "@endo/marshal": "npm:^1.6.1" + "@endo/pass-style": "npm:^1.4.6" + "@endo/patterns": "npm:^1.4.6" + checksum: 10c0/7575dba675c9912968771aa92764fa2e7cb9118f9d8278e40096cdd266f6a035b37e5781f1ea80c41961fc820cc5f3c2174683bba5c0b56c35c98d1890192110 languageName: node linkType: hard -"@agoric/swingset-liveslots@npm:0.10.3-dev-2ca7ea6.0+2ca7ea6": - version: 0.10.3-dev-2ca7ea6.0 - resolution: "@agoric/swingset-liveslots@npm:0.10.3-dev-2ca7ea6.0" +"@agoric/swingset-liveslots@npm:0.10.3-dev-9c73ae4.0+9c73ae4": + version: 0.10.3-dev-9c73ae4.0 + resolution: "@agoric/swingset-liveslots@npm:0.10.3-dev-9c73ae4.0" dependencies: - "@agoric/internal": "npm:0.3.3-dev-2ca7ea6.0+2ca7ea6" - "@agoric/store": "npm:0.9.3-dev-2ca7ea6.0+2ca7ea6" + "@agoric/internal": "npm:0.3.3-dev-9c73ae4.0+9c73ae4" + "@agoric/store": "npm:0.9.3-dev-9c73ae4.0+9c73ae4" "@endo/env-options": "npm:^1.1.7" - "@endo/errors": "npm:^1.2.6" - "@endo/eventual-send": "npm:^1.2.6" - "@endo/exo": "npm:^1.5.4" - "@endo/far": "npm:^1.1.6" - "@endo/init": "npm:^1.1.5" - "@endo/marshal": "npm:^1.5.4" - "@endo/nat": "npm:^5.0.11" - "@endo/pass-style": "npm:^1.4.4" - "@endo/patterns": "npm:^1.4.4" - "@endo/promise-kit": "npm:^1.1.6" - checksum: 10c0/ce6285a5e65b1a9a7ed819c96de337234cba0a24e57dd60737716ff0222889fe98cc53a4ff644950d0aab4ccda8c22abe91fbae861659a7da521b659c2c3bd39 + "@endo/errors": "npm:^1.2.7" + "@endo/eventual-send": "npm:^1.2.7" + "@endo/exo": "npm:^1.5.6" + "@endo/far": "npm:^1.1.8" + "@endo/init": "npm:^1.1.6" + "@endo/marshal": "npm:^1.6.1" + "@endo/nat": "npm:^5.0.12" + "@endo/pass-style": "npm:^1.4.6" + "@endo/patterns": "npm:^1.4.6" + "@endo/promise-kit": "npm:^1.1.7" + checksum: 10c0/93ed5fb3373a05007bc372c84344d3c714561a027ca5a835364e3755ae8bb7a4caa639ebabda78a0062bf209ece1b3d582cc403d6d42235d75d13ab9ac59e656 languageName: node linkType: hard @@ -123,30 +123,30 @@ __metadata: languageName: node linkType: hard -"@agoric/vat-data@npm:0.5.3-dev-2ca7ea6.0+2ca7ea6": - version: 0.5.3-dev-2ca7ea6.0 - resolution: "@agoric/vat-data@npm:0.5.3-dev-2ca7ea6.0" +"@agoric/vat-data@npm:0.5.3-dev-9c73ae4.0+9c73ae4": + version: 0.5.3-dev-9c73ae4.0 + resolution: "@agoric/vat-data@npm:0.5.3-dev-9c73ae4.0" dependencies: - "@agoric/base-zone": "npm:0.1.1-dev-2ca7ea6.0+2ca7ea6" - "@agoric/store": "npm:0.9.3-dev-2ca7ea6.0+2ca7ea6" - "@agoric/swingset-liveslots": "npm:0.10.3-dev-2ca7ea6.0+2ca7ea6" - "@endo/errors": "npm:^1.2.6" - "@endo/exo": "npm:^1.5.4" - "@endo/patterns": "npm:^1.4.4" - checksum: 10c0/c5d24f9715e9c6621f9dab6fd4626d18ec3d7642e9a4ed425a95181afe706bebb378023f00b39221bef012ba1e804d2c4ca1339510b0cc714c835d9cc85b28a7 + "@agoric/base-zone": "npm:0.1.1-dev-9c73ae4.0+9c73ae4" + "@agoric/store": "npm:0.9.3-dev-9c73ae4.0+9c73ae4" + "@agoric/swingset-liveslots": "npm:0.10.3-dev-9c73ae4.0+9c73ae4" + "@endo/errors": "npm:^1.2.7" + "@endo/exo": "npm:^1.5.6" + "@endo/patterns": "npm:^1.4.6" + checksum: 10c0/903940756aa8d38ca7f9aa5c35ac1e4df922f0a2efca4b7557ef96f560ac348117da16886894ef6a73ce3b9051067f6f80299c14f36c69c6c914ca0760c31305 languageName: node linkType: hard -"@agoric/zone@npm:0.2.3-dev-2ca7ea6.0+2ca7ea6": - version: 0.2.3-dev-2ca7ea6.0 - resolution: "@agoric/zone@npm:0.2.3-dev-2ca7ea6.0" +"@agoric/zone@npm:0.2.3-dev-9c73ae4.0+9c73ae4": + version: 0.2.3-dev-9c73ae4.0 + resolution: "@agoric/zone@npm:0.2.3-dev-9c73ae4.0" dependencies: - "@agoric/base-zone": "npm:0.1.1-dev-2ca7ea6.0+2ca7ea6" - "@agoric/vat-data": "npm:0.5.3-dev-2ca7ea6.0+2ca7ea6" - "@endo/errors": "npm:^1.2.6" - "@endo/far": "npm:^1.1.6" - "@endo/pass-style": "npm:^1.4.4" - checksum: 10c0/2d8c3798bd4394ee89f333f1788cefe4f967cc8a4a3feceb7d5de51ae7f83012ef11a448c385918ecfe75fcde17aeb12a55889555a346d73fd1f61d26ad7c95c + "@agoric/base-zone": "npm:0.1.1-dev-9c73ae4.0+9c73ae4" + "@agoric/vat-data": "npm:0.5.3-dev-9c73ae4.0+9c73ae4" + "@endo/errors": "npm:^1.2.7" + "@endo/far": "npm:^1.1.8" + "@endo/pass-style": "npm:^1.4.6" + checksum: 10c0/0c4da29f8422c4da27b2ee7abbc21c52f73f3f305c99ee39dcb2dfb9d62199d49aa306d60335b0f9dc3b0e8cf8b3470d95fddfa5e8152bee2ef47ffe2932a5ca languageName: node linkType: hard @@ -157,14 +157,14 @@ __metadata: languageName: node linkType: hard -"@endo/common@npm:^1.2.6": - version: 1.2.6 - resolution: "@endo/common@npm:1.2.6" +"@endo/common@npm:^1.2.7": + version: 1.2.7 + resolution: "@endo/common@npm:1.2.7" dependencies: - "@endo/errors": "npm:^1.2.6" - "@endo/eventual-send": "npm:^1.2.6" - "@endo/promise-kit": "npm:^1.1.6" - checksum: 10c0/7cca372677bd1ab535a0f8fc250eca9bfc202ef7dfd24cac6ff1260003aa4512f8db18419dd632b3e57270a589fec54891496904ff2e8a97047f1e187e3f1401 + "@endo/errors": "npm:^1.2.7" + "@endo/eventual-send": "npm:^1.2.7" + "@endo/promise-kit": "npm:^1.1.7" + checksum: 10c0/2bd25ffc528afd6308b6168650583977139c64e56547b3ea2fee313e01650fb1a041d7b7ce54c6bb70f26c9e78345db2a50fbe3ebccabe9a5c5696eda2cca706 languageName: node linkType: hard @@ -175,135 +175,135 @@ __metadata: languageName: node linkType: hard -"@endo/errors@npm:^1.2.2, @endo/errors@npm:^1.2.6": - version: 1.2.6 - resolution: "@endo/errors@npm:1.2.6" +"@endo/errors@npm:^1.2.2, @endo/errors@npm:^1.2.7": + version: 1.2.7 + resolution: "@endo/errors@npm:1.2.7" dependencies: - ses: "npm:^1.9.0" - checksum: 10c0/cbc541c2d26fbfeb1567dd1cdf0e1f110ee9866430c5a1b91d87270d6f00bc8293cc8512301217c4eda35e60677ce62a941d11eb32c7da1f72d450a011ad2bb5 + ses: "npm:^1.9.1" + checksum: 10c0/17eed5d01dd968d8e266db37ac44e76859d14894a2b70457d120f2f05021819671aaf1bdf7dc7c2506b84f6df616402e029695d62309fbdbdd13ed4ba34890dd languageName: node linkType: hard -"@endo/eventual-send@npm:^1.2.6": - version: 1.2.6 - resolution: "@endo/eventual-send@npm:1.2.6" +"@endo/eventual-send@npm:^1.2.7": + version: 1.2.7 + resolution: "@endo/eventual-send@npm:1.2.7" dependencies: "@endo/env-options": "npm:^1.1.7" - checksum: 10c0/6983d6b88bf4e99f6c469d4ca037793582b06cc0bfa2da085b5bc7ad67333a1fba6e5e7077b7f279be23ccfba1dff9e06c7f85f9980b09fd002227f89a673c11 + checksum: 10c0/4a483169bcd9ead47a7a07d3f69bdebdc0d1e2a3198f35ad422b82c1646b64528234bdddc9e0544ac38c2ede84a50af1e126eb4086aa8b4ae4b0002895b55e86 languageName: node linkType: hard -"@endo/exo@npm:^1.5.4": - version: 1.5.4 - resolution: "@endo/exo@npm:1.5.4" +"@endo/exo@npm:^1.5.6": + version: 1.5.6 + resolution: "@endo/exo@npm:1.5.6" dependencies: - "@endo/common": "npm:^1.2.6" + "@endo/common": "npm:^1.2.7" "@endo/env-options": "npm:^1.1.7" - "@endo/errors": "npm:^1.2.6" - "@endo/eventual-send": "npm:^1.2.6" - "@endo/far": "npm:^1.1.6" - "@endo/pass-style": "npm:^1.4.4" - "@endo/patterns": "npm:^1.4.4" - checksum: 10c0/674d2bfc50f8e7abdf7eb38ba3bb11dac9e84395bdccc3b06eef1076fcab3fd8fe38b9559bfef69f9d78287cd6c2d77533cfec68a57526812d833f1d57151425 + "@endo/errors": "npm:^1.2.7" + "@endo/eventual-send": "npm:^1.2.7" + "@endo/far": "npm:^1.1.8" + "@endo/pass-style": "npm:^1.4.6" + "@endo/patterns": "npm:^1.4.6" + checksum: 10c0/280fe019ec6006f5649b40093453c4ecaa71dfdcbb9767efc9f0ed4fc23081b120818c4a12896e8ec2221444889031736b569229117834048d596ecb09a44057 languageName: node linkType: hard -"@endo/far@npm:^1.0.0, @endo/far@npm:^1.1.5, @endo/far@npm:^1.1.6": - version: 1.1.6 - resolution: "@endo/far@npm:1.1.6" +"@endo/far@npm:^1.0.0, @endo/far@npm:^1.1.5, @endo/far@npm:^1.1.8": + version: 1.1.8 + resolution: "@endo/far@npm:1.1.8" dependencies: - "@endo/errors": "npm:^1.2.6" - "@endo/eventual-send": "npm:^1.2.6" - "@endo/pass-style": "npm:^1.4.4" - checksum: 10c0/0d001686f717601fd2a4609161e37235603bc2721fe1cc967247019f4d9bb583d6eafc56940a7d95d252ae2064a663f552d36b7fd15528ce207c3cd748f820ec + "@endo/errors": "npm:^1.2.7" + "@endo/eventual-send": "npm:^1.2.7" + "@endo/pass-style": "npm:^1.4.6" + checksum: 10c0/efb0f063e7a19fd67fe150e0a018a9e4b2abd5238b3c3d136830732aa3294bdb829c1a920607360c285eb6e3a3ae5337b6a1e9847cfcf5618247431af02c5a1e languageName: node linkType: hard -"@endo/init@npm:^1.1.4, @endo/init@npm:^1.1.5": - version: 1.1.5 - resolution: "@endo/init@npm:1.1.5" +"@endo/init@npm:^1.1.4, @endo/init@npm:^1.1.6": + version: 1.1.6 + resolution: "@endo/init@npm:1.1.6" dependencies: "@endo/base64": "npm:^1.0.8" - "@endo/eventual-send": "npm:^1.2.6" - "@endo/lockdown": "npm:^1.0.11" - "@endo/promise-kit": "npm:^1.1.6" - checksum: 10c0/08abda8a0204450cb39c296270d074189f320cdeb03892e87b27a75f6b98c0b5d9c8471e242c53545843211fe713b01b281eec0eabc1c58ca0760a068b90335c + "@endo/eventual-send": "npm:^1.2.7" + "@endo/lockdown": "npm:^1.0.12" + "@endo/promise-kit": "npm:^1.1.7" + checksum: 10c0/1885429e475c780bb5b7ff0fd4fcd5d76bc3a5cb1c3c2f9f2dfc06152ff1c8c3be512b6760483c8c18598e1977129cb3dd766a32c9f6efb19d70b54a1844c683 languageName: node linkType: hard -"@endo/lockdown@npm:^1.0.11": - version: 1.0.11 - resolution: "@endo/lockdown@npm:1.0.11" +"@endo/lockdown@npm:^1.0.12": + version: 1.0.12 + resolution: "@endo/lockdown@npm:1.0.12" dependencies: - ses: "npm:^1.9.0" - checksum: 10c0/03bb96f370e7ee69d9d8e26b7b124b6381994d3b28a99dc2bcafe77139283701d7543b40e978226cd49443c149e64610e4dd49c32ada931610ba091809478a11 + ses: "npm:^1.9.1" + checksum: 10c0/0b9d36f359ffe8eadd1e799aa0340ccb0680d48c9b6249c380c27724824c4d875dada9fbec096fb4e2ac76b32c7536955524d3eb6579451a618707602fb958f4 languageName: node linkType: hard -"@endo/marshal@npm:^1.5.4": - version: 1.5.4 - resolution: "@endo/marshal@npm:1.5.4" +"@endo/marshal@npm:^1.5.3, @endo/marshal@npm:^1.6.1": + version: 1.6.1 + resolution: "@endo/marshal@npm:1.6.1" dependencies: - "@endo/common": "npm:^1.2.6" - "@endo/errors": "npm:^1.2.6" - "@endo/eventual-send": "npm:^1.2.6" - "@endo/nat": "npm:^5.0.11" - "@endo/pass-style": "npm:^1.4.4" - "@endo/promise-kit": "npm:^1.1.6" - checksum: 10c0/4668a3678567cfbeefc3a47217912ca3f5d8bdb37e0d5d53339953b4ab83950683505f45c0f5c30b415c989bb9df4fa0859849d23b11f5b1064b0da0a13943ab + "@endo/common": "npm:^1.2.7" + "@endo/errors": "npm:^1.2.7" + "@endo/eventual-send": "npm:^1.2.7" + "@endo/nat": "npm:^5.0.12" + "@endo/pass-style": "npm:^1.4.6" + "@endo/promise-kit": "npm:^1.1.7" + checksum: 10c0/e64983abccd833b2a7eb63547e8c5a629f073d3e422229475d470ace95c2640a89e9a9879c46e8389cca8c9e75823ea1c27e27cbeeb8c1c4005146b2c530c530 languageName: node linkType: hard -"@endo/nat@npm:^5.0.11": - version: 5.0.11 - resolution: "@endo/nat@npm:5.0.11" - checksum: 10c0/50cd9033657dd35288f0333a966984f788213f1c836e6772c0d731361d5854d0ce24b8a7d6f351d56618eb40f6b369e130c1ad939c83d5833246c09119b2e777 +"@endo/nat@npm:^5.0.12": + version: 5.0.12 + resolution: "@endo/nat@npm:5.0.12" + checksum: 10c0/deb792b6a0c9fe9c0e7cf74cc725d8bc36934571f4f06ac3b6def2a0622ac79b0278753c574f9b55a88b063d1186fd6971bbe63326077a7d37982c4c37a1a24c languageName: node linkType: hard -"@endo/pass-style@npm:^1.4.4": - version: 1.4.4 - resolution: "@endo/pass-style@npm:1.4.4" +"@endo/pass-style@npm:^1.4.6": + version: 1.4.6 + resolution: "@endo/pass-style@npm:1.4.6" dependencies: "@endo/env-options": "npm:^1.1.7" - "@endo/errors": "npm:^1.2.6" - "@endo/eventual-send": "npm:^1.2.6" - "@endo/promise-kit": "npm:^1.1.6" + "@endo/errors": "npm:^1.2.7" + "@endo/eventual-send": "npm:^1.2.7" + "@endo/promise-kit": "npm:^1.1.7" "@fast-check/ava": "npm:^1.1.5" - checksum: 10c0/d6c6268b269d4c14541087ce6b2975f9e31847893d01360c0ea92392ae93316d94fbf59cd7299853a0fb5214176ad7ffc4616b9b2581fd720bdb55ef96d0beeb + checksum: 10c0/fec9d21bb4c70314e92c72f7ae41ec147ac839a23d54f613d689b84f81206e49e657f3fb414db454cbd6ab67dd2a319b1ae25c42b3a1c881edd5de120496b8b4 languageName: node linkType: hard -"@endo/patterns@npm:^1.4.4": - version: 1.4.4 - resolution: "@endo/patterns@npm:1.4.4" +"@endo/patterns@npm:^1.4.6": + version: 1.4.6 + resolution: "@endo/patterns@npm:1.4.6" dependencies: - "@endo/common": "npm:^1.2.6" - "@endo/errors": "npm:^1.2.6" - "@endo/eventual-send": "npm:^1.2.6" - "@endo/marshal": "npm:^1.5.4" - "@endo/promise-kit": "npm:^1.1.6" - checksum: 10c0/3e4c43b916134201b98cb3b17480f01aa15c2ce7a447b574f6ad8b8d1796fd929f2a21e7c541c50ad83afab51044cf03719bef9e48b455a0bec89d940287487b + "@endo/common": "npm:^1.2.7" + "@endo/errors": "npm:^1.2.7" + "@endo/eventual-send": "npm:^1.2.7" + "@endo/marshal": "npm:^1.6.1" + "@endo/promise-kit": "npm:^1.1.7" + checksum: 10c0/518cf4f88ff6aaf6c9df01fbe9f63570aaace763f2a169f986145b039cbd872802154b21736f751fc4cce497d3380aa6be41d2d51e169c8e63a1edb1751d1808 languageName: node linkType: hard -"@endo/promise-kit@npm:^1.1.6": - version: 1.1.6 - resolution: "@endo/promise-kit@npm:1.1.6" +"@endo/promise-kit@npm:^1.1.7": + version: 1.1.7 + resolution: "@endo/promise-kit@npm:1.1.7" dependencies: - ses: "npm:^1.9.0" - checksum: 10c0/d60de663e58f9de32b6705268c62c63c4721f1874813d3c10cae7846e921e4ea8a60c8622ef8c937741a3387fbc60110076b25304afedf270727af7b0c3fecb3 + ses: "npm:^1.9.1" + checksum: 10c0/98a8d743c437f106f266871874acd811c0e028fc89553738bbd46a0fea5871b9ba7ef0449ec38e7e3768fc21684993ecdbbd06f5f3429cd69fbe4b867d4c2bd5 languageName: node linkType: hard -"@endo/stream@npm:^1.2.6": - version: 1.2.6 - resolution: "@endo/stream@npm:1.2.6" +"@endo/stream@npm:^1.2.7": + version: 1.2.7 + resolution: "@endo/stream@npm:1.2.7" dependencies: - "@endo/eventual-send": "npm:^1.2.6" - "@endo/promise-kit": "npm:^1.1.6" - ses: "npm:^1.9.0" - checksum: 10c0/76b686aae1614c23aff63ba1b5d5c57a25d331aba98becf4c02ca374e76cb13d455112841d84b1c6274e518f6d855cbcb8556efcb0ac09f8ccec2aa8c393f743 + "@endo/eventual-send": "npm:^1.2.7" + "@endo/promise-kit": "npm:^1.1.7" + ses: "npm:^1.9.1" + checksum: 10c0/d96a2350200cc76ede5eed49e5d780d6d21f63007b42a83658bf5174e7b61e96bfe6b8f11ed2d33ad7f9eb6c3ec2db2a79079bc1679260ac7dac04a34a4a515e languageName: node linkType: hard @@ -613,25 +613,25 @@ __metadata: languageName: node linkType: hard -"@vercel/nft@npm:^0.26.2": - version: 0.26.5 - resolution: "@vercel/nft@npm:0.26.5" +"@vercel/nft@npm:^0.27.5": + version: 0.27.5 + resolution: "@vercel/nft@npm:0.27.5" dependencies: "@mapbox/node-pre-gyp": "npm:^1.0.5" "@rollup/pluginutils": "npm:^4.0.0" acorn: "npm:^8.6.0" - acorn-import-attributes: "npm:^1.9.2" + acorn-import-attributes: "npm:^1.9.5" async-sema: "npm:^3.1.1" bindings: "npm:^1.4.0" estree-walker: "npm:2.0.2" glob: "npm:^7.1.3" graceful-fs: "npm:^4.2.9" - micromatch: "npm:^4.0.2" + micromatch: "npm:^4.0.8" node-gyp-build: "npm:^4.2.2" resolve-from: "npm:^5.0.0" bin: nft: out/cli.js - checksum: 10c0/b7034b2f851384f26316c856a731c0973a99bd02f6bb349916a750328a4919944ed6fd12c321b38ec6535d29dfb627d7fa8ab0f1e8c1c3cabd71e3350bd77548 + checksum: 10c0/24455056df4330c9709400e0f9bd5c25870a7694256ef2f1e0812d3435386db3b95a7c7a57f4d74774b081eaf225d4afbf2bd891ce82f9b3e14da10b413538fe languageName: node linkType: hard @@ -649,7 +649,7 @@ __metadata: languageName: node linkType: hard -"acorn-import-attributes@npm:^1.9.2": +"acorn-import-attributes@npm:^1.9.5": version: 1.9.5 resolution: "acorn-import-attributes@npm:1.9.5" peerDependencies: @@ -658,7 +658,7 @@ __metadata: languageName: node linkType: hard -"acorn-walk@npm:^8.3.2": +"acorn-walk@npm:^8.3.4": version: 8.3.4 resolution: "acorn-walk@npm:8.3.4" dependencies: @@ -667,12 +667,12 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.11.0, acorn@npm:^8.11.3, acorn@npm:^8.6.0": - version: 8.13.0 - resolution: "acorn@npm:8.13.0" +"acorn@npm:^8.11.0, acorn@npm:^8.13.0, acorn@npm:^8.6.0": + version: 8.14.0 + resolution: "acorn@npm:8.14.0" bin: acorn: bin/acorn - checksum: 10c0/f35dd53d68177c90699f4c37d0bb205b8abe036d955d0eb011ddb7f14a81e6fd0f18893731c457c1b5bd96754683f4c3d80d9a5585ddecaa53cdf84e0b3d68f7 + checksum: 10c0/6d4ee461a7734b2f48836ee0fbb752903606e576cc100eb49340295129ca0b452f3ba91ddd4424a1d4406a98adfb2ebb6bd0ff4c49d7a0930c10e462719bbfd7 languageName: node linkType: hard @@ -796,17 +796,17 @@ __metadata: linkType: hard "ava@npm:^6.1.2": - version: 6.1.3 - resolution: "ava@npm:6.1.3" + version: 6.2.0 + resolution: "ava@npm:6.2.0" dependencies: - "@vercel/nft": "npm:^0.26.2" - acorn: "npm:^8.11.3" - acorn-walk: "npm:^8.3.2" + "@vercel/nft": "npm:^0.27.5" + acorn: "npm:^8.13.0" + acorn-walk: "npm:^8.3.4" ansi-styles: "npm:^6.2.1" arrgv: "npm:^1.0.2" arrify: "npm:^3.0.0" - callsites: "npm:^4.1.0" - cbor: "npm:^9.0.1" + callsites: "npm:^4.2.0" + cbor: "npm:^9.0.2" chalk: "npm:^5.3.0" chunkd: "npm:^2.0.1" ci-info: "npm:^4.0.0" @@ -816,10 +816,10 @@ __metadata: common-path-prefix: "npm:^3.0.0" concordance: "npm:^5.0.4" currently-unhandled: "npm:^0.4.1" - debug: "npm:^4.3.4" - emittery: "npm:^1.0.1" - figures: "npm:^6.0.1" - globby: "npm:^14.0.0" + debug: "npm:^4.3.7" + emittery: "npm:^1.0.3" + figures: "npm:^6.1.0" + globby: "npm:^14.0.2" ignore-by-default: "npm:^2.1.0" indent-string: "npm:^5.0.0" is-plain-object: "npm:^5.0.0" @@ -827,17 +827,17 @@ __metadata: matcher: "npm:^5.0.0" memoize: "npm:^10.0.0" ms: "npm:^2.1.3" - p-map: "npm:^7.0.1" + p-map: "npm:^7.0.2" package-config: "npm:^5.0.0" - picomatch: "npm:^3.0.1" + picomatch: "npm:^4.0.2" plur: "npm:^5.1.0" - pretty-ms: "npm:^9.0.0" + pretty-ms: "npm:^9.1.0" resolve-cwd: "npm:^3.0.0" stack-utils: "npm:^2.0.6" strip-ansi: "npm:^7.1.0" supertap: "npm:^3.0.1" temp-dir: "npm:^3.0.0" - write-file-atomic: "npm:^5.0.1" + write-file-atomic: "npm:^6.0.0" yargs: "npm:^17.7.2" peerDependencies: "@ava/typescript": "*" @@ -846,7 +846,7 @@ __metadata: optional: true bin: ava: entrypoints/cli.mjs - checksum: 10c0/108b28aceb0dfdb077bcf4c96109d736667999c2ce9f564489c4747482ea0e0c455d4d96fdfaad610b1125b74129b7b1d2ce570ec4903e767f6950d7f722f2cd + checksum: 10c0/25a37413c9ee1b5322dc5a266f546236ea4b52e5c04ae4b52a7b26db9263eebe2dbcda687bf4d464867e558e9148e4567aa09a7ec91d46e3218ab93204e3c653 languageName: node linkType: hard @@ -960,14 +960,14 @@ __metadata: languageName: node linkType: hard -"callsites@npm:^4.1.0": +"callsites@npm:^4.2.0": version: 4.2.0 resolution: "callsites@npm:4.2.0" checksum: 10c0/8f7e269ec09fc0946bb22d838a8bc7932e1909ab4a833b964749f4d0e8bdeaa1f253287c4f911f61781f09620b6925ccd19a5ea4897489c4e59442c660c312a3 languageName: node linkType: hard -"cbor@npm:^9.0.1": +"cbor@npm:^9.0.2": version: 9.0.2 resolution: "cbor@npm:9.0.2" dependencies: @@ -1160,7 +1160,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:^4.3.4, debug@npm:^4.3.7": version: 4.3.7 resolution: "debug@npm:4.3.7" dependencies: @@ -1209,7 +1209,7 @@ __metadata: languageName: node linkType: hard -"emittery@npm:^1.0.1": +"emittery@npm:^1.0.3": version: 1.0.3 resolution: "emittery@npm:1.0.3" checksum: 10c0/91605d044f3891dd1f8ab731aeb94b520488b21e707f7064dcbcf5303bac3b4e7133dfa23c343ede1fc970340bd78a9b1aed522b805bc15104606bba630dd71e @@ -1398,8 +1398,8 @@ __metadata: linkType: hard "execa@npm:^9.3.1": - version: 9.4.1 - resolution: "execa@npm:9.4.1" + version: 9.5.0 + resolution: "execa@npm:9.5.0" dependencies: "@sindresorhus/merge-streams": "npm:^4.0.0" cross-spawn: "npm:^7.0.3" @@ -1413,7 +1413,7 @@ __metadata: signal-exit: "npm:^4.1.0" strip-final-newline: "npm:^4.0.0" yoctocolors: "npm:^2.0.0" - checksum: 10c0/2166de02c8c940312481e480ef47f54636725b24a402d25bdbaeca97f6eeb82b21def1279e00378872fbe6a0c675d6b5cd144be4d52c1485a7a3895e611ac03e + checksum: 10c0/93bc077249a778accc019bce141e0ebdf85e1a19ea02eaa1ed00cb49436b0751a7983fee0c0d95434e81727808639c512eea8d1308104598859cac2f01336bb1 languageName: node linkType: hard @@ -1469,7 +1469,7 @@ __metadata: languageName: node linkType: hard -"figures@npm:^6.0.1, figures@npm:^6.1.0": +"figures@npm:^6.1.0": version: 6.1.0 resolution: "figures@npm:6.1.0" dependencies: @@ -1658,7 +1658,7 @@ __metadata: languageName: node linkType: hard -"globby@npm:^14.0.0": +"globby@npm:^14.0.2": version: 14.0.2 resolution: "globby@npm:14.0.2" dependencies: @@ -2047,7 +2047,7 @@ __metadata: languageName: node linkType: hard -"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4": +"micromatch@npm:^4.0.4, micromatch@npm:^4.0.8": version: 4.0.8 resolution: "micromatch@npm:4.0.8" dependencies: @@ -2347,7 +2347,7 @@ __metadata: languageName: node linkType: hard -"p-map@npm:^7.0.1": +"p-map@npm:^7.0.2": version: 7.0.2 resolution: "p-map@npm:7.0.2" checksum: 10c0/e10548036648d1c043153f9997112fe5a7de54a319210238628f8ea22ee36587fd6ee740811f88b60bbf29d932e23ae35df7fced40df477116c84c18e797047e @@ -2423,10 +2423,10 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^3.0.1": - version: 3.0.1 - resolution: "picomatch@npm:3.0.1" - checksum: 10c0/70ec738569f1864658378b7abdab8939d15dae0718c1df994eae3346fd33daf6a3c1ff4e0c1a0cd1e2c0319130985b63a2cff34d192f2f2acbb78aca76111736 +"picomatch@npm:^4.0.2": + version: 4.0.2 + resolution: "picomatch@npm:4.0.2" + checksum: 10c0/7c51f3ad2bb42c776f49ebf964c644958158be30d0a510efd5a395e8d49cb5acfed5b82c0c5b365523ce18e6ab85013c9ebe574f60305892ec3fa8eee8304ccc languageName: node linkType: hard @@ -2461,7 +2461,7 @@ __metadata: languageName: node linkType: hard -"pretty-ms@npm:^9.0.0": +"pretty-ms@npm:^9.0.0, pretty-ms@npm:^9.1.0": version: 9.1.0 resolution: "pretty-ms@npm:9.1.0" dependencies: @@ -2659,12 +2659,12 @@ __metadata: languageName: node linkType: hard -"ses@npm:^1.9.0": - version: 1.9.0 - resolution: "ses@npm:1.9.0" +"ses@npm:^1.9.1": + version: 1.9.1 + resolution: "ses@npm:1.9.1" dependencies: "@endo/env-options": "npm:^1.1.7" - checksum: 10c0/356f9601b04a87f33403a15fc627caf0c649d86d8d7ee1f4b3c75b947ab05c31b254c94c0aa26e9904862787c73950d5a60f3435deebe5dba23017e20c40b0cb + checksum: 10c0/1e795542954f635aaee2749a1d548460f2978257cb29daaea76b814ef99ffa64ab5cca05fbc3d51a814a57cf9fc4563988ee93312cc53bae4eb63dfff0f0682a languageName: node linkType: hard @@ -2956,8 +2956,8 @@ __metadata: linkType: hard "tsx@npm:^4.17.0": - version: 4.19.1 - resolution: "tsx@npm:4.19.1" + version: 4.19.2 + resolution: "tsx@npm:4.19.2" dependencies: esbuild: "npm:~0.23.0" fsevents: "npm:~2.3.3" @@ -2967,7 +2967,7 @@ __metadata: optional: true bin: tsx: dist/cli.mjs - checksum: 10c0/cbea9baf57e7406fa0ecc2c03b9bb2501ee740dc28c938f949180a646a28e5d65e7cccbfba340508923bfd45e90320ef9eef7f815cae4515b6ef2ee429edc7ee + checksum: 10c0/63164b889b1d170403e4d8753a6755dec371f220f5ce29a8e88f1f4d6085a784a12d8dc2ee669116611f2c72757ac9beaa3eea5c452796f541bdd2dc11753721 languageName: node linkType: hard @@ -3130,13 +3130,13 @@ __metadata: languageName: node linkType: hard -"write-file-atomic@npm:^5.0.1": - version: 5.0.1 - resolution: "write-file-atomic@npm:5.0.1" +"write-file-atomic@npm:^6.0.0": + version: 6.0.0 + resolution: "write-file-atomic@npm:6.0.0" dependencies: imurmurhash: "npm:^0.1.4" signal-exit: "npm:^4.0.1" - checksum: 10c0/e8c850a8e3e74eeadadb8ad23c9d9d63e4e792bd10f4836ed74189ef6e996763959f1249c5650e232f3c77c11169d239cbfc8342fc70f3fe401407d23810505d + checksum: 10c0/ae2f1c27474758a9aca92037df6c1dd9cb94c4e4983451210bd686bfe341f142662f6aa5913095e572ab037df66b1bfe661ed4ce4c0369ed0e8219e28e141786 languageName: node linkType: hard From 1ea3f06e705b34b50dffa069f4f469f8d9a8184e Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Mon, 28 Oct 2024 03:50:12 +0000 Subject: [PATCH 42/56] fix(internal): better stream error handling --- packages/internal/src/node/fs-stream.js | 51 ++++++++++++++----------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/packages/internal/src/node/fs-stream.js b/packages/internal/src/node/fs-stream.js index eb7f335f45c..cdb09e1a6ec 100644 --- a/packages/internal/src/node/fs-stream.js +++ b/packages/internal/src/node/fs-stream.js @@ -56,31 +56,35 @@ export const makeFsStreamWriter = async filePath => { let flushed = Promise.resolve(); let closed = false; - const write = async data => { - if (closed) { - throw Error('Stream closed'); - } - - /** @type {Promise} */ - const written = new Promise((resolve, reject) => { - stream.write(data, err => { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); + const updateFlushed = p => { flushed = flushed.then( - () => written, - async err => - Promise.reject( - written.then( - () => err, - writtenError => AggregateError([err, writtenError]), - ), + () => p, + err => + p.then( + () => Promise.reject(err), + pError => + Promise.reject( + pError !== err ? AggregateError([err, pError]) : err, + ), ), ); + flushed.catch(() => {}); + }; + + const write = async data => { + /** @type {Promise} */ + const written = closed + ? Promise.reject(Error('Stream closed')) + : new Promise((resolve, reject) => { + stream.write(data, err => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + updateFlushed(written); return written; }; @@ -95,10 +99,13 @@ export const makeFsStreamWriter = async filePath => { }; const close = async () => { + // TODO: Consider creating a single Error here to use a write rejection closed = true; await flush(); stream.close(); }; + stream.on('error', err => updateFlushed(Promise.reject(err))); + return harden({ write, flush, close }); }; From 91089d7273ef3d41555b34d84471120d45602497 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Mon, 28 Oct 2024 03:51:17 +0000 Subject: [PATCH 43/56] fix(telemetry): silence slogfile write errors They'll be reported on flush --- packages/telemetry/src/slog-file.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/telemetry/src/slog-file.js b/packages/telemetry/src/slog-file.js index 3c091211a5f..e7b49d559bd 100644 --- a/packages/telemetry/src/slog-file.js +++ b/packages/telemetry/src/slog-file.js @@ -11,7 +11,7 @@ export const makeSlogSender = async ({ env: { SLOGFILE } = {} } = {}) => { const slogSender = (slogObj, jsonObj = serializeSlogObj(slogObj)) => { // eslint-disable-next-line prefer-template - void stream.write(jsonObj + '\n'); + stream.write(jsonObj + '\n').catch(() => {}); }; return Object.assign(slogSender, { From d4b8dfa91155789f7ceda5cc3cef06019b9527e7 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Mon, 28 Oct 2024 03:52:30 +0000 Subject: [PATCH 44/56] fix(telemetry): avoid polluting stdout in ingest-slog --- packages/telemetry/src/ingest-slog-entrypoint.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/telemetry/src/ingest-slog-entrypoint.js b/packages/telemetry/src/ingest-slog-entrypoint.js index 3a10d8463f2..baead039bbe 100755 --- a/packages/telemetry/src/ingest-slog-entrypoint.js +++ b/packages/telemetry/src/ingest-slog-entrypoint.js @@ -78,7 +78,7 @@ async function run() { fs.writeFileSync(progressFileName, JSON.stringify(progress)); }; - console.log(`parsing`, slogFileName); + console.warn(`parsing`, slogFileName); let update = false; const maybeUpdateStats = async now => { @@ -136,7 +136,7 @@ async function run() { } await stats(true); - console.log( + console.warn( `done parsing`, slogFileName, `(${lineCount} lines, ${byteCount} bytes)`, From b4af8296e8af37eecf80449870c18546e4c8856a Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Mon, 28 Oct 2024 03:54:04 +0000 Subject: [PATCH 45/56] feat(internal): fs stream to stdout --- packages/internal/src/node/fs-stream.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/internal/src/node/fs-stream.js b/packages/internal/src/node/fs-stream.js index cdb09e1a6ec..8e7ae36a48b 100644 --- a/packages/internal/src/node/fs-stream.js +++ b/packages/internal/src/node/fs-stream.js @@ -1,8 +1,11 @@ import { createWriteStream } from 'node:fs'; +import process from 'node:process'; import { open } from 'node:fs/promises'; /** - * @param {import('fs').ReadStream | import('fs').WriteStream} stream + * @param {import('fs').ReadStream + * | import('fs').WriteStream + * | import('net').Socket} stream * @returns {Promise} */ export const fsStreamReady = stream => @@ -48,9 +51,11 @@ export const makeFsStreamWriter = async filePath => { return undefined; } - const handle = await open(filePath, 'a'); + const handle = await (filePath !== '-' ? open(filePath, 'a') : undefined); - const stream = createWriteStream(noPath, { fd: handle.fd }); + const stream = handle + ? createWriteStream(noPath, { fd: handle.fd }) + : process.stdout; await fsStreamReady(stream); let flushed = Promise.resolve(); @@ -90,7 +95,7 @@ export const makeFsStreamWriter = async filePath => { const flush = async () => { await flushed; - await handle.sync().catch(err => { + await handle?.sync().catch(err => { if (err.code === 'EINVAL') { return; } @@ -102,7 +107,8 @@ export const makeFsStreamWriter = async filePath => { // TODO: Consider creating a single Error here to use a write rejection closed = true; await flush(); - stream.close(); + // @ts-expect-error calling a possibly missing method + stream.close?.(); }; stream.on('error', err => updateFlushed(Promise.reject(err))); From 63367c4aaf9bafbd6553a1f4cb808c96bc90845a Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Mon, 28 Oct 2024 03:55:29 +0000 Subject: [PATCH 46/56] feat(telemetry): ingest-slog explicitly supports `-` for stdin --- packages/telemetry/src/ingest-slog-entrypoint.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/telemetry/src/ingest-slog-entrypoint.js b/packages/telemetry/src/ingest-slog-entrypoint.js index baead039bbe..3d48ae2d1fe 100755 --- a/packages/telemetry/src/ingest-slog-entrypoint.js +++ b/packages/telemetry/src/ingest-slog-entrypoint.js @@ -29,7 +29,7 @@ async function run() { return; } - const [slogFile] = args; + const slogFile = args[0] === '-' ? undefined : args[0]; const slogSender = await makeSlogSender({ serviceName, stateDir: '.', From 62589ca7b6d4aaa9eb7042f95ec7aec633db27f9 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Mon, 28 Oct 2024 03:56:21 +0000 Subject: [PATCH 47/56] fix(telemetry): ingest-slog avoid writing progress file for stdin --- .../telemetry/src/ingest-slog-entrypoint.js | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/telemetry/src/ingest-slog-entrypoint.js b/packages/telemetry/src/ingest-slog-entrypoint.js index 3d48ae2d1fe..73e54fb700c 100755 --- a/packages/telemetry/src/ingest-slog-entrypoint.js +++ b/packages/telemetry/src/ingest-slog-entrypoint.js @@ -56,12 +56,18 @@ async function run() { const lines = readline.createInterface({ input: slogF }); const slogFileName = slogFile || '*stdin*'; - const progressFileName = `${slogFileName}.ingest-progress`; - if (!fs.existsSync(progressFileName)) { - const progress = { virtualTimeOffset: 0, lastSlogTime: 0 }; - fs.writeFileSync(progressFileName, JSON.stringify(progress)); + const progressFileName = slogFile && `${slogFileName}.ingest-progress`; + const progress = { virtualTimeOffset: 0, lastSlogTime: 0 }; + if (progressFileName) { + if (!fs.existsSync(progressFileName)) { + fs.writeFileSync(progressFileName, JSON.stringify(progress)); + } else { + Object.assign( + progress, + JSON.parse(fs.readFileSync(progressFileName).toString()), + ); + } } - const progress = JSON.parse(fs.readFileSync(progressFileName).toString()); let linesProcessedThisPeriod = 0; let startOfLastPeriod = 0; @@ -75,7 +81,9 @@ async function run() { return; } await slogSender.forceFlush?.(); - fs.writeFileSync(progressFileName, JSON.stringify(progress)); + if (progressFileName) { + fs.writeFileSync(progressFileName, JSON.stringify(progress)); + } }; console.warn(`parsing`, slogFileName); From 21349448b3b9379a9da43218a59a7e7eaf4f5a9e Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Mon, 28 Oct 2024 03:57:47 +0000 Subject: [PATCH 48/56] feat(telemetry): ingest-slog throttle and flush per block --- .../telemetry/src/ingest-slog-entrypoint.js | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/telemetry/src/ingest-slog-entrypoint.js b/packages/telemetry/src/ingest-slog-entrypoint.js index 73e54fb700c..ee6c6fca36e 100755 --- a/packages/telemetry/src/ingest-slog-entrypoint.js +++ b/packages/telemetry/src/ingest-slog-entrypoint.js @@ -11,7 +11,8 @@ import { makeSlogSender } from './make-slog-sender.js'; const LINE_COUNT_TO_FLUSH = 10000; const ELAPSED_MS_TO_FLUSH = 3000; -const MAX_LINE_COUNT_PER_PERIOD = 1000; +const MAX_LINE_COUNT_PER_PERIOD = 10000; +const MAX_BLOCKS_PER_PERIOD = 10; const PROCESSING_PERIOD = 1000; async function run() { @@ -70,6 +71,7 @@ async function run() { } let linesProcessedThisPeriod = 0; + let blocksInThisPeriod = 0; let startOfLastPeriod = 0; let lastTime = Date.now(); @@ -114,9 +116,14 @@ async function run() { continue; } + const isAfterCommit = obj.type === 'cosmic-swingset-after-commit-stats'; + // Maybe wait for the next period to process a bunch of lines. let maybeWait; - if (linesProcessedThisPeriod >= MAX_LINE_COUNT_PER_PERIOD) { + if ( + linesProcessedThisPeriod >= MAX_LINE_COUNT_PER_PERIOD || + blocksInThisPeriod >= MAX_BLOCKS_PER_PERIOD + ) { const delayMS = PROCESSING_PERIOD - (now - startOfLastPeriod); maybeWait = new Promise(resolve => setTimeout(resolve, delayMS)); } @@ -126,8 +133,8 @@ async function run() { if (now - startOfLastPeriod >= PROCESSING_PERIOD) { startOfLastPeriod = now; linesProcessedThisPeriod = 0; + blocksInThisPeriod = 0; } - linesProcessedThisPeriod += 1; if (progress.virtualTimeOffset) { const virtualTime = obj.time + progress.virtualTimeOffset; @@ -141,6 +148,13 @@ async function run() { // Use the original. slogSender(obj); } + + linesProcessedThisPeriod += 1; + if (isAfterCommit) { + blocksInThisPeriod += 1; + lastTime = Date.now(); + await stats(true); + } } await stats(true); From acbd3ae486c3749b66bf068dcca83585a34f9110 Mon Sep 17 00:00:00 2001 From: Usman Saleem <57341641+usmanmani1122@users.noreply.github.com> Date: Tue, 29 Oct 2024 05:19:55 +0500 Subject: [PATCH 49/56] Context building slog sender (#10300) closes: #10269 ## Description Adds a slog sender which will build various contexts along the way and report them along with the slogs for better logs querying and identification ### Security Considerations None ### Scaling Considerations This uses a json file storage ### Documentation Considerations This is a new slogger which can be opted into ### Testing Considerations This will be deployed on testnets (already deployed on one of the testnets and log link is added in a comment below) ### Upgrade Considerations This can be configured on existing deployments by bumping the telemetry package --- packages/telemetry/package.json | 3 + .../telemetry/src/context-aware-slog-file.js | 42 +++ packages/telemetry/src/context-aware-slog.js | 345 ++++++++++++++++++ .../telemetry/src/otel-context-aware-slog.js | 131 +++++++ yarn.lock | 92 ++++- 5 files changed, 607 insertions(+), 6 deletions(-) create mode 100644 packages/telemetry/src/context-aware-slog-file.js create mode 100644 packages/telemetry/src/context-aware-slog.js create mode 100644 packages/telemetry/src/otel-context-aware-slog.js diff --git a/packages/telemetry/package.json b/packages/telemetry/package.json index 4ca8fa5f285..cd0cb0585c0 100644 --- a/packages/telemetry/package.json +++ b/packages/telemetry/package.json @@ -29,9 +29,12 @@ "@endo/marshal": "^1.6.1", "@endo/stream": "^1.2.7", "@opentelemetry/api": "~1.3.0", + "@opentelemetry/api-logs": "0.53.0", "@opentelemetry/exporter-prometheus": "~0.35.0", + "@opentelemetry/exporter-logs-otlp-http": "0.53.0", "@opentelemetry/exporter-trace-otlp-http": "~0.35.0", "@opentelemetry/resources": "~1.9.0", + "@opentelemetry/sdk-logs": "0.53.0", "@opentelemetry/sdk-metrics": "~1.9.0", "@opentelemetry/sdk-trace-base": "~1.9.0", "@opentelemetry/semantic-conventions": "~1.27.0", diff --git a/packages/telemetry/src/context-aware-slog-file.js b/packages/telemetry/src/context-aware-slog-file.js new file mode 100644 index 00000000000..003680fc2bd --- /dev/null +++ b/packages/telemetry/src/context-aware-slog-file.js @@ -0,0 +1,42 @@ +/* eslint-env node */ + +import { makeFsStreamWriter } from '@agoric/internal/src/node/fs-stream.js'; +import { makeContextualSlogProcessor } from './context-aware-slog.js'; +import { serializeSlogObj } from './serialize-slog-obj.js'; + +/** + * @param {import('./index.js').MakeSlogSenderOptions} options + */ +export const makeSlogSender = async options => { + const { CHAIN_ID, CONTEXTUAL_SLOGFILE } = options.env || {}; + if (!CONTEXTUAL_SLOGFILE) + return console.warn( + 'Ignoring invocation of slogger "context-aware-slog-file" without the presence of "CONTEXTUAL_SLOGFILE"', + ); + + const stream = await makeFsStreamWriter(CONTEXTUAL_SLOGFILE); + + if (!stream) + return console.error( + `Couldn't create a write stream on file "${CONTEXTUAL_SLOGFILE}"`, + ); + + const contextualSlogProcessor = makeContextualSlogProcessor({ + 'chain-id': CHAIN_ID, + }); + + /** + * @param {import('./context-aware-slog.js').Slog} slog + */ + const slogSender = slog => { + const contextualizedSlog = contextualSlogProcessor(slog); + + // eslint-disable-next-line prefer-template + stream.write(serializeSlogObj(contextualizedSlog) + '\n').catch(() => {}); + }; + + return Object.assign(slogSender, { + forceFlush: () => stream.flush(), + shutdown: () => stream.close(), + }); +}; diff --git a/packages/telemetry/src/context-aware-slog.js b/packages/telemetry/src/context-aware-slog.js new file mode 100644 index 00000000000..34bb4867422 --- /dev/null +++ b/packages/telemetry/src/context-aware-slog.js @@ -0,0 +1,345 @@ +/* eslint-env node */ + +/** + * @typedef {Partial<{ + * 'block.height': Slog['blockHeight']; + * 'block.time': Slog['blockTime']; + * 'crank.deliveryNum': Slog['deliveryNum']; + * 'crank.num': Slog['crankNum']; + * 'crank.type': Slog['crankType']; + * 'crank.vatID': Slog['vatID']; + * init: boolean; + * replay: boolean; + * 'run.id': string; + * 'run.num': string | null; + * 'run.trigger.blockHeight': Slog['blockHeight']; + * 'run.trigger.msgIdx': number; + * 'run.trigger.sender': Slog['sender']; + * 'run.trigger.source': Slog['source']; + * 'run.trigger.time': Slog['blockTime']; + * 'run.trigger.txHash': string; + * 'run.trigger.type': string; + * }> + * } Context + * + * @typedef {{ + * 'crank.syscallNum'?: Slog['syscallNum']; + * 'process.uptime': Slog['monotime']; + * } & Context} LogAttributes + * + * @typedef {{ + * blockHeight?: number; + * blockTime?: number; + * crankNum?: bigint; + * crankType?: string; + * deliveryNum?: bigint; + * inboundNum?: string; + * monotime: number; + * remainingBeans?: bigint; + * replay?: boolean; + * runNum?: number; + * sender?: string; + * source?: string; + * syscallNum?: number; + * time: number; + * type: string; + * vatID?: string; + * }} Slog + */ + +const SLOG_TYPES = { + CLIST: 'clist', + CONSOLE: 'console', + COSMIC_SWINGSET: { + AFTER_COMMIT_STATS: 'cosmic-swingset-after-commit-stats', + BEGIN_BLOCK: 'cosmic-swingset-begin-block', + BOOTSTRAP_BLOCK: { + FINISH: 'cosmic-swingset-bootstrap-block-finish', + START: 'cosmic-swingset-bootstrap-block-start', + }, + BRIDGE_INBOUND: 'cosmic-swingset-bridge-inbound', + COMMIT: { + FINISH: 'cosmic-swingset-commit-finish', + START: 'cosmic-swingset-commit-start', + }, + DELIVER_INBOUND: 'cosmic-swingset-deliver-inbound', + END_BLOCK: { + FINISH: 'cosmic-swingset-end-block-finish', + START: 'cosmic-swingset-end-block-start', + }, + // eslint-disable-next-line no-restricted-syntax + RUN: { + FINISH: 'cosmic-swingset-run-finish', + START: 'cosmic-swingset-run-start', + }, + }, + CRANK: { + FINISH: 'crank-finish', + START: 'crank-start', + }, + DELIVER: 'deliver', + DELIVER_RESULT: 'deliver-result', + KERNEL: { + INIT: { + FINISH: 'kernel-init-finish', + START: 'kernel-init-start', + }, + }, + REPLAY: { + FINISH: 'finish-replay', + START: 'start-replay', + }, + SYSCALL: 'syscall', + SYSCALL_RESULT: 'syscall-result', +}; + +/** + * @template {Record} [T={}] + * @param {T} [staticContext] + * @param {Partial<{ persistContext: (context: Context) => void; restoreContext: () => Context | null; }>} [persistenceUtils] + */ +export const makeContextualSlogProcessor = ( + staticContext, + persistenceUtils = {}, +) => { + /** @type Array */ + let [ + blockContext, + crankContext, + initContext, + lastPersistedTriggerContext, + replayContext, + triggerContext, + ] = [null, null, null, null, null, null]; + + /** + * @param {Context} context + */ + const persistContext = context => { + lastPersistedTriggerContext = context; + return persistenceUtils?.persistContext?.(context); + }; + + const restoreContext = () => { + if (!lastPersistedTriggerContext) + lastPersistedTriggerContext = + persistenceUtils?.restoreContext?.() || null; + return lastPersistedTriggerContext; + }; + + /** + * @param {Slog} slog + * @returns {{ attributes: T & LogAttributes, body: Partial; timestamp: Slog['time'] }} + */ + const slogProcessor = ({ monotime, time: timestamp, ...body }) => { + const finalBody = { ...body }; + + /** @type {{'crank.syscallNum'?: Slog['syscallNum']}} */ + const eventLogAttributes = {}; + + /** + * Add any before report operations here + * like setting context data + */ + switch (body.type) { + case SLOG_TYPES.KERNEL.INIT.START: { + initContext = { init: true }; + break; + } + case SLOG_TYPES.COSMIC_SWINGSET.BEGIN_BLOCK: { + blockContext = { + 'block.height': finalBody.blockHeight, + 'block.time': finalBody.blockTime, + }; + break; + } + case SLOG_TYPES.COSMIC_SWINGSET.BOOTSTRAP_BLOCK.START: { + blockContext = { + 'block.height': finalBody.blockHeight || 0, + 'block.time': finalBody.blockTime, + }; + break; + } + case SLOG_TYPES.COSMIC_SWINGSET.END_BLOCK.START: + case SLOG_TYPES.COSMIC_SWINGSET.END_BLOCK.FINISH: + case SLOG_TYPES.COSMIC_SWINGSET.BOOTSTRAP_BLOCK.FINISH: + case SLOG_TYPES.COSMIC_SWINGSET.COMMIT.START: + case SLOG_TYPES.COSMIC_SWINGSET.COMMIT.FINISH: + case SLOG_TYPES.COSMIC_SWINGSET.AFTER_COMMIT_STATS: { + assert(!!blockContext && !triggerContext); + break; + } + case SLOG_TYPES.COSMIC_SWINGSET.BRIDGE_INBOUND: + case SLOG_TYPES.COSMIC_SWINGSET.DELIVER_INBOUND: { + const [blockHeight, txHash, msgIdx] = ( + finalBody.inboundNum || '' + ).split('-'); + const [, triggerType] = + /cosmic-swingset-([^-]+)-inbound/.exec(body.type) || []; + + triggerContext = { + 'run.num': undefined, + 'run.id': `${triggerType}-${finalBody.inboundNum}`, + 'run.trigger.type': triggerType, + 'run.trigger.source': finalBody.source, + 'run.trigger.sender': finalBody.sender, + 'run.trigger.blockHeight': Number(blockHeight), + 'run.trigger.txHash': txHash, + 'run.trigger.msgIdx': Number(msgIdx), + }; + break; + } + // eslint-disable-next-line no-restricted-syntax + case SLOG_TYPES.COSMIC_SWINGSET.RUN.START: { + if (!finalBody.runNum) { + assert(!triggerContext); + triggerContext = restoreContext(); // Restore persisted context if any + } else if (!triggerContext) { + assert(!!blockContext); + // TODO: add explicit slog events of both timer poll and install bundle + // https://github.com/Agoric/agoric-sdk/issues/10332 + triggerContext = { + 'run.num': undefined, + 'run.id': `unknown-${finalBody.blockHeight}-${finalBody.runNum}`, + 'run.trigger.type': 'unknown', + 'run.trigger.blockHeight': finalBody.blockHeight, + }; + } + + if (!triggerContext) triggerContext = {}; + triggerContext['run.num'] = `${finalBody.runNum}`; + + break; + } + case SLOG_TYPES.CRANK.START: { + crankContext = { + 'crank.num': finalBody.crankNum, + 'crank.type': finalBody.crankType, + }; + break; + } + case SLOG_TYPES.CLIST: { + assert(!!crankContext); + crankContext['crank.vatID'] = finalBody.vatID; + break; + } + case SLOG_TYPES.REPLAY.START: + case SLOG_TYPES.REPLAY.FINISH: { + replayContext = { replay: true, 'crank.vatID': finalBody.vatID }; + break; + } + case SLOG_TYPES.DELIVER: { + if (replayContext) { + assert(finalBody.replay); + replayContext = { + ...replayContext, + 'crank.vatID': finalBody.vatID, + 'crank.deliveryNum': finalBody.deliveryNum, + }; + } else { + assert(!!crankContext && !finalBody.replay); + crankContext = { + ...crankContext, + 'crank.vatID': finalBody.vatID, + 'crank.deliveryNum': finalBody.deliveryNum, + }; + } + + delete finalBody.deliveryNum; + delete finalBody.replay; + + break; + } + case SLOG_TYPES.DELIVER_RESULT: { + delete finalBody.deliveryNum; + delete finalBody.replay; + + break; + } + case SLOG_TYPES.SYSCALL: + case SLOG_TYPES.SYSCALL_RESULT: { + eventLogAttributes['crank.syscallNum'] = finalBody.syscallNum; + + delete finalBody.deliveryNum; + delete finalBody.replay; + delete finalBody.syscallNum; + + break; + } + case SLOG_TYPES.CONSOLE: { + delete finalBody.crankNum; + delete finalBody.deliveryNum; + + break; + } + default: + // All other log types are logged as is (using existing contexts) without + // any change to the slogs or any contributions to the contexts. This also + // means that any unexpected slog type will pass through. To fix that, add + // all remaining cases of expected slog types above with a simple break + // statement and log a warning here + break; + } + + const logAttributes = { + ...staticContext, + 'process.uptime': monotime, + ...initContext, // Optional prelude + ...blockContext, // Block is the first level of execution nesting + ...triggerContext, // run and trigger info is nested next + ...crankContext, // Finally cranks are the last level of nesting + ...replayContext, // Replay is a substitute for crank context during vat page in + ...eventLogAttributes, + }; + + /** + * Add any after report operations here + * like resetting context data + */ + switch (body.type) { + case SLOG_TYPES.KERNEL.INIT.FINISH: { + initContext = null; + break; + } + case SLOG_TYPES.COSMIC_SWINGSET.BOOTSTRAP_BLOCK.START: { + triggerContext = { + 'run.num': undefined, + 'run.id': `bootstrap-${finalBody.blockTime}`, + 'run.trigger.type': 'bootstrap', + 'run.trigger.time': finalBody.blockTime, + }; + break; + } + case SLOG_TYPES.COSMIC_SWINGSET.AFTER_COMMIT_STATS: + case SLOG_TYPES.COSMIC_SWINGSET.BOOTSTRAP_BLOCK.FINISH: { + blockContext = null; + break; + } + // eslint-disable-next-line no-restricted-syntax + case SLOG_TYPES.COSMIC_SWINGSET.RUN.FINISH: { + assert(!!triggerContext); + persistContext(finalBody.remainingBeans ? {} : triggerContext); + triggerContext = null; + break; + } + case SLOG_TYPES.CRANK.FINISH: { + crankContext = null; + break; + } + case SLOG_TYPES.REPLAY.FINISH: { + replayContext = null; + break; + } + default: + break; + } + + return { + attributes: /** @type {T & LogAttributes} */ (logAttributes), + body: finalBody, + timestamp, + }; + }; + + return slogProcessor; +}; diff --git a/packages/telemetry/src/otel-context-aware-slog.js b/packages/telemetry/src/otel-context-aware-slog.js new file mode 100644 index 00000000000..51891460eba --- /dev/null +++ b/packages/telemetry/src/otel-context-aware-slog.js @@ -0,0 +1,131 @@ +/* eslint-env node */ +import { logs, SeverityNumber } from '@opentelemetry/api-logs'; +import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'; +import { Resource } from '@opentelemetry/resources'; +import { + LoggerProvider, + SimpleLogRecordProcessor, +} from '@opentelemetry/sdk-logs'; +import { readFileSync, writeFileSync } from 'fs'; +import { makeContextualSlogProcessor } from './context-aware-slog.js'; +import { getResourceAttributes } from './index.js'; +import { serializeSlogObj } from './serialize-slog-obj.js'; + +const DEFAULT_CONTEXT_FILE = 'slog-context.json'; +const FILE_ENCODING = 'utf8'; + +/** + * @param {string} filePath + */ +export const getContextFilePersistenceUtils = filePath => { + console.warn(`Using file ${filePath} for slogger context`); + + return { + /** + * @param {import('./context-aware-slog.js').Context} context + */ + persistContext: context => { + try { + writeFileSync(filePath, serializeSlogObj(context), FILE_ENCODING); + } catch (err) { + console.error('Error writing context to file: ', err); + } + }, + + /** + * @returns {import('./context-aware-slog.js').Context | null} + */ + restoreContext: () => { + try { + return JSON.parse(readFileSync(filePath, FILE_ENCODING)); + } catch (parseErr) { + console.error('Error reading context from file: ', parseErr); + return null; + } + }, + }; +}; + +/** + * @param {import('./index.js').MakeSlogSenderOptions} options + */ +export const makeSlogSender = async options => { + const { CHAIN_ID, OTEL_EXPORTER_OTLP_ENDPOINT } = options.env || {}; + if (!(OTEL_EXPORTER_OTLP_ENDPOINT && options.stateDir)) + return console.error( + 'Ignoring invocation of slogger "context-aware-slog" without the presence of "OTEL_EXPORTER_OTLP_ENDPOINT" and "stateDir"', + ); + + const loggerProvider = new LoggerProvider({ + resource: new Resource(getResourceAttributes(options)), + }); + + const otelLogExporter = new OTLPLogExporter({ keepAlive: true }); + const logRecordProcessor = new SimpleLogRecordProcessor(otelLogExporter); + + loggerProvider.addLogRecordProcessor(logRecordProcessor); + + logs.setGlobalLoggerProvider(loggerProvider); + const logger = logs.getLogger('default'); + + const persistenceUtils = getContextFilePersistenceUtils( + process.env.SLOG_CONTEXT_FILE_PATH || + `${options.stateDir}/${DEFAULT_CONTEXT_FILE}`, + ); + + const contextualSlogProcessor = makeContextualSlogProcessor( + { 'chain-id': CHAIN_ID }, + persistenceUtils, + ); + + /** + * @param {import('./context-aware-slog.js').Slog} slog + */ + const slogSender = slog => { + const { timestamp, ...logRecord } = contextualSlogProcessor(slog); + + const [secondsStr, fractionStr] = String(timestamp).split('.'); + const seconds = parseInt(secondsStr, 10); + const nanoSeconds = parseInt( + (fractionStr || String(0)).padEnd(9, String(0)).slice(0, 9), + 10, + ); + + logger.emit({ + ...JSON.parse(serializeSlogObj(logRecord)), + severityNumber: SeverityNumber.INFO, + timestamp: [seconds, nanoSeconds], + }); + }; + + const shutdown = async () => { + await Promise.resolve(); + const errors = []; + + try { + await logRecordProcessor.shutdown(); + } catch (err) { + errors.push(err); + } + + try { + await otelLogExporter.forceFlush(); + } catch (err) { + errors.push(err); + } + + switch (errors.length) { + case 0: + return; + case 1: + throw errors[0]; + default: + throw AggregateError(errors); + } + }; + + return Object.assign(slogSender, { + forceFlush: () => otelLogExporter.forceFlush(), + shutdown, + }); +}; diff --git a/yarn.lock b/yarn.lock index f2059f6e6bc..bb8a5ec3a88 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3042,6 +3042,13 @@ dependencies: "@octokit/openapi-types" "^18.0.0" +"@opentelemetry/api-logs@0.53.0": + version "0.53.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz#c478cbd8120ec2547b64edfa03a552cfe42170be" + integrity sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw== + dependencies: + "@opentelemetry/api" "^1.0.0" + "@opentelemetry/api@^1.0.0": version "1.4.1" resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.4.1.tgz#ff22eb2e5d476fbc2450a196e40dd243cc20c28f" @@ -3052,6 +3059,13 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.3.0.tgz#27c6f776ac3c1c616651e506a89f438a0ed6a055" integrity sha512-YveTnGNsFFixTKJz09Oi4zYkiLT5af3WpZDu4aIUM7xX+2bHAkOJayFTVQd6zB8kkWPpbua4Ha6Ql00grdLlJQ== +"@opentelemetry/core@1.26.0": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.26.0.tgz#7d84265aaa850ed0ca5813f97d831155be42b328" + integrity sha512-1iKxXXE8415Cdv0yjG3G6hQnB5eVEsJce3QaawX8SjDn0mAS0ZM8fAbZZJD4ajvhC15cePvosSCut404KrIIvQ== + dependencies: + "@opentelemetry/semantic-conventions" "1.27.0" + "@opentelemetry/core@1.9.1": version "1.9.1" resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.9.1.tgz#e343337e1a7bf30e9a6aef3ef659b9b76379762a" @@ -3066,6 +3080,17 @@ dependencies: "@opentelemetry/semantic-conventions" "1.15.2" +"@opentelemetry/exporter-logs-otlp-http@0.53.0": + version "0.53.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.53.0.tgz#1b4a152ea427ec4581532880fd0d620cc559cb11" + integrity sha512-cSRKgD/n8rb+Yd+Cif6EnHEL/VZg1o8lEcEwFji1lwene6BdH51Zh3feAD9p2TyVoBKrl6Q9Zm2WltSp2k9gWQ== + dependencies: + "@opentelemetry/api-logs" "0.53.0" + "@opentelemetry/core" "1.26.0" + "@opentelemetry/otlp-exporter-base" "0.53.0" + "@opentelemetry/otlp-transformer" "0.53.0" + "@opentelemetry/sdk-logs" "0.53.0" + "@opentelemetry/exporter-prometheus@~0.35.0": version "0.35.1" resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.35.1.tgz#c2fd5fcd17dac8106b33e5d354d98c47ebaa8804" @@ -3093,6 +3118,14 @@ dependencies: "@opentelemetry/core" "1.9.1" +"@opentelemetry/otlp-exporter-base@0.53.0": + version "0.53.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.53.0.tgz#dfe51874b869c687c3cb463b70cddda7de282762" + integrity sha512-UCWPreGQEhD6FjBaeDuXhiMf6kkBODF0ZQzrk/tuQcaVDJ+dDQ/xhJp192H9yWnKxVpEjFrSSLnpqmX4VwX+eA== + dependencies: + "@opentelemetry/core" "1.26.0" + "@opentelemetry/otlp-transformer" "0.53.0" + "@opentelemetry/otlp-transformer@0.35.1": version "0.35.1" resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-transformer/-/otlp-transformer-0.35.1.tgz#d4333b71324b83dbb1b0b3a4cfd769b3e214c6f9" @@ -3103,6 +3136,27 @@ "@opentelemetry/sdk-metrics" "1.9.1" "@opentelemetry/sdk-trace-base" "1.9.1" +"@opentelemetry/otlp-transformer@0.53.0": + version "0.53.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-transformer/-/otlp-transformer-0.53.0.tgz#55d435db5ed5cf56b99c010827294dd4921c45c2" + integrity sha512-rM0sDA9HD8dluwuBxLetUmoqGJKSAbWenwD65KY9iZhUxdBHRLrIdrABfNDP7aiTjcgK8XFyTn5fhDz7N+W6DA== + dependencies: + "@opentelemetry/api-logs" "0.53.0" + "@opentelemetry/core" "1.26.0" + "@opentelemetry/resources" "1.26.0" + "@opentelemetry/sdk-logs" "0.53.0" + "@opentelemetry/sdk-metrics" "1.26.0" + "@opentelemetry/sdk-trace-base" "1.26.0" + protobufjs "^7.3.0" + +"@opentelemetry/resources@1.26.0": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.26.0.tgz#da4c7366018bd8add1f3aa9c91c6ac59fd503cef" + integrity sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw== + dependencies: + "@opentelemetry/core" "1.26.0" + "@opentelemetry/semantic-conventions" "1.27.0" + "@opentelemetry/resources@1.9.1", "@opentelemetry/resources@~1.9.0": version "1.9.1" resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.9.1.tgz#5ad3d80ba968a3a0e56498ce4bc82a6a01f2682f" @@ -3111,6 +3165,23 @@ "@opentelemetry/core" "1.9.1" "@opentelemetry/semantic-conventions" "1.9.1" +"@opentelemetry/sdk-logs@0.53.0": + version "0.53.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-logs/-/sdk-logs-0.53.0.tgz#ec8b69278c4e683c13c58ed4285a47c27f5799c6" + integrity sha512-dhSisnEgIj/vJZXZV6f6KcTnyLDx/VuQ6l3ejuZpMpPlh9S1qMHiZU9NMmOkVkwwHkMy3G6mEBwdP23vUZVr4g== + dependencies: + "@opentelemetry/api-logs" "0.53.0" + "@opentelemetry/core" "1.26.0" + "@opentelemetry/resources" "1.26.0" + +"@opentelemetry/sdk-metrics@1.26.0": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-1.26.0.tgz#37bb0afb1d4447f50aab9cdd05db6f2d8b86103e" + integrity sha512-0SvDXmou/JjzSDOjUmetAAvcKQW6ZrvosU0rkbDGpXvvZN+pQF6JbK/Kd4hNdK4q/22yeruqvukXEJyySTzyTQ== + dependencies: + "@opentelemetry/core" "1.26.0" + "@opentelemetry/resources" "1.26.0" + "@opentelemetry/sdk-metrics@1.9.1", "@opentelemetry/sdk-metrics@~1.9.0": version "1.9.1" resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-1.9.1.tgz#babc162a81df9884c16b1e67c2dd26ab478f3080" @@ -3120,6 +3191,15 @@ "@opentelemetry/resources" "1.9.1" lodash.merge "4.6.2" +"@opentelemetry/sdk-trace-base@1.26.0": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.26.0.tgz#0c913bc6d2cfafd901de330e4540952269ae579c" + integrity sha512-olWQldtvbK4v22ymrKLbIcBi9L2SpMO84sCPY54IVsJhP9fRsxJT194C/AVaAuJzLE30EdhhM1VmvVYR7az+cw== + dependencies: + "@opentelemetry/core" "1.26.0" + "@opentelemetry/resources" "1.26.0" + "@opentelemetry/semantic-conventions" "1.27.0" + "@opentelemetry/sdk-trace-base@1.9.1", "@opentelemetry/sdk-trace-base@~1.9.0": version "1.9.1" resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.9.1.tgz#c349491b432a7e0ae7316f0b48b2d454d79d2b84" @@ -3134,16 +3214,16 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.15.2.tgz#3bafb5de3e20e841dff6cb3c66f4d6e9694c4241" integrity sha512-CjbOKwk2s+3xPIMcd5UNYQzsf+v94RczbdNix9/kQh38WiQkM90sUOi3if8eyHFgiBjBjhwXrA7W3ydiSQP9mw== +"@opentelemetry/semantic-conventions@1.27.0", "@opentelemetry/semantic-conventions@~1.27.0": + version "1.27.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz#1a857dcc95a5ab30122e04417148211e6f945e6c" + integrity sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg== + "@opentelemetry/semantic-conventions@1.9.1": version "1.9.1" resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.9.1.tgz#ad3367684a57879392513479e0a436cb2ac46dad" integrity sha512-oPQdbFDmZvjXk5ZDoBGXG8B4tSB/qW5vQunJWQMFUBp7Xe8O1ByPANueJ+Jzg58esEBegyyxZ7LRmfJr7kFcFg== -"@opentelemetry/semantic-conventions@~1.27.0": - version "1.27.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz#1a857dcc95a5ab30122e04417148211e6f945e6c" - integrity sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg== - "@parcel/watcher@2.0.4": version "2.0.4" resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.4.tgz#f300fef4cc38008ff4b8c29d92588eced3ce014b" @@ -10052,7 +10132,7 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= -protobufjs@^6.8.8, protobufjs@^7.2.4, protobufjs@^7.2.6: +protobufjs@^6.8.8, protobufjs@^7.2.4, protobufjs@^7.2.6, protobufjs@^7.3.0: version "7.4.0" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.4.0.tgz#7efe324ce9b3b61c82aae5de810d287bc08a248a" integrity sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw== From 21cf6ef646c8f6b3aca30c28862f9fdd8d40d33e Mon Sep 17 00:00:00 2001 From: Jorge-Lopes Date: Mon, 28 Oct 2024 13:19:31 +0000 Subject: [PATCH 50/56] chore(a3p): setup oracle and push price at use phase rel: https://github.com/Agoric/BytePitchPartnerEng/issues/26 --- .../proposals/n:upgrade-next/use.sh | 3 +++ .../n:upgrade-next/verifyPushedPrice.js | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 a3p-integration/proposals/n:upgrade-next/verifyPushedPrice.js diff --git a/a3p-integration/proposals/n:upgrade-next/use.sh b/a3p-integration/proposals/n:upgrade-next/use.sh index c68eaeafecc..fa6c4e42f65 100644 --- a/a3p-integration/proposals/n:upgrade-next/use.sh +++ b/a3p-integration/proposals/n:upgrade-next/use.sh @@ -5,3 +5,6 @@ set -uxeo pipefail node ./addGov4 ./acceptInvites.js + +./verifyPushedPrice.js 'ATOM' 12.01 +./verifyPushedPrice.js 'stATOM' 12.01 diff --git a/a3p-integration/proposals/n:upgrade-next/verifyPushedPrice.js b/a3p-integration/proposals/n:upgrade-next/verifyPushedPrice.js new file mode 100644 index 00000000000..98449c316a5 --- /dev/null +++ b/a3p-integration/proposals/n:upgrade-next/verifyPushedPrice.js @@ -0,0 +1,21 @@ +#!/usr/bin/env node + +import { + registerOraclesForBrand, + generateOracleMap, +} from '@agoric/synthetic-chain'; +import { argv } from 'node:process'; +import { verifyPushedPrice } from './test-lib/price-feed.js'; + +const brand = argv[2]; +const price = Number(argv[3]); + +const BASE_ID = 'n-upgrade'; +const ROUND_ID = 1; + +const oraclesByBrand = generateOracleMap(BASE_ID, [brand]); +await registerOraclesForBrand(brand, oraclesByBrand); +console.log(`Registering Oracle for ${brand}`); + +await verifyPushedPrice(price, brand, oraclesByBrand, ROUND_ID); +console.log(`Price pushed for ${brand}`); From 8d45260c53c91ef345a06fd7871f2cc29318f598 Mon Sep 17 00:00:00 2001 From: Jorge-Lopes Date: Mon, 28 Oct 2024 13:19:58 +0000 Subject: [PATCH 51/56] test(a3p): update test with new helper functions --- .../n:upgrade-next/priceFeedUpdate.test.js | 30 +++++--- .../n:upgrade-next/test-lib/price-feed.js | 62 ++++++++++++++++ .../n:upgrade-next/test-lib/sync-tools.js | 72 +++++++++++++++++++ 3 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 a3p-integration/proposals/n:upgrade-next/test-lib/price-feed.js create mode 100644 a3p-integration/proposals/n:upgrade-next/test-lib/sync-tools.js diff --git a/a3p-integration/proposals/n:upgrade-next/priceFeedUpdate.test.js b/a3p-integration/proposals/n:upgrade-next/priceFeedUpdate.test.js index 5cbacd019ad..d0bdaa1b055 100644 --- a/a3p-integration/proposals/n:upgrade-next/priceFeedUpdate.test.js +++ b/a3p-integration/proposals/n:upgrade-next/priceFeedUpdate.test.js @@ -14,10 +14,12 @@ import { getVaultPrices, getVatDetails, openVault, - pushPrices, - registerOraclesForBrand, USER1ADDR, } from '@agoric/synthetic-chain'; +import { + getPriceFeedRoundId, + verifyPushedPrice, +} from './test-lib/price-feed.js'; import { BID_OFFER_ID } from './agd-tools.js'; @@ -37,12 +39,17 @@ const checkPriceFeedVatsUpdated = async t => { await checkForOracle(t, 'stATOM'); }; -console.log('adding oracle for each brand'); -const oraclesByBrand = generateOracleMap('f-priceFeeds', ['ATOM', 'stATOM']); -await registerOraclesForBrand('ATOM', oraclesByBrand); -await registerOraclesForBrand('stATOM', oraclesByBrand); +/* + * The Oracle for ATOM and stATOM brands are being registered in the offer made at file: + * a3p-integration/proposals/n:upgrade-next/verifyPushedPrice.js + * which is being executed during the use phase of upgrade-next proposal + */ +const oraclesByBrand = generateOracleMap('n-upgrade', ['ATOM', 'stATOM']); -let roundId = 1; +const latestAtomRoundId = await getPriceFeedRoundId('ATOM'); +const latestStAtomRoundId = await getPriceFeedRoundId('stATOM'); +let atomRoundId = latestAtomRoundId + 1; +let stAtomRoundId = latestStAtomRoundId + 1; const tryPushPrices = async t => { // There are no old prices for the other currencies. @@ -52,9 +59,10 @@ const tryPushPrices = async t => { // t.is(stAtomOutPre, '+12010000'); t.log('pushing new prices'); - await pushPrices(13.4, 'ATOM', oraclesByBrand, roundId); - await pushPrices(13.7, 'stATOM', oraclesByBrand, roundId); - roundId += 1; + await verifyPushedPrice(13.4, 'ATOM', oraclesByBrand, atomRoundId); + await verifyPushedPrice(13.7, 'stATOM', oraclesByBrand, stAtomRoundId); + atomRoundId += 1; + stAtomRoundId += 1; t.log('awaiting new quotes'); const atomOut = await getPriceQuote('ATOM'); @@ -89,7 +97,7 @@ const openMarginalVault = async t => { }; const triggerAuction = async t => { - await pushPrices(5.2, 'ATOM', oraclesByBrand, roundId); + await verifyPushedPrice(5.2, 'ATOM', oraclesByBrand, atomRoundId); const atomOut = await getPriceQuote('ATOM'); t.is(atomOut, '+5200000'); diff --git a/a3p-integration/proposals/n:upgrade-next/test-lib/price-feed.js b/a3p-integration/proposals/n:upgrade-next/test-lib/price-feed.js new file mode 100644 index 00000000000..c0e2acd311d --- /dev/null +++ b/a3p-integration/proposals/n:upgrade-next/test-lib/price-feed.js @@ -0,0 +1,62 @@ +/* eslint-env node */ + +import { + agoric, + getContractInfo, + pushPrices, + getPriceQuote, +} from '@agoric/synthetic-chain'; +import { retryUntilCondition } from './sync-tools.js'; + +export const scale6 = x => BigInt(x * 1_000_000); + +/** + * + * @param {number} price + * @param {string} brand + * @param {Map} oraclesByBrand + * @param {number} roundId + * @returns {Promise} + */ +export const verifyPushedPrice = async ( + price, + brand, + oraclesByBrand, + roundId, +) => { + const pushPriceRetryOpts = { + maxRetries: 5, // arbitrary + retryIntervalMs: 5000, // in ms + }; + + await pushPrices(price, brand, oraclesByBrand, roundId); + console.log(`Pushing price ${price} for ${brand}`); + + await retryUntilCondition( + () => getPriceQuote(brand), + res => res === `+${scale6(price).toString()}`, + 'price not pushed yet', + { + log: console.log, + setTimeout: global.setTimeout, + ...pushPriceRetryOpts, + }, + ); + console.log(`Price ${price} pushed for ${brand}`); +}; + +/** + * + * @param {string} brand + * @returns {Promise} + */ +export const getPriceFeedRoundId = async brand => { + const latestRoundPath = `published.priceFeed.${brand}-USD_price_feed.latestRound`; + const latestRound = await getContractInfo(latestRoundPath, { + agoric, + prefix: '', + }); + + console.log('latestRound: ', latestRound); + return Number(latestRound.roundId); +}; diff --git a/a3p-integration/proposals/n:upgrade-next/test-lib/sync-tools.js b/a3p-integration/proposals/n:upgrade-next/test-lib/sync-tools.js new file mode 100644 index 00000000000..4a0e727c465 --- /dev/null +++ b/a3p-integration/proposals/n:upgrade-next/test-lib/sync-tools.js @@ -0,0 +1,72 @@ +/* eslint-env node */ + +/** + * @file These tools mostly duplicate code that will be added in other PRs + * and eventually migrated to synthetic-chain. Sorry for the duplication. + */ + +/** + * @typedef {object} RetryOptions + * @property {number} [maxRetries] + * @property {number} [retryIntervalMs] + * @property {(...arg0: string[]) => void} log + * @property {(object) => void} [setTimeout] + * @property {string} [errorMessage=Error] + */ + +const ambientSetTimeout = global.setTimeout; + +/** + * From https://github.com/Agoric/agoric-sdk/blob/442f07c8f0af03281b52b90e90c27131eef6f331/multichain-testing/tools/sleep.ts#L10 + * + * @param {number} ms + * @param {*} sleepOptions + */ +const sleep = (ms, { log = () => {}, setTimeout = ambientSetTimeout }) => + new Promise(resolve => { + log(`Sleeping for ${ms}ms...`); + setTimeout(resolve, ms); + }); + +/** + * From https://github.com/Agoric/agoric-sdk/blob/442f07c8f0af03281b52b90e90c27131eef6f331/multichain-testing/tools/sleep.ts#L24 + * + * @param {() => Promise} operation + * @param {(result: any) => boolean} condition + * @param {string} message + * @param {RetryOptions} options + */ +export const retryUntilCondition = async ( + operation, + condition, + message, + { maxRetries = 6, retryIntervalMs = 3500, log, setTimeout }, +) => { + console.log({ maxRetries, retryIntervalMs, message }); + let retries = 0; + + await null; + while (retries < maxRetries) { + try { + const result = await operation(); + log('RESULT', result); + if (condition(result)) { + return result; + } + } catch (error) { + if (error instanceof Error) { + log(`Error: ${error.message}`); + } else { + log(`Unknown error: ${String(error)}`); + } + } + + retries += 1; + console.log( + `Retry ${retries}/${maxRetries} - Waiting for ${retryIntervalMs}ms for ${message}...`, + ); + await sleep(retryIntervalMs, { log, setTimeout }); + } + + throw Error(`${message} condition failed after ${maxRetries} retries.`); +}; From 5a3ad840bd134bfc591cd9fb16e12258e384e847 Mon Sep 17 00:00:00 2001 From: Jorge-Lopes Date: Tue, 29 Oct 2024 09:15:19 +0000 Subject: [PATCH 52/56] fix(a3p): remove call to register oracle and push price from test rel: https://github.com/Agoric/BytePitchPartnerEng/issues/26 --- .../proposals/z:acceptance/vaults.test.js | 38 ------------------- 1 file changed, 38 deletions(-) diff --git a/a3p-integration/proposals/z:acceptance/vaults.test.js b/a3p-integration/proposals/z:acceptance/vaults.test.js index 5545a9381be..73f6c9433a2 100644 --- a/a3p-integration/proposals/z:acceptance/vaults.test.js +++ b/a3p-integration/proposals/z:acceptance/vaults.test.js @@ -8,53 +8,15 @@ import { adjustVault, closeVault, getISTBalance, - getPriceQuote, - pushPrices, getContractInfo, ATOM_DENOM, USER1ADDR, waitForBlock, - registerOraclesForBrand, - generateOracleMap, } from '@agoric/synthetic-chain'; import { getBalances, agopsVaults } from './test-lib/utils.js'; -import { retryUntilCondition } from './test-lib/sync-tools.js'; export const scale6 = x => BigInt(x * 1_000_000); -// There may be a new vaultFactory that doesn't have prices yet, so we publish -// prices now -test.before(async t => { - const pushPriceRetryOpts = { - maxRetries: 5, // arbitrary - retryIntervalMs: 5000, // in ms - }; - t.context = { - roundId: 1, - retryOpts: { - pushPriceRetryOpts, - }, - }; - const oraclesByBrand = generateOracleMap('z-acc', ['ATOM']); - await registerOraclesForBrand('ATOM', oraclesByBrand); - - const price = 15.2; - // @ts-expect-error t.context is fine - await pushPrices(price, 'ATOM', oraclesByBrand, t.context.roundId); - - await retryUntilCondition( - () => getPriceQuote('ATOM'), - res => res === `+${scale6(price).toString()}`, - 'price not pushed yet', - { - log: t.log, - setTimeout: global.setTimeout, - // @ts-expect-error t.context is fine - ...t.context.pushPriceRetryOpts, - }, - ); -}); - test.serial('attempt to open vaults under the minimum amount', async t => { const activeVaultsBefore = await agopsVaults(USER1ADDR); await bankSend(USER1ADDR, `20000000${ATOM_DENOM}`); From dedf1f3e320e31ab73366fe7c07a47805887ed3c Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Wed, 2 Oct 2024 02:39:40 +0300 Subject: [PATCH 53/56] chore: run auction, WIP Refs: https://github.com/Agoric/BytePitchPartnerEng/issues/8 --- .../proposals/z:acceptance/auction.test.js | 221 ++++++++++++++++++ .../z:acceptance/scripts/test-vaults.mts | 6 +- 2 files changed, 224 insertions(+), 3 deletions(-) create mode 100644 a3p-integration/proposals/z:acceptance/auction.test.js diff --git a/a3p-integration/proposals/z:acceptance/auction.test.js b/a3p-integration/proposals/z:acceptance/auction.test.js new file mode 100644 index 00000000000..ea45cb580be --- /dev/null +++ b/a3p-integration/proposals/z:acceptance/auction.test.js @@ -0,0 +1,221 @@ +/** + * @file In this file we aim to test auctioneer in an isolated manner. Here's the scenario to test; + * - Send 100 ATOMs to gov1 from validator + * - Make sure auctioneer params like ClockStep, StartFrequency are reduced + * - For book0, ATOM is collateral, set two types of bids; by price and by percentage, user1 is the bidder + * - Deposit some collateral into book0, gov1 is the depositor + * - Wait until placed bids get their payouts + * - Make sure the depositer gets correct amounts + */ + +import { + addPreexistingOracles, + agd, + agopsInter, + agoric, + ATOM_DENOM, + bankSend, + createBid, + executeOffer, + getLiveOffers, + getPriceQuote, + GOV1ADDR, + pushPrices, + USER1ADDR, + waitForBlock, +} from '@agoric/synthetic-chain'; +import '@endo/init'; +import test from 'ava'; +import { boardSlottingMarshaller, makeFromBoard } from './test-lib/rpc.js'; +import { + retryUntilCondition, + waitUntilAccountFunded, + waitUntilOfferResult, +} from './test-lib/sync-tools.js'; + +const ambientAuthroity = { + query: agd.query, + follow: agoric.follow, + setTimeout: globalThis.setTimeout, +}; + +const config = { + price: 9.99, + bidsSetup: [ + { + give: '80IST', + discount: 0.1, + }, + { + give: '90IST', + price: 9.0, + }, + { + give: '150IST', + discount: 0.15, + }, + ], + bidsOutcome: [ + { + payouts: { + Bid: 0, + Collateral: 8.897786, + }, + }, + { + payouts: { + Bid: 0, + Collateral: 10.01001, + }, + }, + { + payouts: { + Bid: 10.46, + Collateral: 16.432903, + }, + }, + ], +}; + +const oraclesByBrand = new Map(); + +let roundId = 2; + +const setupOracles = async t => { + await addPreexistingOracles('ATOM', oraclesByBrand); + + await pushPrices(9.99, 'ATOM', oraclesByBrand, roundId); + roundId += 1; + await retryUntilCondition( + () => getPriceQuote('ATOM'), + res => res === '+9990000', + 'error', + { log: t.log, setTimeout: globalThis.setTimeout }, + ); +}; + +const BID_OFFER_ID = `bid-acceptance-${Date.now()}`; +const DEPOSIT_OFFER_ID = `gov1-deposit-${Date.now()}`; + +const createNewBid = async t => { + await createBid('20', USER1ADDR, BID_OFFER_ID); + const liveOffers = await getLiveOffers(USER1ADDR); + t.true(liveOffers[0].includes(BID_OFFER_ID)); +}; + +const getBalance = async (target, addr) => { + const { balances } = await agd.query('bank', 'balances', addr); + const { amount } = balances.find(({ denom }) => denom === target); + return Number(amount); +}; + +const fundAccts = async ( + depositorAmt = '100000000', + bidderAmt = '100000000', +) => { + await Promise.all([ + bankSend(GOV1ADDR, `${depositorAmt}${ATOM_DENOM}`), + bankSend(USER1ADDR, `${bidderAmt}${ATOM_DENOM}`), + ]); + + await Promise.all([ + waitUntilAccountFunded( + GOV1ADDR, + ambientAuthroity, + { denom: 'uist', value: Number(depositorAmt) }, + { errorMessage: 'gov1 not funded yet' }, + ), + waitUntilAccountFunded( + USER1ADDR, + ambientAuthroity, + { denom: ATOM_DENOM, value: Number(bidderAmt) }, + { errorMessage: 'user1 not funded yet' }, + ), + ]); +}; + +const bidByPrice = (price, give, offerId) => { + agopsInter( + 'bid', + 'by-price', + `--price ${price}`, + `--give ${give}`, + '--from', + USER1ADDR, + '--keyring-backend test', + `--offer-id ${offerId}`, + ); + + return waitUntilOfferResult(USER1ADDR, offerId, true, ambientAuthroity, { + errorMessage: 'bid not settled yet', + maxRetries: 10, + retryIntervalMs: 10000, + }); +}; + +const depositCollateral = async t => { + const fromBoard = makeFromBoard(); + const marshaller = boardSlottingMarshaller(fromBoard.convertSlotToVal); + + const brandsRaw = await agoric.follow( + '-lF', + ':published.agoricNames.brand', + '-o', + 'text', + ); + const brands = Object.fromEntries( + marshaller.fromCapData(JSON.parse(brandsRaw)), + ); + t.log(brands); + + const offerSpec = { + id: DEPOSIT_OFFER_ID, + invitationSpec: { + source: 'agoricContract', + instancePath: ['auctioneer'], + callPipe: [['makeDepositInvitation']], + }, + proposal: { + give: { + Collateral: { brand: brands.ATOM, value: 100_000_000n }, + }, + }, + }; + + const spendAction = { + method: 'executeOffer', + offer: offerSpec, + }; + + const offer = JSON.stringify(marshaller.toCapData(harden(spendAction))); + t.log('OFFER', offer); + + executeOffer(GOV1ADDR, offer); + return waitUntilOfferResult(GOV1ADDR, DEPOSIT_OFFER_ID, true, ambientAuthroity, { + errorMessage: 'proceeds not distributed yet', + maxRetries: 10, + retryIntervalMs: 10000, + }); +}; + +test.only('run auction', async t => { + await setupOracles(t); + await fundAccts(); + const settleBidP = bidByPrice( + config.bidsSetup[1].price, + config.bidsSetup[1].give, + BID_OFFER_ID, + ); + const proceedsP = depositCollateral(t); + + await Promise.all([settleBidP, proceedsP]); + + const [gov1Results, user1Results] = await Promise.all([ + agoric.follow('-lF', `:published.wallet.${GOV1ADDR}`), + agoric.follow('-lF', `:published.wallet.${USER1ADDR}`), + ]); + t.log('GOV1', gov1Results.status.payouts); + t.log('USER1', user1Results.status.payouts); + t.log('DONE!'); + t.pass(); +}); diff --git a/a3p-integration/proposals/z:acceptance/scripts/test-vaults.mts b/a3p-integration/proposals/z:acceptance/scripts/test-vaults.mts index 7fdbcef510a..7a1df695135 100755 --- a/a3p-integration/proposals/z:acceptance/scripts/test-vaults.mts +++ b/a3p-integration/proposals/z:acceptance/scripts/test-vaults.mts @@ -15,9 +15,9 @@ import { 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 START_FREQUENCY = 120; // StartFrequency: 600s (auction runs every 10m) +const CLOCK_STEP = 10; // ClockStep: 20s (ensures auction completes in time) +const PRICE_LOCK_PERIOD = 60; const oraclesAddresses = [GOV1ADDR, GOV2ADDR]; const oracles = [] as { address: string; id: string }[]; From 5bbdde88cd1a82de34dbf6697195bd96964de69b Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Fri, 4 Oct 2024 17:09:47 +0300 Subject: [PATCH 54/56] chore(acceptance): support long living bids, improve robustness against time related complexities fix: apply change requests from PR review, resolve rebase conflicts, style fixes * change `performAction.js` name to `submitBid.js` * remove `Math.round` from `scale6` * update indexing of bids and other constants in `config` object to improve readability (`auction.test.js`) * move helper functions in `auction.test.js` to `test-lib/auction-lib.js` * move `lib/vaults.mts` to `test-lib/vaults.mts` and remove empty `lib` directory * let it be known `sync-tools.js` is a stand-in code for #10171 Refs: https://github.com/Agoric/BytePitchPartnerEng/issues/8 fix: style fixes fix(acceptance-auction): lint fixes --- .../proposals/n:upgrade-next/submitBid.js | 40 +++ .../proposals/n:upgrade-next/tsconfig.json | 3 +- .../proposals/n:upgrade-next/use.sh | 1 + .../proposals/z:acceptance/auction.test.js | 337 +++++++++--------- .../z:acceptance/scripts/test-vaults.mts | 8 +- .../z:acceptance/test-lib/auction-lib.js | 306 ++++++++++++++++ .../z:acceptance/test-lib/auction-lib.test.js | 122 +++++++ .../z:acceptance/{lib => test-lib}/vaults.mts | 0 .../proposals/z:acceptance/test.sh | 5 +- 9 files changed, 645 insertions(+), 177 deletions(-) create mode 100644 a3p-integration/proposals/n:upgrade-next/submitBid.js create mode 100644 a3p-integration/proposals/z:acceptance/test-lib/auction-lib.js create mode 100644 a3p-integration/proposals/z:acceptance/test-lib/auction-lib.test.js rename a3p-integration/proposals/z:acceptance/{lib => test-lib}/vaults.mts (100%) diff --git a/a3p-integration/proposals/n:upgrade-next/submitBid.js b/a3p-integration/proposals/n:upgrade-next/submitBid.js new file mode 100644 index 00000000000..36b17c62a29 --- /dev/null +++ b/a3p-integration/proposals/n:upgrade-next/submitBid.js @@ -0,0 +1,40 @@ +#!/usr/bin/env node + +import { + GOV1ADDR, + CHAINID, + agd, + agopsInter, + addUser, + waitForBlock, + provisionSmartWallet, + ATOM_DENOM, +} from '@agoric/synthetic-chain'; + +export const bankSend = (from, addr, wanted) => { + const chain = ['--chain-id', CHAINID]; + const fromArg = ['--from', from]; + const testKeyring = ['--keyring-backend', 'test']; + const noise = [...fromArg, ...chain, ...testKeyring, '--yes']; + + return agd.tx('bank', 'send', from, addr, wanted, ...noise); +}; + +const bidder = await addUser('long-living-bidder'); +console.log('BIDDDER', bidder); +await bankSend(GOV1ADDR, bidder, `80000000uist`); +console.log('IST sent'); +await provisionSmartWallet(bidder, `20000000ubld,100000000${ATOM_DENOM}`); +console.log('Provision sent'); +await waitForBlock(3); +console.log('Wait For Block done. Sending bid offer'); +agopsInter( + 'bid', + 'by-price', + `--price 49.0`, + `--give 80IST`, + '--from', + bidder, + '--keyring-backend test', + `--offer-id long-living-bid-for-acceptance`, +); diff --git a/a3p-integration/proposals/n:upgrade-next/tsconfig.json b/a3p-integration/proposals/n:upgrade-next/tsconfig.json index 39de5a422e9..835db5a0316 100644 --- a/a3p-integration/proposals/n:upgrade-next/tsconfig.json +++ b/a3p-integration/proposals/n:upgrade-next/tsconfig.json @@ -1,7 +1,8 @@ { "compilerOptions": { "target": "esnext", - "module": "esnext", + "module": "NodeNext", + "moduleResolution": "NodeNext", "allowJs": true, "checkJs": true, "strict": false, diff --git a/a3p-integration/proposals/n:upgrade-next/use.sh b/a3p-integration/proposals/n:upgrade-next/use.sh index fa6c4e42f65..129c3b92444 100644 --- a/a3p-integration/proposals/n:upgrade-next/use.sh +++ b/a3p-integration/proposals/n:upgrade-next/use.sh @@ -8,3 +8,4 @@ node ./addGov4 ./verifyPushedPrice.js 'ATOM' 12.01 ./verifyPushedPrice.js 'stATOM' 12.01 +./submitBid.js diff --git a/a3p-integration/proposals/z:acceptance/auction.test.js b/a3p-integration/proposals/z:acceptance/auction.test.js index ea45cb580be..91c633a9ba2 100644 --- a/a3p-integration/proposals/z:acceptance/auction.test.js +++ b/a3p-integration/proposals/z:acceptance/auction.test.js @@ -1,221 +1,216 @@ +/* eslint-env node */ /** * @file In this file we aim to test auctioneer in an isolated manner. Here's the scenario to test; - * - Send 100 ATOMs to gov1 from validator - * - Make sure auctioneer params like ClockStep, StartFrequency are reduced - * - For book0, ATOM is collateral, set two types of bids; by price and by percentage, user1 is the bidder - * - Deposit some collateral into book0, gov1 is the depositor + * + * - Prerequisites: In one of the earlier proposal(n:upgrade-next), a user called "long-living-bidder" + * has placed a bid where { give: 80IST, price: 49.0 } + * - Push price so that 1 ATOM is 50 ISTs + * - Wait until the auctioneer captures the price we just pushed + * - Fund actors + * - gov1 gets 100 ATOMs + * - user1 gets 90 ISTs + * - gov3 gets 150 ISTs + * - Place bids for user1 and gov3 following the values in "config" + * - Deposit 100 ATOMs into book0, gov1 is the depositor * - Wait until placed bids get their payouts - * - Make sure the depositer gets correct amounts + * - Wait until proceeds are distributed to the depositor + * - Make sure all actors receive the correct payouts */ +// Typo will be fixed with https://github.com/Agoric/agoric-sdk/pull/10171 +/** @typedef {import('./test-lib/sync-tools.js').RetryOptions} RetryOptions */ + import { - addPreexistingOracles, agd, - agopsInter, agoric, - ATOM_DENOM, - bankSend, - createBid, - executeOffer, - getLiveOffers, - getPriceQuote, + getUser, GOV1ADDR, - pushPrices, + GOV3ADDR, USER1ADDR, - waitForBlock, } from '@agoric/synthetic-chain'; import '@endo/init'; import test from 'ava'; import { boardSlottingMarshaller, makeFromBoard } from './test-lib/rpc.js'; +import { retryUntilCondition } from './test-lib/sync-tools.js'; import { - retryUntilCondition, - waitUntilAccountFunded, - waitUntilOfferResult, -} from './test-lib/sync-tools.js'; - -const ambientAuthroity = { + calculateRetryUntilNextStartTime, + checkBidsOutcome, + checkDepositOutcome, + checkPrice, + depositCollateral, + fundAccts, + getCapturedPrice, + placeBids, + pushPricesForAuction, + scale6, +} from './test-lib/auction-lib.js'; + +const ambientAuthority = { query: agd.query, follow: agoric.follow, - setTimeout: globalThis.setTimeout, + setTimeout, }; +const fromBoard = makeFromBoard(); +const marshaller = boardSlottingMarshaller(fromBoard.convertSlotToVal); + const config = { - price: 9.99, - bidsSetup: [ - { - give: '80IST', - discount: 0.1, - }, - { + depositor: { + name: 'gov1', + addr: GOV1ADDR, + depositValue: '100000000', + offerId: `gov1-deposit-${Date.now()}`, + }, + price: 50.0, + longLivingBidSetup: { + name: 'long-living-bidder', + // This bid is placed in an earlier proposal + give: '80IST', + }, + currentBidsSetup: { + user1: { + bidder: USER1ADDR, + bidderFund: { + value: 90000000, + denom: 'uist', + }, + offerId: `user1-bid-${Date.now()}`, give: '90IST', - price: 9.0, + price: 46, }, - { + gov3: { + bidder: GOV3ADDR, + bidderFund: { + value: 150000000, + denom: 'uist', + }, + offerId: `gov3-bid-${Date.now()}`, give: '150IST', - discount: 0.15, + discount: '13', }, - ], - bidsOutcome: [ - { + }, + bidsOutcome: { + longLivingBidder: { payouts: { Bid: 0, - Collateral: 8.897786, + Collateral: 1.68421, }, }, - { + user1: { payouts: { Bid: 0, - Collateral: 10.01001, + Collateral: 2.0, }, }, - { + gov3: { payouts: { - Bid: 10.46, - Collateral: 16.432903, + Bid: 0, + Collateral: 3.448275, }, }, - ], + }, }; -const oraclesByBrand = new Map(); - -let roundId = 2; - -const setupOracles = async t => { - await addPreexistingOracles('ATOM', oraclesByBrand); - - await pushPrices(9.99, 'ATOM', oraclesByBrand, roundId); - roundId += 1; - await retryUntilCondition( - () => getPriceQuote('ATOM'), - res => res === '+9990000', - 'error', - { log: t.log, setTimeout: globalThis.setTimeout }, - ); -}; - -const BID_OFFER_ID = `bid-acceptance-${Date.now()}`; -const DEPOSIT_OFFER_ID = `gov1-deposit-${Date.now()}`; - -const createNewBid = async t => { - await createBid('20', USER1ADDR, BID_OFFER_ID); - const liveOffers = await getLiveOffers(USER1ADDR); - t.true(liveOffers[0].includes(BID_OFFER_ID)); -}; - -const getBalance = async (target, addr) => { - const { balances } = await agd.query('bank', 'balances', addr); - const { amount } = balances.find(({ denom }) => denom === target); - return Number(amount); -}; - -const fundAccts = async ( - depositorAmt = '100000000', - bidderAmt = '100000000', -) => { - await Promise.all([ - bankSend(GOV1ADDR, `${depositorAmt}${ATOM_DENOM}`), - bankSend(USER1ADDR, `${bidderAmt}${ATOM_DENOM}`), - ]); - - await Promise.all([ - waitUntilAccountFunded( - GOV1ADDR, - ambientAuthroity, - { denom: 'uist', value: Number(depositorAmt) }, - { errorMessage: 'gov1 not funded yet' }, - ), - waitUntilAccountFunded( - USER1ADDR, - ambientAuthroity, - { denom: ATOM_DENOM, value: Number(bidderAmt) }, - { errorMessage: 'user1 not funded yet' }, - ), - ]); -}; - -const bidByPrice = (price, give, offerId) => { - agopsInter( - 'bid', - 'by-price', - `--price ${price}`, - `--give ${give}`, - '--from', - USER1ADDR, - '--keyring-backend test', - `--offer-id ${offerId}`, - ); - - return waitUntilOfferResult(USER1ADDR, offerId, true, ambientAuthroity, { - errorMessage: 'bid not settled yet', - maxRetries: 10, - retryIntervalMs: 10000, - }); -}; +test.before(async t => { + /** @type {RetryOptions} */ + const pushPriceRetryOpts = { + maxRetries: 5, // arbitrary + retryIntervalMs: 5000, // in ms + }; -const depositCollateral = async t => { - const fromBoard = makeFromBoard(); - const marshaller = boardSlottingMarshaller(fromBoard.convertSlotToVal); + /** @type {RetryOptions} */ + const bankSendRetryOpts = { + maxRetries: 3, // arbitrary + retryIntervalMs: 3000, // in ms + }; - const brandsRaw = await agoric.follow( + // Get current round id + const round = await agoric.follow( '-lF', - ':published.agoricNames.brand', - '-o', - 'text', - ); - const brands = Object.fromEntries( - marshaller.fromCapData(JSON.parse(brandsRaw)), + ':published.priceFeed.ATOM-USD_price_feed.latestRound', ); - t.log(brands); - - const offerSpec = { - id: DEPOSIT_OFFER_ID, - invitationSpec: { - source: 'agoricContract', - instancePath: ['auctioneer'], - callPipe: [['makeDepositInvitation']], - }, - proposal: { - give: { - Collateral: { brand: brands.ATOM, value: 100_000_000n }, - }, + t.context = { + roundId: parseInt(round.roundId, 10), + retryOpts: { + bankSendRetryOpts, + pushPriceRetryOpts, }, }; +}); - const spendAction = { - method: 'executeOffer', - offer: offerSpec, - }; - - const offer = JSON.stringify(marshaller.toCapData(harden(spendAction))); - t.log('OFFER', offer); - - executeOffer(GOV1ADDR, offer); - return waitUntilOfferResult(GOV1ADDR, DEPOSIT_OFFER_ID, true, ambientAuthroity, { - errorMessage: 'proceeds not distributed yet', - maxRetries: 10, - retryIntervalMs: 10000, - }); -}; +test('run auction', async t => { + // Push the price to a point where only our bids can settle + await pushPricesForAuction(t, config.price); -test.only('run auction', async t => { - await setupOracles(t); - await fundAccts(); - const settleBidP = bidByPrice( - config.bidsSetup[1].price, - config.bidsSetup[1].give, - BID_OFFER_ID, + // Wait until next round starts. Retry error message is useful for debugging + const retryOptions = await calculateRetryUntilNextStartTime(); + await retryUntilCondition( + () => getCapturedPrice('book0'), + res => checkPrice(res, scale6(config.price).toString()), // scale price to uist + 'price not captured yet [AUCTION TEST]', + { + log: t.log, + ...ambientAuthority, + ...retryOptions, + }, ); - const proceedsP = depositCollateral(t); - await Promise.all([settleBidP, proceedsP]); + // Make sure depositor and bidders have enough balance + await fundAccts(t, config.depositor, config.currentBidsSetup); + const bidsP = placeBids(t, config.currentBidsSetup); + const proceedsP = depositCollateral(t, config.depositor); - const [gov1Results, user1Results] = await Promise.all([ - agoric.follow('-lF', `:published.wallet.${GOV1ADDR}`), - agoric.follow('-lF', `:published.wallet.${USER1ADDR}`), + // Resolves when auction finalizes and depositor gets payouts + const [longLivingBidderAddr] = await Promise.all([ + getUser(config.longLivingBidSetup.name), + ...bidsP, + proceedsP, ]); - t.log('GOV1', gov1Results.status.payouts); - t.log('USER1', user1Results.status.payouts); - t.log('DONE!'); - t.pass(); + + // Query wallets of the actors involved for assertions + const [gov1Results, longLivingBidResults, user1Results, gov3Results, brands] = + await Promise.all([ + agoric + .follow( + '-lF', + `:published.wallet.${config.depositor.addr}`, + '-o', + 'text', + ) + .then(res => marshaller.fromCapData(JSON.parse(res))), + agoric + .follow( + '-lF', + `:published.wallet.${longLivingBidderAddr}`, + '-o', + 'text', + ) + .then(res => marshaller.fromCapData(JSON.parse(res))), + agoric + .follow('-lF', `:published.wallet.${USER1ADDR}`, '-o', 'text') + .then(res => marshaller.fromCapData(JSON.parse(res))), + agoric + .follow('-lF', `:published.wallet.${GOV3ADDR}`, '-o', 'text') + .then(res => marshaller.fromCapData(JSON.parse(res))), + agoric + .follow('-lF', ':published.agoricNames.brand', '-o', 'text') + .then(res => + Object.fromEntries(marshaller.fromCapData(JSON.parse(res))), + ), + ]); + + // Assert depositor paid correctly + checkDepositOutcome(t, gov1Results.status.payouts, config, brands); + + // Assert bidders paid correctly + checkBidsOutcome( + t, + { + 'longLivingBidder.results': longLivingBidResults, + 'user1.results': user1Results, + 'gov3.results': gov3Results, + }, + config.bidsOutcome, + brands, + ); }); diff --git a/a3p-integration/proposals/z:acceptance/scripts/test-vaults.mts b/a3p-integration/proposals/z:acceptance/scripts/test-vaults.mts index 7a1df695135..6aa5cf15577 100755 --- a/a3p-integration/proposals/z:acceptance/scripts/test-vaults.mts +++ b/a3p-integration/proposals/z:acceptance/scripts/test-vaults.mts @@ -13,11 +13,11 @@ import { ISTunit, provisionWallet, setDebtLimit, -} from '../lib/vaults.mjs'; +} from '../test-lib/vaults.mjs'; -const START_FREQUENCY = 120; // StartFrequency: 600s (auction runs every 10m) -const CLOCK_STEP = 10; // ClockStep: 20s (ensures auction completes in time) -const PRICE_LOCK_PERIOD = 60; +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 }[]; diff --git a/a3p-integration/proposals/z:acceptance/test-lib/auction-lib.js b/a3p-integration/proposals/z:acceptance/test-lib/auction-lib.js new file mode 100644 index 00000000000..3db752700b9 --- /dev/null +++ b/a3p-integration/proposals/z:acceptance/test-lib/auction-lib.js @@ -0,0 +1,306 @@ +/* eslint-env node */ +import { + addPreexistingOracles, + agd, + agopsInter, + agoric, + ATOM_DENOM, + CHAINID, + executeOffer, + getPriceQuote, + GOV1ADDR, + pushPrices, + VALIDATORADDR, +} from '@agoric/synthetic-chain'; +import { AmountMath } from '@agoric/ertp'; +import { + retryUntilCondition, + waitUntilAccountFunded, + waitUntilOfferResult, +} from './sync-tools.js'; +import { boardSlottingMarshaller, makeFromBoard } from './rpc.js'; + +/** + * Typo will be fixed with https://github.com/Agoric/agoric-sdk/pull/10171 + * @typedef {import('./sync-tools.js').RetryOptions} RetryOptions + */ + +const ambientAuthority = { + query: agd.query, + follow: agoric.follow, + setTimeout, +}; + +export const scale6 = x => BigInt(x * 1_000_000); + +const fromBoard = makeFromBoard(); +const marshaller = boardSlottingMarshaller(fromBoard.convertSlotToVal); + +// Import from synthetic-chain once it is updated +export const bankSend = (from, addr, wanted) => { + const chain = ['--chain-id', CHAINID]; + const fromArg = ['--from', from]; + const testKeyring = ['--keyring-backend', 'test']; + const noise = [...fromArg, ...chain, ...testKeyring, '--yes']; + + return agd.tx('bank', 'send', from, addr, wanted, ...noise); +}; + +export const pushPricesForAuction = async (t, price) => { + const oraclesByBrand = new Map(); + await addPreexistingOracles('ATOM', oraclesByBrand); + + await pushPrices(price, 'ATOM', oraclesByBrand, t.context.roundId + 1); + + await retryUntilCondition( + () => getPriceQuote('ATOM'), + res => res === `+${scale6(price).toString()}`, + 'price not pushed yet', + { + log: t.log, + setTimeout, + ...t.context.pushPriceRetryOpts, + }, + ); +}; + +/** + * @param {any} t + * @param {{ + * name: string + * offerId: string, + * depositValue: string, + * }} depositor + * @param {Record} bidders + */ +export const fundAccts = async (t, depositor, bidders) => { + const retryOpts = t.context.retryOpts.bankSendRetryOpts; + + await bankSend( + VALIDATORADDR, + GOV1ADDR, + `${depositor.depositValue}${ATOM_DENOM}`, + ); + await waitUntilAccountFunded( + GOV1ADDR, + ambientAuthority, + { denom: ATOM_DENOM, value: Number(depositor.depositValue) }, + { errorMessage: `${depositor.name} not funded yet`, ...retryOpts }, + ); + + for await (const [key, value] of [...Object.entries(bidders)]) { + const fund = value.bidderFund; + await bankSend(GOV1ADDR, value.bidder, `${fund.value}${fund.denom}`); + await waitUntilAccountFunded(value.bidder, ambientAuthority, fund, { + errorMessage: `${key} not funded yet`, + ...retryOpts, + }); + } +}; + +export const bidByPrice = (price, give, offerId, bidder) => { + return agopsInter( + 'bid', + 'by-price', + `--price ${price}`, + `--give ${give}`, + '--from', + bidder, + '--keyring-backend test', + `--offer-id ${offerId}`, + ); +}; + +export const bidByDiscount = (discount, give, offerId, bidder) => { + return agopsInter( + 'bid', + 'by-discount', + `--discount ${discount}`, + `--give ${give}`, + '--from', + bidder, + '--keyring-backend test', + `--offer-id ${offerId}`, + ); +}; + +export const placeBids = (t, bidsSetup) => { + return [...Object.values(bidsSetup)].map( + ({ bidder, offerId, price, give, discount }) => { + if (price) return bidByPrice(price, give, offerId, bidder); + return bidByDiscount(discount, give, offerId, bidder); + }, + ); +}; + +/** + * Calculates retry options based on "nextStartTime" + */ +export const calculateRetryUntilNextStartTime = async () => { + const schedule = await agoric.follow('-lF', ':published.auction.schedule'); + const nextStartTime = parseInt(schedule.nextStartTime.absValue, 10); + + /** @type {RetryOptions} */ + const capturePriceRetryOpts = { + maxRetries: Math.round((nextStartTime * 1000 - Date.now()) / 10000) + 2, // wait until next schedule + retryIntervalMs: 10000, // 10 seconds in ms + }; + + return capturePriceRetryOpts; +}; + +/** + * + * @param {any} t + * @param {{ + * offerId: string, + * depositValue: string, + * addr: string + * }} depositor + */ +export const depositCollateral = async (t, depositor) => { + const [brandsRaw, retryOptions] = await Promise.all([ + agoric.follow('-lF', ':published.agoricNames.brand', '-o', 'text'), + calculateRetryUntilNextStartTime(), + ]); + const brands = Object.fromEntries( + marshaller.fromCapData(JSON.parse(brandsRaw)), + ); + + const offerSpec = { + id: depositor.offerId, + invitationSpec: { + source: 'agoricContract', + instancePath: ['auctioneer'], + callPipe: [['makeDepositInvitation']], + }, + proposal: { + give: { + Collateral: { + brand: brands.ATOM, + value: BigInt(depositor.depositValue), + }, + }, + }, + }; + + const spendAction = { + method: 'executeOffer', + offer: offerSpec, + }; + + const offer = JSON.stringify(marshaller.toCapData(harden(spendAction))); + t.log('OFFER', offer); + + executeOffer(depositor.addr, offer); + return waitUntilOfferResult( + depositor.addr, + depositor.offerId, + true, + ambientAuthority, + { + errorMessage: 'proceeds not distributed yet', + ...retryOptions, + }, + ); +}; + +export const checkBidsOutcome = (t, settledBids, bidsOutcome, brands) => { + [...Object.entries(settledBids)] + .map(([key, bidResult]) => [key.split('.')[0], bidResult.status.payouts]) + .forEach(([key, { Bid, Collateral }]) => { + t.log({ bidsOutcome }); + const { + payouts: { Bid: outcomeBidVal, Collateral: outcomeColVal }, + } = bidsOutcome[key]; + t.log({ outcomeBidVal, outcomeColVal }); + t.is( + AmountMath.isEqual( + Bid, + AmountMath.make(brands.IST, scale6(outcomeBidVal)), + ), + true, + ); + t.is( + AmountMath.isGTE( + Collateral, + AmountMath.make(brands.ATOM, scale6(outcomeColVal)), + ), + true, + ); + }); +}; + +export const checkDepositOutcome = (t, depositorPayouts, config, brands) => { + // Assert depositor paid correctly + const { Bid: depositorBid, Collateral: depositorCol } = depositorPayouts; + const { + depositor, + longLivingBidSetup: { give: longLivingBidGive }, + currentBidsSetup, + bidsOutcome, + } = config; + + const getNumberFromGive = give => + parseInt(give.substring(0, give.length - 3), 10); + + const calculateGiveTotal = () => { + let currentBidSum = getNumberFromGive(longLivingBidGive); + [...Object.values(currentBidsSetup)].forEach(({ give }) => { + currentBidSum += getNumberFromGive(give); + }); + + return scale6(currentBidSum); + }; + + const calculateOutcomeTotal = () => { + let total = 0n; + [...Object.values(bidsOutcome)] + .map(outcome => outcome.payouts.Collateral) + .forEach(element => { + t.log(element); + total += scale6(element); + }); + + return total; + }; + + t.is( + AmountMath.isEqual( + depositorBid, + AmountMath.make(brands.IST, calculateGiveTotal()), + ), + true, + ); + t.is( + AmountMath.isGTE( + AmountMath.make( + brands.ATOM, + BigInt(depositor.depositValue) - calculateOutcomeTotal(), + ), + depositorCol, + ), + true, + ); +}; + +export const getCapturedPrice = async bookId => { + const result = await agoric.follow('-lF', `:published.auction.${bookId}`); + return result; +}; + +export const checkPrice = (res, expected) => { + if (res.startPrice === null) return false; + else if (res.startPrice.numerator.value === expected) return true; + return false; +}; diff --git a/a3p-integration/proposals/z:acceptance/test-lib/auction-lib.test.js b/a3p-integration/proposals/z:acceptance/test-lib/auction-lib.test.js new file mode 100644 index 00000000000..1fa71951158 --- /dev/null +++ b/a3p-integration/proposals/z:acceptance/test-lib/auction-lib.test.js @@ -0,0 +1,122 @@ +import '@endo/init'; +import { AmountMath } from '@agoric/ertp'; +import { Far } from '@endo/far'; +import test from 'ava'; +import { GOV3ADDR, USER1ADDR } from '@agoric/synthetic-chain'; +import { checkBidsOutcome, checkDepositOutcome } from './auction-lib.js'; + +// From auction.test.js +const config = { + depositor: { + name: 'gov1', + depositValue: '100000000', + offerId: `gov1-deposit-${Date.now()}`, + }, + longLivingBidSetup: { + name: 'long-living-bidder', + // This bid is placed in an earlier proposal + give: '80IST', + }, + currentBidsSetup: { + user1: { + bidder: USER1ADDR, + bidderFund: { + value: 90000000, + denom: 'uist', + }, + offerId: `user1-bid-${Date.now()}`, + give: '90IST', + price: 46, + }, + gov3: { + bidder: GOV3ADDR, + bidderFund: { + value: 150000000, + denom: 'uist', + }, + offerId: `gov3-bid-${Date.now()}`, + give: '150IST', + discount: '13', + }, + }, + bidsOutcome: { + longLivingBidder: { + payouts: { + Bid: 0, + Collateral: 1.68421, + }, + }, + user1: { + payouts: { + Bid: 0, + Collateral: 2.0, + }, + }, + gov3: { + payouts: { + Bid: 0, + Collateral: 3.448275, + }, + }, + }, +}; + +test.before(t => { + const mockIST = Far('IST', {}); + const mockATOM = Far('ATOM', {}); + + t.context = { + brands: { + IST: mockIST, + ATOM: mockATOM, + }, + }; +}); + +test('make sure check* functions work properly', t => { + // @ts-expect-error + const { brands } = t.context; + const result = { + status: { + payouts: { + Bid: AmountMath.make(brands.IST, 0n), + Collateral: AmountMath.make(brands.ATOM, 1684210n), + }, + }, + }; + + checkBidsOutcome( + t, + { + 'longLivingBidder.results': result, + 'user1.results': { + status: { + payouts: { + Bid: AmountMath.make(brands.IST, 0n), + Collateral: AmountMath.make(brands.ATOM, 2000000n), + }, + }, + }, + 'gov3.results': { + status: { + payouts: { + Bid: AmountMath.make(brands.IST, 0n), + Collateral: AmountMath.make(brands.ATOM, 3448275n), + }, + }, + }, + }, + config.bidsOutcome, + brands, + ); + + checkDepositOutcome( + t, + harden({ + Bid: AmountMath.make(brands.IST, 320000000n), + Collateral: AmountMath.make(brands.ATOM, 100_000_000n - 7_132_485n), + }), + config, + brands, + ); +}); diff --git a/a3p-integration/proposals/z:acceptance/lib/vaults.mts b/a3p-integration/proposals/z:acceptance/test-lib/vaults.mts similarity index 100% rename from a3p-integration/proposals/z:acceptance/lib/vaults.mts rename to a3p-integration/proposals/z:acceptance/test-lib/vaults.mts diff --git a/a3p-integration/proposals/z:acceptance/test.sh b/a3p-integration/proposals/z:acceptance/test.sh index ee4e473496d..7f33803cfbd 100755 --- a/a3p-integration/proposals/z:acceptance/test.sh +++ b/a3p-integration/proposals/z:acceptance/test.sh @@ -5,7 +5,7 @@ set -ueo pipefail # The effects of this step are not persisted in further proposal layers. # test the state right after the previous proposals -yarn ava initial.test.js +# yarn ava initial.test.js # XXX some of these tests have path dependencies so no globs yarn ava core-eval.test.js @@ -29,6 +29,9 @@ yarn ava wallet.test.js echo ACCEPTANCE TESTING vaults yarn ava vaults.test.js +echo ACCEPTANCE TESTING auction +yarn ava auction.test.js + echo ACCEPTANCE TESTING psm yarn ava psm.test.js From bf054144ec43667195d237a0f72f3cb1760e4ff9 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Wed, 30 Oct 2024 11:17:04 +0300 Subject: [PATCH 55/56] chore(auction-acceptance): workaround for auctioneer vats vstorage collision Refs: https://github.com/Agoric/BytePitchPartnerEng/issues/31 fix(auction-acceptance): formatting fixes fix(auction-acceptance): formatting fixes --- .../proposals/z:acceptance/auction.test.js | 18 +++--- .../z:acceptance/test-lib/auction-lib.js | 57 ++++++++++--------- 2 files changed, 42 insertions(+), 33 deletions(-) diff --git a/a3p-integration/proposals/z:acceptance/auction.test.js b/a3p-integration/proposals/z:acceptance/auction.test.js index 91c633a9ba2..02b6c8df86f 100644 --- a/a3p-integration/proposals/z:acceptance/auction.test.js +++ b/a3p-integration/proposals/z:acceptance/auction.test.js @@ -17,7 +17,6 @@ * - Make sure all actors receive the correct payouts */ -// Typo will be fixed with https://github.com/Agoric/agoric-sdk/pull/10171 /** @typedef {import('./test-lib/sync-tools.js').RetryOptions} RetryOptions */ import { @@ -36,10 +35,9 @@ import { calculateRetryUntilNextStartTime, checkBidsOutcome, checkDepositOutcome, - checkPrice, + checkPriceCaptured, depositCollateral, fundAccts, - getCapturedPrice, placeBids, pushPricesForAuction, scale6, @@ -143,11 +141,12 @@ test('run auction', async t => { await pushPricesForAuction(t, config.price); // Wait until next round starts. Retry error message is useful for debugging - const retryOptions = await calculateRetryUntilNextStartTime(); + const { retryOptions, nextStartTime } = + await calculateRetryUntilNextStartTime(); await retryUntilCondition( - () => getCapturedPrice('book0'), - res => checkPrice(res, scale6(config.price).toString()), // scale price to uist - 'price not captured yet [AUCTION TEST]', + () => Promise.resolve(Date.now()), + res => res >= nextStartTime * 1000, + 'next auction round not started yet [AUCTION TEST]', { log: t.log, ...ambientAuthority, @@ -160,6 +159,11 @@ test('run auction', async t => { const bidsP = placeBids(t, config.currentBidsSetup); const proceedsP = depositCollateral(t, config.depositor); + // Make sure price captured + // We have to check this after bids are placed and collateral deposited + // because of https://github.com/Agoric/BytePitchPartnerEng/issues/31 + await checkPriceCaptured('book0', scale6(config.price).toString()); + // Resolves when auction finalizes and depositor gets payouts const [longLivingBidderAddr] = await Promise.all([ getUser(config.longLivingBidSetup.name), diff --git a/a3p-integration/proposals/z:acceptance/test-lib/auction-lib.js b/a3p-integration/proposals/z:acceptance/test-lib/auction-lib.js index 3db752700b9..9ce1e3ddee1 100644 --- a/a3p-integration/proposals/z:acceptance/test-lib/auction-lib.js +++ b/a3p-integration/proposals/z:acceptance/test-lib/auction-lib.js @@ -1,12 +1,12 @@ /* eslint-env node */ import { - addPreexistingOracles, agd, agopsInter, agoric, ATOM_DENOM, CHAINID, executeOffer, + generateOracleMap, getPriceQuote, GOV1ADDR, pushPrices, @@ -36,6 +36,9 @@ export const scale6 = x => BigInt(x * 1_000_000); const fromBoard = makeFromBoard(); const marshaller = boardSlottingMarshaller(fromBoard.convertSlotToVal); +// From n-upgrade/verifyPushedPrice.js +const BASE_ID = 'n-upgrade'; + // Import from synthetic-chain once it is updated export const bankSend = (from, addr, wanted) => { const chain = ['--chain-id', CHAINID]; @@ -47,8 +50,7 @@ export const bankSend = (from, addr, wanted) => { }; export const pushPricesForAuction = async (t, price) => { - const oraclesByBrand = new Map(); - await addPreexistingOracles('ATOM', oraclesByBrand); + const oraclesByBrand = generateOracleMap(BASE_ID, ['ATOM']); await pushPrices(price, 'ATOM', oraclesByBrand, t.context.roundId + 1); @@ -67,20 +69,20 @@ export const pushPricesForAuction = async (t, price) => { /** * @param {any} t * @param {{ - * name: string - * offerId: string, - * depositValue: string, + * name: string + * offerId: string, + * depositValue: string, * }} depositor * @param {Record} bidders */ export const fundAccts = async (t, depositor, bidders) => { @@ -152,11 +154,11 @@ export const calculateRetryUntilNextStartTime = async () => { /** @type {RetryOptions} */ const capturePriceRetryOpts = { - maxRetries: Math.round((nextStartTime * 1000 - Date.now()) / 10000) + 2, // wait until next schedule + maxRetries: Math.ceil((nextStartTime * 1000 - Date.now()) / 10000) + 2, // wait until next schedule retryIntervalMs: 10000, // 10 seconds in ms }; - return capturePriceRetryOpts; + return { retryOptions: capturePriceRetryOpts, nextStartTime }; }; /** @@ -169,7 +171,7 @@ export const calculateRetryUntilNextStartTime = async () => { * }} depositor */ export const depositCollateral = async (t, depositor) => { - const [brandsRaw, retryOptions] = await Promise.all([ + const [brandsRaw, { retryOptions }] = await Promise.all([ agoric.follow('-lF', ':published.agoricNames.brand', '-o', 'text'), calculateRetryUntilNextStartTime(), ]); @@ -294,13 +296,16 @@ export const checkDepositOutcome = (t, depositorPayouts, config, brands) => { ); }; -export const getCapturedPrice = async bookId => { - const result = await agoric.follow('-lF', `:published.auction.${bookId}`); - return result; -}; - -export const checkPrice = (res, expected) => { - if (res.startPrice === null) return false; - else if (res.startPrice.numerator.value === expected) return true; - return false; +/** + * + * @param {string} bookId + * @param {string} expected + */ +export const checkPriceCaptured = async (bookId, expected) => { + const { + startPrice: { + numerator: { value }, + }, + } = await agoric.follow('-lF', `:published.auction.${bookId}`); + assert(value, expected); }; From 990126165e1c8ba2ac7cc8a52ad696cf7fc347a3 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Thu, 31 Oct 2024 11:15:16 +0300 Subject: [PATCH 56/56] chore(auction-acceptance): address pr suggestions Refs: https://github.com/Agoric/BytePitchPartnerEng/issues/8 Refs: https://github.com/Agoric/agoric-sdk/issues/10111 --- .../proposals/n:upgrade-next/submitBid.js | 13 ++++++++++--- .../proposals/z:acceptance/auction.test.js | 4 ++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/a3p-integration/proposals/n:upgrade-next/submitBid.js b/a3p-integration/proposals/n:upgrade-next/submitBid.js index 36b17c62a29..5ac4c1be879 100644 --- a/a3p-integration/proposals/n:upgrade-next/submitBid.js +++ b/a3p-integration/proposals/n:upgrade-next/submitBid.js @@ -9,9 +9,16 @@ import { waitForBlock, provisionSmartWallet, ATOM_DENOM, + VALIDATORADDR, } from '@agoric/synthetic-chain'; -export const bankSend = (from, addr, wanted) => { +/** + * + * @param {string} addr + * @param {string} wanted + * @param {string | undefined} from + */ +export const bankSend = (addr, wanted, from = VALIDATORADDR) => { const chain = ['--chain-id', CHAINID]; const fromArg = ['--from', from]; const testKeyring = ['--keyring-backend', 'test']; @@ -21,8 +28,8 @@ export const bankSend = (from, addr, wanted) => { }; const bidder = await addUser('long-living-bidder'); -console.log('BIDDDER', bidder); -await bankSend(GOV1ADDR, bidder, `80000000uist`); +console.log('BIDDER', bidder); +await bankSend(bidder, `80000000uist`, GOV1ADDR); console.log('IST sent'); await provisionSmartWallet(bidder, `20000000ubld,100000000${ATOM_DENOM}`); console.log('Provision sent'); diff --git a/a3p-integration/proposals/z:acceptance/auction.test.js b/a3p-integration/proposals/z:acceptance/auction.test.js index 02b6c8df86f..eda93ca260c 100644 --- a/a3p-integration/proposals/z:acceptance/auction.test.js +++ b/a3p-integration/proposals/z:acceptance/auction.test.js @@ -129,10 +129,10 @@ test.before(async t => { ); t.context = { roundId: parseInt(round.roundId, 10), - retryOpts: { + retryOpts: harden({ bankSendRetryOpts, pushPriceRetryOpts, - }, + }), }; });