@agoric/deploy-script-support 0.10.4-dev-fe77bfe.0.fe77bfe → 0.10.4-dev-6ce1cfd.0.6ce1cfd

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/deploy-script-support",
3
- "version": "0.10.4-dev-fe77bfe.0.fe77bfe",
3
+ "version": "0.10.4-dev-6ce1cfd.0.6ce1cfd",
4
4
  "description": "Helpers and other support for writing deploy scripts",
5
5
  "type": "module",
6
6
  "main": "src/helpers.js",
@@ -34,25 +34,33 @@
34
34
  },
35
35
  "homepage": "https://github.com/Agoric/agoric-sdk#readme",
36
36
  "dependencies": {
37
- "@agoric/ertp": "0.16.3-dev-fe77bfe.0.fe77bfe",
38
- "@agoric/import-manager": "0.3.12-dev-fe77bfe.0.fe77bfe",
39
- "@agoric/internal": "0.3.3-dev-fe77bfe.0.fe77bfe",
40
- "@agoric/notifier": "0.6.3-dev-fe77bfe.0.fe77bfe",
41
- "@agoric/pola-io": "0.1.1-dev-fe77bfe.0.fe77bfe",
42
- "@agoric/store": "0.9.3-dev-fe77bfe.0.fe77bfe",
43
- "@agoric/time": "0.3.3-dev-fe77bfe.0.fe77bfe",
44
- "@agoric/zoe": "0.26.3-dev-fe77bfe.0.fe77bfe",
37
+ "@agoric/client-utils": "0.1.1-dev-6ce1cfd.0.6ce1cfd",
38
+ "@agoric/ertp": "0.16.3-dev-6ce1cfd.0.6ce1cfd",
39
+ "@agoric/import-manager": "0.3.12-dev-6ce1cfd.0.6ce1cfd",
40
+ "@agoric/internal": "0.3.3-dev-6ce1cfd.0.6ce1cfd",
41
+ "@agoric/notifier": "0.6.3-dev-6ce1cfd.0.6ce1cfd",
42
+ "@agoric/orchestration": "0.1.1-dev-6ce1cfd.0.6ce1cfd",
43
+ "@agoric/pola-io": "0.1.1-dev-6ce1cfd.0.6ce1cfd",
44
+ "@agoric/portfolio-api": "0.1.1-dev-6ce1cfd.0.6ce1cfd",
45
+ "@agoric/smart-wallet": "0.5.4-dev-6ce1cfd.0.6ce1cfd",
46
+ "@agoric/store": "0.9.3-dev-6ce1cfd.0.6ce1cfd",
47
+ "@agoric/time": "0.3.3-dev-6ce1cfd.0.6ce1cfd",
48
+ "@agoric/zoe": "0.26.3-dev-6ce1cfd.0.6ce1cfd",
49
+ "@agoric/zone": "0.2.3-dev-6ce1cfd.0.6ce1cfd",
45
50
  "@endo/base64": "^1.0.12",
46
51
  "@endo/bundle-source": "^4.1.2",
47
52
  "@endo/errors": "^1.2.13",
53
+ "@endo/eventual-send": "^1.3.4",
48
54
  "@endo/far": "^1.1.14",
49
55
  "@endo/marshal": "^1.8.0",
50
56
  "@endo/nat": "^5.1.3",
57
+ "@endo/pass-style": "^1.6.3",
58
+ "@endo/patterns": "^1.7.0",
51
59
  "@endo/promise-kit": "^1.1.13",
52
60
  "@endo/zip": "^1.0.11"
53
61
  },
54
62
  "devDependencies": {
55
- "@agoric/vats": "0.15.2-dev-fe77bfe.0.fe77bfe",
63
+ "@agoric/vats": "0.15.2-dev-6ce1cfd.0.6ce1cfd",
56
64
  "@endo/init": "^1.1.12",
57
65
  "ava": "^6.4.1",
58
66
  "import-meta-resolve": "^4.1.0"
@@ -75,5 +83,5 @@
75
83
  "typeCoverage": {
76
84
  "atLeast": 84.46
77
85
  },
78
- "gitHead": "fe77bfe6dd676c5538e17e61cb618750f0f5aac0"
86
+ "gitHead": "6ce1cfd05fd9608126491636eb6658078d91cba9"
79
87
  }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @file Get a promise for a deposit facet without
