@agoric/fast-usdc 0.1.1-other-dev-3eb1a1d.0 → 0.1.1-upgrade-19-dev-c605745.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.
Files changed (40) hide show
  1. package/README.md +43 -0
  2. package/package.json +29 -25
  3. package/src/add-operators.core.js +63 -0
  4. package/src/cli/bridge-action.js +40 -0
  5. package/src/cli/cli.js +46 -153
  6. package/src/cli/config-commands.js +108 -0
  7. package/src/cli/config.js +15 -9
  8. package/src/cli/lp-commands.js +160 -0
  9. package/src/cli/operator-commands.js +146 -0
  10. package/src/cli/transfer.js +63 -13
  11. package/src/{util → cli/util}/agoric.js +1 -1
  12. package/src/cli/util/bank.js +12 -0
  13. package/src/{util → cli/util}/cctp.js +1 -1
  14. package/src/{util → cli/util}/file.js +1 -1
  15. package/src/clientSupport.js +98 -0
  16. package/src/constants.js +25 -2
  17. package/src/distribute-fees.core.js +93 -0
  18. package/src/exos/advancer.js +220 -106
  19. package/src/exos/liquidity-pool.js +130 -81
  20. package/src/exos/operator-kit.js +16 -12
  21. package/src/exos/settler.js +360 -64
  22. package/src/exos/status-manager.js +316 -65
  23. package/src/exos/transaction-feed.js +121 -42
  24. package/src/fast-usdc-policy.core.js +65 -0
  25. package/src/fast-usdc.contract.js +165 -84
  26. package/src/fast-usdc.flows.js +10 -0
  27. package/src/main.js +1 -0
  28. package/src/pool-share-math.js +55 -9
  29. package/src/{fast-usdc.start.js → start-fast-usdc.core.js} +48 -86
  30. package/src/type-guards.js +75 -24
  31. package/src/types.ts +89 -14
  32. package/src/utils/chain-policies.js +140 -0
  33. package/src/utils/core-eval.js +73 -0
  34. package/src/utils/deploy-config.js +127 -0
  35. package/src/utils/fees.js +3 -4
  36. package/tools/cli-tools.ts +9 -0
  37. package/tools/mock-io.ts +14 -0
  38. package/src/exos/README.md +0 -26
  39. package/src/utils/address.js +0 -71
  40. /package/src/{util → cli/util}/noble.js +0 -0
@@ -1,28 +1,40 @@
1
1
  import { makeTracer } from '@agoric/internal';
2
2
  import { prepareDurablePublishKit } from '@agoric/notifier';
3
- import { M } from '@endo/patterns';
4
- import { CctpTxEvidenceShape } from '../type-guards.js';
3
+ import { keyEQ, M } from '@endo/patterns';
4
+ import { Fail, quote } from '@endo/errors';
5
+ import { CctpTxEvidenceShape, RiskAssessmentShape } from '../type-guards.js';
5
6
  import { defineInertInvitation } from '../utils/zoe.js';
6
7
  import { prepareOperatorKit } from './operator-kit.js';
7
8
 
8
9
  /**
9
10
  * @import {Zone} from '@agoric/zone';
11
+ * @import {MapStore} from '@agoric/store';
10
12
  * @import {OperatorKit} from './operator-kit.js';
11
- * @import {CctpTxEvidence} from '../types.js';
13
+ * @import {CctpTxEvidence, EvidenceWithRisk, RiskAssessment} from '../types.js';
12
14
  */
13
15
 
14
16
  const trace = makeTracer('TxFeed', true);
15
17
 
18
+ /**
19
+ * @typedef {Pick<OperatorKit, 'invitationMakers' | 'operator'>} OperatorOfferResult
20
+ */
21
+
16
22
  /** Name in the invitation purse (keyed also by this contract instance) */
17
23
  export const INVITATION_MAKERS_DESC = 'oracle operator invitation';
18
24
 
