ox 0.14.41 → 0.14.43
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 +25 -0
- package/_cjs/tempo/MultisigOperation.js +27 -0
- package/_cjs/tempo/MultisigOperation.js.map +1 -1
- package/_cjs/tempo/MultisigSimulation.js +115 -0
- package/_cjs/tempo/MultisigSimulation.js.map +1 -0
- package/_cjs/tempo/SignatureEnvelope.js +12 -21
- package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
- package/_cjs/tempo/TransactionRequest.js +7 -7
- package/_cjs/tempo/TransactionRequest.js.map +1 -1
- package/_cjs/tempo/index.js +2 -2
- package/_cjs/tempo/index.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/tempo/MultisigOperation.js +48 -0
- package/_esm/tempo/MultisigOperation.js.map +1 -1
- package/_esm/tempo/MultisigSimulation.js +145 -0
- package/_esm/tempo/MultisigSimulation.js.map +1 -0
- package/_esm/tempo/SignatureEnvelope.js +12 -21
- package/_esm/tempo/SignatureEnvelope.js.map +1 -1
- package/_esm/tempo/TransactionRequest.js +7 -7
- package/_esm/tempo/TransactionRequest.js.map +1 -1
- package/_esm/tempo/index.js +4 -4
- package/_esm/tempo/index.js.map +1 -1
- package/_esm/version.js +1 -1
- package/_types/tempo/KeyAuthorization.d.ts +1 -1
- package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
- package/_types/tempo/MultisigOperation.d.ts +36 -0
- package/_types/tempo/MultisigOperation.d.ts.map +1 -1
- package/_types/tempo/MultisigSimulation.d.ts +132 -0
- package/_types/tempo/MultisigSimulation.d.ts.map +1 -0
- package/_types/tempo/RpcSchemaTempo.d.ts +10 -0
- package/_types/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/_types/tempo/SignatureEnvelope.d.ts +10 -34
- package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/_types/tempo/TransactionRequest.d.ts +9 -9
- package/_types/tempo/TransactionRequest.d.ts.map +1 -1
- package/_types/tempo/index.d.ts +4 -4
- package/_types/tempo/index.d.ts.map +1 -1
- package/_types/version.d.ts +1 -1
- package/package.json +9 -9
- package/tempo/AuthorizationTempo.test.ts +4 -0
- package/tempo/KeyAuthorization.test-d.ts +1 -12
- package/tempo/KeyAuthorization.test.ts +34 -6
- package/tempo/KeyAuthorization.ts +3 -3
- package/tempo/MultisigOperation.test.ts +94 -0
- package/tempo/MultisigOperation.ts +83 -0
- package/tempo/MultisigSimulation/package.json +6 -0
- package/tempo/MultisigSimulation.test-d/package.json +6 -0
- package/tempo/MultisigSimulation.test-d.ts +33 -0
- package/tempo/MultisigSimulation.test.ts +284 -0
- package/tempo/MultisigSimulation.ts +266 -0
- package/tempo/RpcSchemaTempo.test-d.ts +14 -4
- package/tempo/RpcSchemaTempo.ts +9 -0
- package/tempo/SignatureEnvelope.test-d.ts +8 -44
- package/tempo/SignatureEnvelope.test.ts +92 -51
- package/tempo/SignatureEnvelope.ts +27 -61
- package/tempo/Transaction.test.ts +61 -0
- package/tempo/TransactionRequest.test-d.ts +9 -9
- package/tempo/TransactionRequest.test.ts +122 -49
- package/tempo/TransactionRequest.ts +20 -16
- package/tempo/index.ts +4 -4
- package/tempo/multisig.e2e.test.ts +51 -1
- package/version.ts +1 -1
- package/_cjs/tempo/MultisigWitness.js +0 -92
- package/_cjs/tempo/MultisigWitness.js.map +0 -1
- package/_esm/tempo/MultisigWitness.js +0 -123
- package/_esm/tempo/MultisigWitness.js.map +0 -1
- package/_types/tempo/MultisigWitness.d.ts +0 -106
- package/_types/tempo/MultisigWitness.d.ts.map +0 -1
- package/tempo/MultisigWitness/package.json +0 -6
- package/tempo/MultisigWitness.test-d/package.json +0 -6
- package/tempo/MultisigWitness.test-d.ts +0 -42
- package/tempo/MultisigWitness.test.ts +0 -289
- package/tempo/MultisigWitness.ts +0 -210
|
@@ -326,6 +326,7 @@ describe('fromRpc', () => {
|
|
|
326
326
|
type: 'p256',
|
|
327
327
|
},
|
|
328
328
|
})
|
|
329
|
+
if (typeof result.signature === 'string') throw new Error('unreachable')
|
|
329
330
|
expect(result.signature.type).toBe('p256')
|
|
330
331
|
expect(result.signature).toHaveProperty('prehash', true)
|
|
331
332
|
expect(result.signature).toHaveProperty('publicKey')
|
|
@@ -348,6 +349,7 @@ describe('fromRpc', () => {
|
|
|
348
349
|
'0x49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976305000000007b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a22222c226f726967696e223a2268747470733a2f2f6578616d706c652e636f6d222c2263726f73734f726967696e223a66616c73657d',
|
|
349
350
|
},
|
|
350
351
|
})
|
|
352
|
+
if (typeof result.signature === 'string') throw new Error('unreachable')
|
|
351
353
|
expect(result.signature.type).toBe('webAuthn')
|
|
352
354
|
expect(result.signature).toHaveProperty('metadata')
|
|
353
355
|
expect(result.signature).toHaveProperty('publicKey')
|
|
@@ -949,6 +951,7 @@ describe('toRpc', () => {
|
|
|
949
951
|
type: 'p256',
|
|
950
952
|
},
|
|
951
953
|
})
|
|
954
|
+
if (typeof result.signature === 'string') throw new Error('unreachable')
|
|
952
955
|
expect(result.signature.type).toBe('p256')
|
|
953
956
|
})
|
|
954
957
|
|
|
@@ -970,6 +973,7 @@ describe('toRpc', () => {
|
|
|
970
973
|
type: 'webAuthn',
|
|
971
974
|
},
|
|
972
975
|
})
|
|
976
|
+
if (typeof result.signature === 'string') throw new Error('unreachable')
|
|
973
977
|
expect(result.signature.type).toBe('webAuthn')
|
|
974
978
|
})
|
|
975
979
|
})
|
|
@@ -55,18 +55,7 @@ test('accepts multisig signatures', () => {
|
|
|
55
55
|
KeyAuthorization.Signed['signature']
|
|
56
56
|
>()
|
|
57
57
|
|
|
58
|
-
const multisigRpc =
|
|
59
|
-
account: multisig.account,
|
|
60
|
-
config: { ...multisig.config, version: 0 },
|
|
61
|
-
signatures: [
|
|
62
|
-
{
|
|
63
|
-
r: '0x01',
|
|
64
|
-
s: '0x02',
|
|
65
|
-
type: 'secp256k1',
|
|
66
|
-
yParity: '0x0',
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
} as const satisfies SignatureEnvelope.MultisigRpc
|
|
58
|
+
const multisigRpc = '0xf8' as const satisfies SignatureEnvelope.MultisigRpc
|
|
70
59
|
|
|
71
60
|
const rpc: KeyAuthorization.Rpc = {
|
|
72
61
|
chainId: '0x1',
|
|
@@ -69,6 +69,8 @@ const signature_multisig = {
|
|
|
69
69
|
signatures: [SignatureEnvelope.from(signature_secp256k1)],
|
|
70
70
|
type: 'multisig',
|
|
71
71
|
} as const satisfies SignatureEnvelope.Multisig
|
|
72
|
+
const signature_multisig_rpc =
|
|
73
|
+
'0xf89794be95c3f554e9fc85ec51be69a3d807a0d55bcf2cf83ba000000000000000000000000000000000000000000000000000000000000000000101d7d694f39fd6e51aad88f6f4ce6ab8827279cfffb9226601f843b841fa78c5905fb0b9d6066ef531f962a62bc6ef0d5eb59ecb134056d206f75aaed7780926ff2601a935c2c79707d9e1799948c9f19dcdde1e090e903b19a07923d01c' as const
|
|
72
74
|
|
|
73
75
|
const signature_keychain = {
|
|
74
76
|
inner: SignatureEnvelope.from(signature_secp256k1),
|
|
@@ -527,16 +529,42 @@ describe('from', () => {
|
|
|
527
529
|
})
|
|
528
530
|
|
|
529
531
|
describe('fromRpc', () => {
|
|
530
|
-
test('multisig', () => {
|
|
532
|
+
test('behavior: multisig', () => {
|
|
531
533
|
const authorization = KeyAuthorization.fromRpc({
|
|
532
534
|
chainId: '0x1',
|
|
533
535
|
expiry: null,
|
|
534
536
|
keyId: address,
|
|
535
537
|
keyType: 'secp256k1',
|
|
536
|
-
signature:
|
|
538
|
+
signature: signature_multisig_rpc,
|
|
537
539
|
})
|
|
538
540
|
|
|
539
|
-
expect(authorization.signature).
|
|
541
|
+
expect(authorization.signature).toMatchInlineSnapshot(`
|
|
542
|
+
{
|
|
543
|
+
"account": "0xbe95c3f554e9fc85ec51be69a3d807a0d55bcf2c",
|
|
544
|
+
"config": {
|
|
545
|
+
"owners": [
|
|
546
|
+
{
|
|
547
|
+
"owner": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
|
|
548
|
+
"weight": 1,
|
|
549
|
+
},
|
|
550
|
+
],
|
|
551
|
+
"salt": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
552
|
+
"threshold": 1,
|
|
553
|
+
"version": 1n,
|
|
554
|
+
},
|
|
555
|
+
"signatures": [
|
|
556
|
+
{
|
|
557
|
+
"signature": {
|
|
558
|
+
"r": 113291597329930009559670063131885256927775966057121513567941051428123344285399n,
|
|
559
|
+
"s": 54293712598725100598138577281441749550405991478212695085505730636505228583888n,
|
|
560
|
+
"yParity": 1,
|
|
561
|
+
},
|
|
562
|
+
"type": "secp256k1",
|
|
563
|
+
},
|
|
564
|
+
],
|
|
565
|
+
"type": "multisig",
|
|
566
|
+
}
|
|
567
|
+
`)
|
|
540
568
|
})
|
|
541
569
|
|
|
542
570
|
test('rejects a keychain signature', () => {
|
|
@@ -1340,7 +1368,7 @@ describe('serialize', () => {
|
|
|
1340
1368
|
})
|
|
1341
1369
|
|
|
1342
1370
|
describe('toRpc', () => {
|
|
1343
|
-
test('multisig', () => {
|
|
1371
|
+
test('behavior: multisig', () => {
|
|
1344
1372
|
const authorization = KeyAuthorization.toRpc({
|
|
1345
1373
|
address,
|
|
1346
1374
|
chainId: 1n,
|
|
@@ -1348,8 +1376,8 @@ describe('toRpc', () => {
|
|
|
1348
1376
|
type: 'secp256k1',
|
|
1349
1377
|
})
|
|
1350
1378
|
|
|
1351
|
-
expect(authorization.signature).
|
|
1352
|
-
|
|
1379
|
+
expect(authorization.signature).toMatchInlineSnapshot(
|
|
1380
|
+
`"${signature_multisig_rpc}"`,
|
|
1353
1381
|
)
|
|
1354
1382
|
})
|
|
1355
1383
|
|
|
@@ -93,9 +93,9 @@ export type Signature<bigintType = bigint, numberType = number> = OneOf<
|
|
|
93
93
|
>
|
|
94
94
|
|
|
95
95
|
/** RPC-formatted signature that can authorize an access key. */
|
|
96
|
-
export type SignatureRpc =
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
export type SignatureRpc =
|
|
97
|
+
| SignatureEnvelope.MultisigRpc
|
|
98
|
+
| SignatureEnvelope.PrimitiveRpc
|
|
99
99
|
|
|
100
100
|
/** Input type for a Key Authorization. */
|
|
101
101
|
export type Input = KeyAuthorization<
|
|
@@ -677,6 +677,100 @@ describe('selectApprovals', () => {
|
|
|
677
677
|
})
|
|
678
678
|
})
|
|
679
679
|
|
|
680
|
+
describe('serializeKeyAuthorization', () => {
|
|
681
|
+
test('behavior: current and initial authorizations', async () => {
|
|
682
|
+
const results = []
|
|
683
|
+
for (const applicableConfig of [currentConfig, config]) {
|
|
684
|
+
const hash = MultisigOperation.getHash({
|
|
685
|
+
account,
|
|
686
|
+
config: applicableConfig,
|
|
687
|
+
keyAuthorization,
|
|
688
|
+
type: 'keyAuthorization',
|
|
689
|
+
})
|
|
690
|
+
const selection = await MultisigOperation.selectApprovals({
|
|
691
|
+
account,
|
|
692
|
+
approvals: [
|
|
693
|
+
signApproval(owners[1]!, hash),
|
|
694
|
+
signApproval(owners[0]!, hash),
|
|
695
|
+
],
|
|
696
|
+
config: applicableConfig,
|
|
697
|
+
hash,
|
|
698
|
+
})
|
|
699
|
+
const serialized = MultisigOperation.serializeKeyAuthorization(
|
|
700
|
+
keyAuthorization,
|
|
701
|
+
{
|
|
702
|
+
account,
|
|
703
|
+
approvals: selection.selectedApprovals,
|
|
704
|
+
config: applicableConfig,
|
|
705
|
+
},
|
|
706
|
+
)
|
|
707
|
+
const value = KeyAuthorization.deserialize(serialized)
|
|
708
|
+
results.push({
|
|
709
|
+
account:
|
|
710
|
+
value.signature?.type === 'multisig'
|
|
711
|
+
? value.signature.account
|
|
712
|
+
: undefined,
|
|
713
|
+
hash: Hash.keccak256(serialized),
|
|
714
|
+
signatureCount:
|
|
715
|
+
value.signature?.type === 'multisig'
|
|
716
|
+
? value.signature.signatures.length
|
|
717
|
+
: 0,
|
|
718
|
+
version:
|
|
719
|
+
value.signature?.type === 'multisig'
|
|
720
|
+
? value.signature.config.version
|
|
721
|
+
: undefined,
|
|
722
|
+
})
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
expect(results).toMatchInlineSnapshot(`
|
|
726
|
+
[
|
|
727
|
+
{
|
|
728
|
+
"account": "0xf81b7763d3a6876195d780865bd783dbd97dd36e",
|
|
729
|
+
"hash": "0x14b3a60f7c3120e3983b1f6e085ab5b234e71f58f0a4f39d19a5ff64b89a2afe",
|
|
730
|
+
"signatureCount": 2,
|
|
731
|
+
"version": 1n,
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
"account": "0xf81b7763d3a6876195d780865bd783dbd97dd36e",
|
|
735
|
+
"hash": "0xbfdc90810a0634f413e627bdefea88ae8c42ed41d6670a70fd1d7574aedda2a4",
|
|
736
|
+
"signatureCount": 2,
|
|
737
|
+
"version": 0n,
|
|
738
|
+
},
|
|
739
|
+
]
|
|
740
|
+
`)
|
|
741
|
+
})
|
|
742
|
+
|
|
743
|
+
test('error: signed authorization', async () => {
|
|
744
|
+
const selection = await MultisigOperation.selectApprovals({
|
|
745
|
+
account,
|
|
746
|
+
approvals: [
|
|
747
|
+
signApproval(owners[0]!, keyAuthorizationHash),
|
|
748
|
+
signApproval(owners[1]!, keyAuthorizationHash),
|
|
749
|
+
],
|
|
750
|
+
config: currentConfig,
|
|
751
|
+
hash: keyAuthorizationHash,
|
|
752
|
+
})
|
|
753
|
+
const signed = MultisigOperation.serializeKeyAuthorization(
|
|
754
|
+
keyAuthorization,
|
|
755
|
+
{
|
|
756
|
+
account,
|
|
757
|
+
approvals: selection.selectedApprovals,
|
|
758
|
+
config: currentConfig,
|
|
759
|
+
},
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
expect(() =>
|
|
763
|
+
MultisigOperation.serializeKeyAuthorization(signed, {
|
|
764
|
+
account,
|
|
765
|
+
approvals: selection.selectedApprovals,
|
|
766
|
+
config: currentConfig,
|
|
767
|
+
}),
|
|
768
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
769
|
+
`[MultisigOperation.InvalidOperationError: Invalid multisig operation: keyAuthorization must not contain a signature.]`,
|
|
770
|
+
)
|
|
771
|
+
})
|
|
772
|
+
})
|
|
773
|
+
|
|
680
774
|
describe('serializeTransaction', () => {
|
|
681
775
|
test('current and initial transactions', async () => {
|
|
682
776
|
const results = []
|
|
@@ -228,6 +228,89 @@ export declare namespace selectApprovals {
|
|
|
228
228
|
| Errors.GlobalErrorType
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Serializes a key authorization with selected multisig owner approvals.
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```ts twoslash
|
|
236
|
+
* // @noErrors
|
|
237
|
+
* import { MultisigOperation } from 'ox/tempo'
|
|
238
|
+
*
|
|
239
|
+
* const authorization = MultisigOperation.serializeKeyAuthorization(
|
|
240
|
+
* keyAuthorization,
|
|
241
|
+
* {
|
|
242
|
+
* account,
|
|
243
|
+
* approvals: selection.selectedApprovals,
|
|
244
|
+
* config,
|
|
245
|
+
* },
|
|
246
|
+
* )
|
|
247
|
+
* ```
|
|
248
|
+
*
|
|
249
|
+
* @param keyAuthorization - Canonical serialized unsigned key authorization.
|
|
250
|
+
* @param options - Multisig account, config, and selected approvals.
|
|
251
|
+
* @returns The signed serialized key authorization.
|
|
252
|
+
*/
|
|
253
|
+
export function serializeKeyAuthorization(
|
|
254
|
+
keyAuthorization: Hex.Hex,
|
|
255
|
+
options: serializeKeyAuthorization.Options,
|
|
256
|
+
): Hex.Hex {
|
|
257
|
+
const authorization = KeyAuthorization_.deserialize(keyAuthorization)
|
|
258
|
+
if (authorization.signature)
|
|
259
|
+
throw new InvalidOperationError({
|
|
260
|
+
reason: 'keyAuthorization must not contain a signature',
|
|
261
|
+
})
|
|
262
|
+
if (
|
|
263
|
+
KeyAuthorization_.serialize(authorization).toLowerCase() !==
|
|
264
|
+
keyAuthorization.toLowerCase()
|
|
265
|
+
)
|
|
266
|
+
throw new InvalidOperationError({
|
|
267
|
+
reason: 'keyAuthorization is not canonically serialized',
|
|
268
|
+
})
|
|
269
|
+
const config = MultisigConfig.from(options.config)
|
|
270
|
+
const signatures = SignatureEnvelope.sortMultisigApprovals({
|
|
271
|
+
account: options.account,
|
|
272
|
+
config,
|
|
273
|
+
payload: KeyAuthorization_.getSignPayload(authorization),
|
|
274
|
+
signatures: options.approvals.map((approval) =>
|
|
275
|
+
SignatureEnvelope.deserialize(approval),
|
|
276
|
+
),
|
|
277
|
+
})
|
|
278
|
+
return KeyAuthorization_.serialize(
|
|
279
|
+
KeyAuthorization_.from(authorization, {
|
|
280
|
+
signature: SignatureEnvelope.from({
|
|
281
|
+
account: options.account,
|
|
282
|
+
config,
|
|
283
|
+
signatures,
|
|
284
|
+
}),
|
|
285
|
+
}),
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export declare namespace serializeKeyAuthorization {
|
|
290
|
+
/** Options for `serializeKeyAuthorization`. */
|
|
291
|
+
export type Options = {
|
|
292
|
+
/** Root multisig account. */
|
|
293
|
+
account: Address.Address
|
|
294
|
+
/** Selected serialized owner approvals. */
|
|
295
|
+
approvals: readonly SignatureEnvelope.Serialized[]
|
|
296
|
+
/** Complete applicable root multisig config. */
|
|
297
|
+
config: MultisigConfig.Config
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Error type for `serializeKeyAuthorization`. */
|
|
301
|
+
export type ErrorType =
|
|
302
|
+
| InvalidOperationError
|
|
303
|
+
| KeyAuthorization_.deserialize.ErrorType
|
|
304
|
+
| KeyAuthorization_.from.ErrorType
|
|
305
|
+
| KeyAuthorization_.getSignPayload.ErrorType
|
|
306
|
+
| KeyAuthorization_.serialize.ErrorType
|
|
307
|
+
| MultisigConfig.assert.ErrorType
|
|
308
|
+
| SignatureEnvelope.assert.ErrorType
|
|
309
|
+
| SignatureEnvelope.InvalidSerializedError
|
|
310
|
+
| SignatureEnvelope.sortMultisigApprovals.ErrorType
|
|
311
|
+
| Errors.GlobalErrorType
|
|
312
|
+
}
|
|
313
|
+
|
|
231
314
|
/**
|
|
232
315
|
* Serializes a multisig transaction operation with selected owner approvals.
|
|
233
316
|
*
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { expectTypeOf, test } from 'vitest'
|
|
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,284 @@
|
|
|
1
|
+
import { MultisigConfig, MultisigSimulation } from 'ox/tempo'
|
|
2
|
+
import { describe, expect, test } from 'vitest'
|
|
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
|
+
`[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: noncanonical config encoding.]`,
|
|
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(
|
|
174
|
+
MultisigSimulation.toRpc(MultisigSimulation.fromRpc(rpc)),
|
|
175
|
+
).toMatchInlineSnapshot(`
|
|
176
|
+
{
|
|
177
|
+
"account": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
178
|
+
"approvals": [
|
|
179
|
+
{
|
|
180
|
+
"keyData": "0x0005",
|
|
181
|
+
"keyType": "webAuthn",
|
|
182
|
+
"owner": "0x1111111111111111111111111111111111111111",
|
|
183
|
+
"type": "primitive",
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"spec": {
|
|
187
|
+
"account": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
188
|
+
"approvals": [
|
|
189
|
+
{
|
|
190
|
+
"keyData": "0x0006",
|
|
191
|
+
"keyType": "secp256k1",
|
|
192
|
+
"owner": "0x2222222222222222222222222222222222222222",
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
"config": "${nestedConfig}",
|
|
196
|
+
},
|
|
197
|
+
"type": "multisig",
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
"config": "${config}",
|
|
201
|
+
}
|
|
202
|
+
`)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
test('behavior: preserves short key data', () => {
|
|
206
|
+
const spec = MultisigSimulation.fromRpc({
|
|
207
|
+
...rpc,
|
|
208
|
+
approvals: [{ ...rpc.approvals[0], keyData: '0x0102' }],
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
expect(MultisigSimulation.toRpc(spec).approvals).toMatchInlineSnapshot(`
|
|
212
|
+
[
|
|
213
|
+
{
|
|
214
|
+
"keyData": "0x0102",
|
|
215
|
+
"keyType": "webAuthn",
|
|
216
|
+
"owner": "0x1111111111111111111111111111111111111111",
|
|
217
|
+
"type": "primitive",
|
|
218
|
+
},
|
|
219
|
+
]
|
|
220
|
+
`)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
test('behavior: encodes the maximum uint64 configuration version', () => {
|
|
224
|
+
const spec = MultisigSimulation.fromRpc(rpc)
|
|
225
|
+
|
|
226
|
+
expect(
|
|
227
|
+
MultisigSimulation.toRpc({
|
|
228
|
+
...spec,
|
|
229
|
+
approvals: [],
|
|
230
|
+
config: MultisigConfig.from({
|
|
231
|
+
owners: [
|
|
232
|
+
{
|
|
233
|
+
owner: '0x1111111111111111111111111111111111111111',
|
|
234
|
+
weight: 1,
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
threshold: 1,
|
|
238
|
+
version: MultisigConfig.maxVersion,
|
|
239
|
+
}),
|
|
240
|
+
}).config,
|
|
241
|
+
).toMatchInlineSnapshot(
|
|
242
|
+
`"0xf843a0000000000000000000000000000000000000000000000000000000000000000088ffffffffffffffff01d7d694111111111111111111111111111111111111111101"`,
|
|
243
|
+
)
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
test('error: rejects excess root approvals', () => {
|
|
247
|
+
const spec = MultisigSimulation.fromRpc(rpc)
|
|
248
|
+
|
|
249
|
+
expect(() =>
|
|
250
|
+
MultisigSimulation.toRpc({
|
|
251
|
+
...spec,
|
|
252
|
+
approvals: Array.from({ length: 9 }, () => spec.approvals[0]!),
|
|
253
|
+
}),
|
|
254
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
255
|
+
`[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: approval count exceeds 8.]`,
|
|
256
|
+
)
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
test('error: rejects excess nested approvals', () => {
|
|
260
|
+
const spec = MultisigSimulation.fromRpc(rpc)
|
|
261
|
+
const nested = spec.approvals[1]!
|
|
262
|
+
if (nested.type !== 'multisig') throw new Error('unreachable')
|
|
263
|
+
|
|
264
|
+
expect(() =>
|
|
265
|
+
MultisigSimulation.toRpc({
|
|
266
|
+
...spec,
|
|
267
|
+
approvals: [
|
|
268
|
+
{
|
|
269
|
+
...nested,
|
|
270
|
+
spec: {
|
|
271
|
+
...nested.spec,
|
|
272
|
+
approvals: Array.from(
|
|
273
|
+
{ length: 9 },
|
|
274
|
+
() => nested.spec.approvals[0]!,
|
|
275
|
+
),
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
}),
|
|
280
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
281
|
+
`[MultisigSimulation.InvalidSimulationError: Invalid multisig simulation: approval count exceeds 8.]`,
|
|
282
|
+
)
|
|
283
|
+
})
|
|
284
|
+
})
|