ox 0.14.38 → 0.14.39

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 (40) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/_cjs/tempo/MultisigConfig.js +71 -15
  3. package/_cjs/tempo/MultisigConfig.js.map +1 -1
  4. package/_cjs/tempo/MultisigOperation.js +42 -36
  5. package/_cjs/tempo/MultisigOperation.js.map +1 -1
  6. package/_cjs/tempo/SignatureEnvelope.js +71 -108
  7. package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
  8. package/_cjs/version.js +1 -1
  9. package/_esm/tempo/MultisigConfig.js +140 -71
  10. package/_esm/tempo/MultisigConfig.js.map +1 -1
  11. package/_esm/tempo/MultisigOperation.js +42 -37
  12. package/_esm/tempo/MultisigOperation.js.map +1 -1
  13. package/_esm/tempo/SignatureEnvelope.js +84 -143
  14. package/_esm/tempo/SignatureEnvelope.js.map +1 -1
  15. package/_esm/version.js +1 -1
  16. package/_types/tempo/KeyAuthorization.d.ts +1 -1
  17. package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
  18. package/_types/tempo/MultisigConfig.d.ts +137 -73
  19. package/_types/tempo/MultisigConfig.d.ts.map +1 -1
  20. package/_types/tempo/MultisigOperation.d.ts +1 -1
  21. package/_types/tempo/MultisigOperation.d.ts.map +1 -1
  22. package/_types/tempo/SignatureEnvelope.d.ts +81 -75
  23. package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
  24. package/_types/version.d.ts +1 -1
  25. package/package.json +6 -1
  26. package/tempo/KeyAuthorization.test-d.ts +12 -0
  27. package/tempo/KeyAuthorization.test.ts +54 -0
  28. package/tempo/KeyAuthorization.ts +1 -1
  29. package/tempo/MultisigConfig.test-d/package.json +6 -0
  30. package/tempo/MultisigConfig.test-d.ts +52 -0
  31. package/tempo/MultisigConfig.test.ts +291 -263
  32. package/tempo/MultisigConfig.ts +250 -95
  33. package/tempo/MultisigOperation.test-d.ts +6 -0
  34. package/tempo/MultisigOperation.test.ts +113 -72
  35. package/tempo/MultisigOperation.ts +41 -35
  36. package/tempo/SignatureEnvelope.test-d.ts +57 -40
  37. package/tempo/SignatureEnvelope.test.ts +523 -480
  38. package/tempo/SignatureEnvelope.ts +250 -257
  39. package/tempo/multisig.e2e.test.ts +113 -86
  40. package/version.ts +1 -1
@@ -1,339 +1,367 @@
1
+ import { Hash, Hex, Rlp } from 'ox'
1
2
  import { MultisigConfig } from 'ox/tempo'
2
3
  import { describe, expect, test } from 'vitest'
3
4
 
4
- // Ground-truth vectors independently computed via `cast keccak` over the exact
5
- // preimages defined by TIP-1061 / the Tempo reference implementation.
6
- const owner1 = '0x1111111111111111111111111111111111111111'
7
- const owner2 = '0x2222222222222222222222222222222222222222'
8
-
9
- const singleOwnerConfig = {
5
+ const owner_1 = '0x1111111111111111111111111111111111111111'
6
+ const owner_2 = '0x2222222222222222222222222222222222222222'
7
+ const payload = `0x${'42'.repeat(32)}` as const
8
+ const config = MultisigConfig.from({
9
+ owners: [{ owner: owner_1, weight: 1 }],
10
10
  threshold: 1,
11
- owners: [{ owner: owner1, weight: 1 }],
12
- } as const
11
+ })
12
+ const account = MultisigConfig.getAddress(config)
13
13
 