19
25
  const TransactionFeedKitI = harden({
20
26
  operatorPowers: M.interface('Transaction Feed Admin', {
21
- submitEvidence: M.call(CctpTxEvidenceShape, M.any()).returns(),
27
+ attest: M.call(
28
+ CctpTxEvidenceShape,
29
+ RiskAssessmentShape,
30
+ M.string(),
31
+ ).returns(),
22
32
  }),
23
33
  creator: M.interface('Transaction Feed Creator', {
24
- // TODO narrow the return shape to OperatorKit
25
- initOperator: M.call(M.string()).returns(M.record()),
34
+ initOperator: M.call(M.string()).returns({
35
+ invitationMakers: M.remotable(),
36
+ operator: M.remotable(),
37
+ }),
26
38
  makeOperatorInvitation: M.call(M.string()).returns(M.promise()),
27
39
  removeOperator: M.call(M.string()).returns(),
28
40
  }),
@@ -31,6 +43,28 @@ const TransactionFeedKitI = harden({
31
43
  }),
32
44
  });
33
45
 
46
+ /**
47
+ * @param {MapStore<string, RiskAssessment>[]} riskStores
48
+ * @param {string} txHash
49
+ */
50
+ const allRisksIdentified = (riskStores, txHash) => {
51
+ /** @type {Set<string>} */
52
+ const setOfRisks = new Set();
53
+ for (const store of riskStores) {
54
+ const next = store.get(txHash);
55
+ for (const risk of next.risksIdentified ?? []) {
56
+ setOfRisks.add(risk);
57
+ }
58
+ }
59
+ return [...setOfRisks.values()].sort();
60
+ };
61
+
62
+ export const stateShape = {
63
+ operators: M.remotable(),
64
+ pending: M.remotable(),
65
+ risks: M.remotable(),
66
+ };
67
+
34
68
  /**
35
69
  * @param {Zone} zone
36
70
  * @param {ZCF} zcf
@@ -41,7 +75,7 @@ export const prepareTransactionFeedKit = (zone, zcf) => {
41
75
  kinds,
42
76
  'Transaction Feed',
43
77
  );
44
- /** @type {PublishKit<CctpTxEvidence>} */
78
+ /** @type {PublishKit<EvidenceWithRisk>} */
45
79
  const { publisher, subscriber } = makeDurablePublishKit();
46
80
 
47
81
  const makeInertInvitation = defineInertInvitation(zcf, 'submitting evidence');
