@agoric/cosmic-swingset 0.40.0 → 0.40.1-dev-800c2bb.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/src/params.js CHANGED
@@ -1,16 +1,14 @@
1
1
  // @ts-check
2
- import { assert, details as X } from '@agoric/assert';
3
- import { Nat, isNat } from '@agoric/nat';
2
+ // @jessie-check
3
+
4
+ import { Fail } from '@agoric/assert';
5
+ import { Nat, isNat } from '@endo/nat';
4
6
 
5
7
  export const stringToNat = s => {
6
- assert.typeof(s, 'string', X`${s} must be a string`);
8
+ typeof s === 'string' || Fail`${s} must be a string`;
7
9
  const bint = BigInt(s);
8
10
  const nat = Nat(bint);
9
- assert.equal(
10
- `${nat}`,
11
- s,
12
- X`${s} must be the canonical representation of ${nat}`,
13
- );
11
+ `${nat}` === s || Fail`${s} must be the canonical representation of ${nat}`;
14
12
  return nat;
15
13
  };
16
14
 
@@ -18,15 +16,18 @@ export const stringToNat = s => {
18
16
  export const parseQueueSizes = queueSizeEntries =>
19
17
  Object.fromEntries(
20
18
  queueSizeEntries.map(({ key, size }) => {
21
- assert.typeof(key, 'string', X`Key ${key} must be a string`);
22
- assert(isNat(size), X`Size ${size} is not a positive integer`);
19
+ typeof key === 'string' || Fail`Key ${key} must be a string`;
20
+ isNat(size) || Fail`Size ${size} is not a positive integer`;
23
21
  return [key, size];
24
22
  }),
25
23
  );
26
24
 
27
25
  /** @param {Record<string, number>} queueSizes */
28
- export const serializeQueueSizes = queueSizes =>
29
- Object.entries(queueSizes).map(([key, size]) => ({ key, size }));
26
+ export const encodeQueueSizes = queueSizes =>
27
+ Object.entries(queueSizes).map(([key, size]) => {
28
+ isNat(size) || Fail`Size ${size} is not a positive integer`;
29
+ return { key, size };
30
+ });
30
31
 
31
32
  // Map the SwingSet parameters to a deterministic data structure.
32
33
  export const parseParams = params => {
@@ -36,24 +37,22 @@ export const parseParams = params => {
36
37
  queue_max: rawQueueMax,
37
38
  } = params;
38
39
  Array.isArray(rawBeansPerUnit) ||
39
- assert.fail(X`beansPerUnit must be an array, not ${rawBeansPerUnit}`);
40
+ Fail`beansPerUnit must be an array, not ${rawBeansPerUnit}`;
40
41
  const beansPerUnit = Object.fromEntries(
41
42
  rawBeansPerUnit.map(({ key, beans }) => {
42
- assert.typeof(key, 'string', X`Key ${key} must be a string`);
43
+ typeof key === 'string' || Fail`Key ${key} must be a string`;
43
44
  return [key, stringToNat(beans)];
44
45
  }),
45
46
  );
46
47
  Array.isArray(rawFeeUnitPrice) ||
47
- assert.fail(X`feeUnitPrice ${rawFeeUnitPrice} must be an array`);
48
+ Fail`feeUnitPrice ${rawFeeUnitPrice} must be an array`;
48
49
  const feeUnitPrice = rawFeeUnitPrice.map(({ denom, amount }) => {
49
- assert.typeof(denom, 'string', X`denom ${denom} must be a string`);
50
- assert(denom, X`denom ${denom} must be non-empty`);
50
+ typeof denom === 'string' || Fail`denom ${denom} must be a string`;
51
+ denom || Fail`denom ${denom} must be non-empty`;
51
52
  return { denom, amount: stringToNat(amount) };
52
53
  });
53
- assert(
54
- Array.isArray(rawQueueMax),
55
- X`queueMax must be an array, not ${rawQueueMax}`,
56
- );
54
+ Array.isArray(rawQueueMax) ||
55
+ Fail`queueMax must be an array, not ${rawQueueMax}`;
57
56
  const queueMax = parseQueueSizes(rawQueueMax);
58
57
 
59
58
  return { beansPerUnit, feeUnitPrice, queueMax };
package/src/sim-chain.js CHANGED
@@ -12,14 +12,15 @@ import anylogger from 'anylogger';
12
12
  import { makeSlogSender } from '@agoric/telemetry';
13
13
 
14
14
  import { resolve as importMetaResolve } from 'import-meta-resolve';
15
- import { assert, details as X } from '@agoric/assert';
16
- import { makeWithQueue } from '@agoric/vats/src/queue.js';
17
- import { makeBatchedDeliver } from '@agoric/vats/src/batched-deliver.js';
18
- import stringify from './json-stable-stringify.js';
15
+ import { Fail } from '@agoric/assert';
16
+ import { makeWithQueue } from '@agoric/internal/src/queue.js';
17
+ import { makeBatchedDeliver } from '@agoric/internal/src/batched-deliver.js';
18
+ import stringify from './helpers/json-stable-stringify.js';
19
19
  import { launch } from './launch-chain.js';
20
20
  import { getTelemetryProviders } from './kernel-stats.js';
21
21
  import { DEFAULT_SIM_SWINGSET_PARAMS, QueueInbound } from './sim-params.js';
22
22
  import { parseQueueSizes } from './params.js';
23
+ import { makeQueue, makeQueueStorageMock } from './helpers/make-queue.js';
23
24
 
24
25
  const console = anylogger('fake-chain');
25
26
 
@@ -38,14 +39,14 @@ async function makeMapStorage(file) {
38
39
  await fs.promises.writeFile(file, json);
39
40
  };
40
41
 
41
- let obj = {};
42
- try {
42
+ await (async () => {
43
43
  content = await fs.promises.readFile(file);
44
- obj = JSON.parse(content);
45
- } catch (e) {
46
- return map;
47
- }
48
- Object.entries(obj).forEach(([k, v]) => map.set(k, importMailbox(v)));
44
+ return JSON.parse(content);
45
+ })().then(
46
+ obj =>
47
+ Object.entries(obj).forEach(([k, v]) => map.set(k, importMailbox(v))),
48
+ () => {},
49
+ );
49
50
 
50
51
  return map;
51
52
  }
@@ -58,7 +59,6 @@ export async function connectToFakeChain(basedir, GCI, delay, inbound) {
58
59
  const mailboxStorage = await makeMapStorage(mailboxFile);
59
60
 
60
61
  const argv = {
61
- ROLE: 'sim-chain',
62
62
  giveMeAllTheAgoricPowers: true,
63
63
  hardcodedClientAddresses: [bootAddress],
64
64
  bootMsg: {
@@ -70,18 +70,17 @@ export async function connectToFakeChain(basedir, GCI, delay, inbound) {
70
70
  },
71
71
  };
72
72
 
73
- const vatconfig = new URL(
74
- await importMetaResolve(
75
- process.env.CHAIN_BOOTSTRAP_VAT_CONFIG ||
76
- argv.bootMsg.params.bootstrap_vat_config,
77
- import.meta.url,
78
- ),
79
- ).pathname;
73
+ const url = await importMetaResolve(
74
+ process.env.CHAIN_BOOTSTRAP_VAT_CONFIG ||
75
+ argv.bootMsg.params.bootstrap_vat_config,
76
+ import.meta.url,
77
+ );
78
+ const vatconfig = new URL(url).pathname;
80
79
  const stateDBdir = path.join(basedir, `fake-chain-${GCI}-state`);
81
80
  function replayChainSends() {
82
- assert.fail(X`Replay not implemented`);
81
+ Fail`Replay not implemented`;
83
82
  }
84
- function clearChainSends() {
83
+ async function clearChainSends() {
85
84
  return [];
86
85
  }
87
86
 
@@ -92,26 +91,19 @@ export async function connectToFakeChain(basedir, GCI, delay, inbound) {
92
91
  serviceName: TELEMETRY_SERVICE_NAME,
93
92
  });
94
93
 
95
- const { LMDB_MAP_SIZE } = env;
96
- const mapSize = (LMDB_MAP_SIZE && parseInt(LMDB_MAP_SIZE, 10)) || undefined;
97
94
  const slogSender = await makeSlogSender({
98
95
  stateDir: stateDBdir,
99
96
  serviceName: TELEMETRY_SERVICE_NAME,
100
97
  env,
101
98
  });
102
99
 
103
- let aqContents = [];
104
- const actionQueue = {
105
- /** @returns {Iterable<unknown>} */
106
- consumeAll: () => {
107
- const iterable = aqContents;
108
- aqContents = [];
109
- return iterable;
110
- },
111
- };
100
+ const actionQueueStorage = makeQueueStorageMock().storage;
101
+ const highPriorityQueueStorage = makeQueueStorageMock().storage;
102
+ const actionQueue = makeQueue(actionQueueStorage);
112
103
 
113
104
  const s = await launch({
114
- actionQueue,
105
+ actionQueueStorage,
106
+ highPriorityQueueStorage,
115
107
  kernelStateDBDir: stateDBdir,
116
108
  mailboxStorage,
117
109
  clearChainSends,
@@ -121,13 +113,11 @@ export async function connectToFakeChain(basedir, GCI, delay, inbound) {
121
113
  debugName: GCI,
122
114
  metricsProvider,
123
115
  slogSender,
124
- mapSize,
125
116
  });
126
117
 
127
- const { blockingSend, savedHeight, savedBlockTime } = s;
118
+ const { blockingSend, savedHeight } = s;
128
119
 
129
120
  let blockHeight = savedHeight;
130
- let blockTime = savedBlockTime || scaleBlockTime(Date.now());
131
121
  const intoChain = [];
132
122
  let nextBlockTimeout = 0;
133
123
 
@@ -136,7 +126,7 @@ export async function connectToFakeChain(basedir, GCI, delay, inbound) {
136
126
  const withBlockQueue = makeWithQueue();
137
127
  const unhandledSimulateBlock = withBlockQueue(
138
128
  async function unqueuedSimulateBlock() {
139
- blockTime = scaleBlockTime(Date.now());
129
+ const blockTime = scaleBlockTime(Date.now());
140
130
  blockHeight += 1;
141
131
 
142
132
  const params = DEFAULT_SIM_SWINGSET_PARAMS;
@@ -146,22 +136,27 @@ export async function connectToFakeChain(basedir, GCI, delay, inbound) {
146
136
  blockTime,
147
137
  params,
148
138
  };
149
- const beginBlockResult = await blockingSend(beginAction);
150
- assert(beginBlockResult);
151
- const queueAllowed = parseQueueSizes(beginBlockResult.queue_allowed);
152
- assert(QueueInbound in queueAllowed);
139
+ await blockingSend(beginAction);
140
+ const inboundQueueMax = parseQueueSizes(params.queue_max)[QueueInbound];
141
+ const inboundQueueAllowed = Math.max(
142
+ 0,
143
+ inboundQueueMax - actionQueue.size(),
144
+ );
153
145
 
154
146
  // Gather up the new messages into the latest block.
155
- const thisBlock = intoChain.splice(0, queueAllowed[QueueInbound]);
156
-
157
- for (const [newMessages, acknum] of thisBlock) {
158
- aqContents.push({
159
- type: 'DELIVER_INBOUND',
160
- peer: bootAddress,
161
- messages: newMessages,
162
- ack: acknum,
163
- blockHeight,
164
- blockTime,
147
+ const thisBlock = intoChain.splice(0, inboundQueueAllowed);
148
+
149
+ for (const [i, [newMessages, acknum]] of thisBlock.entries()) {
150
+ actionQueue.push({
151
+ action: {
152
+ type: 'DELIVER_INBOUND',
153
+ peer: bootAddress,
154
+ messages: newMessages,
155
+ ack: acknum,
156
+ blockHeight,
157
+ blockTime,
158
+ },
159
+ context: { blockHeight, txHash: 'simTx', msgIdx: i },
165
160
  });
166
161
  }
167
162
  const endAction = { type: 'END_BLOCK', blockHeight, blockTime };
@@ -194,10 +189,12 @@ export async function connectToFakeChain(basedir, GCI, delay, inbound) {
194
189
 
195
190
  intoChain.push([newMessages, acknum]);
196
191
  // Only actually simulate a block if we're not in bootstrap.
192
+ let p;
197
193
  if (blockHeight && !delay) {
198
194
  clearTimeout(nextBlockTimeout);
199
- await simulateBlock();
195
+ p = simulateBlock();
200
196
  }
197
+ await p;
201
198
  }
202
199
 
203
200
  const bootSimChain = async () => {
@@ -207,7 +204,10 @@ export async function connectToFakeChain(basedir, GCI, delay, inbound) {
207
204
  // The before-first-block is special... do it now.
208
205
  // This emulates what x/swingset does to run a BOOTSTRAP_BLOCK
209
206
  // before continuing with the real initialHeight.
210
- await blockingSend({ type: 'BOOTSTRAP_BLOCK', blockTime });
207
+ await blockingSend({
208
+ type: 'BOOTSTRAP_BLOCK',
209
+ blockTime: scaleBlockTime(Date.now()),
210
+ });
211
211
  blockHeight = initialHeight;
212
212
  };
213
213
 
@@ -222,5 +222,9 @@ export async function connectToFakeChain(basedir, GCI, delay, inbound) {
222
222
  });
223
223
 
224
224
  const batchDelayMs = delay ? delay * 1000 : undefined;
225
- return makeBatchedDeliver(deliver, batchDelayMs);
225
+ return makeBatchedDeliver(
226
+ deliver,
227
+ { clearTimeout, setTimeout },
228
+ batchDelayMs,
229
+ );
226
230
  }
package/src/sim-params.js CHANGED
@@ -1,4 +1,6 @@
1
- import { Nat } from '@agoric/nat';
1
+ // @jessie-check
2
+
3
+ import { Nat } from '@endo/nat';
2
4
 
3
5
  const makeStringBeans = (key, beans) => ({ key, beans: `${Nat(beans)}` });
4
6
  const makePowerFlagFee = (powerFlag, fee) => ({ powerFlag, fee });
@@ -58,8 +60,7 @@ export const defaultBeansPerUnit = [
58
60
  makeStringBeans(BeansPerXsnapComputron, defaultBeansPerXsnapComputron),
59
61
  ];
60
62
 
61
- export const defaultBootstrapVatConfig =
62
- '@agoric/vats/decentral-demo-config.json';
63
+ const defaultBootstrapVatConfig = '@agoric/vats/decentral-demo-config.json';
63
64
 
64
65
  export const defaultPowerFlagFees = [
65
66
  makePowerFlagFee('SMART_WALLET', [makeCoin('ubld', 10_000_000n)]),
package/transfer.ros ADDED
@@ -0,0 +1,161 @@
1
+ request_funds(1){
2
+ find_account{
3
+ currency = {"symbol":"ubld", "decimals":0};
4
+ random_account = find_balance({
5
+ "minimum_balance":{
6
+ "value": "0",
7
+ "currency": {{currency}}
8
+ },
9
+ "create_limit":1
10
+ });
11
+ },
12
+ send_funds{
13
+ account_identifier = {{random_account.account_identifier}};
14
+ address = {{account_identifier.address}};
15
+ idk = http_request({
16
+ "method": "POST",
17
+ "url": "http:\/\/127.0.0.1:7000",
18
+ "timeout": 10,
19
+ "body": {{random_account.account_identifier.address}}
20
+ });
21
+ },
22
+ // Create a separate scenario to request funds so that
23
+ // the address we are using to request funds does not
24
+ // get rolled back if funds do not yet exist.
25
+ request{
26
+ loaded_account = find_balance({
27
+ "account_identifier": {{random_account.account_identifier}},
28
+ "minimum_balance":{
29
+ "value": "50",
30
+ "currency": {{currency}}
31
+ }
32
+ });
33
+ }
34
+ }
35
+ create_account(1){
36
+ create{
37
+ network = {"network":"agoriclocal", "blockchain":"app"};
38
+ key = generate_key({"curve_type": "secp256k1"});
39
+ account = derive({
40
+ "network_identifier": {{network}},
41
+ "public_key": {{key.public_key}}
42
+ });
43
+ // If the account is not saved, the key will be lost!
44
+ save_account({
45
+ "account_identifier": {{account.account_identifier}},
46
+ "keypair": {{key}}
47
+ });
48
+ }
49
+ }
50
+ transferBLD(3){
51
+ transfer{
52
+ transfer.network = {"network":"agoriclocal", "blockchain":"app"};
53
+ currency = {"symbol":"ubld", "decimals":0};
54
+ sender = find_balance({
55
+ "minimum_balance":{
56
+ "value": "1000000",
57
+ "currency": {{currency}}
58
+ }
59
+ });
60
+ acc_identifier = {{sender.account_identifier}};
61
+ sender_address = {{acc_identifier.address}};
62
+ // Set the recipient_amount as some value <= sender.balance-max_fee
63
+ max_fee = "0";
64
+ fee_amount = "1";
65
+ fee_value = 0 - {{fee_amount}};
66
+ available_amount = {{sender.balance.value}} - {{max_fee}};
67
+ recipient_amount = random_number({"minimum": "1", "maximum": {{available_amount}}});
68
+ print_message({"recipient_amount":{{recipient_amount}}});
69
+ // Find recipient and construct operations
70
+ sender_amount = 0 - {{recipient_amount}};
71
+ recipient = find_balance({
72
+ "not_account_identifier":[{{sender.account_identifier}}],
73
+ "minimum_balance":{
74
+ "value": "0",
75
+ "currency": {{currency}}
76
+ },
77
+ "create_limit": 100,
78
+ "create_probability": 50
79
+ });
80
+ transfer.confirmation_depth = "1";
81
+ recipient_account_identifier = {{recipient.account_identifier}};
82
+ recipient_address = {{recipient_account_identifier.address}};
83
+ transfer.operations = [
84
+ {
85
+ "operation_identifier":{"index":0},
86
+ "type":"/cosmos.bank.v1beta1.MsgSend",
87
+ "account":{{sender.account_identifier}},
88
+ "metadata": {
89
+ "amount": [
90
+ {
91
+ "amount": {{recipient_amount}},
92
+ "denom": {{currency.symbol}}
93
+ }
94
+ ],
95
+ "from_address": {{sender_address}},
96
+ "to_address": {{recipient_address}}
97
+ }
98
+ }
99
+ ];
100
+ transfer.preprocess_metadata = {
101
+ "gas_price": "0.01ubld",
102
+ "gas_limit": 250000
103
+ };
104
+ }
105
+ }
106
+ transferIST(4){
107
+ transfer{
108
+ transfer.network = {"network":"agoriclocal", "blockchain":"app"};
109
+ currency = {"symbol":"uist", "decimals":0};
110
+ sender = find_balance({
111
+ "minimum_balance":{
112
+ "value": "1000000",
113
+ "currency": {{currency}}
114
+ }
115
+ });
116
+ acc_identifier = {{sender.account_identifier}};
117
+ sender_address = {{acc_identifier.address}};
118
+ // Set the recipient_amount as some value <= sender.balance-max_fee
119
+ max_fee = "0";
120
+ fee_amount = "1";
121
+ fee_value = 0 - {{fee_amount}};
122
+ available_amount = {{sender.balance.value}} - {{max_fee}};
123
+ recipient_amount = random_number({"minimum": "1", "maximum": {{available_amount}}});
124
+ print_message({"recipient_amount":{{recipient_amount}}});
125
+ // Find recipient and construct operations
126
+ sender_amount = 0 - {{recipient_amount}};
127
+ recipient = find_balance({
128
+ "not_account_identifier":[{{sender.account_identifier}}],
129
+ "minimum_balance":{
130
+ "value": "0",
131
+ "currency": {{currency}}
132
+ },
133
+ "create_limit": 100,
134
+ "create_probability": 50
135
+ });
136
+ transfer.confirmation_depth = "1";
137
+ recipient_account_identifier = {{recipient.account_identifier}};
138
+ recipient_address = {{recipient_account_identifier.address}};
139
+ transfer.operations = [
140
+ {
141
+ "operation_identifier":{"index":0},
142
+ "type":"/cosmos.bank.v1beta1.MsgSend",
143
+ "account":{{sender.account_identifier}},
144
+ "metadata": {
145
+ "amount": [
146
+ {
147
+ "amount": {{recipient_amount}},
148
+ "denom": {{currency.symbol}}
149
+ }
150
+ ],
151
+ "from_address": {{sender_address}},
152
+ "to_address": {{recipient_address}}
153
+ }
154
+ }
155
+ ];
156
+ transfer.preprocess_metadata = {
157
+ "gas_price": "0.01ubld",
158
+ "gas_limit": 250000
159
+ };
160
+ }
161
+ }
@@ -1,13 +0,0 @@
1
- export const BOOTSTRAP_BLOCK = 'BOOTSTRAP_BLOCK';
2
- export const BEGIN_BLOCK = 'BEGIN_BLOCK';
3
- export const CALCULATE_FEES_IN_BEANS = 'CALCULATE_FEES_IN_BEANS';
4
- export const CORE_EVAL = 'CORE_EVAL';
5
- export const DELIVER_INBOUND = 'DELIVER_INBOUND';
6
- export const END_BLOCK = 'END_BLOCK';
7
- export const COMMIT_BLOCK = 'COMMIT_BLOCK';
8
- export const IBC_EVENT = 'IBC_EVENT';
9
- export const PLEASE_PROVISION = 'PLEASE_PROVISION';
10
- export const VBANK_BALANCE_UPDATE = 'VBANK_BALANCE_UPDATE';
11
- export const WALLET_ACTION = 'WALLET_ACTION';
12
- export const WALLET_SPEND_ACTION = 'WALLET_SPEND_ACTION';
13
- export const INSTALL_BUNDLE = 'INSTALL_BUNDLE';
package/src/make-queue.js DELETED
@@ -1,86 +0,0 @@
1
- /**
2
- * @typedef {object} QueueStorage
3
- * @property {() => void} commit
4
- * @property {() => void} abort
5
- * @property {(key: string) => unknown} get
6
- * @property {(key: string, value: unknown) => void} set
7
- * @property {(key: string) => void} delete
8
- */
9
-
10
- /**
11
- * Create a queue backed by some sort of scoped storage.
12
- *
13
- * The queue writes the following bare keys, and expect any prefixing/scoping
14
- * to be handled by the storage:
15
- * - `head`: the index of the first entry of the queue.
16
- * - `tail`: the index *past* the last entry in the queue.
17
- * - `<index>`: the contents of the queue at the given index.
18
- *
19
- * For the `actionQueue`, the Cosmos side of the queue will push into the queue,
20
- * updating `<prefix>tail` and `<prefix><index>`. The JS side will shift the
21
- * queue, updating `<prefix>head` and reading and deleting `<prefix><index>`.
22
- *
23
- * Parallel access is not supported, only a single outstanding operation at a
24
- * time.
25
- *
26
- * @param {QueueStorage} storage a scoped queue storage
27
- */
28
- export const makeQueue = storage => {
29
- const queue = {
30
- size: () => {
31
- const tail = storage.get('tail') || 0;
32
- const head = storage.get('head') || 0;
33
- return tail - head;
34
- },
35
- push: obj => {
36
- const tail = storage.get('tail') || 0;
37
- storage.set('tail', tail + 1);
38
- storage.set(`${tail}`, obj);
39
- storage.commit();
40
- },
41
- /** @type {Iterable<unknown>} */
42
- consumeAll: () => ({
43
- [Symbol.iterator]: () => {
44
- let done = false;
45
- let head = storage.get('head') || 0;
46
- const tail = storage.get('tail') || 0;
47
- return {
48
- next: () => {
49
- if (done) return { done };
50
- if (head < tail) {
51
- // Still within the queue.
52
- const headKey = `${head}`;
53
- const value = storage.get(headKey);
54
- storage.delete(headKey);
55
- head += 1;
56
- return { value, done };
57
- }
58
- // Reached the end, so clean up our indices.
59
- storage.delete('head');
60
- storage.delete('tail');
61
- storage.commit();
62
- done = true;
63
- return { done };
64
- },
65
- return: () => {
66
- if (done) return { done };
67
- // We're done consuming, so save our state.
68
- storage.set('head', head);
69
- storage.commit();
70
- done = true;
71
- return { done };
72
- },
73
- throw: err => {
74
- if (done) return { done };
75
- // Don't change our state.
76
- storage.abort();
77
- done = true;
78
- throw err;
79
- },
80
- };
81
- },
82
- }),
83
- };
84
- return queue;
85
- };
86
- harden(makeQueue);
@@ -1,109 +0,0 @@
1
- import { assert, details as X } from '@agoric/assert';
2
-
3
- import * as ActionType from './action-types.js';
4
-
5
- const MAX_BODY_DEPTH = 50n;
6
-
7
- const SMART_WALLET_BODY_KEYS = new Set([
8
- '',
9
- 'brand',
10
- 'digits',
11
- 'give',
12
- 'id',
13
- 'iface',
14
- 'In',
15
- 'index',
16
- 'instance',
17
- 'invitationSpec',
18
- 'method',
19
- 'offer',
20
- 'Out',
21
- 'proposal',
22
- 'publicInvitationMaker',
23
- '@qclass',
24
- 'source',
25
- 'value',
26
- 'want',
27
- ]);
28
-
29
- // From economicCommitteeAddresses in `decentral-main-psm-config.json`
30
- const SMART_WALLET_EC_COMMITTEE = new Set(
31
- Object.values({
32
- 'Jason Potts': 'agoric1gx9uu7y6c90rqruhesae2t7c2vlw4uyyxlqxrx',
33
- 'Chloe White': 'agoric1d4228cvelf8tj65f4h7n2td90sscavln2283h5',
34
- 'Thibault Schrepel': 'agoric14543m33dr28x7qhwc558hzlj9szwhzwzpcmw6a',
35
- 'Chris Berg': 'agoric13p9adwk0na5npfq64g22l6xucvqdmu3xqe70wq',
36
- 'Youssef Amrani': 'agoric1el6zqs8ggctj5vwyukyk4fh50wcpdpwgugd5l5',
37
- 'Joe Clark': 'agoric1zayxg4e9vd0es9c9jlpt36qtth255txjp6a8yc',
38
- }),
39
- );
40
-
41
- export const validateWalletAction = async action => {
42
- if (SMART_WALLET_EC_COMMITTEE.has(action.owner)) {
43
- // The Economic Committee uses the smart wallet for governance votes
44
- // which have a less structured payload.
45
- return;
46
- }
47
-
48
- const actualAction =
49
- action.type === ActionType.WALLET_SPEND_ACTION
50
- ? action.spendAction
51
- : action.action;
52
- assert(
53
- actualAction !== undefined,
54
- X`Wallet action undefined for ${action.type}`,
55
- );
56
- const actualPayloadBody = JSON.parse(actualAction).body;
57
-
58
- assert.typeof(actualPayloadBody, 'string');
59
- assert(
60
- !actualPayloadBody.startsWith('#'),
61
- X`Unexpected smallcaps in smart wallet payload`,
62
- );
63
-
64
- let bodyMaxDepth = 0n;
65
- try {
66
- bodyMaxDepth = JSON.parse(
67
- actualPayloadBody,
68
- function reviverCheck(key, value) {
69
- if (!Array.isArray(this)) {
70
- assert(
71
- SMART_WALLET_BODY_KEYS.has(key),
72
- X`Unexpected key in smart wallet payload: ${key}`,
73
- );
74
- }
75
-
76
- switch (typeof value) {
77
- case 'object': {
78
- if (!value) return 0n;
79
- let maxChildDepth = 0n;
80
-
81
- for (const childDepth of Object.values(value)) {
82
- if (childDepth > maxChildDepth) {
83
- maxChildDepth = childDepth;
84
- }
85
- }
86
- return 1n + maxChildDepth;
87
- }
88
- case 'boolean':
89
- case 'number':
90
- case 'string':
91
- return 0n;
92
- default:
93
- assert.fail(X`Unexpected parsed type`);
94
- }
95
- },
96
- );
97
- } catch (err) {
98
- if (err.name === 'SyntaxError') {
99
- console.warn('Invalid smart wallet body', action, err);
100
- } else {
101
- throw err;
102
- }
103
- }
104
-
105
- assert(
106
- bodyMaxDepth < MAX_BODY_DEPTH,
107
- X`Smart wallet message body too deep: ${bodyMaxDepth}`,
108
- );
109
- };