3
+ * access to all of namesByAddressAdmin.
4
+ */
5
+
6
+ import { E } from '@endo/eventual-send';
7
+
8
+ const trace = (...args) => console.log('---- AttD', ...args);
9
+
10
+ /**
11
+ * @import {DepositFacet} from '@agoric/ertp';
12
+ * @import {BootstrapPowers, PromiseSpaceOf} from '@agoric/vats/src/core/types.js';
13
+ */
14
+ const depositFacetKey = 'depositFacet';
15
+
16
+ /**
17
+ * XXX move this into BootstrapPowers
18
+ * @typedef {PromiseSpaceOf<{
19
+ * getDepositFacet: (addr: string, debugName?: string) => Promise<DepositFacet>
20
+ * }>} AttenuatedDepositPowers
21
+ */
22
+
23
+ /**
24
+ * @param {BootstrapPowers & AttenuatedDepositPowers} powers
25
+ */
26
+ export const produceAttenuatedDeposit = powers => {
27
+ powers.produce.getDepositFacet.reset();
28
+ const { namesByAddress, namesByAddressAdmin } = powers.consume;
29
+
30
+ /**
31
+ *
32
+ * @param {string} addr
33
+ * @param {string} [debugName]
34
+ */
35
+ const getDepositFacet = async (addr, debugName = 'party') => {
36
+ trace('reserve nameHub for', debugName, addr);
37
+ await E(namesByAddressAdmin).reserve(addr);
38
+ trace('lookup depositFacet for', debugName, addr);
39
+ const df = await E(namesByAddress).lookup(addr, depositFacetKey);
40
+ trace('got depositFacet for', debugName, addr);
41
+ return df;
42
+ };
43
+ harden(getDepositFacet);
44
+
45
+ powers.produce.getDepositFacet.resolve(getDepositFacet);
46
+ };
47
+
48
+ export const getManifestForAttenuatedDeposit = () => ({
49
+ manifest: {
50
+ [produceAttenuatedDeposit.name]: {
51
+ consume: { namesByAddress: true, namesByAddressAdmin: true },
52
+ produce: { getDepositFacet: true },
53
+ },
54
+ },
55
+ });
@@ -0,0 +1,187 @@
1
+ import { makeTracer } from '@agoric/internal';
2
+ import { registerChain } from '@agoric/orchestration/src/chain-info.js';
3
+ import { Fail } from '@endo/errors';
4
+ import { E, Far } from '@endo/far';
5
+ import { makeMarshal } from '@endo/marshal';
6
+
7
+ // TODO: refactor overlap with init-chain-info.js in orch pkg
8
+
9
+ /**
10
+ * @import {Remote, ERemote} from '@agoric/internal';
11
+ * @import {ChainInfo} from '@agoric/orchestration';
12
+ * @import {NameHub, NameAdmin} from '@agoric/vats';
13
+ * @typedef {Record<string, { chainInfo: ChainInfo }>} AxelarChainConfigMap
14
+ * @import {ERef} from '@endo/eventual-send';
15
+ * @import {StorageNode} from '@agoric/internal/src/lib-chainStorage.js';
16
+ * @import {PromiseSpaceOf} from '@agoric/vats/src/core/types.js';
17
+ * @import {BootstrapPowers} from '@agoric/vats/src/core/types.js';
18
+ */
19
+
20
+ const trace = makeTracer('ChainInfoCore', true);
21
+
22
+ // chainInfo has no cap data but we need to marshal bigints
23
+ const marshalData = makeMarshal(_val => Fail`data only`);
24
+
25
+ // See also: exos/chain-hub. Consistency is enforced by test.
26
+ export const HubName = {
27
+ Chain: 'chain',
28
+ ChainConnection: 'chainConnection',
29
+ ChainAssets: 'chainAssets',
30
+ };
31
+
32
+ /**
33
+ * Similar to publishAgoricNamesToChainStorage but publishes a node per chain
34
+ * instead of one list of entries
35
+ */
36
+
37
+ /**
38
+ * For each HubName, provide a NameHubKit reflected into vstorage unless there's
39
+ * already an agorcNames key by that name.
40
+ *
41
+ * @param {ERef<NameAdmin>} agoricNamesAdmin
42
+ * @param {ERemote<StorageNode>} agoricNamesNode
43
+ * @param {ERef<NameHub>} agoricNames
44
+ */
45
+ const publishChainInfoToChainStorage = async (
46
+ agoricNamesAdmin,
47
+ agoricNamesNode,
48
+ agoricNames,
49
+ ) => {
50
+ /**
51
+ * @param {string} subpath
52
+ */
53
+ const echoNameUpdates = async subpath => {
54
+ trace('reflecting', subpath, 'from agoricNames to vstorage');
55
+ const chainNamesNode = E(agoricNamesNode).makeChildNode(subpath);
56
+ const { nameAdmin } = await E(agoricNamesAdmin).provideChild(subpath);
57
+
58
+ /**
59
+ * Previous entries, to prevent redundant updates
60
+ *
61
+ * @type {Record<string, string>} chainName => stringified chainInfo
62
+ */
63
+ const prev = {};
64
+
65
+ // XXX cannot be changed until we upgrade vat-agoricNames to allow it
66
+ await E(nameAdmin).onUpdate(
67
+ // XXX will live on the heap in the bootstrap vat. When we upgrade or kill
68
+ // that this handler will sever and vat-agoricNames will need to be upgraded
69
+ // to allow changing the handler, or to use pubsub mechanics instead.
70
+ Far('chain info writer', {
71
+ write(entries) {
72
+ for (const [chainName, info] of entries) {
73
+ const value = JSON.stringify(marshalData.toCapData(info));
74
+ if (prev[chainName] === value) {
75
+ continue;
76
+ }
77
+ const chainNode = E(chainNamesNode).makeChildNode(chainName);
78
+ prev[chainName] = value;
79
+ void E(chainNode)
80
+ .setValue(value)
81
+ .catch(() => delete prev[chainName]);
82
+ }
83
+ },
84
+ }),
85
+ );
86
+ };
87
+ const existingKeys = await E(agoricNames).keys();
88
+ await Promise.all(
89
+ Object.values(HubName)
90
+ .filter(k => !existingKeys.includes(k))
91
+ .map(echoNameUpdates),
92
+ );
93
+ };
94
+
95
+ /**
96
+ * null chainStorage case is vestigial
97
+ *
98
+ * @typedef {{ consume: { chainStorage: Promise<Remote<StorageNode>> } }} ChainStoragePresent
99
+ */
100
+
101
+ /**
102
+ * XXX move this into BootstrapPowers
103
+ * @typedef {PromiseSpaceOf<{
104
+ * chainInfoPublished: unknown
105
+ * }>} ChainInfoPowers
106
+ */
107
+
108
+ /**
109
+ * WARNING: prunes any data that was previously published
110
+ *
111
+ * @param {BootstrapPowers & ChainStoragePresent & ChainInfoPowers} powers
112
+ * @param {{
113
+ * options: {
114
+ * chainInfo?: Record<string, ChainInfo>;
115
+ * axelarConfig: AxelarChainConfigMap;
116
+ * };
117
+ * }} config
118
+ */
119
+ export const publishChainInfo = async (
120
+ {
121
+ consume: { agoricNames, agoricNamesAdmin, chainStorage },
122
+ produce: { chainInfoPublished },
123
+ },
124
+ config,
125
+ ) => {
126
+ const { keys } = Object;
127
+ const { chainInfo = {} } = config.options;
128
+ trace('publishChainInfo', keys(chainInfo));
129
+
130
+ const agoricNamesNode = E(chainStorage).makeChildNode('agoricNames');
131
+
132
+ // Ensure updates go to vstorage
133
+ await publishChainInfoToChainStorage(
134
+ agoricNamesAdmin,
135
+ agoricNamesNode,
136
+ agoricNames,
137
+ );
138
+
139
+ for (const kind of Object.values(HubName)) {
140
+ const hub = E(agoricNames).lookup(kind);
141
+ /** @type {string[]} */
142
+ const oldKeys = await E(hub).keys();
143
+ trace('clearing old', kind, oldKeys);
144
+ if (!oldKeys.length) continue;
145
+
146
+ const admin = E(agoricNamesAdmin).lookupAdmin(kind);
147
+ await Promise.all(oldKeys.map(k => E(admin).delete(k)));
148
+ const node = E(agoricNamesNode).makeChildNode(kind);
149
+ // XXX setValue('') deletes a vstorage key (right?)
150
+ await Promise.all(
151
+ oldKeys.map(k =>
152
+ E(E(node).makeChildNode(k, { sequence: false })).setValue(''),
153
+ ),
154
+ );
155
+ }
156
+
157
+ const handledConnections = new Set();
158
+ for await (const [name, info] of Object.entries(chainInfo)) {
159
+ await registerChain(
160
+ agoricNamesAdmin,
161
+ name,
162
+ info,
163
+ trace,
164
+ handledConnections,
165
+ );
166
+ trace('@@@registered', name, info);
167
+ }
168
+ trace('@@@conn', ...handledConnections);
169
+
170
+ chainInfoPublished.resolve(true);
171
+ trace('publishChainInfo done');
172
+ };
173
+ harden(publishChainInfo);
174
+
175
+ export const getManifestForChainInfo = (_u, { options }) => ({
176
+ manifest: {
177
+ [publishChainInfo.name]: {
178
+ consume: {
179
+ agoricNames: true,
180
+ agoricNamesAdmin: true,
181
+ chainStorage: true,
182
+ },
183
+ produce: { chainInfoPublished: true },
184
+ },
185
+ },
186
+ options,
187
+ });
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @import { CoreEvalBuilder, DeployScriptFunction } from '@agoric/deploy-script-support/src/externalTypes.js';
3
+ */
4
+
5
+ import { makeHelpers } from '../helpers.js';
6
+ import { getManifestForDeliverContractControl } from './contract-control.core.js';
7
+
8
+ const sourceSpec = './contract-control.core.js';
9
+
10
+ /**
11
+ * @satisfies {CoreEvalBuilder}
12
+ */
13
+ const defaultProposalBuilder = async () => {
14
+ return harden({
15
+ sourceSpec,
16
+ getManifestCall: [getManifestForDeliverContractControl.name],
17
+ });
18
+ };
19
+
20
+ /** @type {DeployScriptFunction} */
21
+ const build = async (homeP, endowments) => {
22
+ const { writeCoreEval } = await makeHelpers(homeP, endowments);
23
+
24
+ await writeCoreEval('eval-contract-control', () => defaultProposalBuilder());
25
+ };
26
+ harden(build);
27
+ export default build;
@@ -0,0 +1,386 @@
1
+ /**
2
+ * @file contract-control: install, upgrade, terminate, reset chain-storage,
3
+ * for a contract in a branch of agoricNames / chain-storage.
4
+ */
5
+
6
+ import { makeTracer } from '@agoric/internal/src/debug.js';
7
+ import { Fail, q } from '@endo/errors';
8
+ import { E, passStyleOf } from '@endo/far';
9
+ import { M, objectMap } from '@endo/patterns';
10
+
11
+ const trace = makeTracer('CCtrl');
12
+
13
+ /**
14
+ * @import {Zone} from '@agoric/base-zone';
15
+ * @import {ContractStartFunction, StartResult} from '@agoric/zoe/src/zoeService/utils.js';
16
+ * @import {Installation, Instance, IssuerKeywordRecord, ZoeService} from '@agoric/zoe';
17
+ * @import {Remote} from '@agoric/internal';
18
+ * @import {StorageNode} from '@agoric/internal/src/lib-chainStorage.js';
19
+ * @import {Board, NameHubKit} from '@agoric/vats';
20
+ * @import {ERef} from '@agoric/vow';
21
+ * @import {StartedInstanceKitWithLabel, StartUpgradable} from '@agoric/vats/src/core/types.js';
22
+ * @import {StartedInstanceKit} from '@agoric/vats/src/core/types.js';
23
+ */
24
+
25
+ /**
26
+ * @typedef {<SF extends ContractStartFunction>(instance: Instance<SF>, privateArgs: Parameters<SF>[1]) => void} UpdatePrivateArgs
27
+ */
28
+
29
+ const IssuerShape = M.remotable('Issuer');
30
+ const InstallationShape = M.remotable('Installation');
31
+ const PublicParts = harden({
32
+ label: M.string(),
33
+ instance: M.remotable('Instance'),
34
+ publicFacet: M.remotable('publicFacet'),
35
+ });
36
+ const BundleIdShape = M.string();
37
+ const VatUpgradeResultsShape = M.splitRecord({ incarnationNumber: M.number() });
38
+ const IssuersShape = M.recordOf(M.string(), IssuerShape);
39
+ const PrivateArgsOverridesShape = M.record();
40
+
41
+ const iface = M.interface('ContractControl', {
42
+ install: M.callWhen(M.string()).returns({
43
+ boardId: M.string(),
44
+ installation: InstallationShape,
45
+ }),
46
+ start: M.callWhen(
47
+ M.splitRecord(
48
+ { installation: InstallationShape },
49
+ {
50
+ issuers: IssuersShape,
51
+ privateArgsOverrides: PrivateArgsOverridesShape,
52
+ },
53
+ {}, // Refuse unsupported options
54
+ ),
55
+ ).returns(PublicParts),
56
+ installAndStart: M.callWhen(
57
+ M.splitRecord(
58
+ { bundleId: BundleIdShape },
59
+ {
60
+ issuers: IssuersShape,
61
+ privateArgsOverrides: PrivateArgsOverridesShape,
62
+ },
63
+ {}, // Refuse unsupported options
64
+ ),
65
+ ).returns(PublicParts),
66
+ getPublicFacet: M.call().returns(M.remotable('publicFacet')),
67
+ getCreatorFacet: M.call().returns(M.remotable('creatorFacet')),
68
+ upgrade: M.callWhen(
69
+ M.or(
70
+ BundleIdShape, // Backward compat with old signature
71
+ M.splitRecord(
72
+ { bundleId: BundleIdShape },
73
+ {
74
+ privateArgsOverrides: PrivateArgsOverridesShape,
75
+ },
76
+ {}, // Refuse unsupported options
77
+ ),
78
+ ),
79
+ ).returns(VatUpgradeResultsShape),
80
+ terminate: M.callWhen()
81
+ .optional(
82
+ M.splitRecord(
83
+ {},
84
+ { message: M.string(), target: M.string(), revoke: M.boolean() },
85
+ {}, // Refuse unsupported options
86
+ ),
87
+ )
88
+ .returns(),
89
+ pruneChainStorage: M.callWhen(
90
+ M.recordOf(M.string(), M.arrayOf(M.string())),
91
+ ).returns(M.number()),
92
+ revoke: M.call().returns(),
93
+ });
94
+
95
+ /**
96
+ * @param {Zone} zone
97
+ * @param {{
98
+ * agoricNamesAdmin: ERef<NameHubKit['nameAdmin']>,
99
+ * board: ERef<Board>,
100
+ * zoe: ERef<ZoeService>,
101
+ * startUpgradable: ERef<StartUpgradable>,
102
+ * updatePrivateArgs: ERef<UpdatePrivateArgs>
103
+ * }} svcs
104
+ */
105
+ export const prepareContractControl = (zone, svcs) => {
106
+ /** @template {ContractStartFunction} SF */
107
+ const makeMaker = () =>
108
+ zone.exoClass(
109
+ 'ContractControl',
110
+ iface,
111
+ /**
112
+ * Install by bundleId and publish to (the board and) agoricNames,
113
+ * replacing anything that was there before.
114
+ *
115
+ * Former installations can be reached from the board.
116
+ *
117
+ * @param {ContractControlOpts} initial
118
+ */
119
+ initial => ({
120
+ initialPrivateArgs:
121
+ initial.initialPrivateArgs || initial.kit?.privateArgs,
122
+ kit: undefined,
123
+ ...initial,
124
+ revoked: false,
125
+ }),
126
+ {
127
+ /** @param {string} bundleId */
128
+ async install(bundleId) {
129
+ const { name, revoked } = this.state;
130
+ trace(name, 'install', bundleId);
131
+ !revoked || Fail`revoked`;
132
+ const { zoe, board, agoricNamesAdmin } = svcs;
133
+ const installation = await E(zoe).installBundleID(bundleId);
134
+ const installationAdmin =
135
+ E(agoricNamesAdmin).lookupAdmin('installation');
136
+ await E(installationAdmin).update(name, installation);
137
+ const boardId = await E(board).getId(installation);
138
+ trace(name, 'installed', { bundleId, installation, boardId });
139
+ return { boardId, installation };
140
+ },
141
+
142
+ /**
143
+ * Start the contract; publish the instance to (the board and) agoricNames.
144
+ *
145
+ * @throws if already running
146
+ * @param {object} opts
147
+ * @param {Installation<SF>} opts.installation
148
+ * @param {IssuerKeywordRecord} [opts.issuers]
149
+ * @param {Partial<Parameters<SF>[1]>} [opts.privateArgsOverrides]
150
+ */
151
+ async start({ installation, issuers, privateArgsOverrides }) {
152
+ const { name, storageNode, revoked, initialPrivateArgs } = this.state;
153
+ !revoked || Fail`revoked`;
154
+ !this.state.kit || Fail`${name} already started`;
155
+ const { startUpgradable, board } = svcs;
156
+ const installationId = await E(board).getId(installation);
157
+ trace(name, 'startUpgradable', { installation, installationId });
158
+ const privateArgs = harden({
159
+ ...initialPrivateArgs,
160
+ storageNode,
161
+ ...privateArgsOverrides,
162
+ });
163
+ const kit = await E(startUpgradable)({
164
+ label: name,
165
+ installation,
166
+ issuerKeywordRecord: issuers,
167
+ // terms: customTerms,
168
+ privateArgs,
169
+ });
170
+ /** @type {StartedInstanceKitWithLabel<SF>} */
171
+ const fullKit = harden({ ...kit, privateArgs });
172
+ trace(name, 'started', objectMap(fullKit, passStyleOf));
173
+ this.state.kit = fullKit;
174
+
175
+ const { agoricNamesAdmin } = svcs;
176
+ const instanceAdmin = E(agoricNamesAdmin).lookupAdmin('instance');
177
+ await E(instanceAdmin).update(name, kit.instance);
178
+ const boardId = await E(board).getId(kit.instance);
179
+ trace(name, 'published', { boardId, installationId });
180
+
181
+ return harden({
182
+ label: name,
183
+ instance: kit.instance,
184
+ publicFacet: kit.publicFacet,
185
+ });
186
+ },
187
+
188
+ /**
189
+ * @param {object} opts
190
+ * @param {string} opts.bundleId
191
+ * @param {IssuerKeywordRecord} [opts.issuers]
192
+ * @param {Partial<Parameters<SF>[1]>} [opts.privateArgsOverrides]
193
+ */
194
+ async installAndStart({ bundleId, issuers, privateArgsOverrides }) {
195
+ trace(this.state.name, 'installAndStart');
196
+ !this.state.revoked || Fail`revoked`;
197
+ const { self } = this;
198
+ const { installation } = await self.install(bundleId);
199
+ return self.start({
200
+ installation,
201
+ issuers,
202
+ privateArgsOverrides,
203
+ });
204
+ },
205
+
206
+ /** @returns {StartResult<SF>['publicFacet']} */
207
+ getPublicFacet() {
208
+ const { name, revoked, kit } = this.state;
209
+ trace(name, 'getPublicFacet');
210
+ !revoked || Fail`revoked`;
211
+ if (!kit) throw Fail`${q(name)}: no StartedInstanceKit`;
212
+ return kit.publicFacet;
213
+ },
214
+
215
+ /** @returns {StartResult<SF>['creatorFacet']} */
216
+ getCreatorFacet() {
217
+ const { name, revoked, kit } = this.state;
218
+ trace(name, 'getCreatorFacet');
219
+ !revoked || Fail`revoked`;
220
+ if (!kit) throw Fail`${q(name)}: no StartedInstanceKit`;
221
+ return kit.creatorFacet;
222
+ },
223
+
224
+ /** @param {string | {bundleId: string; privateArgsOverrides?: Partial<Parameters<SF>[1]>; }} opts */
225
+ async upgrade(opts) {
226
+ if (typeof opts === 'string') opts = { bundleId: opts };
227
+ const { bundleId, privateArgsOverrides = {} } = opts;
228
+
229
+ const { updatePrivateArgs } = svcs;
230
+
231
+ const { name, revoked, kit, initialPrivateArgs } = this.state;
232
+ trace(name, 'upgrade', bundleId);
233
+ !revoked || Fail`revoked`;
234
+ if (!kit) throw Fail`${q(name)}: no StartedInstanceKit`;
235
+ const { privateArgs: previousPrivateArgs } = kit;
236
+ const privateArgs = {
237
+ ...initialPrivateArgs,
238
+ ...previousPrivateArgs,
239
+ ...privateArgsOverrides,
240
+ };
241
+ const result = await E(kit.adminFacet).upgradeContract(
242
+ bundleId,
243
+ privateArgs,
244
+ );
245
+ const newKit = harden({ ...kit, privateArgs });
246
+ trace(name, 'upgrade result', result);
247
+ this.state.kit = newKit;
248
+ await E(updatePrivateArgs)(kit.instance, privateArgs);
249
+ return result;
250
+ },
251
+
252
+ /**
253
+ * @param {object} opts
254
+ * @param {string} [opts.target] boardId to confirm is current
255
+ * @param {string} [opts.message] for termination error
256
+ * @param {boolean} [opts.revoke] neuter this object
257
+ */
258
+ async terminate(opts = {}) {
259
+ const { name, kit, revoked } = this.state;
260
+ trace(name, 'terminate', kit?.adminFacet, opts);
261
+ !revoked || Fail`revoked`;
262
+ const { target, message = 'terminated', revoke } = opts;
263
+ if (!kit) {
264
+ if (revoke) {
265
+ trace(name, 'revoked');
266
+ this.state.revoked = true;
267
+ return;
268
+ }
269
+ throw Fail`${q(name)}: no StartedInstanceKit`;
270
+ }
271
+
272
+ await null;
273
+ if (target) {
274
+ const current = await E(svcs.board).getId(kit.instance);
275
+ assert.equal(current, target);
276
+ }
277
+
278
+ try {
279
+ await E(kit.adminFacet).terminateContract(harden(Error(message)));
280
+ } catch (err) {
281
+ console.error('terminateContract failed; forgetting kit', err);
282
+ }
283
+ this.state.kit = undefined;
284
+ if (revoke) {
285
+ trace(name, 'revoked');
286
+ this.state.revoked = true;
287
+ }
288
+ const { agoricNamesAdmin } = svcs;
289
+ const instanceAdmin = E(agoricNamesAdmin).lookupAdmin('instance');
290
+ await E(instanceAdmin).delete(name);
291
+ },
292
+
293
+ /** @param {Record<string, string[]>} parentToChildren */
294
+ async pruneChainStorage(parentToChildren) {
295
+ const { name, storageNode, revoked } = this.state;
296
+ trace(
297
+ name,
298
+ 'pruneChainStorage',
299
+ Object.keys(parentToChildren).length,
300
+ );
301
+ !revoked || Fail`revoked`;
302
+
303
+ /** @param {string[]} path */
304
+ const makePathNode = path => {
305
+ /** @type {Promise<Remote<StorageNode>>} */
306
+ let node = Promise.resolve(storageNode);
307
+ for (const segment of path) {
308
+ node = E(node).makeChildNode(segment);
309
+ }
310
+ return node;
311
+ };
312
+
313
+ const prefix = await E(storageNode).getPath();
314
+ for (const parent of Object.keys(parentToChildren)) {
315
+ parent.startsWith(prefix) ||
316
+ Fail`${parent} must start with ${prefix}`;
317
+ }
318
+
319
+ let qty = 0;
320
+ for (const [parent, children] of Object.entries(parentToChildren)) {
321
+ trace(name, 'pruning', parent, children);
322
+ const suffix = parent.slice(prefix.length + '.'.length);
323
+ const segments = suffix ? suffix.split('.') : [];
324
+ const parentNode = makePathNode(segments);
325
+ // on failure, trace rather than aborting the whole job
326
+ await Promise.allSettled(
327
+ children.map(async k => {
328
+ await null;
329
+ try {
330
+ await E(
331
+ E(parentNode).makeChildNode(k, { sequence: false }),
332
+ ).setValue('');
333
+ qty += 1;
334
+ } catch (err) {
335
+ trace('rejected:', parent, k, err);
336
+ }
337
+ }),
338
+ );
339
+ }
340
+ trace('done');
341
+ return qty;
342
+ },
343
+
344
+ revoke() {
345
+ const { name, kit, revoked } = this.state;
346
+ trace(name, 'revoke', kit?.adminFacet);
347
+ !revoked || Fail`revoked`;
348
+
349
+ trace(name, 'revoked');
350
+ this.state.revoked = true;
351
+ },
352
+ },
353
+ );
354
+
355
+ /**
356
+ * @template {ContractStartFunction} [SF=ContractStartFunction]
357
+ * @typedef {ReturnType<ReturnType<typeof makeMaker<SF>>>} ContractControl
358
+ */
359
+
360
+ /** @type {<SF extends ContractStartFunction>(initial: ContractControlOpts<SF>) => ContractControl<SF>} */
361
+ const makeContractControl = makeMaker();
362
+
363
+ return makeContractControl;
364
+ };
365
+ harden(prepareContractControl);
366
+
367
+ /**
368
+ * @template {ContractStartFunction} [SF=ContractStartFunction]
369
+ * @typedef {object} ContractControlOpts
370
+ * @property {string} name contractName
371
+ * @property {Remote<StorageNode>} storageNode
372
+ * @property {StartedInstanceKit<SF> & { privateArgs?: Parameters<SF>[1] }} [kit]
373
+ * @property {Partial<Parameters<SF>[1]>} [initialPrivateArgs]
374
+ */
375
+
376
+ /** @typedef {ReturnType<typeof prepareContractControl>} MakeContractControl */
377
+
378
+ // Hack to allow extracting the generic result of MakeContractControl
379
+ // See https://github.com/microsoft/TypeScript/issues/62524
380
+ // eslint-disable-next-line
381
+ const maker = /** @type {MakeContractControl} */ (
382
+ /** @type {unknown} */ (undefined)
383
+ );
384
+ harden(maker);
385
+
386
+ /** @template {ContractStartFunction} [SF=ContractStartFunction] @typedef {ReturnType<typeof maker<SF>>} ContractControl */
@@ -0,0 +1,138 @@
1
+ /** @file core eval to deliver control of a contract to a smartWallet */
2
+
3
+ import { makeTracer } from '@agoric/internal/src/debug.js';
4
+ import { makeHeapZone } from '@agoric/zone';
5
+ import { Fail } from '@endo/errors';
6
+ import { E } from '@endo/eventual-send';
7
+ import { YMAX_CONTROL_WALLET_KEY } from '@agoric/portfolio-api/src/portfolio-constants.js';
8
+ import { prepareContractControl } from './contract-control.contract.js';
9
+
10
+ /**
11
+ * @import {ContractStartFunction} from '@agoric/zoe/src/zoeService/utils.js';
12
+ * @import {ChainStoragePresent} from './chain-info.core.js'
13
+ * @import {PostalServiceBoot} from './postal-service.core.js';
14
+ * @import {AttenuatedDepositPowers} from './attenuated-deposit.core.js';
15
+ * @import {ContractControlOpts, ContractControl, UpdatePrivateArgs} from './contract-control.contract.js';
16
+ * @import {PromiseSpaceOf} from '@agoric/vats/src/core/types.js';
17
+ * @import {BootstrapPowers} from '@agoric/vats/src/core/types.js';
18
+ */
19
+
20
+ /**
21
+ * @template {ContractStartFunction} SF
22
+ * @typedef {Omit<ContractControlOpts<SF>, 'storageNode'> & {controlAddress: string}} ContractControlDeliverOpts
23
+ */
24
+
25
+ /**
26
+ * @typedef {<SF extends ContractStartFunction>(opts: ContractControlDeliverOpts<SF>) => Promise<{delivered: Promise<void>, contractControl: ContractControl<SF>}>} DeliverContractControl
27
+ */
28
+
29
+ /**
30
+ * @typedef {PromiseSpaceOf<{ deliverContractControl: DeliverContractControl}>} ContractControlPowers
31
+ */
32
+
33
+ /**
34
+ * @param {BootstrapPowers &
35
+ * ChainStoragePresent &
36
+ * PostalServiceBoot &
37
+ * ContractControlPowers &
38
+ * AttenuatedDepositPowers
39
+ * } permitted
40
+ */
41
+ export const produceDeliverContractControl = async permitted => {
42
+ permitted.produce.deliverContractControl.reset();
43
+ await null;
44
+
45
+ const { consume } = permitted;
46
+
47
+ const { chainStorage, getDepositFacet, zoe } = consume;
48
+
49
+ const instancePrivateArgs = await consume.instancePrivateArgs;
50
+
51
+ const postalSvcPub = E.when(
52
+ permitted.instance.consume.postalService,
53
+ instance => E(zoe).getPublicFacet(instance),
54
+ );
55
+
56
+ /** @type {UpdatePrivateArgs} */
57
+ const updatePrivateArgs = (instance, privateArgs) => {
58
+ if (!instancePrivateArgs.has(instance)) {
59
+ Fail`instance doesn't have privateArgs`;
60
+ }
61
+ instancePrivateArgs.set(instance, privateArgs);
62
+ };
63
+
64
+ // Use a heap zone to avoid entanglement with old liveslots
65
+ const zone = makeHeapZone();
66
+ const makeContractControl = prepareContractControl(zone, {
67
+ agoricNamesAdmin: await consume.agoricNamesAdmin,
68
+ board: await consume.board,
69
+ startUpgradable: await consume.startUpgradable,
70
+ updatePrivateArgs,
71
+ zoe: await zoe,
72
+ });
73
+
74
+ /** @type {DeliverContractControl} */
75
+ const deliverContractControl = async ({
76
+ name: contractName,
77
+ controlAddress,
78
+ ...opts
79
+ }) => {
80
+ await null;
81
+
82
+ const trace = makeTracer(`CCtrlCore-${contractName}`);
83
+
84
+ trace('creating contract control and delivering to', controlAddress);
85
+
86
+ const contractControl = makeContractControl({
87
+ name: contractName,
88
+ storageNode: await E(chainStorage).makeChildNode(contractName),
89
+ ...opts,
90
+ });
91
+
92
+ trace('reserving', controlAddress);
93
+ // This can block if the wallet is not provisioned
94
+ await E(getDepositFacet)(controlAddress);
95
+
96
+ trace(`delivering control`, controlAddress, contractControl);
97
+ // don't block on the recipient's offer
98
+ const delivered = E.when(
99
+ E(postalSvcPub).deliverPrize(
100
+ controlAddress,
101
+ contractControl,
102
+ YMAX_CONTROL_WALLET_KEY,
103
+ ),
104
+ () => trace('control received'),
105
+ );
106
+
107
+ return harden({ delivered, contractControl });
108
+ };
109
+ harden(deliverContractControl);
110
+
111
+ permitted.produce.deliverContractControl.resolve(deliverContractControl);
112
+ };
113
+
114
+ export const getManifestForDeliverContractControl = () => ({
115
+ manifest: {
116
+ [produceDeliverContractControl.name]: {
117
+ consume: {
118
+ agoricNamesAdmin: true,
119
+ board: true,
120
+ chainStorage: true,
121
+ getDepositFacet: true,
122
+ instancePrivateArgs: true,
123
+ startUpgradable: true,
124
+ zoe: true,
125
+ },
126
+ produce: {
127
+ deliverContractControl: true,
128
+ },
129
+ instance: {
130
+ consume: { postalService: true },
131
+ // provide: true,
132
+ },
133
+ installation: {
134
+ // provide: true,
135
+ },
136
+ },
137
+ },
138
+ });
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @import { CoreEvalBuilder, DeployScriptFunction } from '@agoric/deploy-script-support/src/externalTypes.js';
3
+ */
4
+
5
+ import { makeHelpers } from '../helpers.js';
6
+ import { getManifestForGetUpgradeKit } from './get-upgrade-kit.core.js';
7
+
8
+ const sourceSpec =
9
+ '@agoric/deploy-script-support/src/control/get-upgrade-kit.core.js';
10
+
11
+ /**
12
+ * @satisfies {CoreEvalBuilder}
13
+ */
14
+ const defaultProposalBuilder = async () => {
15
+ return harden({
16
+ sourceSpec,
17
+ getManifestCall: [getManifestForGetUpgradeKit.name],
18
+ });
19
+ };
20
+
21
+ /** @type {DeployScriptFunction} */
22
+ const build = async (homeP, endowments) => {
23
+ const { writeCoreEval } = await makeHelpers(homeP, endowments);
24
+
25
+ await writeCoreEval('eval-get-upgrade-kit', () => defaultProposalBuilder());
26
+ };
27
+
28
+ export default build;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @file Get a contract upgrade kit by name.
3
+ */
4
+
5
+ import { E } from '@endo/eventual-send';
6
+
7
+ /**
8
+ * @import {ContractStartFunction, Instance} from '@agoric/zoe/src/zoeService/utils.js';
9
+ * @import {StartedInstanceKit, StartedInstanceKitWithLabel} from '@agoric/vats/src/core/types.js';
10
+ * @import {PromiseSpaceOf} from '@agoric/vats/src/core/types.js';
11
+ * @import {BootstrapPowers} from '@agoric/vats/src/core/types.js';
12
+ */
13
+
14
+ const trace = (...args) => console.log('---- GetUpgradeKit', ...args);
15
+
16
+ /**
17
+ * Similar to StartedInstanceKit, but with label and optional privateArgs
18
+ * managed by ContractControl.
19
+ *
20
+ * @template {ContractStartFunction} SF
21
+ * @typedef {StartedInstanceKit<SF> & {
22
+ * label: string;
23
+ * privateArgs?: Parameters<SF>[1];
24
+ * }} UpgradeKit
25
+ */
26
+
27
+ /**
28
+ * `getUpgradeKit` is a power allowing to retrieve a contract upgrade kit by
29
+ * the contract's name, without giving full access to the underlying stores.
30
+ *
31
+ * @typedef {PromiseSpaceOf<{
32
+ * getUpgradeKit: <SF extends ContractStartFunction>(name: string) => Promise<StartedInstanceKitWithLabel<SF>>
33
+ * }>} GetUpgradeKitPowers
34
+ */
35
+
36
+ /**
37
+ * @param {BootstrapPowers & GetUpgradeKitPowers} powers
38
+ */
39
+ export const produceGetUpgradeKit = async powers => {
40
+ powers.produce.getUpgradeKit.reset();
41
+ const { consume } = powers;
42
+
43
+ const agoricNames = await consume.agoricNames;
44
+ const contractKits = await consume.contractKits;
45
+ const instancePrivateArgs = await consume.instancePrivateArgs;
46
+
47
+ /**
48
+ * @template {ContractStartFunction} SF
49
+ * @param {string} name
50
+ * @returns {Promise<StartedInstanceKitWithLabel<SF>>}
51
+ */
52
+ const getUpgradeKit = async name => {
53
+ trace('getting instance for', name);
54
+ /** @type {Instance<SF>} */
55
+ const instance = await E(agoricNames).lookup('instance', name);
56
+
57
+ trace('getting contract kit for', name);
58
+ const kit = /** @type {UpgradeKit<SF>} */ (contractKits.get(instance));
59
+
60
+ trace('getting privateArgs for', name);
61
+ const privateArgs = instancePrivateArgs.has(instance)
62
+ ? /** @type {Parameters<SF>[1]} */ (instancePrivateArgs.get(instance))
63
+ : undefined;
64
+
65
+ return { ...kit, privateArgs };
66
+ };
67
+ harden(getUpgradeKit);
68
+
69
+ powers.produce.getUpgradeKit.resolve(getUpgradeKit);
70
+ };
71
+
72
+ export const getManifestForGetUpgradeKit = () => ({
73
+ manifest: {
74
+ [produceGetUpgradeKit.name]: {
75
+ consume: {
76
+ agoricNames: true,
77
+ contractKits: true,
78
+ instancePrivateArgs: true,
79
+ },
80
+ produce: { getUpgradeKit: true },
81
+ },
82
+ },
83
+ });
@@ -0,0 +1,59 @@
1
+ import { E } from '@endo/eventual-send';
2
+ import { passStyleOf } from '@endo/pass-style';
3
+ import { objectMap } from '@endo/patterns';
4
+ import { makeTracer } from '@agoric/internal/src/debug.js';
5
+
6
+ const contractName = 'postalService';
7
+
8
+ const trace = makeTracer('CCtrlCore');
9
+
10
+ /**
11
+ * @import {Installation} from '@agoric/zoe';
12
+ * @import {Instance} from '@agoric/zoe';
13
+ * @import {PromiseSpaceOf} from '@agoric/vats/src/core/types.js';
14
+ * @import {BootstrapPowers} from '@agoric/vats/src/core/types.js';
15
+ * @import {start} from '@aglocal/portfolio-deploy/src/control/postal-service.contract.js';
16
+ */
17
+ /**
18
+ * @typedef {typeof start} PostalServiceStartFn
19
+ *
20
+ * @typedef {{
21
+ * installation: PromiseSpaceOf<{ postalService: Installation<PostalServiceStartFn>}>;
22
+ * instance: PromiseSpaceOf<{ postalService: Instance<PostalServiceStartFn>}>;
23
+ * }} PostalServiceBoot
24
+ */
25
+
26
+ /** @param {BootstrapPowers & PostalServiceBoot} permitted */
27
+ export const deployPostalService = async permitted => {
28
+ permitted.instance.produce.postalService.reset();
29
+ const { startUpgradable, namesByAddress } = permitted.consume;
30
+ const { postalService: installationP } = permitted.installation.consume;
31
+ trace('await: installation.postalService');
32
+ const installation = await installationP;
33
+ trace('await: namesByAddress');
34
+ const privateArgs = harden({
35
+ namesByAddress: await namesByAddress,
36
+ });
37
+ trace('await: startUpgradable(...)');
38
+ const kit = await E(startUpgradable)({
39
+ label: contractName,
40
+ installation,
41
+ privateArgs,
42
+ });
43
+ trace('kit', objectMap(kit, passStyleOf));
44
+ permitted.instance.produce.postalService.resolve(kit.instance);
45
+ };
46
+
47
+ export const getManifestForPostalService = (
48
+ { restoreRef },
49
+ { installKeys },
50
+ ) => ({
51
+ manifest: {
52
+ [deployPostalService.name]: {
53
+ consume: { startUpgradable: true, namesByAddress: true },
54
+ installation: { consume: { postalService: true } },
55
+ instance: { produce: { postalService: true } },
56
+ },
57
+ },
58
+ installations: { postalService: restoreRef(installKeys.postalService) },
59
+ });
@@ -0,0 +1,150 @@
1
+ /* global globalThis */
2
+ /**
3
+ * @file Reusable wallet utilities for portfolio contract testing and operations
4
+ *
5
+ * This module consolidates wallet-related utilities that were previously
6
+ * duplicated across multichain-testing and a3p-integration.
7
+ */
8
+
9
+ import { retryUntilCondition } from '@agoric/client-utils';
10
+ import { YMAX_CONTROL_WALLET_KEY } from '@agoric/portfolio-api/src/portfolio-constants.js';
11
+
12
+ /**
13
+ * @import {UpdateRecord} from '@agoric/smart-wallet/src/smartWallet.js';
14
+ * @import {RetryOptions} from '@agoric/client-utils';
15
+ * @import {SigningSmartWalletKit} from '@agoric/client-utils';
16
+ * @import {Instance} from '@agoric/zoe';
17
+ * @import {BridgeAction} from '@agoric/smart-wallet/src/smartWallet.js';
18
+ */
19
+
20
+ /**
21
+ * Helper to wait for specific wallet updates
22
+ *
23
+ * This provides a higher-level API over retryUntilCondition for common
24
+ * wallet update patterns.
25
+ *
26
+ * @param {() => Promise<UpdateRecord>} getLastUpdate
27
+ * @param {RetryOptions & {
28
+ * log: (...args: unknown[]) => void;
29
+ * setTimeout: typeof globalThis.setTimeout;
30
+ * }} retryOpts
31
+ */
32
+ export const walletUpdates = (getLastUpdate, retryOpts) => {
33
+ return harden({
34
+ /**
35
+ * Wait for an invocation to complete
36
+ * @param {string | number} id
37
+ */
38
+ invocation: async id => {
39
+ const done = /** @type {UpdateRecord & { updated: 'invocation' }} */ (
40
+ await retryUntilCondition(
41
+ getLastUpdate,
42
+ update =>
43
+ update.updated === 'invocation' &&
44
+ update.id === id &&
45
+ !!(update.result || update.error),
46
+ `${id}`,
47
+ retryOpts,
48
+ )
49
+ );
50
+ if (done.error) throw Error(done.error);
51
+ return done.result;
52
+ },
53
+
54
+ /**
55
+ * Wait for an offer to complete
56
+ * @param {string | number} id
57
+ */
58
+ offerResult: async id => {
59
+ const done = await retryUntilCondition(
60
+ getLastUpdate,
61
+ update =>
62
+ // walletAction implies an error, so also stop on that
63
+ update.updated === 'walletAction' ||
64
+ // if it's offerStatus, it can be in progress until result or error
65
+ (update.updated === 'offerStatus' &&
66
+ update.status.id === id &&
67
+ (!!update.status.result || !!update.status.error)),
68
+ `${id}`,
69
+ retryOpts,
70
+ );
71
+ switch (done.updated) {
72
+ case 'walletAction':
73
+ throw Error(`walletAction failure: ${done.status.error}`);
74
+ case 'offerStatus':
75
+ if (done.status.error) {
76
+ throw Error(`offerStatus failure: ${done.status.error}`);
77
+ }
78
+ if (!done.status.result) {
79
+ throw Error(`offerStatus missing result`);
80
+ }
81
+ return done.status.result;
82
+ default:
83
+ throw Error(`unexpected update type ${done.updated}`);
84
+ }
85
+ },
86
+ });
87
+ };
88
+
89
+ /**
90
+ * Redeem the ymaxControl invitation from the postal service
91
+ *
92
+ * This is a common pattern in ymax testing where we need to redeem
93
+ * the control facet invitation delivered via postal service.
94
+ *
95
+ * @param {{
96
+ * sig: SigningSmartWalletKit;
97
+ * postalServiceInstance: Instance;
98
+ * fresh: () => string | number;
99
+ * log?: (...args: unknown[]) => void;
100
+ * }} options
101
+ * @returns {Promise<void>}
102
+ */
103
+ export const redeemYmaxControlInvitation = async ({
104
+ sig,
105
+ postalServiceInstance,
106
+ fresh,
107
+ log = () => {},
108
+ }) => {
109
+ const id = `redeem-${YMAX_CONTROL_WALLET_KEY}-${fresh()}`;
110
+ log('Redeeming ymaxControl invitation', id);
111
+
112
+ /** @type {BridgeAction} */
113
+ const redeemAction = harden({
114
+ method: 'executeOffer',
115
+ offer: {
116
+ id,
117
+ invitationSpec: {
118
+ source: 'purse',
119
+ instance: postalServiceInstance,
120
+ description: `deliver ${YMAX_CONTROL_WALLET_KEY}`,
121
+ },
122
+ proposal: {},
123
+ saveResult: { name: YMAX_CONTROL_WALLET_KEY, overwrite: true },
124
+ },
125
+ });
126
+
127
+ const tx = await sig.sendBridgeAction(redeemAction);
128
+ if (tx.code !== 0) {
129
+ throw Error(`Failed to redeem invitation: ${tx.rawLog}`);
130
+ }
131
+
132
+ const wup = walletUpdates(sig.query.getLastUpdate, {
133
+ setTimeout: globalThis.setTimeout,
134
+ log,
135
+ });
136
+
137
+ await wup.offerResult(id);
138
+ log('ymaxControl invitation redeemed successfully');
139
+ };
140
+
141
+ /**
142
+ * Helper to create action IDs with a consistent format
143
+ *
144
+ * @param {string} description
145
+ * @param {() => string | number} [fresh]
146
+ * @returns {string}
147
+ */
148
+ export const makeActionId = (description, fresh = () => Date.now()) => {
149
+ return `${description}.${fresh()}`;
150
+ };