agoric 0.19.1-dev-acefd94.0 → 0.19.1-dev-d42f8d0.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 (2) hide show
  1. package/package.json +17 -17
  2. package/src/commands/oracle.js +194 -52
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agoric",
3
- "version": "0.19.1-dev-acefd94.0+acefd94",
3
+ "version": "0.19.1-dev-d42f8d0.0+d42f8d0",
4
4
  "description": "Manage the Agoric Javascript smart contract platform",
5
5
  "type": "module",
6
6
  "main": "src/main.js",
@@ -24,25 +24,25 @@
24
24
  "lint:eslint": "eslint ."
25
25
  },
26
26
  "devDependencies": {
27
- "@agoric/deploy-script-support": "0.10.1-dev-acefd94.0+acefd94",
27
+ "@agoric/deploy-script-support": "0.10.1-dev-d42f8d0.0+d42f8d0",
28
28
  "ava": "^5.2.0",
29
29
  "c8": "^7.13.0"
30
30
  },
31
31
  "dependencies": {
32
- "@agoric/access-token": "0.4.22-dev-acefd94.0+acefd94",
33
- "@agoric/assert": "0.6.1-dev-acefd94.0+acefd94",
34
- "@agoric/cache": "0.3.1-dev-acefd94.0+acefd94",
35
- "@agoric/casting": "0.4.1-dev-acefd94.0+acefd94",
36
- "@agoric/cosmic-proto": "0.3.1-dev-acefd94.0+acefd94",
37
- "@agoric/ertp": "0.16.1-dev-acefd94.0+acefd94",
38
- "@agoric/inter-protocol": "0.14.1-dev-acefd94.0+acefd94",
39
- "@agoric/internal": "0.3.1-dev-acefd94.0+acefd94",
40
- "@agoric/smart-wallet": "0.5.1-dev-acefd94.0+acefd94",
41
- "@agoric/store": "0.9.1-dev-acefd94.0+acefd94",
42
- "@agoric/swingset-vat": "0.32.1-dev-acefd94.0+acefd94",
43
- "@agoric/vats": "0.14.1-dev-acefd94.0+acefd94",
44
- "@agoric/zoe": "0.26.1-dev-acefd94.0+acefd94",
45
- "@agoric/zone": "0.2.1-dev-acefd94.0+acefd94",
32
+ "@agoric/access-token": "0.4.22-dev-d42f8d0.0+d42f8d0",
33
+ "@agoric/assert": "0.6.1-dev-d42f8d0.0+d42f8d0",
34
+ "@agoric/cache": "0.3.1-dev-d42f8d0.0+d42f8d0",
35
+ "@agoric/casting": "0.4.1-dev-d42f8d0.0+d42f8d0",
36
+ "@agoric/cosmic-proto": "0.3.1-dev-d42f8d0.0+d42f8d0",
37
+ "@agoric/ertp": "0.16.1-dev-d42f8d0.0+d42f8d0",
38
+ "@agoric/inter-protocol": "0.14.1-dev-d42f8d0.0+d42f8d0",
39
+ "@agoric/internal": "0.3.1-dev-d42f8d0.0+d42f8d0",
40
+ "@agoric/smart-wallet": "0.5.1-dev-d42f8d0.0+d42f8d0",
41
+ "@agoric/store": "0.9.1-dev-d42f8d0.0+d42f8d0",
42
+ "@agoric/swingset-vat": "0.32.1-dev-d42f8d0.0+d42f8d0",
43
+ "@agoric/vats": "0.14.1-dev-d42f8d0.0+d42f8d0",
44
+ "@agoric/zoe": "0.26.1-dev-d42f8d0.0+d42f8d0",
45
+ "@agoric/zone": "0.2.1-dev-d42f8d0.0+d42f8d0",
46
46
  "@confio/relayer": "^0.9.0",
47
47
  "@cosmjs/crypto": "^0.30.1",
48
48
  "@cosmjs/encoding": "^0.30.1",
@@ -88,5 +88,5 @@
88
88
  "timeout": "2m",
89
89
  "workerThreads": false
90
90
  },
91
- "gitHead": "acefd94c2191c7fad2ebcb3f31f3d960582fc111"
91
+ "gitHead": "d42f8d099cb6457d27a5d04e3c91797948040502"
92
92
  }
