ox 1.7.2 → 1.7.4
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 +27 -0
- package/dist/tempo/KeyAuthorization.d.ts +1 -1
- package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/tempo/MultisigOperation.d.ts +42 -26
- package/dist/tempo/MultisigOperation.d.ts.map +1 -1
- package/dist/tempo/MultisigOperation.js +78 -48
- package/dist/tempo/MultisigOperation.js.map +1 -1
- package/dist/tempo/MultisigSimulation.d.ts +132 -0
- package/dist/tempo/MultisigSimulation.d.ts.map +1 -0
- package/dist/tempo/MultisigSimulation.js +140 -0
- package/dist/tempo/MultisigSimulation.js.map +1 -0
- package/dist/tempo/RpcSchemaTempo.d.ts +20 -10
- package/dist/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/dist/tempo/SignatureEnvelope.d.ts +9 -20
- package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/tempo/SignatureEnvelope.js +12 -18
- package/dist/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/tempo/TransactionRequest.d.ts +6 -6
- package/dist/tempo/TransactionRequest.d.ts.map +1 -1
- package/dist/tempo/TransactionRequest.js +7 -7
- package/dist/tempo/TransactionRequest.js.map +1 -1
- package/dist/tempo/index.d.ts +4 -4
- package/dist/tempo/index.d.ts.map +1 -1
- package/dist/tempo/index.js +4 -4
- package/dist/tempo/index.js.map +1 -1
- package/dist/zod/tempo/AuthorizationTempo.d.ts +10 -130
- package/dist/zod/tempo/AuthorizationTempo.d.ts.map +1 -1
- package/dist/zod/tempo/KeyAuthorization.d.ts +5 -65
- package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/zod/tempo/MultisigOperation.d.ts +0 -10
- package/dist/zod/tempo/MultisigOperation.d.ts.map +1 -1
- package/dist/zod/tempo/MultisigOperation.js +0 -3
- package/dist/zod/tempo/MultisigOperation.js.map +1 -1
- package/dist/zod/tempo/{MultisigWitness.d.ts → MultisigSimulation.d.ts} +17 -49
- package/dist/zod/tempo/MultisigSimulation.d.ts.map +1 -0
- package/dist/zod/tempo/{MultisigWitness.js → MultisigSimulation.js} +26 -24
- package/dist/zod/tempo/MultisigSimulation.js.map +1 -0
- package/dist/zod/tempo/RpcSchemaTempo.d.ts +83 -204
- package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/dist/zod/tempo/RpcSchemaTempo.js +19 -10
- package/dist/zod/tempo/RpcSchemaTempo.js.map +1 -1
- package/dist/zod/tempo/SignatureEnvelope.d.ts +3 -39
- package/dist/zod/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/dist/zod/tempo/SignatureEnvelope.js +4 -14
- package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
- package/dist/zod/tempo/Transaction.d.ts +18 -234
- package/dist/zod/tempo/Transaction.d.ts.map +1 -1
- package/dist/zod/tempo/TransactionRequest.d.ts +36 -192
- package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
- package/dist/zod/tempo/TransactionRequest.js +10 -10
- package/dist/zod/tempo/TransactionRequest.js.map +1 -1
- package/dist/zod/tempo/z.d.ts +1 -1
- package/dist/zod/tempo/z.d.ts.map +1 -1
- package/dist/zod/tempo/z.js +1 -1
- package/dist/zod/tempo/z.js.map +1 -1
- package/package.json +9 -9
- package/src/tempo/AuthorizationTempo.test.ts +4 -0
- package/src/tempo/KeyAuthorization.test-d.ts +1 -12
- package/src/tempo/KeyAuthorization.test.ts +44 -3
- package/src/tempo/KeyAuthorization.ts +3 -3
- package/src/tempo/MultisigOperation.test-d.ts +8 -6
- package/src/tempo/MultisigOperation.test.ts +236 -169
- package/src/tempo/MultisigOperation.ts +122 -83
- package/src/tempo/MultisigSimulation.test-d.ts +33 -0
- package/src/tempo/MultisigSimulation.test.ts +283 -0
- package/src/tempo/MultisigSimulation.ts +266 -0
- package/src/tempo/RpcSchemaTempo.test-d.ts +45 -10
- package/src/tempo/RpcSchemaTempo.ts +15 -6
- package/src/tempo/SignatureEnvelope.test-d.ts +3 -59
- package/src/tempo/SignatureEnvelope.test.ts +61 -51
- package/src/tempo/SignatureEnvelope.ts +27 -44
- package/src/tempo/Transaction.test.ts +61 -0
- package/src/tempo/TransactionRequest.test-d.ts +7 -7
- package/src/tempo/TransactionRequest.test.ts +129 -208
- package/src/tempo/TransactionRequest.ts +16 -12
- package/src/tempo/index.ts +4 -4
- package/src/tempo/multisig.e2e.test.ts +51 -1
- package/src/version.ts +1 -1
- package/src/zod/tempo/MultisigOperation.ts +0 -3
- package/src/zod/tempo/{MultisigWitness.ts → MultisigSimulation.ts} +39 -42
- package/src/zod/tempo/RpcSchemaTempo.ts +20 -10
- package/src/zod/tempo/SignatureEnvelope.ts +11 -35
- package/src/zod/tempo/TransactionRequest.ts +14 -14
- package/src/zod/tempo/_test/KeyAuthorization.test.ts +2 -15
- package/src/zod/tempo/_test/MultisigOperation.test.ts +0 -2
- package/src/zod/tempo/_test/MultisigSimulation.test-d.ts +13 -0
- package/src/zod/tempo/_test/MultisigSimulation.test.ts +78 -0
- package/src/zod/tempo/_test/RpcSchemaTempo.test-d.ts +19 -2
- package/src/zod/tempo/_test/RpcSchemaTempo.test.ts +46 -4
- package/src/zod/tempo/_test/SignatureEnvelope.test.ts +58 -112
- package/src/zod/tempo/_test/TransactionRequest.test.ts +19 -160
- package/src/zod/tempo/z.ts +1 -1
- package/dist/tempo/MultisigWitness.d.ts +0 -106
- package/dist/tempo/MultisigWitness.d.ts.map +0 -1
- package/dist/tempo/MultisigWitness.js +0 -118
- package/dist/tempo/MultisigWitness.js.map +0 -1
- package/dist/zod/tempo/MultisigWitness.d.ts.map +0 -1
- package/dist/zod/tempo/MultisigWitness.js.map +0 -1
- package/src/tempo/MultisigWitness.test-d.ts +0 -42
- package/src/tempo/MultisigWitness.test.ts +0 -288
- package/src/tempo/MultisigWitness.ts +0 -210
- package/src/zod/tempo/_test/MultisigWitness.test-d.ts +0 -13
- package/src/zod/tempo/_test/MultisigWitness.test.ts +0 -149
|
@@ -15,14 +15,10 @@ export type Base<quantity = bigint> = {
|
|
|
15
15
|
approvals: readonly Hex.Hex[]
|
|
16
16
|
/** Root configuration used to verify approvals. */
|
|
17
17
|
config: MultisigConfig.Config<quantity>
|
|
18
|
-
/** Root configuration version. */
|
|
19
|
-
configVersion: quantity
|
|
20
18
|
/** Unix creation time in milliseconds. */
|
|
21
19
|
createdAt: number
|
|
22
20
|
/** Deterministic multisig operation hash. */
|
|
23
21
|
hash: Hex.Hex
|
|
24
|
-
/** Whether the operation initializes the root multisig account. */
|
|
25
|
-
init: boolean
|
|
26
22
|
/** Number of approvals selected for quorum evaluation. */
|
|
27
23
|
signatureCount: number
|
|
28
24
|
/** Required root owner weight. */
|
|
@@ -73,9 +69,6 @@ export type KeyAuthorizationRpc = KeyAuthorizationOperation<Hex.Hex>
|
|
|
73
69
|
/** JSON-RPC multisig operation. */
|
|
74
70
|
export type Rpc = Operation<Hex.Hex>
|
|
75
71
|
|
|
76
|
-
/** Maximum supported multisig configuration version. */
|
|
77
|
-
const maxConfigVersion = 2n ** 64n - 1n
|
|
78
|
-
|
|
79
72
|
/**
|
|
80
73
|
* Derives the deterministic hash for a multisig operation.
|
|
81
74
|
*
|
|
@@ -86,7 +79,7 @@ const maxConfigVersion = 2n ** 64n - 1n
|
|
|
86
79
|
*
|
|
87
80
|
* const hash = MultisigOperation.getHash({
|
|
88
81
|
* account,
|
|
89
|
-
*
|
|
82
|
+
* config,
|
|
90
83
|
* transaction,
|
|
91
84
|
* type: 'transaction'
|
|
92
85
|
* })
|
|
@@ -96,7 +89,7 @@ const maxConfigVersion = 2n ** 64n - 1n
|
|
|
96
89
|
* @returns The operation hash signed by each owner.
|
|
97
90
|
*/
|
|
98
91
|
export function getHash(options: getHash.Options): Hex.Hex {
|
|
99
|
-
const { account,
|
|
92
|
+
const { account, config } = options
|
|
100
93
|
const payload =
|
|
101
94
|
options.type === 'transaction'
|
|
102
95
|
? TxEnvelopeTempo.getSignPayload(
|
|
@@ -107,7 +100,7 @@ export function getHash(options: getHash.Options): Hex.Hex {
|
|
|
107
100
|
)
|
|
108
101
|
return MultisigConfig.getSignPayload({
|
|
109
102
|
account,
|
|
110
|
-
config
|
|
103
|
+
config,
|
|
111
104
|
payload,
|
|
112
105
|
})
|
|
113
106
|
}
|
|
@@ -117,8 +110,8 @@ export declare namespace getHash {
|
|
|
117
110
|
export type Options = {
|
|
118
111
|
/** Root multisig account. */
|
|
119
112
|
account: Address.Address
|
|
120
|
-
/**
|
|
121
|
-
|
|
113
|
+
/** Complete root multisig configuration witness. */
|
|
114
|
+
config: MultisigConfig.Config
|
|
122
115
|
} & (
|
|
123
116
|
| {
|
|
124
117
|
/** Canonical serialized key authorization. */
|
|
@@ -160,8 +153,7 @@ export declare namespace getHash {
|
|
|
160
153
|
* account,
|
|
161
154
|
* approvals,
|
|
162
155
|
* config,
|
|
163
|
-
* hash
|
|
164
|
-
* resolveConfig
|
|
156
|
+
* hash
|
|
165
157
|
* })
|
|
166
158
|
* ```
|
|
167
159
|
*
|
|
@@ -171,18 +163,25 @@ export declare namespace getHash {
|
|
|
171
163
|
export async function selectApprovals(
|
|
172
164
|
options: selectApprovals.Options,
|
|
173
165
|
): Promise<selectApprovals.ReturnValue> {
|
|
174
|
-
const { account, approvals, hash
|
|
166
|
+
const { account, approvals, hash } = options
|
|
175
167
|
if (!Address.validate(account) || Hex.toBigInt(account) === 0n)
|
|
176
168
|
throw new InvalidApprovalError({ reason: 'account is invalid' })
|
|
177
169
|
if (!Hash.validate(hash))
|
|
178
170
|
throw new InvalidApprovalError({ reason: 'hash is invalid' })
|
|
171
|
+
const config = MultisigConfig.from(options.config)
|
|
172
|
+
if (
|
|
173
|
+
config.version === 0n &&
|
|
174
|
+
!Address.isEqual(MultisigConfig.getAddress(config), account)
|
|
175
|
+
)
|
|
176
|
+
throw new InvalidApprovalError({
|
|
177
|
+
reason: 'initial config does not derive the root multisig account',
|
|
178
|
+
})
|
|
179
179
|
return selectApprovals_internal(
|
|
180
180
|
{
|
|
181
181
|
account,
|
|
182
182
|
approvals,
|
|
183
|
-
config
|
|
183
|
+
config,
|
|
184
184
|
hash,
|
|
185
|
-
resolveConfig,
|
|
186
185
|
},
|
|
187
186
|
[account.toLowerCase()],
|
|
188
187
|
)
|
|
@@ -199,27 +198,6 @@ export declare namespace selectApprovals {
|
|
|
199
198
|
config: MultisigConfig.Config
|
|
200
199
|
/** Deterministic operation hash approved by root owners. */
|
|
201
200
|
hash: Hex.Hex
|
|
202
|
-
/** Resolves the current configuration of a nested multisig owner. */
|
|
203
|
-
resolveConfig?: ResolveConfig | undefined
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/** Resolves an initialized nested multisig configuration. */
|
|
207
|
-
export type ResolveConfig = (
|
|
208
|
-
options: ResolveConfigOptions,
|
|
209
|
-
) => ResolvedConfig | Promise<ResolvedConfig>
|
|
210
|
-
|
|
211
|
-
/** Nested multisig configuration lookup parameters. */
|
|
212
|
-
export type ResolveConfigOptions = {
|
|
213
|
-
/** Nested multisig account. */
|
|
214
|
-
account: Address.Address
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/** Resolved nested multisig configuration. */
|
|
218
|
-
export type ResolvedConfig = {
|
|
219
|
-
/** Current nested multisig configuration. */
|
|
220
|
-
config: MultisigConfig.Config
|
|
221
|
-
/** Current nested multisig configuration version. */
|
|
222
|
-
version: bigint
|
|
223
201
|
}
|
|
224
202
|
|
|
225
203
|
/** Result of validating and selecting approvals. */
|
|
@@ -248,6 +226,90 @@ export declare namespace selectApprovals {
|
|
|
248
226
|
| Errors.GlobalErrorType
|
|
249
227
|
}
|
|
250
228
|
|
|
229
|
+
/**
|
|
230
|
+
* Serializes a key authorization with selected multisig owner approvals.
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```ts twoslash
|
|
234
|
+
* // @noErrors
|
|
235
|
+
* import { MultisigOperation } from 'ox/tempo'
|
|
236
|
+
*
|
|
237
|
+
* const authorization =
|
|
238
|
+
* MultisigOperation.serializeKeyAuthorization(
|
|
239
|
+
* keyAuthorization,
|
|
240
|
+
* {
|
|
241
|
+
* account,
|
|
242
|
+
* approvals: selection.selectedApprovals,
|
|
243
|
+
* config
|
|
244
|
+
* }
|
|
245
|
+
* )
|
|
246
|
+
* ```
|
|
247
|
+
*
|
|
248
|
+
* @param keyAuthorization - Canonical serialized unsigned key authorization.
|
|
249
|
+
* @param options - Multisig account, config, and selected approvals.
|
|
250
|
+
* @returns The signed serialized key authorization.
|
|
251
|
+
*/
|
|
252
|
+
export function serializeKeyAuthorization(
|
|
253
|
+
keyAuthorization: Hex.Hex,
|
|
254
|
+
options: serializeKeyAuthorization.Options,
|
|
255
|
+
): Hex.Hex {
|
|
256
|
+
const authorization = KeyAuthorization_.deserialize(keyAuthorization)
|
|
257
|
+
if (authorization.signature)
|
|
258
|
+
throw new InvalidOperationError({
|
|
259
|
+
reason: 'keyAuthorization must not contain a signature',
|
|
260
|
+
})
|
|
261
|
+
if (
|
|
262
|
+
KeyAuthorization_.serialize(authorization).toLowerCase() !==
|
|
263
|
+
keyAuthorization.toLowerCase()
|
|
264
|
+
)
|
|
265
|
+
throw new InvalidOperationError({
|
|
266
|
+
reason: 'keyAuthorization is not canonically serialized',
|
|
267
|
+
})
|
|
268
|
+
const config = MultisigConfig.from(options.config)
|
|
269
|
+
const signatures = SignatureEnvelope.sortMultisigApprovals({
|
|
270
|
+
account: options.account,
|
|
271
|
+
config,
|
|
272
|
+
payload: KeyAuthorization_.getSignPayload(authorization),
|
|
273
|
+
signatures: options.approvals.map((approval) =>
|
|
274
|
+
SignatureEnvelope.deserialize(approval),
|
|
275
|
+
),
|
|
276
|
+
})
|
|
277
|
+
return KeyAuthorization_.serialize(
|
|
278
|
+
KeyAuthorization_.from(authorization, {
|
|
279
|
+
signature: SignatureEnvelope.from({
|
|
280
|
+
account: options.account,
|
|
281
|
+
config,
|
|
282
|
+
signatures,
|
|
283
|
+
}),
|
|
284
|
+
}),
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export declare namespace serializeKeyAuthorization {
|
|
289
|
+
/** Options for `serializeKeyAuthorization`. */
|
|
290
|
+
export type Options = {
|
|
291
|
+
/** Root multisig account. */
|
|
292
|
+
account: Address.Address
|
|
293
|
+
/** Selected serialized owner approvals. */
|
|
294
|
+
approvals: readonly SignatureEnvelope.Serialized[]
|
|
295
|
+
/** Complete applicable root multisig config. */
|
|
296
|
+
config: MultisigConfig.Config
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Error type for `serializeKeyAuthorization`. */
|
|
300
|
+
export type ErrorType =
|
|
301
|
+
| InvalidOperationError
|
|
302
|
+
| KeyAuthorization_.deserialize.ErrorType
|
|
303
|
+
| KeyAuthorization_.from.ErrorType
|
|
304
|
+
| KeyAuthorization_.getSignPayload.ErrorType
|
|
305
|
+
| KeyAuthorization_.serialize.ErrorType
|
|
306
|
+
| MultisigConfig.assert.ErrorType
|
|
307
|
+
| SignatureEnvelope.assert.ErrorType
|
|
308
|
+
| SignatureEnvelope.InvalidSerializedError
|
|
309
|
+
| SignatureEnvelope.sortMultisigApprovals.ErrorType
|
|
310
|
+
| Errors.GlobalErrorType
|
|
311
|
+
}
|
|
312
|
+
|
|
251
313
|
/**
|
|
252
314
|
* Serializes a multisig transaction operation with selected owner approvals.
|
|
253
315
|
*
|
|
@@ -340,10 +402,6 @@ export function from<const operation extends Operation>(
|
|
|
340
402
|
): from.ReturnValue<operation> {
|
|
341
403
|
try {
|
|
342
404
|
const config = MultisigConfig.from(operation.config)
|
|
343
|
-
if (config.version !== operation.configVersion)
|
|
344
|
-
throw new InvalidOperationError({
|
|
345
|
-
reason: 'config.version must equal configVersion',
|
|
346
|
-
})
|
|
347
405
|
if (
|
|
348
406
|
typeof config.threshold !== 'number' ||
|
|
349
407
|
config.owners.some((owner) => typeof owner.weight !== 'number')
|
|
@@ -393,22 +451,19 @@ export function fromRpc<const operation extends Rpc>(
|
|
|
393
451
|
operation: operation,
|
|
394
452
|
): fromRpc.ReturnValue<operation> {
|
|
395
453
|
try {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
!Hex.validate(operation.configVersion)
|
|
399
|
-
)
|
|
454
|
+
const version = operation.config?.version
|
|
455
|
+
if (typeof version !== 'string' || !Hex.validate(version))
|
|
400
456
|
throw new InvalidOperationError({
|
|
401
|
-
reason: '
|
|
457
|
+
reason: 'config.version must be a hexadecimal quantity',
|
|
402
458
|
})
|
|
403
|
-
const
|
|
404
|
-
if (Hex.fromNumber(
|
|
459
|
+
const version_ = Hex.toBigInt(version)
|
|
460
|
+
if (Hex.fromNumber(version_) !== version)
|
|
405
461
|
throw new InvalidOperationError({
|
|
406
|
-
reason: '
|
|
462
|
+
reason: 'config.version must use canonical quantity encoding',
|
|
407
463
|
})
|
|
408
464
|
return from({
|
|
409
465
|
...operation,
|
|
410
466
|
config: MultisigConfig.fromRpc(operation.config),
|
|
411
|
-
configVersion,
|
|
412
467
|
} as Operation) as never
|
|
413
468
|
} catch (cause) {
|
|
414
469
|
if (cause instanceof InvalidOperationError) throw cause
|
|
@@ -448,7 +503,6 @@ export function toRpc<const operation extends Operation>(
|
|
|
448
503
|
return {
|
|
449
504
|
...value,
|
|
450
505
|
config: MultisigConfig.toRpc(value.config),
|
|
451
|
-
configVersion: Hex.fromNumber(value.configVersion),
|
|
452
506
|
} as never
|
|
453
507
|
}
|
|
454
508
|
|
|
@@ -460,7 +514,7 @@ export declare namespace toRpc {
|
|
|
460
514
|
: KeyAuthorizationRpc
|
|
461
515
|
|
|
462
516
|
/** Error type for `toRpc`. */
|
|
463
|
-
export type ErrorType = from.ErrorType |
|
|
517
|
+
export type ErrorType = from.ErrorType | MultisigConfig.toRpc.ErrorType
|
|
464
518
|
}
|
|
465
519
|
|
|
466
520
|
/**
|
|
@@ -526,15 +580,17 @@ async function selectApprovals_internal(
|
|
|
526
580
|
throw new InvalidApprovalError({
|
|
527
581
|
reason: `nested multisig owner ${group.address} is invalid`,
|
|
528
582
|
})
|
|
529
|
-
|
|
583
|
+
const config = MultisigConfig.from(nested[0]!.config)
|
|
584
|
+
if (nested.some((signature) => !sameConfig(signature.config, config)))
|
|
530
585
|
throw new InvalidApprovalError({
|
|
531
|
-
reason: `nested multisig owner ${group.address}
|
|
586
|
+
reason: `nested multisig owner ${group.address} has conflicting config witnesses`,
|
|
532
587
|
})
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
588
|
+
if (
|
|
589
|
+
config.version === 0n &&
|
|
590
|
+
!Address.isEqual(MultisigConfig.getAddress(config), group.address)
|
|
591
|
+
)
|
|
536
592
|
throw new InvalidApprovalError({
|
|
537
|
-
reason: `
|
|
593
|
+
reason: `initial config does not derive nested multisig owner ${group.address}`,
|
|
538
594
|
})
|
|
539
595
|
const selected = await selectApprovals_internal(
|
|
540
596
|
{
|
|
@@ -550,7 +606,6 @@ async function selectApprovals_internal(
|
|
|
550
606
|
config,
|
|
551
607
|
payload: options.hash,
|
|
552
608
|
}),
|
|
553
|
-
resolveConfig: options.resolveConfig,
|
|
554
609
|
},
|
|
555
610
|
[...path, group.address.toLowerCase()],
|
|
556
611
|
)
|
|
@@ -692,16 +747,6 @@ function assertBase(operation: Operation, config: MultisigConfig.Config): void {
|
|
|
692
747
|
throw new InvalidOperationError({ reason: 'account cannot be zero' })
|
|
693
748
|
if (!Hash.validate(operation.hash))
|
|
694
749
|
throw new InvalidOperationError({ reason: 'hash is invalid' })
|
|
695
|
-
if (typeof operation.init !== 'boolean')
|
|
696
|
-
throw new InvalidOperationError({ reason: 'init must be a boolean' })
|
|
697
|
-
if (
|
|
698
|
-
typeof operation.configVersion !== 'bigint' ||
|
|
699
|
-
operation.configVersion < 0n ||
|
|
700
|
-
operation.configVersion > maxConfigVersion
|
|
701
|
-
)
|
|
702
|
-
throw new InvalidOperationError({
|
|
703
|
-
reason: 'configVersion must be an unsigned 64-bit integer',
|
|
704
|
-
})
|
|
705
750
|
assertInteger(operation.createdAt, 'createdAt')
|
|
706
751
|
assertInteger(operation.updatedAt, 'updatedAt')
|
|
707
752
|
if (operation.updatedAt < operation.createdAt)
|
|
@@ -781,19 +826,13 @@ function assertBase(operation: Operation, config: MultisigConfig.Config): void {
|
|
|
781
826
|
reason:
|
|
782
827
|
'weight is not reachable by signatureCount retained owner approvals',
|
|
783
828
|
})
|
|
784
|
-
if (
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
operation.account.toLowerCase()
|
|
792
|
-
)
|
|
793
|
-
throw new InvalidOperationError({
|
|
794
|
-
reason: 'bootstrap config does not derive the operation account',
|
|
795
|
-
})
|
|
796
|
-
}
|
|
829
|
+
if (
|
|
830
|
+
config.version === 0n &&
|
|
831
|
+
!Address.isEqual(MultisigConfig.getAddress(config), operation.account)
|
|
832
|
+
)
|
|
833
|
+
throw new InvalidOperationError({
|
|
834
|
+
reason: 'initial config does not derive the operation account',
|
|
835
|
+
})
|
|
797
836
|
}
|
|
798
837
|
|
|
799
838
|
/**
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { expectTypeOf, test } from 'vp/test'
|
|
2
|
+
import * as MultisigSimulation from './MultisigSimulation.js'
|
|
3
|
+
|
|
4
|
+
const rpc = {
|
|
5
|
+
account: '0x2222222222222222222222222222222222222222',
|
|
6
|
+
approvals: [
|
|
7
|
+
{
|
|
8
|
+
owner: '0x1111111111111111111111111111111111111111',
|
|
9
|
+
type: 'primitive',
|
|
10
|
+
},
|
|
11
|
+
],
|
|
12
|
+
config:
|
|
13
|
+
'0xf83ba000000000000000000000000000000000000000000000000000000000000000008001d7d694111111111111111111111111111111111111111101',
|
|
14
|
+
} as const satisfies MultisigSimulation.Rpc
|
|
15
|
+
|
|
16
|
+
test('fromRpc returns a domain spec', () => {
|
|
17
|
+
const spec = MultisigSimulation.fromRpc(rpc)
|
|
18
|
+
|
|
19
|
+
expectTypeOf(spec).toEqualTypeOf<MultisigSimulation.Spec>()
|
|
20
|
+
expectTypeOf(spec.config.version).toEqualTypeOf<bigint>()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('RPC specs use encoded configurations', () => {
|
|
24
|
+
expectTypeOf<
|
|
25
|
+
MultisigSimulation.Rpc['config']
|
|
26
|
+
>().toEqualTypeOf<`0x${string}`>()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('toRpc returns an RPC spec', () => {
|
|
30
|
+
expectTypeOf(
|
|
31
|
+
MultisigSimulation.toRpc(MultisigSimulation.fromRpc(rpc)),
|
|
32
|
+
).toEqualTypeOf<MultisigSimulation.Rpc>()
|
|
33
|
+
})
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { MultisigConfig, MultisigSimulation } from 'ox/tempo'
|
|
2
|
+
import { describe, expect, test } from 'vp/test'
|
|
3
|
+
|
|
4
|
+
const config =
|
|
5
|
+
'0xf852a011111111111111111111111111111111111111111111111111111111111111118002eed694111111111111111111111111111111111111111101d694bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb01' as const
|
|
6
|
+
const nestedConfig =
|
|
7
|
+
'0xf83ba022222222222222222222222222222222222222222222222222222222222222220101d7d694222222222222222222222222222222222222222201' as const
|
|
8
|
+
const rpc = {
|
|
9
|
+
account: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
10
|
+
approvals: [
|
|
11
|
+
{
|
|
12
|
+
keyData: '0x0102030405',
|
|
13
|
+
keyType: 'webAuthn',
|
|
14
|
+
owner: '0x1111111111111111111111111111111111111111',
|
|
15
|
+
type: 'primitive',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
spec: {
|
|
19
|
+
account: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
|
20
|
+
approvals: [
|
|
21
|
+
{
|
|
22
|
+
keyData: '0x010203040506',
|
|
23
|
+
keyType: 'secp256k1',
|
|
24
|
+
owner: '0x2222222222222222222222222222222222222222',
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
config: nestedConfig,
|
|
28
|
+
},
|
|
29
|
+
type: 'multisig',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
config,
|
|
33
|
+
} as const satisfies MultisigSimulation.Rpc
|
|
34
|
+
|
|
35
|
+
describe('fromRpc', () => {
|
|
36
|
+
test('behavior: decodes root and nested configurations', () => {
|
|
37
|
+
expect(MultisigSimulation.fromRpc(rpc)).toMatchInlineSnapshot(`
|
|
38
|
+
{
|
|
39
|
+
"account": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
40
|
+
"approvals": [
|
|
41
|
+
{
|
|
42
|
+
"keyData": "0x0102030405",
|
|
43
|
+
"keyType": "webAuthn",
|
|
44
|
+
"owner": "0x1111111111111111111111111111111111111111",
|
|
45
|
+
"type": "primitive",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"spec": {
|
|
49
|
+
"account": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
50
|
+
"approvals": [
|
|
51
|
+
{
|
|
52
|
+
"keyData": "0x010203040506",
|
|
53
|
+
"keyType": "secp256k1",
|
|
54
|
+
"owner": "0x2222222222222222222222222222222222222222",
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
"config": {
|
|
58
|
+
"owners": [
|
|
59
|
+
{
|
|
60
|
+
"owner": "0x2222222222222222222222222222222222222222",
|
|
61
|
+
"weight": 1,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
"salt": "0x2222222222222222222222222222222222222222222222222222222222222222",
|
|
65
|
+
"threshold": 1,
|
|
66
|
+
"version": 1n,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
"type": "multisig",
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
"config": {
|
|
73
|
+
"owners": [
|
|
74
|
+
{
|
|
75
|
+
"owner": "0x1111111111111111111111111111111111111111",
|
|
76
|
+
"weight": 1,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"owner": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
80
|
+
"weight": 1,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
"salt": "0x1111111111111111111111111111111111111111111111111111111111111111",
|
|
84
|
+
"threshold": 2,
|
|
85
|
+
"version": 0n,
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
`)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('behavior: preserves the maximum uint64 configuration version', () => {
|
|
92
|
+
const config =
|
|
93
|
+
'0xf843a0000000000000000000000000000000000000000000000000000000000000000088ffffffffffffffff01d7d694111111111111111111111111111111111111111101' as const
|
|
94
|
+
|
|
95
|
+
expect(
|
|
96
|
+
MultisigSimulation.fromRpc({ ...rpc, approvals: [], config }).config
|
|
97
|
+
.version,
|
|
98
|
+
).toMatchInlineSnapshot(`18446744073709551615n`)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('error: rejects excess root approvals', () => {
|
|
102
|
+
expect(() =>
|
|
103
|
+
MultisigSimulation.fromRpc({
|
|
104
|
+
...rpc,
|
|
105
|
+
approvals: Array.from({ length: 9 }, () => rpc.approvals[0]),
|
|
106
|
+
}),
|
|
107
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
108
|
+
`[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: approval count exceeds 8.]`,
|
|
109
|
+
)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('error: rejects excess nested approvals', () => {
|
|
113
|
+
expect(() =>
|
|
114
|
+
MultisigSimulation.fromRpc({
|
|
115
|
+
...rpc,
|
|
116
|
+
approvals: [
|
|
117
|
+
{
|
|
118
|
+
...rpc.approvals[1],
|
|
119
|
+
spec: {
|
|
120
|
+
...rpc.approvals[1].spec,
|
|
121
|
+
approvals: Array.from(
|
|
122
|
+
{ length: 9 },
|
|
123
|
+
() => rpc.approvals[1].spec.approvals[0],
|
|
124
|
+
),
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
}),
|
|
129
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
130
|
+
`[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: approval count exceeds 8.]`,
|
|
131
|
+
)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test('error: rejects trailing configuration bytes', () => {
|
|
135
|
+
expect(() =>
|
|
136
|
+
MultisigSimulation.fromRpc({ ...rpc, config: `${config}00` }),
|
|
137
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
138
|
+
`[Rlp.TrailingBytesError: RLP payload encodes a single item, but \`1\` trailing byte remains.]`,
|
|
139
|
+
)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('error: rejects malformed configuration RLP', () => {
|
|
143
|
+
expect(() =>
|
|
144
|
+
MultisigSimulation.fromRpc({ ...rpc, config: '0xf8' }),
|
|
145
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
146
|
+
`[Cursor.PositionOutOfBoundsError: Position \`1\` is out of bounds (\`0 < position < 1\`).]`,
|
|
147
|
+
)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test('error: rejects a non-list configuration', () => {
|
|
151
|
+
expect(() =>
|
|
152
|
+
MultisigSimulation.fromRpc({ ...rpc, config: '0x01' }),
|
|
153
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
154
|
+
`[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: invalid config encoding.]`,
|
|
155
|
+
)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('error: rejects noncanonical configuration integers', () => {
|
|
159
|
+
const noncanonical = config
|
|
160
|
+
.replace('0xf852', '0xf853')
|
|
161
|
+
.replace('118002', '11810002') as `0x${string}`
|
|
162
|
+
|
|
163
|
+
expect(() =>
|
|
164
|
+
MultisigSimulation.fromRpc({ ...rpc, config: noncanonical }),
|
|
165
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
166
|
+
`[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: invalid config encoding.]`,
|
|
167
|
+
)
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
describe('toRpc', () => {
|
|
172
|
+
test('behavior: encodes configurations and shims long key data', () => {
|
|
173
|
+
expect(MultisigSimulation.toRpc(MultisigSimulation.fromRpc(rpc)))
|
|
174
|
+
.toMatchInlineSnapshot(`
|
|
175
|
+
{
|
|
176
|
+
"account": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
177
|
+
"approvals": [
|
|
178
|
+
{
|
|
179
|
+
"keyData": "0x0005",
|
|
180
|
+
"keyType": "webAuthn",
|
|
181
|
+
"owner": "0x1111111111111111111111111111111111111111",
|
|
182
|
+
"type": "primitive",
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"spec": {
|
|
186
|
+
"account": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
187
|
+
"approvals": [
|
|
188
|
+
{
|
|
189
|
+
"keyData": "0x0006",
|
|
190
|
+
"keyType": "secp256k1",
|
|
191
|
+
"owner": "0x2222222222222222222222222222222222222222",
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
"config": "${nestedConfig}",
|
|
195
|
+
},
|
|
196
|
+
"type": "multisig",
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
"config": "${config}",
|
|
200
|
+
}
|
|
201
|
+
`)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
test('behavior: preserves short key data', () => {
|
|
205
|
+
const spec = MultisigSimulation.fromRpc({
|
|
206
|
+
...rpc,
|
|
207
|
+
approvals: [{ ...rpc.approvals[0], keyData: '0x0102' }],
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
expect(MultisigSimulation.toRpc(spec).approvals).toMatchInlineSnapshot(`
|
|
211
|
+
[
|
|
212
|
+
{
|
|
213
|
+
"keyData": "0x0102",
|
|
214
|
+
"keyType": "webAuthn",
|
|
215
|
+
"owner": "0x1111111111111111111111111111111111111111",
|
|
216
|
+
"type": "primitive",
|
|
217
|
+
},
|
|
218
|
+
]
|
|
219
|
+
`)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test('behavior: encodes the maximum uint64 configuration version', () => {
|
|
223
|
+
const spec = MultisigSimulation.fromRpc(rpc)
|
|
224
|
+
|
|
225
|
+
expect(
|
|
226
|
+
MultisigSimulation.toRpc({
|
|
227
|
+
...spec,
|
|
228
|
+
approvals: [],
|
|
229
|
+
config: MultisigConfig.from({
|
|
230
|
+
owners: [
|
|
231
|
+
{
|
|
232
|
+
owner: '0x1111111111111111111111111111111111111111',
|
|
233
|
+
weight: 1,
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
threshold: 1,
|
|
237
|
+
version: MultisigConfig.maxVersion,
|
|
238
|
+
}),
|
|
239
|
+
}).config,
|
|
240
|
+
).toMatchInlineSnapshot(
|
|
241
|
+
`"0xf843a0000000000000000000000000000000000000000000000000000000000000000088ffffffffffffffff01d7d694111111111111111111111111111111111111111101"`,
|
|
242
|
+
)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
test('error: rejects excess root approvals', () => {
|
|
246
|
+
const spec = MultisigSimulation.fromRpc(rpc)
|
|
247
|
+
|
|
248
|
+
expect(() =>
|
|
249
|
+
MultisigSimulation.toRpc({
|
|
250
|
+
...spec,
|
|
251
|
+
approvals: Array.from({ length: 9 }, () => spec.approvals[0]!),
|
|
252
|
+
}),
|
|
253
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
254
|
+
`[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: approval count exceeds 8.]`,
|
|
255
|
+
)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
test('error: rejects excess nested approvals', () => {
|
|
259
|
+
const spec = MultisigSimulation.fromRpc(rpc)
|
|
260
|
+
const nested = spec.approvals[1]!
|
|
261
|
+
if (nested.type !== 'multisig') throw new Error('unreachable')
|
|
262
|
+
|
|
263
|
+
expect(() =>
|
|
264
|
+
MultisigSimulation.toRpc({
|
|
265
|
+
...spec,
|
|
266
|
+
approvals: [
|
|
267
|
+
{
|
|
268
|
+
...nested,
|
|
269
|
+
spec: {
|
|
270
|
+
...nested.spec,
|
|
271
|
+
approvals: Array.from(
|
|
272
|
+
{ length: 9 },
|
|
273
|
+
() => nested.spec.approvals[0]!,
|
|
274
|
+
),
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
}),
|
|
279
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
280
|
+
`[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: approval count exceeds 8.]`,
|
|
281
|
+
)
|
|
282
|
+
})
|
|
283
|
+
})
|