14
14
  describe('from', () => {
15
- test('sorts owners ascending by address', () => {
16
- const config = MultisigConfig.from({
17
- threshold: 2,
18
- owners: [
19
- { owner: owner2, weight: 1 },
20
- { owner: owner1, weight: 1 },
21
- ],
22
- })
23
- expect(config.owners.map((o) => o.owner)).toEqual([owner1, owner2])
15
+ test('behavior: normalizes an initial configuration', () => {
16
+ expect(
17
+ MultisigConfig.from({
18
+ owners: [
19
+ { owner: owner_2, weight: 1 },
20
+ { owner: owner_1, weight: 1 },
21
+ ],
22
+ threshold: 2,
23
+ }),
24
+ ).toMatchInlineSnapshot(`
25
+ {
26
+ "owners": [
27
+ {
28
+ "owner": "0x1111111111111111111111111111111111111111",
29
+ "weight": 1,
30
+ },
31
+ {
32
+ "owner": "0x2222222222222222222222222222222222222222",
33
+ "weight": 1,
34
+ },
35
+ ],
36
+ "salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
37
+ "threshold": 2,
38
+ "version": 0n,
39
+ }
40
+ `)
24
41
  })
25
42
 
26
- test('asserts validity', () => {
43
+ test('behavior: normalizes a numeric configuration version', () => {
44
+ expect(
45
+ MultisigConfig.from({ ...config, version: 1 }),
46
+ ).toMatchInlineSnapshot(`
47
+ {
48
+ "owners": [
49
+ {
50
+ "owner": "0x1111111111111111111111111111111111111111",
51
+ "weight": 1,
52
+ },
53
+ ],
54
+ "salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
55
+ "threshold": 1,
56
+ "version": 1n,
57
+ }
58
+ `)
59
+ })
60
+
61
+ test('error: rejects an invalid configuration', () => {
62
+ expect(() =>
63
+ MultisigConfig.from({ owners: [], threshold: 0 }),
64
+ ).toThrowErrorMatchingInlineSnapshot(
65
+ `[MultisigConfig.InvalidConfigError: Invalid native multisig config: owners cannot be empty.]`,
66
+ )
67
+ })
68
+
69
+ test('error: rejects an invalid numeric version', () => {
27
70
  expect(() =>
28
- MultisigConfig.from({ threshold: 0, owners: [] }),
29
- ).toThrowError()
71
+ MultisigConfig.from({ ...config, version: 1.5 }),
72
+ ).toThrowErrorMatchingInlineSnapshot(
73
+ `[MultisigConfig.InvalidConfigError: Invalid native multisig config: version must be an unsigned 64-bit integer.]`,
74
+ )
75
+ })
76
+ })
77
+
78
+ describe('fromRpc/toRpc', () => {
79
+ test('behavior: round trips a configuration', () => {
80
+ const rpc = MultisigConfig.toRpc({ ...config, version: 1n })
81
+ expect(rpc).toMatchInlineSnapshot(`
82
+ {
83
+ "owners": [
84
+ {
85
+ "owner": "0x1111111111111111111111111111111111111111",
86
+ "weight": 1,
87
+ },
88
+ ],
89
+ "salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
90
+ "threshold": 1,
91
+ "version": "0x1",
92
+ }
93
+ `)
94
+ expect(MultisigConfig.fromRpc(rpc)).toStrictEqual({
95
+ ...config,
96
+ version: 1n,
97
+ })
30
98
  })
31
99
  })
32
100
 
33
101
  describe('getAddress', () => {
34
- test('matches independent ground truth', () => {
35
- expect(MultisigConfig.getAddress(singleOwnerConfig)).toMatchInlineSnapshot(
102
+ test('example: matches the frozen version-0 vector', () => {
103
+ expect(account).toMatchInlineSnapshot(
36
104
  `"0x8820d1497eeaf4f68e00b2cfc00a2f3b1dbb00da"`,
37
105
  )
38
106
  })
39
107
 
40
- test('matches independent ground truth (salt + weights)', () => {
108
+ test('behavior: includes salt, threshold, and owners', () => {
41
109
  expect(
42
110
  MultisigConfig.getAddress({
43
- salt: `0x${'42'.repeat(32)}`,
44
- threshold: 2,
45
111
  owners: [
46
- { owner: owner1, weight: 1 },
47
- { owner: owner2, weight: 2 },
112
+ { owner: owner_1, weight: 1 },
113
+ { owner: owner_2, weight: 2 },
48
114
  ],
115
+ salt: `0x${'42'.repeat(32)}`,
116
+ threshold: 2,
49
117
  }),
50
118
  ).toMatchInlineSnapshot(`"0x0773e28146400643e42cb28f6659b74e7c0b451d"`)
51
119
  })
52
120
 
53
- test('is stable across calls', () => {
54
- expect(MultisigConfig.getAddress(singleOwnerConfig)).toBe(
55
- MultisigConfig.getAddress(singleOwnerConfig),
56
- )
57
- })
58
-
59
- test('differs for a different salt', () => {
60
- expect(MultisigConfig.getAddress(singleOwnerConfig)).not.toBe(
61
- MultisigConfig.getAddress({
62
- ...singleOwnerConfig,
63
- salt: `0x${'42'.repeat(32)}`,
64
- }),
121
+ test('error: rejects a current configuration', () => {
122
+ expect(() =>
123
+ MultisigConfig.getAddress({ ...config, version: 1n }),
124
+ ).toThrowErrorMatchingInlineSnapshot(
125
+ `[MultisigConfig.InvalidConfigError: Invalid native multisig config: account address requires version zero.]`,
65
126
  )
66
127
  })
67
128
 
68
- test('address is chain-independent', () => {
69
- // Derivation does not include chain ID; identical config identical address.
70
- const a = MultisigConfig.getAddress(singleOwnerConfig)
71
- const b = MultisigConfig.getAddress(MultisigConfig.from(singleOwnerConfig))
72
- expect(a).toBe(b)
129
+ test('behavior: accepts numeric zero for an initial configuration', () => {
130
+ expect(MultisigConfig.getAddress({ ...config, version: 0 })).toBe(account)
73
131
  })
132
+ })
74
133
 
75
- test('throws on invalid config', () => {
76
- expect(() =>
77
- MultisigConfig.getAddress({
78
- threshold: 5,
79
- owners: singleOwnerConfig.owners,
80
- }),
81
- ).toThrowError()
134
+ describe('getCommitment', () => {
135
+ test('example: matches the frozen initial and current vectors', () => {
136
+ expect({
137
+ current: MultisigConfig.getCommitment({ ...config, version: 1n }),
138
+ currentNumber: MultisigConfig.getCommitment({ ...config, version: 1 }),
139
+ initial: MultisigConfig.getCommitment(config),
140
+ }).toMatchInlineSnapshot(`
141
+ {
142
+ "current": "0x6237ca5930f2265d4fb70a0305dd6ceea4df227053b4a62c304489ede946a2f8",
143
+ "currentNumber": "0x6237ca5930f2265d4fb70a0305dd6ceea4df227053b4a62c304489ede946a2f8",
144
+ "initial": "0xa9e7d1e2ad25e227a4de5f38f3bba31d854ffc8efec46aaa8649097a516bb4ee",
145
+ }
146
+ `)
82
147
  })
83
148
  })
84
149
 
85
150
  describe('getSignPayload', () => {
86
- test('matches independent ground truth', () => {
151
+ test('example: matches the frozen version-0 vector', () => {
87
152
  expect(
88
- MultisigConfig.getSignPayload({
89
- payload: `0x${'42'.repeat(32)}`,
90
- initialConfig: singleOwnerConfig,
91
- }),
153
+ MultisigConfig.getSignPayload({ account, config, payload }),
92
154
  ).toMatchInlineSnapshot(
93
155
  `"0xbf944a7a752b2cfab0418d5fb4591c5a7ff62976488edce11794d7f35fb34f41"`,
94
156
  )
95
157
  })
96
158
 
97
- test('behavior: `initialConfig` and `{ account }` produce identical digests', () => {
98
- const account = MultisigConfig.getAddress(singleOwnerConfig)
99
- const payload = `0x${'42'.repeat(32)}` as const
159
+ test('behavior: binds the configuration version', () => {
100
160
  expect(
101
161
  MultisigConfig.getSignPayload({
162
+ account,
163
+ config: { version: 1 },
102
164
  payload,
103
- initialConfig: singleOwnerConfig,
104
165
  }),
105
- ).toBe(MultisigConfig.getSignPayload({ payload, account }))
106
- })
107
-
108
- test('differs for a different config version', () => {
109
- const value = {
110
- payload: `0x${'42'.repeat(32)}` as const,
111
- initialConfig: singleOwnerConfig,
112
- }
113
- expect(MultisigConfig.getSignPayload(value)).not.toBe(
114
- MultisigConfig.getSignPayload({ ...value, version: 1n }),
115
- )
116
- })
117
-
118
- test('defaults the config version to zero', () => {
119
- const value = {
120
- payload: `0x${'42'.repeat(32)}` as const,
121
- initialConfig: singleOwnerConfig,
122
- }
123
- expect(MultisigConfig.getSignPayload(value)).toBe(
124
- MultisigConfig.getSignPayload({ ...value, version: 0n }),
125
- )
166
+ ).not.toBe(MultisigConfig.getSignPayload({ account, config, payload }))
126
167
  })
127
168
  })
128
169
 
129
- describe('toTuple / fromTuple', () => {
130
- test('round-trips', () => {
131
- const config = MultisigConfig.from({
132
- threshold: 3,
170
+ describe('toTuple/fromTuple', () => {
171
+ test('example: matches the frozen initial and current RLP vectors', () => {
172
+ expect({
173
+ current: Rlp.fromHex(MultisigConfig.toTuple({ ...config, version: 1n })),
174
+ initial: Rlp.fromHex(MultisigConfig.toTuple(config)),
175
+ }).toMatchInlineSnapshot(`
176
+ {
177
+ "current": "0xf83ba000000000000000000000000000000000000000000000000000000000000000000101d7d694111111111111111111111111111111111111111101",
178
+ "initial": "0xf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d694111111111111111111111111111111111111111101",
179
+ }
180
+ `)
181
+ })
182
+
183
+ test('behavior: round trips the complete witness', () => {
184
+ const current = MultisigConfig.from({
133
185
  owners: [
134
- { owner: owner1, weight: 1 },
135
- { owner: owner2, weight: 2 },
186
+ { owner: owner_1, weight: 1 },
187
+ { owner: owner_2, weight: 2 },
136
188
  ],
137
- })
138
- const tuple = MultisigConfig.toTuple(config)
139
- expect(MultisigConfig.fromTuple(tuple)).toEqual(config)
140
- })
141
-
142
- test('encodes each owner as `[owner, weight]`', () => {
143
- const [, , owners] = MultisigConfig.toTuple(singleOwnerConfig)
144
- expect(owners[0]).toEqual([owner1, '0x1'])
145
- })
146
-
147
- test('encodes salt as a full 32-byte string (first element)', () => {
148
- const [salt] = MultisigConfig.toTuple(singleOwnerConfig)
149
- expect(salt).toBe(MultisigConfig.zeroSalt)
150
- })
151
-
152
- test('round-trips a non-zero salt', () => {
153
- const config = MultisigConfig.from({
154
- ...singleOwnerConfig,
155
189
  salt: `0x${'42'.repeat(32)}`,
190
+ threshold: 3,
191
+ version: 1n,
156
192
  })
157
- const tuple = MultisigConfig.toTuple(config)
158
- expect(tuple[0]).toBe(`0x${'42'.repeat(32)}`)
159
- expect(MultisigConfig.fromTuple(tuple)).toEqual(config)
160
- })
161
- })
162
-
163
- describe('assert / validate', () => {
164
- test('valid config', () => {
165
- expect(MultisigConfig.validate(singleOwnerConfig)).toBe(true)
166
- })
167
-
168
- test('empty owners', () => {
169
- expect(MultisigConfig.validate({ threshold: 1, owners: [] })).toBe(false)
170
- })
171
-
172
- test('accepts 48 owners', () => {
173
- const owners = Array.from({ length: 48 }, (_, i) => ({
174
- owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
175
- weight: 1,
176
- }))
177
193
  expect(
178
- MultisigConfig.validate({
179
- threshold: MultisigConfig.maxSignatures,
180
- owners,
181
- }),
182
- ).toBe(true)
183
- })
184
-
185
- test('too many owners', () => {
186
- const owners = Array.from({ length: 49 }, (_, i) => ({
187
- owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
188
- weight: 1,
189
- }))
190
- expect(MultisigConfig.validate({ threshold: 1, owners })).toBe(false)
191
- })
192
-
193
- test('zero threshold', () => {
194
- expect(
195
- MultisigConfig.validate({
196
- threshold: 0,
197
- owners: singleOwnerConfig.owners,
198
- }),
199
- ).toBe(false)
200
- })
201
-
202
- test('accepts a uint8 threshold above the signature limit', () => {
203
- expect(
204
- MultisigConfig.validate({
205
- threshold: 9,
206
- owners: [{ owner: owner1, weight: 9 }],
207
- }),
208
- ).toBe(true)
209
- })
210
-
211
- test('accepts the maximum uint8 threshold', () => {
212
- expect(
213
- MultisigConfig.validate({
214
- threshold: MultisigConfig.maxThreshold,
215
- owners: [{ owner: owner1, weight: MultisigConfig.maxThreshold }],
216
- }),
217
- ).toBe(true)
218
- })
219
-
220
- test('threshold exceeds uint8 maximum', () => {
221
- expect(
222
- MultisigConfig.validate({
223
- threshold: MultisigConfig.maxThreshold + 1,
224
- owners: [{ owner: owner1, weight: MultisigConfig.maxThreshold }],
225
- }),
226
- ).toBe(false)
227
- })
228
-
229
- test('fractional threshold', () => {
230
- expect(
231
- MultisigConfig.validate({
232
- threshold: 1.5,
233
- owners: [{ owner: owner1, weight: 2 }],
234
- }),
235
- ).toBe(false)
236
- })
237
-
238
- test('threshold exceeds total weight', () => {
239
- expect(
240
- MultisigConfig.validate({
241
- threshold: 2,
242
- owners: singleOwnerConfig.owners,
243
- }),
244
- ).toBe(false)
245
- })
246
-
247
- test('threshold cannot require more than eight owners', () => {
248
- const owners = Array.from({ length: 9 }, (_, i) => ({
249
- owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
250
- weight: 1,
251
- }))
252
- expect(MultisigConfig.validate({ threshold: 9, owners })).toBe(false)
253
- })
254
-
255
- test('threshold can be reached by eight of more than eight owners', () => {
256
- const owners = Array.from({ length: 9 }, (_, i) => ({
257
- owner: `0x${(i + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
258
- weight: i === 0 ? 2 : 1,
259
- }))
260
- expect(MultisigConfig.validate({ threshold: 9, owners })).toBe(true)
194
+ MultisigConfig.fromTuple(MultisigConfig.toTuple(current)),
195
+ ).toStrictEqual(current)
261
196
  })
197
+ })
262
198
 
263
- test('total weight exceeds u8 max', () => {
199
+ describe('assert/validate', () => {
200
+ test('example: matches the frozen 48-owner boundary vector', () => {
201
+ const boundary = MultisigConfig.from({
202
+ owners: Array.from({ length: 48 }, (_, index) => ({
203
+ owner: `0x${(index + 1).toString(16).padStart(40, '0')}` as const,
204
+ weight: 1,
205
+ })),
206
+ threshold: 8,
207
+ })
208
+ const rlp = Rlp.fromHex(MultisigConfig.toTuple(boundary))
209
+ expect({
210
+ account: MultisigConfig.getAddress(boundary),
211
+ commitment: MultisigConfig.getCommitment(boundary),
212
+ rlpHash: Hash.keccak256(rlp),
213
+ rlpLength: Hex.size(rlp),
214
+ valid: MultisigConfig.validate(boundary),
215
+ }).toMatchInlineSnapshot(`
216
+ {
217
+ "account": "0x6c67c57e0eed05341137dbf88e4e7a90dc46ef50",
218
+ "commitment": "0x0dc47a7ab45ffa21a01bfd115427e26617b5a57d7ccbea57db2fd4537ba96f56",
219
+ "rlpHash": "0xbaf0d030add91caaa10815d2e99c942f1e39b0d199216973781adb4fc1af6955",
220
+ "rlpLength": 1145,
221
+ "valid": true,
222
+ }
223
+ `)
224
+ })
225
+
226
+ test('behavior: accepts the uint8 weight boundary', () => {
264
227
  expect(
265
228
  MultisigConfig.validate({
266
- threshold: MultisigConfig.maxThreshold,
267
229
  owners: [
268
- { owner: owner1, weight: 128 },
269
- { owner: owner2, weight: 128 },
230
+ { owner: owner_1, weight: 128 },
231
+ { owner: owner_2, weight: 127 },
270
232
  ],
233
+ threshold: 255,
271
234
  }),
272
- ).toBe(false)
273
- })
274
-
275
- test('zero owner weight', () => {
276
- expect(
277
- MultisigConfig.validate({
278
- threshold: 1,
279
- owners: [{ owner: owner1, weight: 0 }],
280
- }),
281
- ).toBe(false)
282
- })
283
-
284
- test('fractional owner weight', () => {
285
- expect(
286
- MultisigConfig.validate({
287
- threshold: 1,
288
- owners: [{ owner: owner1, weight: 1.5 }],
289
- }),
290
- ).toBe(false)
235
+ ).toBe(true)
291
236
  })
292
237
 
293
- test('zero owner address', () => {
294
- expect(
295
- MultisigConfig.validate({
238
+ test.each([
239
+ { config: { owners: [], threshold: 1 }, name: 'empty owners' },
240
+ {
241
+ config: {
242
+ owners: Array.from({ length: 49 }, (_, index) => ({
243
+ owner: `0x${(index + 1).toString(16).padStart(40, '0')}` as const,
244
+ weight: 1,
245
+ })),
296
246
  threshold: 1,
247
+ },
248
+ name: '49 owners',
249
+ },
250
+ {
251
+ config: { owners: config.owners, threshold: 0 },
252
+ name: 'zero threshold',
253
+ },
254
+ {
255
+ config: {
297
256
  owners: [
298
257
  {
299
- owner: '0x0000000000000000000000000000000000000000',
258
+ owner: '0x0000000000000000000000000000000000000000' as const,
300
259
  weight: 1,
301
260
  },
302
261
  ],
303
- }),
304
- ).toBe(false)
305
- })
306
-
307
- test('unsorted owners', () => {
308
- expect(
309
- MultisigConfig.validate({
310
262
  threshold: 1,
263
+ },
264
+ name: 'zero owner',
265
+ },
266
+ {
267
+ config: {
268
+ owners: [{ owner: owner_1, weight: 0 }],
269
+ threshold: 1,
270
+ },
271
+ name: 'zero weight',
272
+ },
273
+ {
274
+ config: {
311
275
  owners: [
312
- { owner: owner2, weight: 1 },
313
- { owner: owner1, weight: 1 },
276
+ { owner: owner_1, weight: 1 },
277
+ { owner: owner_1, weight: 1 },
314
278
  ],
315
- }),
316
- ).toBe(false)
317
- })
318
-
319
- test('duplicate owners', () => {
320
- expect(
321
- MultisigConfig.validate({
322
279
  threshold: 1,
280
+ },
281
+ name: 'duplicate owner',
282
+ },
283
+ {
284
+ config: {
323
285
  owners: [
324
- { owner: owner1, weight: 1 },
325
- { owner: owner1, weight: 1 },
286
+ { owner: owner_2, weight: 1 },
287
+ { owner: owner_1, weight: 1 },
326
288
  ],
327
- }),
328
- ).toBe(false)
289
+ threshold: 1,
290
+ },
291
+ name: 'unsorted owners',
292
+ },
293
+ {
294
+ config: {
295
+ owners: [
296
+ { owner: owner_1, weight: 128 },
297
+ { owner: owner_2, weight: 128 },
298
+ ],
299
+ threshold: 255,
300
+ },
301
+ name: 'weight overflow',
302
+ },
303
+ {
304
+ config: {
305
+ owners: Array.from({ length: 9 }, (_, index) => ({
306
+ owner:
307
+ `0x${(index + 1).toString(16).padStart(40, '0')}` as `0x${string}`,
308
+ weight: 1,
309
+ })),
310
+ threshold: 9,
311
+ },
312
+ name: 'unreachable threshold',
313
+ },
314
+ {
315
+ config: { ...config, salt: '0x42' as const },
316
+ name: 'short salt',
317
+ },
318
+ {
319
+ config: { ...config, version: -1n },
320
+ name: 'negative version',
321
+ },
322
+ {
323
+ config: { ...config, version: -1 },
324
+ name: 'negative numeric version',
325
+ },
326
+ {
327
+ config: { ...config, version: MultisigConfig.maxVersion + 1n },
328
+ name: 'version overflow',
329
+ },
330
+ {
331
+ config: { ...config, version: 1.5 },
332
+ name: 'fractional numeric version',
333
+ },
334
+ {
335
+ config: { ...config, version: Number.MAX_SAFE_INTEGER + 1 },
336
+ name: 'unsafe numeric version',
337
+ },
338
+ ])('error: rejects $name', ({ config }) => {
339
+ expect(MultisigConfig.validate(config as MultisigConfig.Input)).toBe(false)
329
340
  })
341
+ })
330
342
 
331
- test('invalid salt size', () => {
332
- expect(
333
- MultisigConfig.validate({
334
- ...singleOwnerConfig,
335
- salt: '0x42',
336
- }),
337
- ).toBe(false)
338
- })
343
+ test('exports', () => {
344
+ expect(Object.keys(MultisigConfig)).toMatchInlineSnapshot(`
345
+ [
346
+ "maxNestingDepth",
347
+ "maxOwnerSignatureBytes",
348
+ "maxOwners",
349
+ "maxSignatures",
350
+ "maxThreshold",
351
+ "maxVersion",
352
+ "signatureTypeByte",
353
+ "zeroSalt",
354
+ "assert",
355
+ "from",
356
+ "fromRpc",
357
+ "fromTuple",
358
+ "getAddress",
359
+ "getCommitment",
360
+ "getSignPayload",
361
+ "toRpc",
362
+ "toTuple",
363
+ "validate",
364
+ "InvalidConfigError",
365
+ ]
366
+ `)
339
367
  })