agoric 0.21.2-dev-bf2e680.0 → 0.21.2-dev-ae21a7e.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 +21 -19
- package/src/bin-agops.js +2 -2
- package/src/commands/{ec.js → gov.js} +176 -36
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agoric",
|
|
3
|
-
"version": "0.21.2-dev-
|
|
3
|
+
"version": "0.21.2-dev-ae21a7e.0+ae21a7e",
|
|
4
4
|
"description": "Manage the Agoric Javascript smart contract platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main.js",
|
|
@@ -28,28 +28,29 @@
|
|
|
28
28
|
"lint:eslint": "eslint ."
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@agoric/cosmic-swingset": "0.41.4-dev-
|
|
32
|
-
"@agoric/deploy-script-support": "0.10.4-dev-
|
|
31
|
+
"@agoric/cosmic-swingset": "0.41.4-dev-ae21a7e.0+ae21a7e",
|
|
32
|
+
"@agoric/deploy-script-support": "0.10.4-dev-ae21a7e.0+ae21a7e",
|
|
33
33
|
"ava": "^5.3.0",
|
|
34
34
|
"c8": "^7.13.0",
|
|
35
35
|
"dd-trace": "^4.11.1"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@agoric/access-token": "0.4.22-dev-
|
|
39
|
-
"@agoric/assert": "0.6.1-dev-
|
|
40
|
-
"@agoric/cache": "0.3.3-dev-
|
|
41
|
-
"@agoric/casting": "0.4.3-dev-
|
|
42
|
-
"@agoric/cosmic-proto": "0.3.1-dev-
|
|
43
|
-
"@agoric/ertp": "0.16.3-dev-
|
|
44
|
-
"@agoric/
|
|
45
|
-
"@agoric/
|
|
46
|
-
"@agoric/
|
|
47
|
-
"@agoric/
|
|
48
|
-
"@agoric/
|
|
49
|
-
"@agoric/
|
|
50
|
-
"@agoric/
|
|
51
|
-
"@agoric/
|
|
52
|
-
"@agoric/
|
|
38
|
+
"@agoric/access-token": "0.4.22-dev-ae21a7e.0+ae21a7e",
|
|
39
|
+
"@agoric/assert": "0.6.1-dev-ae21a7e.0+ae21a7e",
|
|
40
|
+
"@agoric/cache": "0.3.3-dev-ae21a7e.0+ae21a7e",
|
|
41
|
+
"@agoric/casting": "0.4.3-dev-ae21a7e.0+ae21a7e",
|
|
42
|
+
"@agoric/cosmic-proto": "0.3.1-dev-ae21a7e.0+ae21a7e",
|
|
43
|
+
"@agoric/ertp": "0.16.3-dev-ae21a7e.0+ae21a7e",
|
|
44
|
+
"@agoric/governance": "0.10.4-dev-ae21a7e.0+ae21a7e",
|
|
45
|
+
"@agoric/inter-protocol": "0.16.2-dev-ae21a7e.0+ae21a7e",
|
|
46
|
+
"@agoric/internal": "0.3.3-dev-ae21a7e.0+ae21a7e",
|
|
47
|
+
"@agoric/network": "0.1.1-dev-ae21a7e.0+ae21a7e",
|
|
48
|
+
"@agoric/smart-wallet": "0.5.4-dev-ae21a7e.0+ae21a7e",
|
|
49
|
+
"@agoric/store": "0.9.3-dev-ae21a7e.0+ae21a7e",
|
|
50
|
+
"@agoric/swingset-vat": "0.32.3-dev-ae21a7e.0+ae21a7e",
|
|
51
|
+
"@agoric/vats": "0.15.2-dev-ae21a7e.0+ae21a7e",
|
|
52
|
+
"@agoric/zoe": "0.26.3-dev-ae21a7e.0+ae21a7e",
|
|
53
|
+
"@agoric/zone": "0.2.3-dev-ae21a7e.0+ae21a7e",
|
|
53
54
|
"@confio/relayer": "^0.9.0",
|
|
54
55
|
"@cosmjs/crypto": "^0.30.1",
|
|
55
56
|
"@cosmjs/encoding": "^0.30.1",
|
|
@@ -63,6 +64,7 @@
|
|
|
63
64
|
"@endo/init": "^0.5.59",
|
|
64
65
|
"@endo/marshal": "^0.8.8",
|
|
65
66
|
"@endo/nat": "^4.1.30",
|
|
67
|
+
"@endo/patterns": "^0.2.6",
|
|
66
68
|
"@endo/promise-kit": "^0.2.59",
|
|
67
69
|
"@iarna/toml": "^2.2.3",
|
|
68
70
|
"anylogger": "^0.21.0",
|
|
@@ -93,5 +95,5 @@
|
|
|
93
95
|
"timeout": "2m",
|
|
94
96
|
"workerThreads": false
|
|
95
97
|
},
|
|
96
|
-
"gitHead": "
|
|
98
|
+
"gitHead": "ae21a7ea4f28e41bab2278e38458a746abc4114f"
|
|
97
99
|
}
|
package/src/bin-agops.js
CHANGED
|
@@ -18,7 +18,7 @@ import process from 'process';
|
|
|
18
18
|
import anylogger from 'anylogger';
|
|
19
19
|
import { Command, CommanderError, createCommand } from 'commander';
|
|
20
20
|
import { makeOracleCommand } from './commands/oracle.js';
|
|
21
|
-
import {
|
|
21
|
+
import { makeGovCommand } from './commands/gov.js';
|
|
22
22
|
import { makePsmCommand } from './commands/psm.js';
|
|
23
23
|
import { makeReserveCommand } from './commands/reserve.js';
|
|
24
24
|
import { makeVaultsCommand } from './commands/vaults.js';
|
|
@@ -34,7 +34,7 @@ const program = new Command();
|
|
|
34
34
|
program.name(progname).version('unversioned');
|
|
35
35
|
|
|
36
36
|
program.addCommand(makeOracleCommand(logger));
|
|
37
|
-
program.addCommand(
|
|
37
|
+
program.addCommand(makeGovCommand(logger));
|
|
38
38
|
program.addCommand(makePerfCommand(logger));
|
|
39
39
|
program.addCommand(makePsmCommand(logger));
|
|
40
40
|
program.addCommand(makeVaultsCommand(logger));
|
|
@@ -15,6 +15,13 @@ import {
|
|
|
15
15
|
|
|
16
16
|
/** @typedef {import('@agoric/smart-wallet/src/offers.js').OfferSpec} OfferSpec */
|
|
17
17
|
|
|
18
|
+
const collectValues = (val, memo) => {
|
|
19
|
+
memo.push(val);
|
|
20
|
+
return memo;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const defaultKeyring = process.env.AGORIC_KEYRING_BACKEND || 'test';
|
|
24
|
+
|
|
18
25
|
/**
|
|
19
26
|
* @param {import('anylogger').Logger} _logger
|
|
20
27
|
* @param {{
|
|
@@ -26,7 +33,7 @@ import {
|
|
|
26
33
|
* delay?: (ms: number) => Promise<void>,
|
|
27
34
|
* }} [io]
|
|
28
35
|
*/
|
|
29
|
-
export const
|
|
36
|
+
export const makeGovCommand = (_logger, io = {}) => {
|
|
30
37
|
const {
|
|
31
38
|
// Allow caller to provide access explicitly, but
|
|
32
39
|
// default to conventional ambient IO facilities.
|
|
@@ -38,11 +45,22 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => {
|
|
|
38
45
|
delay = ms => new Promise(resolve => setTimeout(resolve, ms)),
|
|
39
46
|
} = io;
|
|
40
47
|
|
|
41
|
-
const
|
|
48
|
+
const cmd = new Command('gov').description('Electoral governance commands');
|
|
49
|
+
// backwards compatibility with less general "ec" command. To make this work
|
|
50
|
+
// the new CLI options default to the values used for Economic Committee
|
|
51
|
+
cmd.alias('ec');
|
|
52
|
+
cmd.option(
|
|
53
|
+
'--keyring-backend <os|file|test>',
|
|
54
|
+
`keyring's backend (os|file|test) (default "${defaultKeyring}")`,
|
|
55
|
+
defaultKeyring,
|
|
56
|
+
);
|
|
42
57
|
|
|
43
58
|
/** @param {string} literalOrName */
|
|
44
59
|
const normalizeAddress = literalOrName =>
|
|
45
|
-
normalizeAddressWithOptions(literalOrName, {
|
|
60
|
+
normalizeAddressWithOptions(literalOrName, {
|
|
61
|
+
// FIXME does not observe keyring-backend option, which isn't available during arg parsing
|
|
62
|
+
keyringBackend: defaultKeyring,
|
|
63
|
+
});
|
|
46
64
|
|
|
47
65
|
/** @type {(info: unknown, indent?: unknown) => boolean } */
|
|
48
66
|
const show = (info, indent) =>
|
|
@@ -63,15 +81,21 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => {
|
|
|
63
81
|
* @param {{
|
|
64
82
|
* toOffer: (agoricNames: *, current: import('@agoric/smart-wallet/src/smartWallet').CurrentWalletRecord | undefined) => OfferSpec,
|
|
65
83
|
* sendFrom?: string | undefined,
|
|
84
|
+
* keyringBackend: string,
|
|
66
85
|
* instanceName?: string,
|
|
67
86
|
* }} detail
|
|
68
87
|
* @param {Awaited<ReturnType<makeRpcUtils>>} [optUtils]
|
|
69
88
|
*/
|
|
70
|
-
const processOffer = async function (
|
|
89
|
+
const processOffer = async function (
|
|
90
|
+
{ toOffer, sendFrom, keyringBackend },
|
|
91
|
+
optUtils,
|
|
92
|
+
) {
|
|
71
93
|
const networkConfig = await getNetworkConfig(env);
|
|
72
94
|
const utils = await (optUtils || makeRpcUtils({ fetch }));
|
|
73
95
|
const { agoricNames, readLatestHead } = utils;
|
|
74
96
|
|
|
97
|
+
assert(keyringBackend, 'missing keyring-backend option');
|
|
98
|
+
|
|
75
99
|
let current;
|
|
76
100
|
if (sendFrom) {
|
|
77
101
|
current = await getCurrent(sendFrom, { readLatestHead });
|
|
@@ -89,7 +113,7 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => {
|
|
|
89
113
|
const result = await sendAction(
|
|
90
114
|
{ method: 'executeOffer', offer },
|
|
91
115
|
{
|
|
92
|
-
keyring: { backend:
|
|
116
|
+
keyring: { backend: keyringBackend },
|
|
93
117
|
from: sendFrom,
|
|
94
118
|
verbose: false,
|
|
95
119
|
...networkConfig,
|
|
@@ -126,29 +150,38 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => {
|
|
|
126
150
|
show(blockInfo);
|
|
127
151
|
};
|
|
128
152
|
|
|
129
|
-
|
|
130
|
-
.
|
|
153
|
+
cmd
|
|
154
|
+
.command('committee')
|
|
155
|
+
.description('accept invitation to join a committee')
|
|
156
|
+
.requiredOption(
|
|
157
|
+
'--name <string>',
|
|
158
|
+
'Committee instance name',
|
|
159
|
+
String,
|
|
160
|
+
'economicCommittee',
|
|
161
|
+
)
|
|
131
162
|
.option('--voter <number>', 'Voter number', Number, 0)
|
|
132
163
|
.option(
|
|
133
164
|
'--offerId <string>',
|
|
134
165
|
'Offer id',
|
|
135
166
|
String,
|
|
136
|
-
`
|
|
167
|
+
`gov-committee-${Date.now()}`,
|
|
137
168
|
)
|
|
138
169
|
.option(
|
|
139
170
|
'--send-from <name-or-address>',
|
|
140
171
|
'Send from address',
|
|
141
172
|
normalizeAddress,
|
|
142
173
|
)
|
|
143
|
-
.action(async function (opts) {
|
|
174
|
+
.action(async function (opts, options) {
|
|
175
|
+
const { name: instanceName } = opts;
|
|
176
|
+
|
|
144
177
|
/** @type {Parameters<typeof processOffer>[0]['toOffer']} */
|
|
145
178
|
const toOffer = (agoricNames, current) => {
|
|
146
|
-
const instance = agoricNames.instance
|
|
147
|
-
assert(instance, `missing
|
|
179
|
+
const instance = agoricNames.instance[instanceName];
|
|
180
|
+
assert(instance, `missing ${instanceName}`);
|
|
148
181
|
|
|
149
182
|
if (current) {
|
|
150
183
|
const found = findContinuingIds(current, agoricNames);
|
|
151
|
-
abortIfSeen(
|
|
184
|
+
abortIfSeen(instanceName, found);
|
|
152
185
|
}
|
|
153
186
|
|
|
154
187
|
return {
|
|
@@ -164,28 +197,37 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => {
|
|
|
164
197
|
|
|
165
198
|
await processOffer({
|
|
166
199
|
toOffer,
|
|
167
|
-
instanceName
|
|
168
|
-
|
|
200
|
+
instanceName,
|
|
201
|
+
sendFrom: opts.sendFrom,
|
|
202
|
+
keyringBackend: options.optsWithGlobals().keyringBackend,
|
|
169
203
|
});
|
|
170
204
|
});
|
|
171
205
|
|
|
172
|
-
|
|
206
|
+
cmd
|
|
207
|
+
.command('charter')
|
|
173
208
|
.description('accept the charter invitation')
|
|
174
|
-
.
|
|
209
|
+
.requiredOption(
|
|
210
|
+
'--name <string>',
|
|
211
|
+
'Charter instance name',
|
|
212
|
+
'economicCommitteeCharter',
|
|
213
|
+
)
|
|
214
|
+
.option('--offerId <string>', 'Offer id', String, `charter-${Date.now()}`)
|
|
175
215
|
.option(
|
|
176
216
|
'--send-from <name-or-address>',
|
|
177
217
|
'Send from address',
|
|
178
218
|
normalizeAddress,
|
|
179
219
|
)
|
|
180
|
-
.action(async function (opts) {
|
|
220
|
+
.action(async function (opts, options) {
|
|
221
|
+
const { name: instanceName } = opts;
|
|
222
|
+
|
|
181
223
|
/** @type {Parameters<typeof processOffer>[0]['toOffer']} */
|
|
182
224
|
const toOffer = (agoricNames, current) => {
|
|
183
|
-
const instance = agoricNames.instance
|
|
184
|
-
assert(instance, `missing
|
|
225
|
+
const instance = agoricNames.instance[instanceName];
|
|
226
|
+
assert(instance, `missing ${instanceName}`);
|
|
185
227
|
|
|
186
228
|
if (current) {
|
|
187
229
|
const found = findContinuingIds(current, agoricNames);
|
|
188
|
-
abortIfSeen(
|
|
230
|
+
abortIfSeen(instanceName, found);
|
|
189
231
|
}
|
|
190
232
|
|
|
191
233
|
return {
|
|
@@ -201,12 +243,14 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => {
|
|
|
201
243
|
|
|
202
244
|
await processOffer({
|
|
203
245
|
toOffer,
|
|
204
|
-
instanceName
|
|
205
|
-
|
|
246
|
+
instanceName,
|
|
247
|
+
sendFrom: opts.sendFrom,
|
|
248
|
+
keyringBackend: options.optsWithGlobals().keyringBackend,
|
|
206
249
|
});
|
|
207
250
|
});
|
|
208
251
|
|
|
209
|
-
|
|
252
|
+
cmd
|
|
253
|
+
.command('find-continuing-id')
|
|
210
254
|
.description('print id of specified voting continuing invitation')
|
|
211
255
|
.requiredOption(
|
|
212
256
|
'--from <name-or-address>',
|
|
@@ -232,7 +276,8 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => {
|
|
|
232
276
|
console.log(match.offerId);
|
|
233
277
|
});
|
|
234
278
|
|
|
235
|
-
|
|
279
|
+
cmd
|
|
280
|
+
.command('find-continuing-ids')
|
|
236
281
|
.description('print records of voting continuing invitations')
|
|
237
282
|
.requiredOption(
|
|
238
283
|
'--from <name-or-address>',
|
|
@@ -244,12 +289,27 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => {
|
|
|
244
289
|
const current = await getCurrent(opts.from, { readLatestHead });
|
|
245
290
|
|
|
246
291
|
const found = findContinuingIds(current, agoricNames);
|
|
247
|
-
|
|
292
|
+
for (const it of found) {
|
|
293
|
+
show({ ...it, address: opts.from });
|
|
294
|
+
}
|
|
248
295
|
});
|
|
249
296
|
|
|
250
|
-
|
|
251
|
-
.
|
|
252
|
-
.
|
|
297
|
+
cmd
|
|
298
|
+
.command('vote')
|
|
299
|
+
.description('vote on latest question')
|
|
300
|
+
.requiredOption(
|
|
301
|
+
'--instance <string>',
|
|
302
|
+
'Committee name under agoricNames.instances',
|
|
303
|
+
String,
|
|
304
|
+
'economicCommittee',
|
|
305
|
+
)
|
|
306
|
+
.requiredOption(
|
|
307
|
+
'--pathname <string>',
|
|
308
|
+
'Committee name under published.committees',
|
|
309
|
+
String,
|
|
310
|
+
'Economic_Committee',
|
|
311
|
+
)
|
|
312
|
+
.option('--offerId <number>', 'Offer id', String, `gov-vote-${Date.now()}`)
|
|
253
313
|
.requiredOption(
|
|
254
314
|
'--forPosition <number>',
|
|
255
315
|
'index of one position to vote for (within the question description.positions); ',
|
|
@@ -260,17 +320,18 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => {
|
|
|
260
320
|
'Send from address',
|
|
261
321
|
normalizeAddress,
|
|
262
322
|
)
|
|
263
|
-
.action(async function (opts) {
|
|
323
|
+
.action(async function (opts, options) {
|
|
264
324
|
const utils = await makeRpcUtils({ fetch });
|
|
265
325
|
const { readLatestHead } = utils;
|
|
266
326
|
|
|
267
327
|
const info = await readLatestHead(
|
|
268
|
-
|
|
328
|
+
`published.committees.${opts.pathname}.latestQuestion`,
|
|
269
329
|
).catch(err => {
|
|
270
330
|
throw new CommanderError(1, 'VSTORAGE_FAILURE', err.message);
|
|
271
331
|
});
|
|
332
|
+
|
|
272
333
|
// XXX runtime shape-check
|
|
273
|
-
const questionDesc = /** @type {
|
|
334
|
+
const questionDesc = /** @type {QuestionDetails} */ (info);
|
|
274
335
|
|
|
275
336
|
// TODO support multiple position arguments
|
|
276
337
|
const chosenPositions = [questionDesc.positions[opts.forPosition]];
|
|
@@ -279,9 +340,7 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => {
|
|
|
279
340
|
/** @type {Parameters<typeof processOffer>[0]['toOffer']} */
|
|
280
341
|
const toOffer = (agoricNames, current) => {
|
|
281
342
|
const cont = current ? findContinuingIds(current, agoricNames) : [];
|
|
282
|
-
const votingRight = cont.find(
|
|
283
|
-
it => it.instance === agoricNames.instance.economicCommittee,
|
|
284
|
-
);
|
|
343
|
+
const votingRight = cont.find(it => it.instanceName === opts.instance);
|
|
285
344
|
if (!votingRight) {
|
|
286
345
|
console.debug('continuing ids', cont, 'for', current);
|
|
287
346
|
throw new CommanderError(
|
|
@@ -306,8 +365,89 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => {
|
|
|
306
365
|
};
|
|
307
366
|
};
|
|
308
367
|
|
|
309
|
-
await processOffer(
|
|
368
|
+
await processOffer(
|
|
369
|
+
{
|
|
370
|
+
toOffer,
|
|
371
|
+
sendFrom: opts.sendFrom,
|
|
372
|
+
keyringBackend: options.optsWithGlobals().keyringBackend,
|
|
373
|
+
},
|
|
374
|
+
utils,
|
|
375
|
+
);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
cmd
|
|
379
|
+
.command('proposePauseOffers')
|
|
380
|
+
.description('propose a vote to pause offers')
|
|
381
|
+
.option(
|
|
382
|
+
'--send-from <name-or-address>',
|
|
383
|
+
'Send from address',
|
|
384
|
+
normalizeAddress,
|
|
385
|
+
)
|
|
386
|
+
.option(
|
|
387
|
+
'--offerId <string>',
|
|
388
|
+
'Offer id',
|
|
389
|
+
String,
|
|
390
|
+
`proposePauseOffers-${Date.now()}`,
|
|
391
|
+
)
|
|
392
|
+
.requiredOption(
|
|
393
|
+
'--instance <string>',
|
|
394
|
+
'name of governed instance in agoricNames',
|
|
395
|
+
)
|
|
396
|
+
.requiredOption(
|
|
397
|
+
'--substring <string>',
|
|
398
|
+
'an offer string to pause (can be repeated)',
|
|
399
|
+
collectValues,
|
|
400
|
+
[],
|
|
401
|
+
)
|
|
402
|
+
.option(
|
|
403
|
+
'--deadline <minutes>',
|
|
404
|
+
'minutes from now to close the vote',
|
|
405
|
+
Number,
|
|
406
|
+
1,
|
|
407
|
+
)
|
|
408
|
+
.action(async function (opts, options) {
|
|
409
|
+
const { instance: instanceName } = opts;
|
|
410
|
+
|
|
411
|
+
/** @type {Parameters<typeof processOffer>[0]['toOffer']} */
|
|
412
|
+
const toOffer = (agoricNames, current) => {
|
|
413
|
+
const instance = agoricNames.instance[instanceName];
|
|
414
|
+
assert(instance, `missing ${instanceName}`);
|
|
415
|
+
assert(current, 'missing current wallet');
|
|
416
|
+
|
|
417
|
+
const known = findContinuingIds(current, agoricNames);
|
|
418
|
+
|
|
419
|
+
assert(known, 'could not find committee acceptance offer id');
|
|
420
|
+
|
|
421
|
+
// TODO magic string
|
|
422
|
+
const match = known.find(
|
|
423
|
+
r => r.description === 'charter member invitation',
|
|
424
|
+
);
|
|
425
|
+
assert(match, 'no offer found for charter member invitation');
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
id: opts.offerId,
|
|
429
|
+
invitationSpec: {
|
|
430
|
+
source: 'continuing',
|
|
431
|
+
previousOffer: match.offerId,
|
|
432
|
+
invitationMakerName: 'VoteOnPauseOffers',
|
|
433
|
+
// ( instance, strings list, timer deadline seconds )
|
|
434
|
+
invitationArgs: harden([
|
|
435
|
+
instance,
|
|
436
|
+
opts.substring,
|
|
437
|
+
BigInt(opts.deadline * 60 + Math.round(Date.now() / 1000)),
|
|
438
|
+
]),
|
|
439
|
+
},
|
|
440
|
+
proposal: {},
|
|
441
|
+
};
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
await processOffer({
|
|
445
|
+
toOffer,
|
|
446
|
+
instanceName,
|
|
447
|
+
sendFrom: opts.sendFrom,
|
|
448
|
+
keyringBackend: options.optsWithGlobals().keyringBackend,
|
|
449
|
+
});
|
|
310
450
|
});
|
|
311
451
|
|
|
312
|
-
return
|
|
452
|
+
return cmd;
|
|
313
453
|
};
|