agoric 0.20.0 → 0.21.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/CHANGELOG.md CHANGED
@@ -3,6 +3,16 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [0.21.0](https://github.com/Agoric/agoric-sdk/compare/agoric@0.20.0...agoric@0.21.0) (2023-06-02)
7
+
8
+
9
+ ### Features
10
+
11
+ * **agoric-cli:** oracle setPrice: pushPrice from each operator ([9004c46](https://github.com/Agoric/agoric-sdk/commit/9004c46cf65a163795621a415a97fcaee47ac983))
12
+ * **clientSupport:** Offers.fluxAggregator.PushPrice ([bae997a](https://github.com/Agoric/agoric-sdk/commit/bae997ae9d28bad0ec02896975044f27c64d123d))
13
+
14
+
15
+
6
16
  ## [0.20.0](https://github.com/Agoric/agoric-sdk/compare/agoric@0.19.0...agoric@0.20.0) (2023-05-24)
7
17
 
8
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agoric",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
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",
27
+ "@agoric/deploy-script-support": "^0.10.2",
28
28
  "ava": "^5.2.0",
29
29
  "c8": "^7.13.0"
30
30
  },
31
31
  "dependencies": {
32
32
  "@agoric/access-token": "^0.4.21",
33
33
  "@agoric/assert": "^0.6.0",
34
- "@agoric/cache": "^0.3.1",
35
- "@agoric/casting": "^0.4.1",
34
+ "@agoric/cache": "^0.3.2",
35
+ "@agoric/casting": "^0.4.2",
36
36
  "@agoric/cosmic-proto": "^0.3.0",
37
- "@agoric/ertp": "^0.16.1",
38
- "@agoric/inter-protocol": "^0.15.0",
39
- "@agoric/internal": "^0.3.1",
40
- "@agoric/smart-wallet": "^0.5.1",
41
- "@agoric/store": "^0.9.1",
42
- "@agoric/swingset-vat": "^0.32.1",
43
- "@agoric/vats": "^0.14.1",
44
- "@agoric/zoe": "^0.26.1",
45
- "@agoric/zone": "^0.2.1",
37
+ "@agoric/ertp": "^0.16.2",
38
+ "@agoric/inter-protocol": "^0.16.0",
39
+ "@agoric/internal": "^0.3.2",
40
+ "@agoric/smart-wallet": "^0.5.2",
41
+ "@agoric/store": "^0.9.2",
42
+ "@agoric/swingset-vat": "^0.32.2",
43
+ "@agoric/vats": "^0.15.0",
44
+ "@agoric/zoe": "^0.26.2",
45
+ "@agoric/zone": "^0.2.2",
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": "676e947a6fba6e1bab0717ca7228824c32509bda"
91
+ "gitHead": "b66bf7c881ae462fcb617cd6e8d41b5c40ec58ef"
92
92
  }
@@ -2,13 +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
+ import { Offers } from '@agoric/inter-protocol/src/clientSupport.js';
7
8
  import { Nat } from '@endo/nat';
8
9
  import { Command } from 'commander';
10
+ import * as cp from 'child_process';
9
11
  import { inspect } from 'util';
10
- import { makeRpcUtils, storageHelper } from '../lib/rpc.js';
11
- 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';
12
21
 
13
22
  // XXX support other decimal places
14
23
  const COSMOS_UNIT = 1_000_000n;
@@ -16,10 +25,24 @@ const scaleDecimals = num => BigInt(num * Number(COSMOS_UNIT));
16
25
 
17
26
  /**
18
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]
19
34
  */
20
- export const makeOracleCommand = logger => {
21
- const oracle = new Command('oracle').description('Oracle commands').usage(
22
- `
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
+ `
23
46
  WALLET=my-wallet
24
47
  export AGORIC_NET=ollinet
25
48
 
@@ -39,9 +62,18 @@ export const makeOracleCommand = logger => {
39
62
  # sign and send
40
63
  agoric wallet send --from $WALLET --offer offer-12.json
41
64
  `,
42
- );
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
+ );
43
73
 
44
74
  const rpcTools = async () => {
75
+ // XXX pass fetch to getNetworkConfig() explicitly
76
+ const networkConfig = await getNetworkConfig(env);
45
77
  const utils = await makeRpcUtils({ fetch });
46
78
 
47
79
  const lookupPriceAggregatorInstance = ([brandIn, brandOut]) => {
@@ -54,7 +86,7 @@ export const makeOracleCommand = logger => {
54
86
  return instance;
55
87
  };
56
88
 
57
- return { ...utils, lookupPriceAggregatorInstance };
89
+ return { ...utils, networkConfig, lookupPriceAggregatorInstance };
58
90
  };
59
91
 
60
92
  oracle
@@ -95,37 +127,6 @@ export const makeOracleCommand = logger => {
95
127
  console.warn('Now execute the prepared offer');
96
128
  });
97
129
 
98
- oracle
99
- .command('pushPrice')
100
- .description('add a current price sample to a priceAggregator')
101
- .option('--offerId <string>', 'Offer id', String, `pushPrice-${Date.now()}`)
102
- .requiredOption(
103
- '--oracleAdminAcceptOfferId <number>',
104
- 'offer that had continuing invitation result',
105
- Number,
106
- )
107
- .requiredOption('--price <number>', 'price (format TODO)', String)
108
- .action(async function (opts) {
109
- /** @type {import('@agoric/smart-wallet/src/offers.js').OfferSpec} */
110
- const offer = {
111
- id: opts.offerId,
112
- invitationSpec: {
113
- source: 'continuing',
114
- previousOffer: opts.oracleAdminAcceptOfferId,
115
- invitationMakerName: 'PushPrice',
116
- invitationArgs: harden([opts.price]),
117
- },
118
- proposal: {},
119
- };
120
-
121
- outputAction({
122
- method: 'executeOffer',
123
- offer,
124
- });
125
-
126
- console.warn('Now execute the prepared offer');
127
- });
128
-
129
130
  oracle
130
131
  .command('pushPriceRound')
131
132
  .description('add a price for a round to a fluxAggregator')
@@ -143,19 +144,15 @@ export const makeOracleCommand = logger => {
143
144
  .requiredOption('--price <number>', 'price', Number)
144
145
  .option('--roundId <number>', 'round', Number)
145
146
  .action(async function (opts) {
147
+ const { offerId } = opts;
146
148
  const unitPrice = scaleDecimals(opts.price);
147
149
  const roundId = 'roundId' in opts ? Nat(opts.roundId) : undefined;
148
- /** @type {import('@agoric/smart-wallet/src/offers.js').OfferSpec} */
149
- const offer = {
150
- id: opts.offerId,
151
- invitationSpec: {
152
- source: 'continuing',
153
- previousOffer: opts.oracleAdminAcceptOfferId,
154
- invitationMakerName: 'PushPrice',
155
- invitationArgs: harden([{ unitPrice, roundId }]),
156
- },
157
- proposal: {},
158
- };
150
+
151
+ const offer = Offers.fluxAggregator.PushPrice(
152
+ {},
153
+ { offerId, unitPrice, roundId },
154
+ opts.oracleAdminAcceptOfferId,
155
+ );
159
156
 
160
157
  outputAction({
161
158
  method: 'executeOffer',
@@ -165,27 +162,33 @@ export const makeOracleCommand = logger => {
165
162
  console.warn('Now execute the prepared offer');
166
163
  });
167
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
+
168
180
  oracle
169
181
  .command('find-continuing-id')
170
182
  .description('print id of specified oracle continuing invitation')
171
183
  .requiredOption('--from <address>', 'from address', String)
172
184
  .action(async opts => {
173
185
  const { readLatestHead } = await makeRpcUtils({ fetch });
174
- const current = await getCurrent(opts.from, { readLatestHead });
175
-
176
- const { offerToUsedInvitation: entries } = /** @type {any} */ (current);
177
- Array.isArray(entries) || Fail`entries must be an array: ${entries}`;
178
186
 
179
- for (const [offerId, { value }] of entries) {
180
- /** @type {{ description: string, instance: unknown }[]} */
181
- const [{ description }] = value;
182
- if (description === 'oracle invitation') {
183
- console.log(offerId);
184
- return;
185
- }
187
+ const offerId = await findOracleCap(opts.from, readLatestHead);
188
+ if (!offerId) {
189
+ console.error('No continuing ids found');
186
190
  }
187
-
188
- console.error('No continuing ids found');
191
+ console.log(offerId);
189
192
  });
190
193
 
191
194
  oracle
@@ -208,5 +211,141 @@ export const makeOracleCommand = logger => {
208
211
 
209
212
  console.log(inspect(capDatas[0], { depth: 10, colors: true }));
210
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
+ );
211
350
  return oracle;
212
351
  };