@agoric/inter-protocol 0.16.2-dev-79a22bb.0.79a22bb → 0.16.2-dev-57c6564.0.57c6564

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.
@@ -1,618 +0,0 @@
1
- // @ts-check
2
- import { X, q, Fail } from '@endo/errors';
3
- import { E } from '@endo/far';
4
-
5
- import { AmountMath, BrandShape } from '@agoric/ertp';
6
- import { deeplyFulfilledObject, makeTracer } from '@agoric/internal';
7
- import { UnguardedHelperI } from '@agoric/internal/src/typeGuards.js';
8
- import {
9
- observeIteration,
10
- observeNotifier,
11
- subscribeEach,
12
- } from '@agoric/notifier';
13
- import {
14
- M,
15
- makeScalarBigMapStore,
16
- makeScalarBigSetStore,
17
- } from '@agoric/vat-data';
18
- import { makeAtomicProvider, makeScalarMapStore } from '@agoric/store';
19
- import { PowerFlags } from '@agoric/vats/src/walletFlags.js';
20
- import {
21
- PublicTopicShape,
22
- makeRecorderTopic,
23
- } from '@agoric/zoe/src/contractSupport/topics.js';
24
- import { InstanceHandleShape } from '@agoric/zoe/src/typeGuards.js';
25
- import { isUpgradeDisconnection } from '@agoric/internal/src/upgrade-api.js';
26
-
27
- /**
28
- * @import {EReturn} from '@endo/far';
29
- * @import {BridgeMessage} from '@agoric/cosmic-swingset/src/types.js';
30
- * @import {Amount, Brand, Payment, Purse} from '@agoric/ertp';
31
- * @import {ERemote, Remote} from '@agoric/internal';
32
- * @import {StorageNode} from '@agoric/internal/src/lib-chainStorage.js';
33
- * @import {ZCF} from '@agoric/zoe';
34
- * @import {ERef} from '@endo/far'
35
- * @import {Bank, BankManager} from '@agoric/vats/src/vat-bank.js'
36
- * @import {MapStore, SetStore} from '@agoric/store';
37
- * @import {Instance} from '@agoric/zoe/src/zoeService/utils.js';
38
- * @import {start as psmStart} from '@agoric/inter-protocol/src/psm/psm.js';
39
- * @import {NameAdmin} from '@agoric/vats';
40
- * @import {WalletFactoryStartResult} from '@agoric/vats/src/core/startWalletFactory.js';
41
- * @import {Zone} from '@agoric/zone';
42
- * @import {start as StartWalletFactory} from '@agoric/smart-wallet/src/walletFactory.js';
43
- * @import {MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js';
44
- * @import {RecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js';
45
- * @import {BridgeHandler} from '@agoric/vats';
46
- * @import {AssetDescriptor} from '@agoric/vats/src/vat-bank.js';
47
- */
48
-
49
- const trace = makeTracer('ProvPool');
50
-
51
- const FIRST_UPPER_KEYWORD = /^[A-Z][a-zA-Z0-9_$]*$/;
52
- // see https://github.com/Agoric/agoric-sdk/issues/8238
53
- const FIRST_LOWER_NEAR_KEYWORD = /^[a-z][a-zA-Z0-9_$]*$/;
54
-
55
- // XXX when inferred, error TS2742: cannot be named without a reference to '../../../node_modules/@endo/exo/src/get-interface.js'. This is likely not portable. A type annotation is necessary.
56
- /**
57
- * @typedef {{
58
- * machine: {
59
- * addRevivableAddresses: (oldAddresses: string[]) => void;
60
- * getWalletReviver: () => ERef<any>;
61
- * setReferences: (erefs: ProvisionPoolKitReferences) => Promise<void>;
62
- * makeHandler: () => ERef<BridgeHandler>;
63
- * initPSM: (brand: Brand, instance: Instance<typeof psmStart>) => void;
64
- * };
65
- * helper: any;
66
- * forHandler: any;
67
- * public: any;
68
- * }} ProvisionPoolKit
69
- */
70
-
71
- /**
72
- * @typedef {Instance<typeof psmStart>} PsmInstance
73
- */
74
-
75
- /**
76
- * @typedef {object} ProvisionPoolKitReferences
77
- * @property {ERef<BankManager>} bankManager
78
- * @property {ERef<NameAdmin>} namesByAddressAdmin
79
- * @property {ERef<WalletFactoryStartResult['creatorFacet']>} walletFactory
80
- */
81
-
82
- /**
83
- * @typedef {object} MetricsNotification Metrics naming scheme is that nouns are
84
- * present values and past-participles are accumulative.
85
- * @property {bigint} walletsProvisioned count of new wallets provisioned
86
- * @property {Amount<'nat'>} totalMintedProvided running sum of Minted provided
87
- * to new wallets
88
- * @property {Amount<'nat'>} totalMintedConverted running sum of Minted ever
89
- * received by the contract from PSM
90
- */
91
-
92
- /**
93
- * Given attenuated access to the funding purse, handle requests to provision
94
- * smart wallets.
95
- *
96
- * @param {Zone} zone
97
- */
98
- export const prepareBridgeProvisionTool = zone =>
99
- zone.exoClass(
100
- 'smartWalletProvisioningHandler',
101
- M.interface('ProvisionBridgeHandlerMaker', {
102
- fromBridge: M.callWhen(M.record()).returns(),
103
- }),
104
- /**
105
- * @param {ERef<BankManager>} bankManager
106
- * @param {ERef<EReturn<StartWalletFactory>['creatorFacet']>} walletFactory
107
- * @param {ERef<NameAdmin>} namesByAddressAdmin
108
- * @param {ProvisionPoolKit['forHandler']} forHandler
109
- */
110
- (bankManager, walletFactory, namesByAddressAdmin, forHandler) => ({
111
- bankManager,
112
- walletFactory,
113
- namesByAddressAdmin,
114
- forHandler,
115
- }),
116
- {
117
- /** @param {BridgeMessage} obj */
118
- async fromBridge(obj) {
119
- if (obj.type !== 'PLEASE_PROVISION')
120
- throw Fail`Unrecognized request ${obj.type}`;
121
- trace('PLEASE_PROVISION', obj);
122
- const { address, powerFlags } = obj;
123
- // XXX expects powerFlags to be an array, but if it's a string then
124
- // this allows a string that has 'SMART_WALLET' in it.
125
- powerFlags.includes(PowerFlags.SMART_WALLET) ||
126
- Fail`missing SMART_WALLET in powerFlags`;
127
-
128
- const { bankManager, walletFactory, namesByAddressAdmin, forHandler } =
129
- this.state;
130
-
131
- const bank = E(bankManager).getBankForAddress(address);
132
- // only proceed if we can provide funds
133
- await forHandler.sendInitialPayment(bank);
134
-
135
- const [_, created] = await E(walletFactory).provideSmartWallet(
136
- address,
137
- bank,
138
- namesByAddressAdmin,
139
- );
140
- if (created) {
141
- forHandler.onProvisioned();
142
- }
143
- trace(created ? 'provisioned' : 're-provisioned', address);
144
- },
145
- },
146
- );
147
-
148
- /**
149
- * @param {Zone} zone
150
- * @param {{
151
- * makeRecorderKit: MakeRecorderKit;
152
- * params: any;
153
- * poolBank: ERef<Bank>;
154
- * zcf: ZCF;
155
- * makeBridgeProvisionTool: ReturnType<typeof prepareBridgeProvisionTool>;
156
- * }} powers
157
- */
158
- export const prepareProvisionPoolKit = (
159
- zone,
160
- { makeRecorderKit, params, poolBank, zcf, makeBridgeProvisionTool },
161
- ) => {
162
- const zoe = zcf.getZoeService();
163
- const ephemeralPurses = makeScalarMapStore('fundingPurseForBrand');
164
- const purseProvider = makeAtomicProvider(ephemeralPurses);
165
- const getFundingPurseForBrand = async poolBrand => {
166
- await null;
167
- try {
168
- const purse = await purseProvider.provideAsync(poolBrand, brand =>
169
- E(poolBank).getPurse(brand),
170
- );
171
- return purse;
172
- } catch (err) {
173
- trace(`🚨 could not get purse for brand ${poolBrand}`, err);
174
- throw err;
175
- }
176
- };
177
-
178
- const makeProvisionPoolKitInternal = zone.exoClassKit(
179
- 'ProvisionPoolKit',
180
- {
181
- machine: M.interface('ProvisionPoolKit machine', {
182
- addRevivableAddresses: M.call(M.arrayOf(M.string())).returns(),
183
- getWalletReviver: M.call().returns(
184
- M.remotable('ProvisionPoolKit wallet reviver'),
185
- ),
186
- setReferences: M.callWhen({
187
- bankManager: M.eref(M.remotable('bankManager')),
188
- namesByAddressAdmin: M.eref(M.remotable('nameAdmin')),
189
- walletFactory: M.eref(M.remotable('walletFactory')),
190
- }).returns(),
191
- makeHandler: M.call().returns(M.remotable('BridgeHandler')),
192
- initPSM: M.call(BrandShape, InstanceHandleShape).returns(),
193
- }),
194
- walletReviver: M.interface('ProvisionPoolKit wallet reviver', {
195
- reviveWallet: M.callWhen(M.string()).returns(
196
- M.remotable('SmartWallet'),
197
- ),
198
- ackWallet: M.call(M.string()).returns(M.boolean()),
199
- }),
200
- helper: UnguardedHelperI,
201
- forHandler: UnguardedHelperI,
202
- public: M.interface('ProvisionPoolKit public', {
203
- getPublicTopics: M.call().returns({ metrics: PublicTopicShape }),
204
- }),
205
- },
206
- /**
207
- * @param {object} opts
208
- * @param {Purse<'nat'>} [opts.fundPurse]
209
- * @param {Brand<'nat'>} opts.poolBrand
210
- * @param {Remote<StorageNode>} opts.metricsNode
211
- */
212
- ({ fundPurse, poolBrand, metricsNode }) => {
213
- /** @type {RecorderKit<MetricsNotification>} */
214
- const metricsRecorderKit = makeRecorderKit(metricsNode);
215
-
216
- /** @type {MapStore<ERef<Brand>, PsmInstance>} */
217
- const brandToPSM = makeScalarBigMapStore('brandToPSM', { durable: true });
218
- const revivableAddresses = makeScalarBigSetStore('revivableAddresses', {
219
- durable: true,
220
- keyShape: M.string(),
221
- });
222
-
223
- /**
224
- * to be set by `setReferences`
225
- *
226
- * @type {Partial<ProvisionPoolKitReferences>}
227
- */
228
- const references = {
229
- bankManager: undefined,
230
- namesByAddressAdmin: undefined,
231
- walletFactory: undefined,
232
- };
233
-
234
- return {
235
- brandToPSM,
236
- fundPurse,
237
- metricsRecorderKit,
238
- poolBrand,
239
- walletsProvisioned: 0n,
240
- totalMintedProvided: AmountMath.makeEmpty(poolBrand),
241
- totalMintedConverted: AmountMath.makeEmpty(poolBrand),
242
- revivableAddresses,
243
- ...references,
244
- };
245
- },
246
- {
247
- // aka "limitedCreatorFacet"
248
- machine: {
249
- /** @param {string[]} oldAddresses */
250
- addRevivableAddresses(oldAddresses) {
251
- trace('revivableAddresses count', oldAddresses.length);
252
- this.state.revivableAddresses.addAll(oldAddresses);
253
- },
254
- getWalletReviver() {
255
- return this.facets.walletReviver;
256
- },
257
- /** @param {ProvisionPoolKitReferences} erefs */
258
- async setReferences(erefs) {
259
- const { bankManager, namesByAddressAdmin, walletFactory } = erefs;
260
- const obj = harden({
261
- bankManager,
262
- namesByAddressAdmin,
263
- walletFactory,
264
- });
265
- const refs = await deeplyFulfilledObject(obj);
266
- Object.assign(this.state, refs);
267
- },
268
- /** @returns {BridgeHandler} */
269
- makeHandler() {
270
- const { bankManager, namesByAddressAdmin, walletFactory } =
271
- this.state;
272
- if (!bankManager || !namesByAddressAdmin || !walletFactory) {
273
- throw Fail`must set references before handling requests`;
274
- }
275
-
276
- const { forHandler } = this.facets;
277
-
278
- const provisionHandler = makeBridgeProvisionTool(
279
- bankManager,
280
- walletFactory,
281
- namesByAddressAdmin,
282
- forHandler,
283
- );
284
-
285
- return provisionHandler;
286
- },
287
- /**
288
- * @param {Brand} brand
289
- * @param {PsmInstance} instance
290
- */
291
- initPSM(brand, instance) {
292
- const { brandToPSM } = this.state;
293
- brandToPSM.init(brand, instance);
294
- },
295
- },
296
- walletReviver: {
297
- /** @param {string} address */
298
- async reviveWallet(address) {
299
- const {
300
- revivableAddresses,
301
- bankManager,
302
- namesByAddressAdmin,
303
- walletFactory,
304
- } = this.state;
305
- if (!bankManager || !namesByAddressAdmin || !walletFactory) {
306
- throw Fail`must set references before handling requests`;
307
- }
308
- revivableAddresses.has(address) ||
309
- Fail`non-revivable address ${address}`;
310
- const bank = E(bankManager).getBankForAddress(address);
311
- const [wallet, _created] = await E(walletFactory).provideSmartWallet(
312
- address,
313
- bank,
314
- namesByAddressAdmin,
315
- );
316
- return wallet;
317
- },
318
- /**
319
- * @param {string} address
320
- * @returns {boolean} isRevive
321
- */
322
- ackWallet(address) {
323
- const { revivableAddresses } = this.state;
324
- if (!revivableAddresses.has(address)) {
325
- return false;
326
- }
327
- revivableAddresses.delete(address);
328
- return true;
329
- },
330
- },
331
- helper: {
332
- publishMetrics() {
333
- const {
334
- metricsRecorderKit,
335
- walletsProvisioned,
336
- totalMintedConverted,
337
- totalMintedProvided,
338
- } = this.state;
339
- void metricsRecorderKit.recorder.write(
340
- harden({
341
- walletsProvisioned,
342
- totalMintedProvided,
343
- totalMintedConverted,
344
- }),
345
- );
346
- },
347
- onTrade(converted) {
348
- const { state, facets } = this;
349
- state.totalMintedConverted = AmountMath.add(
350
- state.totalMintedConverted,
351
- converted,
352
- );
353
- facets.helper.publishMetrics();
354
- },
355
- onSendFunds(provided) {
356
- const { state, facets } = this;
357
- state.totalMintedProvided = AmountMath.add(
358
- state.totalMintedProvided,
359
- provided,
360
- );
361
- facets.helper.publishMetrics();
362
- },
363
- /**
364
- * @param {Amount} amount
365
- * @param {ERef<Purse>} srcPurse
366
- */
367
- async onPoolDeposit(amount, srcPurse) {
368
- const { helper } = this.facets;
369
- const { brandToPSM, poolBrand } = this.state;
370
-
371
- const { brand } = amount;
372
- if (AmountMath.isEmpty(amount) || brand === poolBrand) {
373
- return;
374
- }
375
-
376
- // `amount` doesn't match the current `poolBrand`, so we need to swap
377
- // it.
378
- if (!brandToPSM.has(brand)) {
379
- console.error('funds arrived but no PSM instance', brand);
380
- return;
381
- }
382
- const instance = brandToPSM.get(brand);
383
- const payment = E(srcPurse).withdraw(amount);
384
- await helper.swap(payment, amount, instance).catch(async reason => {
385
- console.error(X`swap failed: ${reason}`);
386
- const resolvedPayment = await payment;
387
- return E(srcPurse).deposit(resolvedPayment);
388
- });
389
- },
390
- /**
391
- * @param {ERef<Purse>} exchangePurse
392
- * @param {ERef<Brand>} brand
393
- */
394
- watchCurrentAmount(exchangePurse, brand) {
395
- const { helper } = this.facets;
396
- void observeNotifier(E(exchangePurse).getCurrentAmountNotifier(), {
397
- updateState: async amount => {
398
- trace('provisionPool balance update', amount);
399
- await helper.onPoolDeposit(amount, exchangePurse);
400
- },
401
- fail: reason => {
402
- if (isUpgradeDisconnection(reason)) {
403
- void helper.watchCurrentAmount(exchangePurse, brand);
404
- } else {
405
- console.error(reason);
406
- }
407
- },
408
- });
409
- },
410
- watchAssetSubscription() {
411
- const { facets } = this;
412
- const { helper } = facets;
413
-
414
- /** @param {AssetDescriptor} desc */
415
- const repairDesc = desc => {
416
- if (desc.issuerName.match(FIRST_UPPER_KEYWORD)) {
417
- trace(`Saving Issuer ${desc.issuerName}`);
418
- return desc;
419
- } else if (desc.issuerName.match(FIRST_LOWER_NEAR_KEYWORD)) {
420
- const bad = desc.issuerName;
421
- const goodName = bad.replace(bad[0], bad[0].toUpperCase());
422
-
423
- trace(
424
- `Saving Issuer ${desc.issuerName} with repaired keyword ${goodName}`,
425
- );
426
- return { ...desc, issuerName: goodName };
427
- } else {
428
- console.error(
429
- `unable to save issuer with illegal keyword: ${desc.issuerName}`,
430
- );
431
- return undefined;
432
- }
433
- };
434
-
435
- return observeIteration(
436
- subscribeEach(E(poolBank).getAssetSubscription()),
437
- {
438
- updateState: async desc => {
439
- await null;
440
- const issuer = zcf.getTerms().issuers[desc.issuerName];
441
- if (issuer === desc.issuer) {
442
- trace('provisionPool re-notified of known asset', desc.brand);
443
- } else {
444
- const goodDesc = repairDesc(desc);
445
- if (goodDesc) {
446
- await zcf.saveIssuer(goodDesc.issuer, goodDesc.issuerName);
447
- } else {
448
- console.error(
449
- `unable to save issuer with illegal keyword: ${desc.issuerName}`,
450
- );
451
- }
452
- }
453
-
454
- /** @type {ERef<Purse>} */
455
- const exchangePurse = E(poolBank).getPurse(desc.brand);
456
- helper.watchCurrentAmount(exchangePurse, desc.brand);
457
- },
458
- fail: _reason => {
459
- void helper.watchAssetSubscription();
460
- },
461
- },
462
- );
463
- },
464
- /**
465
- * @param {Brand<'nat'>} poolBrand
466
- * @param {object} [options]
467
- * @param {MetricsNotification} [options.metrics]
468
- */
469
- start(poolBrand, { metrics } = {}) {
470
- const { facets, state } = this;
471
- const { helper } = facets;
472
- const lastPoolBrand = state.poolBrand;
473
-
474
- // The PerAccountInitialAmount param must use the correct brand for
475
- // this incarnation.
476
- AmountMath.coerce(poolBrand, params.getPerAccountInitialAmount());
477
-
478
- // Restore old metrics.
479
- if (metrics) {
480
- const {
481
- walletsProvisioned,
482
- totalMintedProvided,
483
- totalMintedConverted,
484
- } = metrics;
485
- assert.typeof(walletsProvisioned, 'bigint');
486
- AmountMath.coerce(lastPoolBrand, totalMintedProvided);
487
- AmountMath.coerce(lastPoolBrand, totalMintedConverted);
488
- Object.assign(state, {
489
- walletsProvisioned,
490
- totalMintedProvided,
491
- totalMintedConverted,
492
- });
493
- helper.publishMetrics();
494
- }
495
-
496
- // Update as needed when `poolBrand` changes.
497
- if (poolBrand !== lastPoolBrand) {
498
- state.poolBrand = poolBrand;
499
- state.fundPurse = undefined;
500
- void getFundingPurseForBrand(poolBrand).then(purse =>
501
- helper.updateFundPurse(purse, poolBrand),
502
- );
503
- state.totalMintedProvided = AmountMath.makeEmpty(poolBrand);
504
- state.totalMintedConverted = AmountMath.makeEmpty(poolBrand);
505
- helper.publishMetrics();
506
- }
507
-
508
- void helper.watchAssetSubscription();
509
- },
510
- /**
511
- * @param {ERef<Payment>} payIn
512
- * @param {Amount} amount
513
- * @param {PsmInstance} instance
514
- */
515
- async swap(payIn, amount, instance) {
516
- await null;
517
- const { facets, state } = this;
518
- const { helper } = facets;
519
- const {
520
- poolBrand,
521
- fundPurse = await getFundingPurseForBrand(poolBrand),
522
- } = state;
523
- const psmPub = E(zoe).getPublicFacet(instance);
524
- const proposal = harden({ give: { In: amount } });
525
- const invitation = E(psmPub).makeWantMintedInvitation();
526
- const seat = E(zoe).offer(invitation, proposal, { In: payIn });
527
- const payout = await E(seat).getPayout('Out');
528
- const rxd = await E(fundPurse).deposit(payout);
529
- helper.onTrade(rxd);
530
- return rxd;
531
- },
532
- /**
533
- * @param {Purse<'nat'>} purse
534
- * @param {Brand<'nat'>} brand
535
- */
536
- updateFundPurse(purse, brand) {
537
- const { state } = this;
538
- if (brand !== state.poolBrand || state.fundPurse) return;
539
- state.fundPurse = purse;
540
- },
541
- },
542
- forHandler: {
543
- onProvisioned() {
544
- const { facets, state } = this;
545
- state.walletsProvisioned += 1n;
546
- facets.helper.publishMetrics();
547
- },
548
- /** @param {ERef<Bank>} destBank */
549
- async sendInitialPayment(destBank) {
550
- await null;
551
- const { facets, state } = this;
552
- const { helper } = facets;
553
- const {
554
- poolBrand,
555
- fundPurse = await getFundingPurseForBrand(poolBrand),
556
- } = state;
557
- const perAccountInitialAmount = /** @type {Amount<'nat'>} */ (
558
- params.getPerAccountInitialAmount()
559
- );
560
- trace('sendInitialPayment withdrawing', perAccountInitialAmount);
561
- const initialPmt = await E(fundPurse).withdraw(
562
- perAccountInitialAmount,
563
- );
564
-
565
- const destPurse = E(destBank).getPurse(poolBrand);
566
- return E(destPurse)
567
- .deposit(initialPmt)
568
- .then(amt => {
569
- helper.onSendFunds(perAccountInitialAmount);
570
- trace('provisionPool sent', amt);
571
- })
572
- .catch(reason => {
573
- console.error(X`initial deposit failed: ${q(reason)}`);
574
- void E(fundPurse).deposit(initialPmt);
575
- throw reason;
576
- });
577
- },
578
- },
579
- public: {
580
- getPublicTopics() {
581
- return {
582
- metrics: makeRecorderTopic(
583
- 'Provision Pool metrics',
584
- this.state.metricsRecorderKit,
585
- ),
586
- };
587
- },
588
- },
589
- },
590
- {
591
- finish: ({ facets }) => {
592
- facets.helper.publishMetrics();
593
- },
594
- },
595
- );
596
-
597
- /**
598
- * Prepare synchronous values before passing to real Exo maker
599
- *
600
- * @param {object} opts
601
- * @param {Brand<'nat'>} opts.poolBrand
602
- * @param {ERemote<StorageNode>} opts.storageNode
603
- * @returns {Promise<ProvisionPoolKit>}
604
- */
605
- const makeProvisionPoolKit = async ({ poolBrand, storageNode }) => {
606
- const fundPurse = await getFundingPurseForBrand(poolBrand);
607
- /** @type {Remote<StorageNode>} */
608
- const metricsNode = await E(storageNode).makeChildNode('metrics');
609
-
610
- return makeProvisionPoolKitInternal({
611
- fundPurse,
612
- poolBrand,
613
- metricsNode,
614
- });
615
- };
616
-
617
- return makeProvisionPoolKit;
618
- };