@@ -2,14 +2,22 @@
2
2
  /* eslint-disable @jessie.js/no-nested-await */
3
3
  // @ts-check
4
4
  /* eslint-disable func-names */
5
- /* global fetch */
5
+ /* global fetch, setTimeout, process */
6
6
  import { Fail } from '@agoric/assert';
7
7
  import { Offers } from '@agoric/inter-protocol/src/clientSupport.js';
8
8
  import { Nat } from '@endo/nat';
9
9
  import { Command } from 'commander';
10
+ import * as cp from 'child_process';
10
11
  import { inspect } from 'util';
11
- import { makeRpcUtils, storageHelper } from '../lib/rpc.js';
12
- import { getCurrent, outputAction } from '../lib/wallet.js';
12
+ import { normalizeAddressWithOptions } from '../lib/chain.js';
13
+ import { getNetworkConfig, makeRpcUtils, storageHelper } from '../lib/rpc.js';
14
+ import {
15
+ getCurrent,
16
+ makeWalletUtils,
17
+ outputAction,
18
+ sendAction,
19
+ } from '../lib/wallet.js';
20
+ import { bigintReplacer } from '../lib/format.js';
13
21
 
14
22
  // XXX support other decimal places
15
23
  const COSMOS_UNIT = 1_000_000n;
@@ -17,10 +25,24 @@ const scaleDecimals = num => BigInt(num * Number(COSMOS_UNIT));
17
25
 
18
26
  /**
19
27
  * @param {import('anylogger').Logger} logger
28
+ * @param {{
29
+ * delay?: (ms: number) => Promise<void>,
30
+ * execFileSync?: typeof import('child_process').execFileSync,
31
+ * env?: Record<string, string | undefined>,
32
+ * stdout?: Pick<import('stream').Writable,'write'>,
33
+ * }} [io]
20
34
  */
21
- export const makeOracleCommand = logger => {
22
- const oracle = new Command('oracle').description('Oracle commands').usage(
23
- `
35
+ export const makeOracleCommand = (logger, io = {}) => {
36
+ const {
37
+ delay = ms => new Promise(resolve => setTimeout(resolve, ms)),
38
+ execFileSync = cp.execFileSync,
39
+ env = process.env,
40
+ stdout = process.stdout,
41
+ } = io;
42
+ const oracle = new Command('oracle')
43
+ .description('Oracle commands')
44
+ .usage(
45
+ `
24
46
  WALLET=my-wallet
25
47
  export AGORIC_NET=ollinet
26
48
 
@@ -40,9 +62,18 @@ export const makeOracleCommand = logger => {
40
62
  # sign and send
41
63
  agoric wallet send --from $WALLET --offer offer-12.json
42
64
  `,
43
- );
65
+ )
66
+ .option(
67
+ '--keyring-backend <os|file|test>',
68
+ `keyring's backend (os|file|test) (default "${
69
+ env.AGORIC_KEYRING_BACKEND || 'os'
70
+ }")`,
71
+ env.AGORIC_KEYRING_BACKEND,
72
+ );
44
73
 
45
74
  const rpcTools = async () => {
75
+ // XXX pass fetch to getNetworkConfig() explicitly
76
+ const networkConfig = await getNetworkConfig(env);
46
77
  const utils = await makeRpcUtils({ fetch });
47
78
 
48
79
  const lookupPriceAggregatorInstance = ([brandIn, brandOut]) => {
@@ -55,7 +86,7 @@ export const makeOracleCommand = logger => {
55
86
  return instance;
56
87
  };
57
88
 
58
- return { ...utils, lookupPriceAggregatorInstance };
89
+ return { ...utils, networkConfig, lookupPriceAggregatorInstance };
59
90
  };
60
91
 
61
92
  oracle
@@ -96,37 +127,6 @@ export const makeOracleCommand = logger => {
96
127
  console.warn('Now execute the prepared offer');
97
128
  });
98
129
 
99
- oracle
100
- .command('pushPrice')
101
- .description('add a current price sample to a priceAggregator')
102
- .option('--offerId <string>', 'Offer id', String, `pushPrice-${Date.now()}`)
103
- .requiredOption(
104
- '--oracleAdminAcceptOfferId <number>',
105
- 'offer that had continuing invitation result',
106
- Number,
107
- )
108
- .requiredOption('--price <number>', 'price (format TODO)', String)
109
- .action(async function (opts) {
110
- /** @type {import('@agoric/smart-wallet/src/offers.js').OfferSpec} */
111
- const offer = {
112
- id: opts.offerId,
113
- invitationSpec: {
114
- source: 'continuing',
115
- previousOffer: opts.oracleAdminAcceptOfferId,
116
- invitationMakerName: 'PushPrice',
117
- invitationArgs: harden([opts.price]),
118
- },
119
- proposal: {},
120
- };
121
-
122
- outputAction({
123
- method: 'executeOffer',
124
- offer,
125
- });
126
-
127
- console.warn('Now execute the prepared offer');
128
- });
129
-
130
130
  oracle
131
131
  .command('pushPriceRound')
132
132
  .description('add a price for a round to a fluxAggregator')
@@ -162,27 +162,33 @@ export const makeOracleCommand = logger => {
162
162
  console.warn('Now execute the prepared offer');
163
163
  });
