ox 1.7.1 → 1.7.2

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