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 +10 -0
- package/package.json +14 -14
- package/src/commands/oracle.js +202 -63
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.
|
|
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.
|
|
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.
|
|
35
|
-
"@agoric/casting": "^0.4.
|
|
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.
|
|
38
|
-
"@agoric/inter-protocol": "^0.
|
|
39
|
-
"@agoric/internal": "^0.3.
|
|
40
|
-
"@agoric/smart-wallet": "^0.5.
|
|
41
|
-
"@agoric/store": "^0.9.
|
|
42
|
-
"@agoric/swingset-vat": "^0.32.
|
|
43
|
-
"@agoric/vats": "^0.
|
|
44
|
-
"@agoric/zoe": "^0.26.
|
|
45
|
-
"@agoric/zone": "^0.2.
|
|
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": "
|
|
91
|
+
"gitHead": "b66bf7c881ae462fcb617cd6e8d41b5c40ec58ef"
|
|
92
92
|
}
|
package/src/commands/oracle.js
CHANGED
|
@@ -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 {
|
|
11
|
-
import {
|
|
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
|
|
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
|
-
|
|
149
|
-
const offer =
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
};
|