164
164
 
165
+ const findOracleCap = async (from, readLatestHead) => {
166
+ const current = await getCurrent(from, { readLatestHead });
167
+
168
+ const { offerToUsedInvitation: entries } = /** @type {any} */ (current);
169
+ Array.isArray(entries) || Fail`entries must be an array: ${entries}`;
170
+
171
+ for (const [offerId, { value }] of entries) {
172
+ /** @type {{ description: string, instance: unknown }[]} */
173
+ const [{ description }] = value;
174
+ if (description === 'oracle invitation') {
175
+ return offerId;
176
+ }
177
+ }
178
+ };
179
+
165
180
  oracle
166
181
  .command('find-continuing-id')
167
182
  .description('print id of specified oracle continuing invitation')
168
183
  .requiredOption('--from <address>', 'from address', String)
169
184
  .action(async opts => {
170
185
  const { readLatestHead } = await makeRpcUtils({ fetch });
171
- const current = await getCurrent(opts.from, { readLatestHead });
172
-
173
- const { offerToUsedInvitation: entries } = /** @type {any} */ (current);
174
- Array.isArray(entries) || Fail`entries must be an array: ${entries}`;
175
186
 
176
- for (const [offerId, { value }] of entries) {
177
- /** @type {{ description: string, instance: unknown }[]} */
178
- const [{ description }] = value;
179
- if (description === 'oracle invitation') {
180
- console.log(offerId);
181
- return;
182
- }
187
+ const offerId = await findOracleCap(opts.from, readLatestHead);
188
+ if (!offerId) {
189
+ console.error('No continuing ids found');
183
190
  }
184
-
185
- console.error('No continuing ids found');
191
+ console.log(offerId);
186
192
  });
187
193
 
188
194
  oracle
@@ -205,5 +211,141 @@ export const makeOracleCommand = logger => {
205
211
 
206
212
  console.log(inspect(capDatas[0], { depth: 10, colors: true }));
207
213
  });