@@ -55,14 +89,12 @@ export const prepareTransactionFeedKit = (zone, zcf) => {
55
89
  TransactionFeedKitI,
56
90
  () => {
57
91
  /** @type {MapStore<string, OperatorKit>} */
58
- const operators = zone.mapStore('operators', {
59
- durable: true,
60
- });
92
+ const operators = zone.mapStore('operators');
61
93
  /** @type {MapStore<string, MapStore<string, CctpTxEvidence>>} */
62
- const pending = zone.mapStore('pending', {
63
- durable: true,
64
- });
65
- return { operators, pending };
94
+ const pending = zone.mapStore('pending');
95
+ /** @type {MapStore<string, MapStore<string, RiskAssessment>>} */
96
+ const risks = zone.mapStore('risks');
97
+ return { operators, pending, risks };
66
98
  },
67
99
  {
68
100
  creator: {
@@ -72,14 +104,14 @@ export const prepareTransactionFeedKit = (zone, zcf) => {
72
104
  * CCTP transactions.
73
105
  *
74
106
  * @param {string} operatorId unique per contract instance
75
- * @returns {Promise<Invitation<OperatorKit>>}
107
+ * @returns {Promise<Invitation<OperatorOfferResult>>}
76
108
  */
77
109
  makeOperatorInvitation(operatorId) {
78
110
  const { creator } = this.facets;
79
111
  trace('makeOperatorInvitation', operatorId);
80
112
 
81
113
  return zcf.makeInvitation(
82
- /** @type {OfferHandler<OperatorKit>} */
114
+ /** @type {OfferHandler<OperatorOfferResult>} */
83
115
  seat => {
84
116
  seat.exit();
85
117
  return creator.initOperator(operatorId);
@@ -87,9 +119,12 @@ export const prepareTransactionFeedKit = (zone, zcf) => {
87
119
  INVITATION_MAKERS_DESC,
88
120
  );
89
121
  },
90
- /** @param {string} operatorId */
122
+ /**
123
+ * @param {string} operatorId
124
+ * @returns {OperatorOfferResult}
125
+ */
91
126
  initOperator(operatorId) {
92
- const { operators, pending } = this.state;
127
+ const { operators, pending, risks } = this.state;
93
128
  trace('initOperator', operatorId);
94
129
 
95
130
  const operatorKit = makeOperatorKit(
@@ -101,12 +136,18 @@ export const prepareTransactionFeedKit = (zone, zcf) => {
101
136
  operatorId,
102
137
  zone.detached().mapStore('pending evidence'),
103
138
  );
139
+ risks.init(operatorId, zone.detached().mapStore('risk assessments'));
104
140
 
105
- return operatorKit;
141
+ // Subset facets to all the off-chain operator needs
142
+ const { invitationMakers, operator } = operatorKit;
143
+ return {
144
+ invitationMakers,
145
+ operator,
146
+ };
106
147
  },
107
148
 
108
149
  /** @param {string} operatorId */
109
- async removeOperator(operatorId) {
150
+ removeOperator(operatorId) {
110
151
  const { operators } = this.state;
111
152
  trace('removeOperator', operatorId);
112
153
  const operatorKit = operators.get(operatorId);
@@ -118,23 +159,17 @@ export const prepareTransactionFeedKit = (zone, zcf) => {
118
159
  /**
119
160
  * Add evidence from an operator.
120
161
  *
162
+ * NB: the operatorKit is responsible for
163
+ *
121
164
  * @param {CctpTxEvidence} evidence
122
- * @param {OperatorKit} operatorKit
165
+ * @param {RiskAssessment} riskAssessment
166
+ * @param {string} operatorId
123
167
  */
124
- submitEvidence(evidence, operatorKit) {
125
- const { pending } = this.state;
126
- trace(
127
- 'submitEvidence',
128
- operatorKit.operator.getStatus().operatorId,
129
- evidence,
130
- );
131
- const { operatorId } = operatorKit.operator.getStatus();
132
-
133
- // TODO should this verify that the operator is one made by this exo?
134
- // This doesn't work...
135
- // operatorKit === operators.get(operatorId) ||
136
- // Fail`operatorKit mismatch`;
168
+ attest(evidence, riskAssessment, operatorId) {
169
+ const { operators, pending, risks } = this.state;
170
+ trace('attest', operatorId, evidence);
137
171
 
172
+ // TODO https://github.com/Agoric/agoric-sdk/pull/10720
138
173
  // TODO validate that it's a valid for Fast USDC before accepting
139
174
  // E.g. that the `recipientAddress` is the FU settlement account and that
140
175
  // the EUD is a chain supported by FU.
@@ -147,6 +182,9 @@ export const prepareTransactionFeedKit = (zone, zcf) => {
147
182
  trace(`operator ${operatorId} already reported ${txHash}`);
148
183
  } else {
149
184
  pendingStore.init(txHash, evidence);
185
+ // accept the risk assessment as well
186
+ const riskStore = risks.get(operatorId);
187
+ riskStore.init(txHash, riskAssessment);
150
188
  }
151
189
  }
152
190
 
@@ -154,25 +192,66 @@ export const prepareTransactionFeedKit = (zone, zcf) => {
154
192
  const found = [...pending.values()].filter(store =>
155
193
  store.has(txHash),
156
194
  );
157
- // TODO determine the real policy for checking agreement
158
- if (found.length < pending.getSize()) {
159
- // not all have seen it
195
+ const minAttestations = Math.ceil(operators.getSize() / 2);
196
+ trace(
197
+ 'transaction',
198
+ txHash,
199
+ 'has',
200
+ found.length,
201
+ 'of',
202
+ minAttestations,
203
+ 'necessary attestations',
204
+ );
205
+ if (found.length < minAttestations) {
160
206
  return;
161
207
  }
162
208
 
163
- // TODO verify that all found deep equal
209
+ let lastEvidence;
210
+ for (const store of found) {
211
+ const next = store.get(txHash);
212
+ if (lastEvidence) {
213
+ if (keyEQ(lastEvidence, next)) {
214
+ lastEvidence = next;
215
+ } else {
216
+ trace(
217
+ '🚨 conflicting evidence for',
218
+ txHash,
219
+ ':',
220
+ lastEvidence,
221
+ '!=',
222
+ next,
223
+ );
224
+ Fail`conflicting evidence for ${quote(txHash)}`;
225
+ }
226
+ }
227
+ lastEvidence = next;
228
+ }
229
+
230
+ const riskStores = [...risks.values()].filter(store =>
231
+ store.has(txHash),
232
+ );
233
+ // take the union of risks identified from all operators
234
+ const risksIdentified = allRisksIdentified(riskStores, txHash);
164
235
 
165
- // all agree, so remove from pending and publish
166
- for (const pendingStore of pending.values()) {
167
- pendingStore.delete(txHash);
236
+ // sufficient agreement, so remove from pending risks, then publish
237
+ for (const store of found) {
238
+ store.delete(txHash);
239
+ }
240
+ for (const store of riskStores) {
241
+ store.delete(txHash);
168
242
  }
169
- publisher.publish(evidence);
243
+ trace('publishing evidence', evidence, risksIdentified);
244
+ publisher.publish({
245
+ evidence,
246
+ risk: { risksIdentified },
247
+ });
170
248
  },
171
249
  },
172
250
  public: {
173
251
  getEvidenceSubscriber: () => subscriber,
174
252
  },
175
253
  },
254
+ { stateShape },
176
255
  );
177
256
  };
178
257
  harden(prepareTransactionFeedKit);
@@ -0,0 +1,65 @@
1
+ /** @file core-eval to publish update to Fast USDC feedPolicy */
2
+
3
+ import { E } from '@endo/far';
4
+ import { fromExternalConfig } from './utils/config-marshal.js';
5
+ import { FeedPolicyShape } from './type-guards.js';
6
+ import { publishFeedPolicy } from './utils/core-eval.js';
7
+
8
+ /**
9
+ * @import {Issuer} from '@agoric/ertp';
10
+ * @import {Passable} from '@endo/pass-style'
11
+ * @import {BootstrapManifest} from '@agoric/vats/src/core/lib-boot.js'
12
+ * @import {LegibleCapData} from './utils/config-marshal.js'
13
+ * @import {FeedPolicy} from './types.js'
14
+ */
15
+
16
+ const contractName = 'fastUsdc';
17
+
18
+ /**
19
+ * @param {BootstrapPowers &
20
+ * { consume: { chainStorage: Promise<StorageNode> }}
21
+ * } powers
22
+ * @param {{ options: LegibleCapData<{feedPolicy: FeedPolicy & Passable}> }} config
23
+ */
24
+ export const updateFastUsdcPolicy = async (
25
+ { consume: { agoricNames, chainStorage } },
26
+ config,
27
+ ) => {
28
+ /** @type {Issuer<'nat'>} */
29
+ const USDCissuer = await E(agoricNames).lookup('issuer', 'USDC');
30
+ const brands = harden({
31
+ USDC: await E(USDCissuer).getBrand(),
32
+ });
33
+ const { feedPolicy } = fromExternalConfig(
34
+ config.options,
35
+ brands,
36
+ harden({ feedPolicy: FeedPolicyShape }),
37
+ );
38
+
39
+ const storageNode = await E(chainStorage).makeChildNode(contractName);
40
+
41
+ await publishFeedPolicy(storageNode, feedPolicy);
42
+ };
43
+
44
+ /**
45
+ * @param {unknown} _utils
46
+ * @param {{
47
+ * options: LegibleCapData<{feedPolicy: FeedPolicy & Passable}>;
48
+ * }} param1
49
+ */
50
+ export const getManifestForUpdateFastUsdcPolicy = (_utils, { options }) => {
51
+ return {
52
+ /** @type {BootstrapManifest} */
53
+ manifest: {
54
+ [updateFastUsdcPolicy.name]: {
55
+ consume: {
56
+ chainStorage: true,
57
+
58
+ // widely shared: name services
59
+ agoricNames: true,
60
+ },
61
+ },
62
+ },
63
+ options,
64
+ };
65
+ };