diff --git a/contract/Makefile b/contract/Makefile index 1fa4f265..11bc234b 100644 --- a/contract/Makefile +++ b/contract/Makefile @@ -27,41 +27,6 @@ mint100: yarn --silent agops vaults open --wantMinted 100 --giveCollateral 100 >/tmp/want-ist.json && \ yarn --silent agops perf satisfaction --executeOffer /tmp/want-ist.json --from user1 --keyring-backend=test -# https://agoric.explorers.guru/proposal/61 -lower-bundle-cost: bundles/lower-bundle-cost.json ./scripts/voteLatestProposalAndWait.sh - agd tx gov submit-proposal param-change bundles/lower-bundle-cost.json \ - $(SIGN_BROADCAST_OPTS) \ - --from user1 - ./scripts/voteLatestProposalAndWait.sh - # agd query swingset params - - -bundles/swingset-params.json: - mkdir -p bundles/ - agd query swingset params -o json >$@ - -.ONESHELL: -bundles/lower-bundle-cost.json: bundles/swingset-params.json - @read PARAMS < bundles/swingset-params.json; export PARAMS - node - <<- EOF >$@ - const storageByte = '20000000'; - const paramChange = { - title: 'Lower Bundle Cost to 0.02 IST/Kb (a la mainnet 61)', - description: '0.02 IST/Kb', - deposit: '10000000ubld', - changes: [{ - subspace: 'swingset', - key: 'beans_per_unit', - value: '...', - }], - }; - const params = JSON.parse(process.env.PARAMS); - const ix = params.beans_per_unit.findIndex(({key}) => key === 'storageByte'); - params.beans_per_unit[ix].beans = storageByte; - paramChange.changes[0].value = params.beans_per_unit; - console.log(JSON.stringify(paramChange, null, 2)); - EOF - # Keep mint4k around a while for compatibility mint4k: make FUNDS=1000$(ATOM) fund-acct @@ -103,19 +68,40 @@ print-key: /root/.agoric/user1.key @agd keys show user1 -a --keyring-backend="test" @echo -SCRIPT=start-sell-concert-tickets.js -PERMIT=start-sell-concert-tickets-permit.json -start-contract: $(SCRIPT) $(PERMIT) install-bundles - scripts/propose-start-contract.sh +start-contract: start-contract-mint start-contract-swap start-contract-pay + +start-contract-mint: + yarn node scripts/deploy-contract.js \ + --install src/sell-concert-tickets.contract.js \ + --eval src/platform-goals/board-aux.core.js \ + --eval src/sell-concert-tickets.proposal.js + +start-contract-swap: + yarn node scripts/deploy-contract.js \ + --install src/swaparoo.contract.js \ + --eval src/swaparoo.proposal.js + +start-contract-pay: + yarn node scripts/deploy-contract.js \ + --install src/postal-service.contract.js \ + --eval src/postal-service.proposal.js -install-bundles: bundles/bundle-list - ./scripts/install-bundles.sh +# bundle-X.json.installed show that bundle-X.json was installed +# see also e2e-tools.js +%.json.installed: %.json + @echo '{"up-to-date": false}' -build-proposal: bundles/bundle-list +# X.js.done shows that X.js core eval ran +%.js.done: %.js + @echo '{"up-to-date": false}' -bundles/bundle-list $(SCRIPT) $(PERMIT): - ./scripts/build-proposal.sh +# Dependencies to re-run core eval when source changes +# NOTE: manually updated +bundles/deploy-send.js: src/start-postalSvc.js + @echo '{"up-to-date": false}' +bundles/deploy-swaparoo.js: src/swaparoo.proposal.js + @echo '{"up-to-date": false}' clean: - @rm -rf $(SCRIPT) $(PERMIT) bundles/ + @rm -rf bundles/ diff --git a/contract/jsconfig.json b/contract/jsconfig.json index e7dca004..3e5c6fbb 100644 --- a/contract/jsconfig.json +++ b/contract/jsconfig.json @@ -15,5 +15,11 @@ "strictNullChecks": true, "moduleResolution": "node" }, - "include": ["src/**/*.js", "test/**/*.js", "exported.js", "globals.d.ts"] + "include": [ + "src/**/*.js", + "test/**/*.js", + "tools/**/*.js", + "exported.js", + "globals.d.ts" + ] } diff --git a/contract/package.json b/contract/package.json index 8d9c8cb4..4b28a62f 100644 --- a/contract/package.json +++ b/contract/package.json @@ -10,22 +10,22 @@ "docker:bash": "docker compose exec agd bash", "docker:make": "docker compose exec agd make -C /workspace/contract", "make:help": "make list", - "start": "yarn docker:make clean start-contract print-key", - "build": "agoric run scripts/build-contract-deployer.js", + "start": "make clean start-contract; yarn docker:make print-key", + "build": "yarn build:deployer", + "build:deployer": "rollup -c rollup.config.mjs", "test": "ava --verbose", "lint": "eslint '**/*.js'", "lint:types": "tsc -p jsconfig.json", "lint:fix": "eslint --fix '**/*.js'" }, "devDependencies": { - "@agoric/deploy-script-support": "^0.10.4-u12.0", + "@agoric/deploy-script-support": "^0.10.4-u14.0", "@agoric/eslint-config": "dev", - "@agoric/smart-wallet": "0.5.3", - "@agoric/vats": "0.15.2-u12.0", - "@agoric/xsnap": "0.14.3-u12.0", - "@endo/bundle-source": "^2.8.0", + "@agoric/smart-wallet": "0.5.4-u14.0", + "@agoric/store": "0.9.3-u14.0", + "@agoric/xsnap": "0.14.3-u14.0", + "@agoric/zone": "0.2.3-u14.0", "@endo/eslint-plugin": "^0.5.2", - "@endo/init": "^0.5.60", "@endo/nat": "^4.1.27", "@endo/promise-kit": "0.2.56", "@endo/ses-ava": "^0.2.44", @@ -50,9 +50,13 @@ "typescript": "~5.2.2" }, "dependencies": { - "@agoric/ertp": "^0.16.3-u12.0", - "@agoric/zoe": "^0.26.3-u12.0", + "@agoric/ertp": "^0.16.3-u14.0", + "@agoric/governance": "^0.10.4-u14.0", + "@agoric/vats": "0.15.2-u14.0", + "@agoric/zoe": "^0.26.3-u14.0", + "@endo/bundle-source": "^2.8.0", "@endo/far": "^0.2.22", + "@endo/init": "^0.5.60", "@endo/marshal": "^0.8.9", "@endo/patterns": "^0.2.5" }, diff --git a/contract/rollup.config.mjs b/contract/rollup.config.mjs new file mode 100644 index 00000000..7c13208e --- /dev/null +++ b/contract/rollup.config.mjs @@ -0,0 +1,77 @@ +/** + * @file rollup configuration to bundle core-eval script + * + * Supports developing core-eval script, permit as a module: + * - import { E } from '@endo/far' + * We can strip this declaration during bundling + * since the core-eval scope includes exports of @endo/far + * - `bundleID = ...` is replaced using updated/cached bundle hash + * - `main` export is appended as script completion value + * - `permit` export is emitted as JSON + */ +// @ts-check +import { + coreEvalGlobals, + moduleToScript, + configureBundleID, + emitPermit, +} from './tools/rollup-plugin-core-eval.js'; +import { permit as postalServicePermit } from './src/postal-service.proposal.js'; +import { permit as swapPermit } from './src/swaparoo.proposal.js'; +import { permit as sellPermit } from './src/sell-concert-tickets.proposal.js'; +import { permit as boardAuxPermit } from './src/platform-goals/board-aux.core.js'; + +/** + * @param {*} opts + * @returns {import('rollup').RollupOptions} + */ +const config1 = ({ + name, + coreEntry = `./src/${name}.proposal.js`, + contractEntry = `./src/${name}.contract.js`, + coreScript = `bundles/deploy-${name}.js`, + permitFile = `deploy-${name}-permit.json`, + permit, +}) => ({ + input: coreEntry, + output: { + globals: coreEvalGlobals, + file: coreScript, + format: 'es', + footer: 'main', + }, + external: ['@endo/far'], + plugins: [ + ...(contractEntry + ? [ + configureBundleID({ + name, + rootModule: contractEntry, + cache: 'bundles', + }), + ] + : []), + moduleToScript(), + emitPermit({ permit, file: permitFile }), + ], +}); + +/** @type {import('rollup').RollupOptions[]} */ +const config = [ + config1({ + name: 'board-aux', + permit: boardAuxPermit, + coreEntry: `./src/platform-goals/board-aux.core.js`, + contractEntry: null, + }), + config1({ + name: 'sell-concert-tickets', + permit: sellPermit, + }), + config1({ name: 'swaparoo', permit: swapPermit }), + config1({ + name: 'postal-service', + permit: postalServicePermit, + }), +]; +export default config; diff --git a/contract/scripts/build-contract-deployer.js b/contract/scripts/build-contract-deployer.js deleted file mode 100644 index 66107c57..00000000 --- a/contract/scripts/build-contract-deployer.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @file Permission Contract Deployment builder - * - * Creates files for starting an instance of the contract: - * * contract source and instantiation proposal bundles to be published via - * `agd tx swingset install-bundle` - * * start-sell-concert-tickets-permit.json and start-sell-concert-tickets.js to submit the - * instantiation proposal via `agd tx gov submit-proposal swingset-core-eval` - * - * Usage: - * agoric run build-contract-deployer.js - */ - -import { makeHelpers } from '@agoric/deploy-script-support'; -import { getManifestForSellConcertTickets } from '../src/sell-concert-tickets-proposal.js'; - -/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */ -export const sellConcertTicketsProposalBuilder = async ({ - publishRef, - install, -}) => { - return harden({ - sourceSpec: '../src/sell-concert-tickets-proposal.js', - getManifestCall: [ - getManifestForSellConcertTickets.name, - { - sellConcertTicketsRef: publishRef( - install( - '../src/sell-concert-tickets.contract.js', - '../bundles/bundle-sell-concert-tickets.js', - { - persist: true, - }, - ), - ), - }, - ], - }); -}; - -/** @type {DeployScriptFunction} */ -export default async (homeP, endowments) => { - const { writeCoreProposal } = await makeHelpers(homeP, endowments); - await writeCoreProposal('start-sell-concert-tickets', sellConcertTicketsProposalBuilder); -}; diff --git a/contract/scripts/build-proposal.sh b/contract/scripts/build-proposal.sh deleted file mode 100755 index 68bdcadb..00000000 --- a/contract/scripts/build-proposal.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -# NOTE: intended to run _inside_ the agd container - -cd /workspace/contract - -mkdir -p bundles -(agoric run ./scripts/build-contract-deployer.js )>/tmp/,run.log -./scripts/parseProposals.mjs bundles/bundle-list - - diff --git a/contract/scripts/deploy-contract.js b/contract/scripts/deploy-contract.js new file mode 100755 index 00000000..6e7e62e4 --- /dev/null +++ b/contract/scripts/deploy-contract.js @@ -0,0 +1,112 @@ +#!/usr/bin/env node +/* global process, fetch, setTimeout */ +// @ts-check +import '@endo/init'; +import fsp from 'node:fs/promises'; +import { execFile, execFileSync } from 'node:child_process'; +import { basename } from 'node:path'; + +import { makeNodeBundleCache } from '@endo/bundle-source/cache.js'; +import { parseArgs } from 'node:util'; +import { makeE2ETools } from '../tools/e2e-tools.js'; + +/** @type {import('node:util').ParseArgsConfig['options']} */ +const options = { + help: { type: 'boolean' }, + install: { type: 'string' }, + eval: { type: 'string', multiple: true }, + service: { type: 'string', default: 'agd' }, + workdir: { type: 'string', default: '/workspace/contract' }, +}; +/** + * @typedef {{ + * help: boolean, + * install?: string, + * eval?: string[], + * service: string, + * workdir: string, + * }} DeployOptions + */ + +const Usage = ` +deploy-contract [options] [--install ] [--eval ]... + +Options: + --help print usage + --install entry module of contract to install + --eval entry module of core evals to run + (cf rollup.config.mjs) + --service SVC docker compose service to run agd (default: ${options.service.default}). + Use . to run agd outside docker. + --workdir DIR workdir for docker service (default: ${options.workdir.default}) +`; + +const mockExecutionContext = () => { + const withSkip = o => + Object.assign(o, { + skip: (..._xs) => {}, + }); + return { + log: withSkip((...args) => console.log(...args)), + is: withSkip((actual, expected, message) => + assert.equal(actual, expected, message), + ), + }; +}; + +const main = async (bundleDir = 'bundles') => { + const { argv } = process; + const { writeFile } = fsp; + + const progress = (...args) => console.warn(...args); // stderr + + const bundleCache = await makeNodeBundleCache(bundleDir, {}, s => import(s)); + + /** @type {{ values: DeployOptions }} */ + // @ts-expect-error options config ensures type + const { values: flags } = parseArgs({ args: argv.slice(2), options }); + if (flags.help) { + progress(Usage); + return; + } + /** @type {{ workdir: string, service: string }} */ + const { workdir, service } = flags; + + /** @type {import('../tools/agd-lib.js').ExecSync} */ + const dockerExec = (file, dargs, opts = { encoding: 'utf-8' }) => { + const execArgs = ['compose', 'exec', '--workdir', workdir, service]; + opts.verbose && + console.log('docker compose exec', JSON.stringify([file, ...dargs])); + return execFileSync('docker', [...execArgs, file, ...dargs], opts); + }; + + const t = mockExecutionContext(); + const tools = makeE2ETools(t, bundleCache, { + execFile, + execFileSync: service === '.' ? execFileSync : dockerExec, + fetch, + setTimeout, + writeFile, + bundleDir, + }); + + const stem = path => basename(path).replace(/\..*/, ''); + + if (flags.install) { + const name = stem(flags.install); + + await tools.installBundles({ [name]: flags.install }, progress); + } + + if (flags.eval) { + for await (const entryFile of flags.eval) { + const result = await tools.runCoreEval({ + name: stem(entryFile), + entryFile, + }); + progress(result); + } + } +}; + +main().catch(err => console.error(err)); diff --git a/contract/scripts/install-bundles.sh b/contract/scripts/install-bundles.sh deleted file mode 100755 index 717257bb..00000000 --- a/contract/scripts/install-bundles.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -# NOTE: intended to run _inside_ the agd container - -set -xueo pipefail - -cd /workspace/contract - -# TODO: try `agoric publish` to better track outcome -install_bundle() { - ls -sh "$1" - agd tx swingset install-bundle --compress "@$1" \ - --from user1 --keyring-backend=test --gas=auto --gas-adjustment=1.2 \ - --chain-id=agoriclocal -bblock --yes -o json -} - -# exit fail if bundle-list is emtpy -[ -s bundles/bundle-list ] || exit 1 - -make balance-q # do we have enough IST? - -for b in $(cat bundles/bundle-list); do - echo installing $b - install_bundle $b -done diff --git a/contract/scripts/parseProposals.mjs b/contract/scripts/parseProposals.mjs deleted file mode 100755 index daab1be3..00000000 --- a/contract/scripts/parseProposals.mjs +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env node - -import fs from 'fs'; - -const Fail = (template, ...args) => { - throw Error(String.raw(template, ...args.map(val => String(val)))); -}; - -/** - * Parse output of `agoric run proposal-builder.js` - * - * @param {string} txt - * - * adapted from packages/boot/test/bootstrapTests/supports.js - */ -const parseProposalParts = txt => { - const evals = [ - ...txt.matchAll(/swingset-core-eval (?\S+) (?