@agoric/fast-usdc 0.1.1-dev-9423fce.0 → 0.1.1-dev-7efdf47.0

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/fast-usdc",
3
- "version": "0.1.1-dev-9423fce.0+9423fce",
3
+ "version": "0.1.1-dev-7efdf47.0+7efdf47",
4
4
  "description": "CLI and library for Fast USDC product",
5
5
  "type": "module",
6
6
  "files": [
@@ -23,9 +23,9 @@
23
23
  "lint:eslint": "eslint ."
24
24
  },
25
25
  "devDependencies": {
26
- "@agoric/swingset-liveslots": "0.10.3-dev-9423fce.0+9423fce",
27
- "@agoric/vats": "0.15.2-dev-9423fce.0+9423fce",
28
- "@agoric/zone": "0.2.3-dev-9423fce.0+9423fce",
26
+ "@agoric/swingset-liveslots": "0.10.3-dev-7efdf47.0+7efdf47",
27
+ "@agoric/vats": "0.15.2-dev-7efdf47.0+7efdf47",
28
+ "@agoric/zone": "0.2.3-dev-7efdf47.0+7efdf47",
29
29
  "@fast-check/ava": "^2.0.1",
30
30
  "ava": "^5.3.0",
31
31
  "c8": "^10.1.2",
@@ -33,16 +33,16 @@
33
33
  "ts-blank-space": "^0.4.4"
34
34
  },
35
35
  "dependencies": {
36
- "@agoric/client-utils": "0.1.1-dev-9423fce.0+9423fce",
37
- "@agoric/cosmic-proto": "0.4.1-dev-9423fce.0+9423fce",
38
- "@agoric/ertp": "0.16.3-dev-9423fce.0+9423fce",
39
- "@agoric/internal": "0.3.3-dev-9423fce.0+9423fce",
40
- "@agoric/notifier": "0.6.3-dev-9423fce.0+9423fce",
41
- "@agoric/orchestration": "0.1.1-dev-9423fce.0+9423fce",
42
- "@agoric/store": "0.9.3-dev-9423fce.0+9423fce",
43
- "@agoric/vat-data": "0.5.3-dev-9423fce.0+9423fce",
44
- "@agoric/vow": "0.1.1-dev-9423fce.0+9423fce",
45
- "@agoric/zoe": "0.26.3-dev-9423fce.0+9423fce",
36
+ "@agoric/client-utils": "0.1.1-dev-7efdf47.0+7efdf47",
37
+ "@agoric/cosmic-proto": "0.4.1-dev-7efdf47.0+7efdf47",
38
+ "@agoric/ertp": "0.16.3-dev-7efdf47.0+7efdf47",
39
+ "@agoric/internal": "0.3.3-dev-7efdf47.0+7efdf47",
40
+ "@agoric/notifier": "0.6.3-dev-7efdf47.0+7efdf47",
41
+ "@agoric/orchestration": "0.1.1-dev-7efdf47.0+7efdf47",
42
+ "@agoric/store": "0.9.3-dev-7efdf47.0+7efdf47",
43
+ "@agoric/vat-data": "0.5.3-dev-7efdf47.0+7efdf47",
44
+ "@agoric/vow": "0.1.1-dev-7efdf47.0+7efdf47",
45
+ "@agoric/zoe": "0.26.3-dev-7efdf47.0+7efdf47",
46
46
  "@cosmjs/proto-signing": "^0.32.4",
47
47
  "@cosmjs/stargate": "^0.32.4",
48
48
  "@endo/base64": "^1.0.9",
@@ -82,5 +82,5 @@
82
82
  "publishConfig": {
83
83
  "access": "public"
84
84
  },
85
- "gitHead": "9423fce41af260a46699c341ad67d4a84ee6a087"
85
+ "gitHead": "7efdf47d7749b9a0c93dca09d51e6939f36aa260"
86
86
  }
@@ -211,6 +211,8 @@ export const prepareAdvancerKit = (
211
211
  poolAccount,
212
212
  harden({ USDC: advanceAmount }),
213
213
  );