214
+
215
+ /** @param {string} literalOrName */
216
+ const normalizeAddress = literalOrName =>
217
+ normalizeAddressWithOptions(literalOrName, oracle.opts(), {
218
+ execFileSync,
219
+ });
220
+ const show = (info, indent = false) =>
221
+ stdout.write(
222
+ `${JSON.stringify(info, bigintReplacer, indent ? 2 : undefined)}\n`,
223
+ );
224
+ /** @param {bigint} secs */
225
+ const fmtSecs = secs => new Date(Number(secs) * 1000).toISOString();
226
+
227
+ oracle
228
+ .command('setPrice')
229
+ .description('set price by pushing from multiple operators')
230
+ .requiredOption(
231
+ '--pair [brandIn.brandOut]',
232
+ 'token pair (brandIn.brandOut)',
233
+ s => s.split('.'),
234
+ ['ATOM', 'USD'],
235
+ )
236
+ .requiredOption(
237
+ '--keys [key1,key2,...]',
238
+ 'key names of operators (comma separated)',
239
+ s => s.split(','),
240
+ ['gov1', 'gov2'],
241
+ )
242
+ .requiredOption('--price <number>', 'price', Number)
243
+ .action(
244
+ async (
245
+ /**
246
+ * @type {{
247
+ * pair: [brandIn: string, brandOut: string],
248
+ * keys: string[],
249
+ * price: number,
250
+ * }}
251
+ */ { pair, keys, price },
252
+ ) => {
253
+ const { readLatestHead, networkConfig } = await rpcTools();
254
+ const wutil = await makeWalletUtils(
255
+ { fetch, execFileSync, delay },
256
+ networkConfig,
257
+ );
258
+ const unitPrice = scaleDecimals(price);
259
+
260
+ console.error(`${pair[0]}-${pair[1]}_price_feed: before setPrice`);
261
+
262
+ const readPrice = () =>
263
+ /** @type {Promise<PriceDescription>} */ (
264
+ readLatestHead(
265
+ `published.priceFeed.${pair[0]}-${pair[1]}_price_feed`,
266
+ ).catch(err => {
267
+ console.warn(`cannot get ${pair[0]}-${pair[1]}_price_feed`, err);
268
+ return undefined;
269
+ })
270
+ );
271
+
272
+ const r4 = x => Math.round(x * 10000) / 10000; // XXX 4 decimals arbitrary
273
+ const fmtFeed = ({
274
+ amountIn: { value: valueIn },
275
+ amountOut: { value: valueOut },
276
+ timestamp: { absValue: ts },
277
+ }) => ({
278
+ timestamp: fmtSecs(ts),
279
+ price: r4(Number(valueOut) / Number(valueIn)),
280
+ });
281
+ const before = await readPrice();
282
+ if (before) {
283
+ show(fmtFeed(before));
284
+ }
285
+
286
+ console.error(
287
+ 'Choose lead oracle operator order based on latestRound...',
288
+ );
289
+ const keyOrder = keys.map(normalizeAddress);
290
+ const latestRoundP = readLatestHead(
291
+ `published.priceFeed.${pair[0]}-${pair[1]}_price_feed.latestRound`,
292
+ );
293
+ await Promise.race([
294
+ delay(5000),
295
+ latestRoundP.then(round => {
296
+ // @ts-expect-error XXX get type from contract
297
+ const { roundId, startedAt, startedBy } = round;
298
+ show({
299
+ startedAt: fmtSecs(startedAt.absValue),
300
+ roundId,
301
+ startedBy,
302
+ });
303
+ if (startedBy === keyOrder[0]) {
304
+ keyOrder.reverse();
305
+ }
306
+ }),
307
+ ]).catch(err => {
308
+ console.warn(err);
309
+ });
310
+
311
+ console.error('pushPrice from each:', keyOrder);
312
+ for await (const from of keyOrder) {
313
+ const oracleAdminAcceptOfferId = await findOracleCap(
314
+ from,
315
+ readLatestHead,
316
+ );
317
+ if (!oracleAdminAcceptOfferId) {
318
+ throw Error(`no oracle invitation found: ${from}`);
319
+ }
320
+ show({ from, oracleAdminAcceptOfferId });
321
+ const offerId = `pushPrice-${Date.now()}`;
322
+ const offer = Offers.fluxAggregator.PushPrice(
323
+ {},
324
+ { offerId, unitPrice },
325
+ oracleAdminAcceptOfferId,
326
+ );
327
+
328
+ const { home, keyringBackend: backend } = oracle.opts();
329
+ const tools = { ...networkConfig, execFileSync, delay, stdout };
330
+ const result = await sendAction(
331
+ { method: 'executeOffer', offer },
332
+ { keyring: { home, backend }, from, verbose: false, ...tools },
333
+ );
334
+ assert(result); // Not dry-run
335
+ const { timestamp, txhash, height } = result;
336
+ console.error('pushPrice', price, 'offer broadcast');
337
+ show({ timestamp, height, offerId: offer.id, txhash });
338
+ const found = await wutil.pollOffer(from, offer.id, result.height);
339
+ console.error('pushPrice', price, 'offer satisfied');
340
+ show(found);
341
+ }
342
+
343
+ const after = await readPrice();
344
+ if (after) {
345
+ console.error('price set:');
346
+ show(fmtFeed(after));
347
+ }
348
+ },
349
+ );
208
350
  return oracle;
209
351
  };