214
+ // WARNING: this must never reject, see handler @throws {never} below
215
+ // void not enforced by linter until #10627 no-floating-vows
214
216
  void watch(depositV, this.facets.depositHandler, {
215
217
  advanceAmount,
216
218
  destination,
@@ -233,6 +235,7 @@ export const prepareAdvancerKit = (
233
235
  /**
234
236
  * @param {undefined} result
235
237
  * @param {AdvancerVowCtx & { tmpSeat: ZCFSeat }} ctx
238
+ * @throws {never} WARNING: this function must not throw, because user funds are at risk
236
239
  */
237
240
  onFulfilled(result, ctx) {
238
241
  const { poolAccount, intermediateRecipient, settlementAddress } =
@@ -265,6 +268,7 @@ export const prepareAdvancerKit = (
265
268
  *
266
269
  * @param {Error} error
267
270
  * @param {AdvancerVowCtx & { tmpSeat: ZCFSeat }} ctx
271
+ * @throws {never} WARNING: this function must not throw, because user funds are at risk
268
272
  */
269
273
  onRejected(error, { tmpSeat, advanceAmount, ...restCtx }) {
270
274
  log(
@@ -285,17 +289,12 @@ export const prepareAdvancerKit = (
285
289
  /**
286
290
  * @param {undefined} result
287
291
  * @param {AdvancerVowCtx} ctx
292
+ * @throws {never} WARNING: this function must not throw, because user funds are at risk
288
293
  */
289
294
  onFulfilled(result, ctx) {
290
295
  const { notifier } = this.state;
291
296
  const { advanceAmount, destination, ...detail } = ctx;
292
297
  log('Advance succeeded', { advanceAmount, destination });
293
- // During development, due to a bug, this call threw.
294
- // The failure was silent (no diagnostics) due to:
295
- // - #10576 Vows do not report unhandled rejections
296
- // For now, the advancer kit relies on consistency between
297
- // notify, statusManager, and callers of handleTransactionEvent().
298
- // TODO: revisit #10576 during #10510
299
298
  notifier.notifyAdvancingResult({ destination, ...detail }, true);
300
299
  },
301
300
  /**
@@ -314,6 +313,8 @@ export const prepareAdvancerKit = (
314
313
  tmpReturnSeat,
315
314
  harden({ USDC: advanceAmount }),
316
315
  );
316
+ // WARNING: this must never reject, see handler @throws {never} below
317
+ // void not enforced by linter until #10627 no-floating-vows
317
318
  void watch(withdrawV, this.facets.withdrawHandler, {
318
319
  advanceAmount,
319
320
  tmpReturnSeat,
@@ -325,6 +326,7 @@ export const prepareAdvancerKit = (
325
326
  *
326
327
  * @param {undefined} result
327
328
  * @param {{ advanceAmount: Amount<'nat'>; tmpReturnSeat: ZCFSeat; }} ctx
329
+ * @throws {never} WARNING: this function must not throw, because user funds are at risk
328
330
  */
329
331
  onFulfilled(result, { advanceAmount, tmpReturnSeat }) {
330
332
  const { borrower } = this.state;
@@ -342,6 +344,7 @@ export const prepareAdvancerKit = (
342
344
  /**
343
345
  * @param {Error} error
344
346
  * @param {{ advanceAmount: Amount<'nat'>; tmpReturnSeat: ZCFSeat; }} ctx
347
+ * @throws {never} WARNING: this function must not throw, because user funds are at risk
345
348
  */
346
349
  onRejected(error, { advanceAmount, tmpReturnSeat }) {
347
350
  log(
@@ -117,9 +117,8 @@ export const prepareStatusManager = (
117
117
  /**
118
118
  * @param {EvmHash} txId
119
119
  * @param {TransactionRecord} record
120
- * @returns {Promise<void>}
121
120
  */
122
- const publishTxnRecord = async (txId, record) => {
121
+ const publishTxnRecord = (txId, record) => {
123
122
  const txNode = E(txnsNode).makeChildNode(txId, {
124
123
  sequence: true, // avoid overwriting other output in the block
125
124
  });
@@ -133,9 +132,10 @@ export const prepareStatusManager = (
133
132
  storedCompletedTxs.add(txId);
134
133
  }
135
134
 
136
- const capData = await E(marshaller).toCapData(record);
137
-
138
- await E(txNode).setValue(JSON.stringify(capData));
135
+ // Don't await, just writing to vstorage.
136
+ void E.when(E(marshaller).toCapData(record), capData =>
137
+ E(txNode).setValue(JSON.stringify(capData)),
138
+ );
139
139
  };
140
140
 
141
141
  /**
@@ -144,10 +144,7 @@ export const prepareStatusManager = (
144
144
  */
145
145
  const publishEvidence = (hash, evidence) => {
146
146
  // Don't await, just writing to vstorage.
147
- void publishTxnRecord(
148
- hash,
149
- harden({ evidence, status: TxStatus.Observed }),
150
- );
147
+ publishTxnRecord(hash, harden({ evidence, status: TxStatus.Observed }));
151
148
  };
152
149
 
153
150
  /**
@@ -174,10 +171,10 @@ export const prepareStatusManager = (
174
171
  );
175
172
  publishEvidence(txHash, evidence);
176
173
  if (status === PendingTxStatus.AdvanceSkipped) {
177
- void publishTxnRecord(txHash, harden({ status, risksIdentified }));
174
+ publishTxnRecord(txHash, harden({ status, risksIdentified }));
178
175
  } else if (status !== PendingTxStatus.Observed) {
179
176
  // publishEvidence publishes Observed
180
- void publishTxnRecord(txHash, harden({ status }));
177
+ publishTxnRecord(txHash, harden({ status }));
181
178
  }
182
179
  };
183
180
 
@@ -200,7 +197,7 @@ export const prepareStatusManager = (
200
197
  ];
201
198
  const txpost = { ...tx, status };
202
199
  pendingSettleTxs.set(key, harden([...prefix, txpost, ...suffix]));
203
- void publishTxnRecord(tx.txHash, harden({ status }));
200
+ publishTxnRecord(tx.txHash, harden({ status }));
204
201
  }
205
202
 
206
203
  return zone.exo(
@@ -288,7 +285,7 @@ export const prepareStatusManager = (
288
285
  * @param {boolean} success whether the Transfer succeeded
289
286
  */
290
287
  advanceOutcomeForMintedEarly(txHash, success) {
291
- void publishTxnRecord(
288
+ publishTxnRecord(
292
289
  txHash,
293
290
  harden({
294
291
  status: success
@@ -381,10 +378,7 @@ export const prepareStatusManager = (
381
378
  * @param {import('./liquidity-pool.js').RepayAmountKWR} split
382
379
  */
383
380
  disbursed(txHash, split) {
384
- void publishTxnRecord(
385
- txHash,
386
- harden({ split, status: TxStatus.Disbursed }),
387
- );
381
+ publishTxnRecord(txHash, harden({ split, status: TxStatus.Disbursed }));
388
382
  },
389
383
 
390
384
  /**
@@ -394,7 +388,7 @@ export const prepareStatusManager = (
394
388
  * @param {boolean} success
395
389
  */
396
390
  forwarded(txHash, success) {
397
- void publishTxnRecord(
391
+ publishTxnRecord(
398
392
  txHash,
399
393
  harden({
400
394
  status: success ? TxStatus.Forwarded : TxStatus.ForwardFailed,
@@ -1,7 +1,8 @@
1
+ /** @file Exo for @see {prepareTransactionFeedKit} */
1
2
  import { makeTracer } from '@agoric/internal';
2
3
  import { prepareDurablePublishKit } from '@agoric/notifier';
3
- import { keyEQ, M } from '@endo/patterns';
4
4
  import { Fail, quote } from '@endo/errors';
5
+ import { keyEQ, M } from '@endo/patterns';
5
6
  import { CctpTxEvidenceShape, RiskAssessmentShape } from '../type-guards.js';
6
7
  import { defineInertInvitation } from '../utils/zoe.js';
7
8
  import { prepareOperatorKit } from './operator-kit.js';
@@ -64,8 +65,14 @@ export const stateShape = {
64
65
  pending: M.remotable(),
65
66
  risks: M.remotable(),
66
67
  };
68
+ harden(stateShape);
67
69
 
68
70
  /**
71
+ * A TransactionFeed is responsible for finding quorum among oracles.
72
+ *
73
+ * It receives attestations, records their evidence, and when enough oracles
74
+ * agree, publishes the results for the advancer to act on.
75
+ *
69
76
  * @param {Zone} zone
70
77
  * @param {ZCF} zcf
71
78
  */
@@ -148,18 +155,20 @@ export const prepareTransactionFeedKit = (zone, zcf) => {
148
155
 
149
156
  /** @param {string} operatorId */
150
157
  removeOperator(operatorId) {
151
- const { operators } = this.state;
158
+ const { operators, pending, risks } = this.state;
152
159
  trace('removeOperator', operatorId);
153
160
  const operatorKit = operators.get(operatorId);
154
161
  operatorKit.admin.disable();
155
162
  operators.delete(operatorId);
163
+ pending.delete(operatorId);
164
+ risks.delete(operatorId);
156
165
  },
157
166
  },
158
167
  operatorPowers: {
159
168
  /**
160
169
  * Add evidence from an operator.
161
170
  *
162
- * NB: the operatorKit is responsible for
171
+ * NB: the operatorKit is responsible for revoking access.
163
172
  *
164
173
  * @param {CctpTxEvidence} evidence
165
174
  * @param {RiskAssessment} riskAssessment
@@ -169,10 +178,6 @@ export const prepareTransactionFeedKit = (zone, zcf) => {
169
178
  const { operators, pending, risks } = this.state;
170
179
  trace('attest', operatorId, evidence);
171
180
 
172
- // TODO https://github.com/Agoric/agoric-sdk/pull/10720
173
- // TODO validate that it's a valid for Fast USDC before accepting
174
- // E.g. that the `recipientAddress` is the FU settlement account and that
175
- // the EUD is a chain supported by FU.
176
181
  const { txHash } = evidence;
177
182
 
178
183
  // accept the evidence
@@ -192,6 +197,29 @@ export const prepareTransactionFeedKit = (zone, zcf) => {
192
197
  const found = [...pending.values()].filter(store =>
193
198
  store.has(txHash),
194
199
  );
200
+
201
+ {
202
+ let lastEvidence;
203
+ for (const store of found) {
204
+ const next = store.get(txHash);
205
+ if (lastEvidence && !keyEQ(lastEvidence, next)) {
206
+ // Ignore conflicting evidence, but treat it as an error
207
+ // because it should never happen and needs to be prevented
208
+ // from happening again.
209
+ trace(
210
+ '🚨 conflicting evidence for',
211
+ txHash,
212
+ ':',
213
+ lastEvidence,
214
+ '!=',
215
+ next,
216
+ );
217
+ Fail`conflicting evidence for ${quote(txHash)}`;
218
+ }
219
+ lastEvidence = next;
220
+ }
221
+ }
222
+
195
223
  const minAttestations = Math.ceil(operators.getSize() / 2);
196
224
  trace(
197
225
  'transaction',
@@ -202,48 +230,37 @@ export const prepareTransactionFeedKit = (zone, zcf) => {
202
230
  minAttestations,
203
231
  'necessary attestations',
204
232
  );
233
+
205
234
  if (found.length < minAttestations) {
235
+ // wait for more
206
236
  return;
207
237
  }
208
238
 
209
- let lastEvidence;
210
- for (const store of found) {
211
- const next = store.get(txHash);
212
- if (lastEvidence && !keyEQ(lastEvidence, next)) {
213
- // Ignore conflicting evidence, but treat it as an error
214
- // because it should never happen and needs to be prevented
215
- // from happening again.
216
- trace(
217
- '🚨 conflicting evidence for',
218
- txHash,
219
- ':',
220
- lastEvidence,
221
- '!=',
222
- next,
223
- );
224
- Fail`conflicting evidence for ${quote(txHash)}`;
225
- }
226
- lastEvidence = next;
227
- }
228
-
229
239
  const riskStores = [...risks.values()].filter(store =>
230
240
  store.has(txHash),
231
241
  );
232
- // take the union of risks identified from all operators
233
- const risksIdentified = allRisksIdentified(riskStores, txHash);
234
242
 
235
- // sufficient agreement, so remove from pending risks, then publish
236
- for (const store of found) {
237
- store.delete(txHash);
243
+ // Publish at the threshold of agreement
244
+ if (found.length === minAttestations) {
245
+ // take the union of risks identified from all operators
246
+ const risksIdentified = allRisksIdentified(riskStores, txHash);
247
+ trace('publishing evidence', evidence, risksIdentified);
248
+ publisher.publish({
249
+ evidence,
250
+ risk: { risksIdentified },
251
+ });
252
+ return;
238
253
  }
239
- for (const store of riskStores) {
240
- store.delete(txHash);
254
+
255
+ if (found.length === pending.getSize()) {
256
+ // all have reported so clean up
257
+ for (const store of found) {
258
+ store.delete(txHash);
259
+ }
260
+ for (const store of riskStores) {
261
+ store.delete(txHash);
262
+ }
241
263
  }
242
- trace('publishing evidence', evidence, risksIdentified);
243
- publisher.publish({
244
- evidence,
245
- risk: { risksIdentified },
246
- });
247
264
  },
248
265
  },
249
266
  public: {
@@ -67,10 +67,11 @@ harden(meta);
67
67
  * @param {ERef<Marshaller>} marshaller
68
68
  * @param {FeeConfig} feeConfig
69
69
  */
70
- const publishFeeConfig = async (node, marshaller, feeConfig) => {
70
+ const publishFeeConfig = (node, marshaller, feeConfig) => {
71
71
  const feeNode = E(node).makeChildNode(FEE_NODE);
72
- const value = await E(marshaller).toCapData(feeConfig);
73
- return E(feeNode).setValue(JSON.stringify(value));
72
+ void E.when(E(marshaller).toCapData(feeConfig), value =>
73
+ E(feeNode).setValue(JSON.stringify(value)),
74
+ );
74
75
  };
75
76
 
76
77
  /**
@@ -247,7 +248,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
247
248
  // So we use zone.exoClassKit above to define the liquidity pool kind
248
249
  // and pass the shareMint into the maker / init function.
249
250
 
250
- void publishFeeConfig(storageNode, marshaller, feeConfig);
251
+ publishFeeConfig(storageNode, marshaller, feeConfig);
251
252
 
252
253
  const shareMint = await provideSingleton(
253
254
  zone.mapStore('mint'),