ox 0.14.38 → 0.14.40
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 +20 -0
- package/_cjs/tempo/MultisigConfig.js +71 -15
- package/_cjs/tempo/MultisigConfig.js.map +1 -1
- package/_cjs/tempo/MultisigOperation.js +43 -35
- package/_cjs/tempo/MultisigOperation.js.map +1 -1
- package/_cjs/tempo/MultisigWitness.js +92 -0
- package/_cjs/tempo/MultisigWitness.js.map +1 -0
- package/_cjs/tempo/SignatureEnvelope.js +71 -108
- package/_cjs/tempo/SignatureEnvelope.js.map +1 -1
- package/_cjs/tempo/TransactionRequest.js +7 -1
- package/_cjs/tempo/TransactionRequest.js.map +1 -1
- package/_cjs/tempo/index.js +2 -1
- package/_cjs/tempo/index.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/tempo/MultisigConfig.js +140 -71
- package/_esm/tempo/MultisigConfig.js.map +1 -1
- package/_esm/tempo/MultisigOperation.js +43 -36
- package/_esm/tempo/MultisigOperation.js.map +1 -1
- package/_esm/tempo/MultisigWitness.js +123 -0
- package/_esm/tempo/MultisigWitness.js.map +1 -0
- package/_esm/tempo/SignatureEnvelope.js +84 -143
- package/_esm/tempo/SignatureEnvelope.js.map +1 -1
- package/_esm/tempo/TransactionRequest.js +7 -1
- package/_esm/tempo/TransactionRequest.js.map +1 -1
- package/_esm/tempo/index.js +9 -0
- 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/MultisigConfig.d.ts +137 -73
- package/_types/tempo/MultisigConfig.d.ts.map +1 -1
- package/_types/tempo/MultisigOperation.d.ts +1 -1
- package/_types/tempo/MultisigOperation.d.ts.map +1 -1
- package/_types/tempo/MultisigWitness.d.ts +106 -0
- package/_types/tempo/MultisigWitness.d.ts.map +1 -0
- package/_types/tempo/SignatureEnvelope.d.ts +81 -75
- package/_types/tempo/SignatureEnvelope.d.ts.map +1 -1
- package/_types/tempo/TransactionRequest.d.ts +6 -3
- package/_types/tempo/TransactionRequest.d.ts.map +1 -1
- package/_types/tempo/index.d.ts +9 -0
- package/_types/tempo/index.d.ts.map +1 -1
- package/_types/version.d.ts +1 -1
- package/package.json +21 -1
- package/tempo/KeyAuthorization.test-d.ts +12 -0
- package/tempo/KeyAuthorization.test.ts +54 -0
- package/tempo/KeyAuthorization.ts +1 -1
- package/tempo/MultisigConfig.test-d/package.json +6 -0
- package/tempo/MultisigConfig.test-d.ts +52 -0
- package/tempo/MultisigConfig.test.ts +291 -263
- package/tempo/MultisigConfig.ts +250 -95
- package/tempo/MultisigOperation.test-d.ts +6 -0
- package/tempo/MultisigOperation.test.ts +200 -81
- package/tempo/MultisigOperation.ts +42 -34
- package/tempo/MultisigWitness/package.json +6 -0
- package/tempo/MultisigWitness.test-d/package.json +6 -0
- package/tempo/MultisigWitness.test-d.ts +42 -0
- package/tempo/MultisigWitness.test.ts +289 -0
- package/tempo/MultisigWitness.ts +210 -0
- package/tempo/RpcSchemaTempo.test-d.ts +25 -0
- package/tempo/SignatureEnvelope.test-d.ts +57 -40
- package/tempo/SignatureEnvelope.test.ts +523 -480
- package/tempo/SignatureEnvelope.ts +250 -257
- package/tempo/TransactionRequest.test-d/package.json +6 -0
- package/tempo/TransactionRequest.test-d.ts +24 -0
- package/tempo/TransactionRequest.test.ts +102 -1
- package/tempo/TransactionRequest.ts +12 -2
- package/tempo/index.ts +9 -0
- package/tempo/multisig.e2e.test.ts +113 -86
- package/version.ts +1 -1
|
@@ -14,7 +14,7 @@ export type Base<quantity = bigint> = {
|
|
|
14
14
|
/** Every retained serialized owner approval. */
|
|
15
15
|
approvals: readonly Hex.Hex[]
|
|
16
16
|
/** Root configuration used to verify approvals. */
|
|
17
|
-
config: MultisigConfig.Config
|
|
17
|
+
config: MultisigConfig.Config<quantity>
|
|
18
18
|
/** Root configuration version. */
|
|
19
19
|
configVersion: quantity
|
|
20
20
|
/** Unix creation time in milliseconds. */
|
|
@@ -109,8 +109,8 @@ export function getHash(options: getHash.Options): Hex.Hex {
|
|
|
109
109
|
)
|
|
110
110
|
return MultisigConfig.getSignPayload({
|
|
111
111
|
account,
|
|
112
|
+
config: { version: configVersion },
|
|
112
113
|
payload,
|
|
113
|
-
version: configVersion,
|
|
114
114
|
})
|
|
115
115
|
}
|
|
116
116
|
|
|
@@ -281,20 +281,15 @@ export function serializeTransaction(
|
|
|
281
281
|
assertRetainedApprovals(value, approvals)
|
|
282
282
|
const signatures = SignatureEnvelope.sortMultisigApprovals({
|
|
283
283
|
account: value.account,
|
|
284
|
+
config: value.config,
|
|
284
285
|
payload: TxEnvelopeTempo.getSignPayload(envelope),
|
|
285
286
|
signatures: approvals,
|
|
286
|
-
version: value.configVersion,
|
|
287
287
|
})
|
|
288
|
-
const signature =
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
})
|
|
294
|
-
: SignatureEnvelope.from({
|
|
295
|
-
account: value.account,
|
|
296
|
-
signatures,
|
|
297
|
-
})
|
|
288
|
+
const signature = SignatureEnvelope.from({
|
|
289
|
+
account: value.account,
|
|
290
|
+
config: value.config,
|
|
291
|
+
signatures,
|
|
292
|
+
})
|
|
298
293
|
return TxEnvelopeTempo.serialize(
|
|
299
294
|
envelope,
|
|
300
295
|
value.transaction.startsWith(TxEnvelopeTempo.feePayerMagic)
|
|
@@ -344,6 +339,10 @@ export function from<const operation extends Operation>(
|
|
|
344
339
|
): from.ReturnValue<operation> {
|
|
345
340
|
try {
|
|
346
341
|
const config = MultisigConfig.from(operation.config)
|
|
342
|
+
if (config.version !== operation.configVersion)
|
|
343
|
+
throw new InvalidOperationError({
|
|
344
|
+
reason: 'config.version must equal configVersion',
|
|
345
|
+
})
|
|
347
346
|
if (
|
|
348
347
|
typeof config.threshold !== 'number' ||
|
|
349
348
|
config.owners.some((owner) => typeof owner.weight !== 'number')
|
|
@@ -351,12 +350,13 @@ export function from<const operation extends Operation>(
|
|
|
351
350
|
throw new InvalidOperationError({
|
|
352
351
|
reason: 'config threshold and owner weights must be numbers',
|
|
353
352
|
})
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
353
|
+
const value = { ...operation, config } as Operation
|
|
354
|
+
assertBase(value, config)
|
|
355
|
+
if (value.type === 'transaction') assertTransaction(value)
|
|
356
|
+
else if (value.type === 'keyAuthorization')
|
|
357
|
+
assertKeyAuthorization(value, config)
|
|
358
358
|
else throw new InvalidOperationError({ reason: 'unknown operation type' })
|
|
359
|
-
return
|
|
359
|
+
return value as never
|
|
360
360
|
} catch (cause) {
|
|
361
361
|
if (cause instanceof InvalidOperationError) throw cause
|
|
362
362
|
throw new InvalidOperationError({ cause })
|
|
@@ -404,7 +404,11 @@ export function fromRpc<const operation extends Rpc>(
|
|
|
404
404
|
throw new InvalidOperationError({
|
|
405
405
|
reason: 'configVersion must use canonical quantity encoding',
|
|
406
406
|
})
|
|
407
|
-
return from({
|
|
407
|
+
return from({
|
|
408
|
+
...operation,
|
|
409
|
+
config: MultisigConfig.fromRpc(operation.config),
|
|
410
|
+
configVersion,
|
|
411
|
+
} as Operation) as never
|
|
408
412
|
} catch (cause) {
|
|
409
413
|
if (cause instanceof InvalidOperationError) throw cause
|
|
410
414
|
throw new InvalidOperationError({ cause })
|
|
@@ -442,6 +446,7 @@ export function toRpc<const operation extends Operation>(
|
|
|
442
446
|
const value = from(operation)
|
|
443
447
|
return {
|
|
444
448
|
...value,
|
|
449
|
+
config: MultisigConfig.toRpc(value.config),
|
|
445
450
|
configVersion: Hex.fromNumber(value.configVersion),
|
|
446
451
|
} as never
|
|
447
452
|
}
|
|
@@ -513,10 +518,6 @@ async function selectApprovals_internal(
|
|
|
513
518
|
throw new InvalidApprovalError({
|
|
514
519
|
reason: `owner ${group.address} has conflicting signature types`,
|
|
515
520
|
})
|
|
516
|
-
if (nested.some((signature) => signature.init))
|
|
517
|
-
throw new InvalidApprovalError({
|
|
518
|
-
reason: `nested multisig owner ${group.address} cannot carry init`,
|
|
519
|
-
})
|
|
520
521
|
if (
|
|
521
522
|
path.length >= MultisigConfig.maxNestingDepth ||
|
|
522
523
|
path.includes(group.address.toLowerCase())
|
|
@@ -529,6 +530,11 @@ async function selectApprovals_internal(
|
|
|
529
530
|
reason: `nested multisig owner ${group.address} requires a config resolver`,
|
|
530
531
|
})
|
|
531
532
|
const resolved = await options.resolveConfig({ account: group.address })
|
|
533
|
+
const config = MultisigConfig.from(resolved.config)
|
|
534
|
+
if (config.version !== resolved.version)
|
|
535
|
+
throw new InvalidApprovalError({
|
|
536
|
+
reason: `resolved config.version must equal version for nested multisig owner ${group.address}`,
|
|
537
|
+
})
|
|
532
538
|
const selected = await selectApprovals_internal(
|
|
533
539
|
{
|
|
534
540
|
account: group.address,
|
|
@@ -537,11 +543,11 @@ async function selectApprovals_internal(
|
|
|
537
543
|
SignatureEnvelope.serialize(approval),
|
|
538
544
|
),
|
|
539
545
|
),
|
|
540
|
-
config
|
|
546
|
+
config,
|
|
541
547
|
hash: MultisigConfig.getSignPayload({
|
|
542
548
|
account: group.address,
|
|
549
|
+
config,
|
|
543
550
|
payload: options.hash,
|
|
544
|
-
version: resolved.version,
|
|
545
551
|
}),
|
|
546
552
|
resolveConfig: options.resolveConfig,
|
|
547
553
|
},
|
|
@@ -552,6 +558,7 @@ async function selectApprovals_internal(
|
|
|
552
558
|
signature: SignatureEnvelope.serialize(
|
|
553
559
|
SignatureEnvelope.from({
|
|
554
560
|
account: group.address,
|
|
561
|
+
config,
|
|
555
562
|
signatures: selected.approvals.map((approval) =>
|
|
556
563
|
SignatureEnvelope.from(approval),
|
|
557
564
|
),
|
|
@@ -564,6 +571,7 @@ async function selectApprovals_internal(
|
|
|
564
571
|
signature: SignatureEnvelope.serialize(
|
|
565
572
|
SignatureEnvelope.from({
|
|
566
573
|
account: group.address,
|
|
574
|
+
config,
|
|
567
575
|
signatures: selected.selectedApprovals.map((approval) =>
|
|
568
576
|
SignatureEnvelope.from(approval),
|
|
569
577
|
),
|
|
@@ -739,6 +747,7 @@ function assertBase(operation: Operation, config: MultisigConfig.Config): void {
|
|
|
739
747
|
const signature = assertApproval(
|
|
740
748
|
operation.account,
|
|
741
749
|
approval as SignatureEnvelope.Serialized,
|
|
750
|
+
config,
|
|
742
751
|
)
|
|
743
752
|
const address = SignatureEnvelope.extractAddress({
|
|
744
753
|
payload: operation.hash,
|
|
@@ -944,13 +953,9 @@ function assertKeyAuthorization(
|
|
|
944
953
|
reason: 'key authorization signatureCount does not match its signature',
|
|
945
954
|
})
|
|
946
955
|
assertSelectedApprovals(operation, signature.signatures, authorization)
|
|
947
|
-
if (
|
|
956
|
+
if (!sameConfig(signature.config, config))
|
|
948
957
|
throw new InvalidOperationError({
|
|
949
|
-
reason: 'key authorization
|
|
950
|
-
})
|
|
951
|
-
if (signature.init && !sameConfig(signature.init, config))
|
|
952
|
-
throw new InvalidOperationError({
|
|
953
|
-
reason: 'key authorization bootstrap config does not match',
|
|
958
|
+
reason: 'key authorization config does not match',
|
|
954
959
|
})
|
|
955
960
|
}
|
|
956
961
|
assertOperationHash(
|
|
@@ -974,10 +979,12 @@ function assertKeyAuthorization(
|
|
|
974
979
|
function assertApproval(
|
|
975
980
|
account: Address.Address,
|
|
976
981
|
serialized: SignatureEnvelope.Serialized,
|
|
982
|
+
config: MultisigConfig.Config,
|
|
977
983
|
): SignatureEnvelope.SignatureEnvelope {
|
|
978
984
|
const approval = SignatureEnvelope.deserialize(serialized)
|
|
979
985
|
SignatureEnvelope.assert({
|
|
980
986
|
account,
|
|
987
|
+
config,
|
|
981
988
|
signatures: [approval],
|
|
982
989
|
type: 'multisig',
|
|
983
990
|
})
|
|
@@ -1039,8 +1046,8 @@ function assertSelectedApprovals(
|
|
|
1039
1046
|
|
|
1040
1047
|
const digest = MultisigConfig.getSignPayload({
|
|
1041
1048
|
account: operation.account,
|
|
1049
|
+
config: operation.config,
|
|
1042
1050
|
payload: KeyAuthorization_.getSignPayload(authorization),
|
|
1043
|
-
version: operation.configVersion,
|
|
1044
1051
|
})
|
|
1045
1052
|
const addresses = selected.map((signature) =>
|
|
1046
1053
|
SignatureEnvelope.extractAddress({ payload: digest, signature }),
|
|
@@ -1075,7 +1082,7 @@ function includesApproval(
|
|
|
1075
1082
|
)
|
|
1076
1083
|
if (retained.account.toLowerCase() !== selected.account.toLowerCase())
|
|
1077
1084
|
return false
|
|
1078
|
-
|
|
1085
|
+
if (!sameConfig(retained.config, selected.config)) return false
|
|
1079
1086
|
let index = 0
|
|
1080
1087
|
for (const approval of selected.signatures) {
|
|
1081
1088
|
while (
|
|
@@ -1119,8 +1126,8 @@ function isWeightReachable(
|
|
|
1119
1126
|
function assertOperationHash(operation: Operation, payload: Hex.Hex): void {
|
|
1120
1127
|
const hash = MultisigConfig.getSignPayload({
|
|
1121
1128
|
account: operation.account,
|
|
1129
|
+
config: operation.config,
|
|
1122
1130
|
payload,
|
|
1123
|
-
version: operation.configVersion,
|
|
1124
1131
|
})
|
|
1125
1132
|
if (hash.toLowerCase() !== operation.hash.toLowerCase())
|
|
1126
1133
|
throw new InvalidOperationError({
|
|
@@ -1157,6 +1164,7 @@ function sameConfig(
|
|
|
1157
1164
|
configB.salt ?? MultisigConfig.zeroSalt,
|
|
1158
1165
|
) &&
|
|
1159
1166
|
configA.threshold === configB.threshold &&
|
|
1167
|
+
configA.version === configB.version &&
|
|
1160
1168
|
configA.owners.length === configB.owners.length &&
|
|
1161
1169
|
configA.owners.every((owner, index) => {
|
|
1162
1170
|
const other = configB.owners[index]!
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { expectTypeOf, test } from 'vitest'
|
|
2
|
+
import * as MultisigWitness from './MultisigWitness.js'
|
|
3
|
+
|
|
4
|
+
const rpc = {
|
|
5
|
+
account: '0x2222222222222222222222222222222222222222',
|
|
6
|
+
approvals: [
|
|
7
|
+
{
|
|
8
|
+
owner: '0x1111111111111111111111111111111111111111',
|
|
9
|
+
type: 'primitive',
|
|
10
|
+
},
|
|
11
|
+
],
|
|
12
|
+
config: {
|
|
13
|
+
owners: [
|
|
14
|
+
{
|
|
15
|
+
owner: '0x1111111111111111111111111111111111111111',
|
|
16
|
+
weight: 1,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
salt: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
20
|
+
threshold: 1,
|
|
21
|
+
version: 0,
|
|
22
|
+
},
|
|
23
|
+
} as const satisfies MultisigWitness.Rpc
|
|
24
|
+
|
|
25
|
+
test('fromRpc returns a domain witness', () => {
|
|
26
|
+
const witness = MultisigWitness.fromRpc(rpc)
|
|
27
|
+
|
|
28
|
+
expectTypeOf(witness).toEqualTypeOf<MultisigWitness.MultisigWitness>()
|
|
29
|
+
expectTypeOf(witness.config.version).toEqualTypeOf<bigint>()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('RPC witnesses use numeric configuration versions', () => {
|
|
33
|
+
expectTypeOf<
|
|
34
|
+
MultisigWitness.Rpc['config']['version']
|
|
35
|
+
>().toEqualTypeOf<number>()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('toRpc returns an RPC witness', () => {
|
|
39
|
+
expectTypeOf(
|
|
40
|
+
MultisigWitness.toRpc(MultisigWitness.fromRpc(rpc)),
|
|
41
|
+
).toEqualTypeOf<MultisigWitness.Rpc>()
|
|
42
|
+
})
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { MultisigWitness } from 'ox/tempo'
|
|
2
|
+
import { describe, expect, test } from 'vitest'
|
|
3
|
+
|
|
4
|
+
const rpc = {
|
|
5
|
+
account: '0xcccccccccccccccccccccccccccccccccccccccc',
|
|
6
|
+
approvals: [
|
|
7
|
+
{
|
|
8
|
+
keyData: '0x0102030405',
|
|
9
|
+
keyType: 'webAuthn',
|
|
10
|
+
owner: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
11
|
+
type: 'primitive',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
type: 'multisig',
|
|
15
|
+
witness: {
|
|
16
|
+
account: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
|
17
|
+
approvals: [
|
|
18
|
+
{
|
|
19
|
+
keyData: '0x010203040506',
|
|
20
|
+
keyType: 'secp256k1',
|
|
21
|
+
owner: '0xdddddddddddddddddddddddddddddddddddddddd',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
owner: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
config: {
|
|
28
|
+
owners: [
|
|
29
|
+
{
|
|
30
|
+
owner: '0xdddddddddddddddddddddddddddddddddddddddd',
|
|
31
|
+
weight: 1,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
owner: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
|
|
35
|
+
weight: 1,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
salt: '0x2222222222222222222222222222222222222222222222222222222222222222',
|
|
39
|
+
threshold: 2,
|
|
40
|
+
version: 2,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
config: {
|
|
46
|
+
owners: [
|
|
47
|
+
{
|
|
48
|
+
owner: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
49
|
+
weight: 1,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
owner: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
|
53
|
+
weight: 1,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
salt: '0x1111111111111111111111111111111111111111111111111111111111111111',
|
|
57
|
+
threshold: 2,
|
|
58
|
+
version: 1,
|
|
59
|
+
},
|
|
60
|
+
} as const satisfies MultisigWitness.Rpc
|
|
61
|
+
|
|
62
|
+
describe('fromRpc', () => {
|
|
63
|
+
test('behavior: converts numeric configuration versions to bigint', () => {
|
|
64
|
+
expect(MultisigWitness.fromRpc(rpc)).toMatchInlineSnapshot(`
|
|
65
|
+
{
|
|
66
|
+
"account": "0xcccccccccccccccccccccccccccccccccccccccc",
|
|
67
|
+
"approvals": [
|
|
68
|
+
{
|
|
69
|
+
"keyData": "0x0102030405",
|
|
70
|
+
"keyType": "webAuthn",
|
|
71
|
+
"owner": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
72
|
+
"type": "primitive",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"type": "multisig",
|
|
76
|
+
"witness": {
|
|
77
|
+
"account": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
78
|
+
"approvals": [
|
|
79
|
+
{
|
|
80
|
+
"keyData": "0x010203040506",
|
|
81
|
+
"keyType": "secp256k1",
|
|
82
|
+
"owner": "0xdddddddddddddddddddddddddddddddddddddddd",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"owner": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
"config": {
|
|
89
|
+
"owners": [
|
|
90
|
+
{
|
|
91
|
+
"owner": "0xdddddddddddddddddddddddddddddddddddddddd",
|
|
92
|
+
"weight": 1,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"owner": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
|
96
|
+
"weight": 1,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
"salt": "0x2222222222222222222222222222222222222222222222222222222222222222",
|
|
100
|
+
"threshold": 2,
|
|
101
|
+
"version": 2n,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
"config": {
|
|
107
|
+
"owners": [
|
|
108
|
+
{
|
|
109
|
+
"owner": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
110
|
+
"weight": 1,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"owner": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
114
|
+
"weight": 1,
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
"salt": "0x1111111111111111111111111111111111111111111111111111111111111111",
|
|
118
|
+
"threshold": 2,
|
|
119
|
+
"version": 1n,
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
`)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('error: rejects unsafe numeric configuration versions', () => {
|
|
126
|
+
expect(() =>
|
|
127
|
+
MultisigWitness.fromRpc({
|
|
128
|
+
...rpc,
|
|
129
|
+
config: {
|
|
130
|
+
...rpc.config,
|
|
131
|
+
version: Number.MAX_SAFE_INTEGER + 1,
|
|
132
|
+
},
|
|
133
|
+
}),
|
|
134
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
135
|
+
`[MultisigConfig.InvalidConfigError: Invalid native multisig config: version must be an unsigned 64-bit integer.]`,
|
|
136
|
+
)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test('error: rejects excess root approvals', () => {
|
|
140
|
+
expect(() =>
|
|
141
|
+
MultisigWitness.fromRpc({
|
|
142
|
+
...rpc,
|
|
143
|
+
approvals: Array.from({ length: 9 }, () => rpc.approvals[0]),
|
|
144
|
+
}),
|
|
145
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
146
|
+
`[MultisigWitness.InvalidWitnessError: Invalid multisig witness: approval count exceeds 8.]`,
|
|
147
|
+
)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test('error: rejects excess nested approvals', () => {
|
|
151
|
+
expect(() =>
|
|
152
|
+
MultisigWitness.fromRpc({
|
|
153
|
+
...rpc,
|
|
154
|
+
approvals: [
|
|
155
|
+
{
|
|
156
|
+
...rpc.approvals[1],
|
|
157
|
+
witness: {
|
|
158
|
+
...rpc.approvals[1].witness,
|
|
159
|
+
approvals: Array.from(
|
|
160
|
+
{ length: 9 },
|
|
161
|
+
() => rpc.approvals[1].witness.approvals[0],
|
|
162
|
+
),
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
}),
|
|
167
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
168
|
+
`[MultisigWitness.InvalidWitnessError: Invalid multisig witness: approval count exceeds 8.]`,
|
|
169
|
+
)
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
describe('toRpc', () => {
|
|
174
|
+
test('behavior: converts bigint configuration versions to numbers', () => {
|
|
175
|
+
expect(
|
|
176
|
+
MultisigWitness.toRpc(MultisigWitness.fromRpc(rpc)),
|
|
177
|
+
).toMatchInlineSnapshot(`
|
|
178
|
+
{
|
|
179
|
+
"account": "0xcccccccccccccccccccccccccccccccccccccccc",
|
|
180
|
+
"approvals": [
|
|
181
|
+
{
|
|
182
|
+
"keyData": "0x0005",
|
|
183
|
+
"keyType": "webAuthn",
|
|
184
|
+
"owner": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
185
|
+
"type": "primitive",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"type": "multisig",
|
|
189
|
+
"witness": {
|
|
190
|
+
"account": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
191
|
+
"approvals": [
|
|
192
|
+
{
|
|
193
|
+
"keyData": "0x0006",
|
|
194
|
+
"keyType": "secp256k1",
|
|
195
|
+
"owner": "0xdddddddddddddddddddddddddddddddddddddddd",
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"owner": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
"config": {
|
|
202
|
+
"owners": [
|
|
203
|
+
{
|
|
204
|
+
"owner": "0xdddddddddddddddddddddddddddddddddddddddd",
|
|
205
|
+
"weight": 1,
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"owner": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
|
|
209
|
+
"weight": 1,
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
"salt": "0x2222222222222222222222222222222222222222222222222222222222222222",
|
|
213
|
+
"threshold": 2,
|
|
214
|
+
"version": 2,
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
"config": {
|
|
220
|
+
"owners": [
|
|
221
|
+
{
|
|
222
|
+
"owner": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
223
|
+
"weight": 1,
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"owner": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
227
|
+
"weight": 1,
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
"salt": "0x1111111111111111111111111111111111111111111111111111111111111111",
|
|
231
|
+
"threshold": 2,
|
|
232
|
+
"version": 1,
|
|
233
|
+
},
|
|
234
|
+
}
|
|
235
|
+
`)
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
test('error: rejects versions that JSON cannot represent exactly', () => {
|
|
239
|
+
const witness = MultisigWitness.fromRpc(rpc)
|
|
240
|
+
expect(() =>
|
|
241
|
+
MultisigWitness.toRpc({
|
|
242
|
+
...witness,
|
|
243
|
+
config: {
|
|
244
|
+
...witness.config,
|
|
245
|
+
version: BigInt(Number.MAX_SAFE_INTEGER) + 1n,
|
|
246
|
+
},
|
|
247
|
+
}),
|
|
248
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
249
|
+
`[Hex.IntegerOutOfRangeError: Number \`9007199254740992\` is not in safe unsigned integer range (\`0\` to \`9007199254740991\`)]`,
|
|
250
|
+
)
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
test('error: rejects excess root approvals', () => {
|
|
254
|
+
const witness = MultisigWitness.fromRpc(rpc)
|
|
255
|
+
expect(() =>
|
|
256
|
+
MultisigWitness.toRpc({
|
|
257
|
+
...witness,
|
|
258
|
+
approvals: Array.from({ length: 9 }, () => witness.approvals[0]!),
|
|
259
|
+
}),
|
|
260
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
261
|
+
`[MultisigWitness.InvalidWitnessError: Invalid multisig witness: approval count exceeds 8.]`,
|
|
262
|
+
)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
test('error: rejects excess nested approvals', () => {
|
|
266
|
+
const witness = MultisigWitness.fromRpc(rpc)
|
|
267
|
+
const nested = witness.approvals[1]!
|
|
268
|
+
if (nested.type !== 'multisig') throw new Error('unreachable')
|
|
269
|
+
expect(() =>
|
|
270
|
+
MultisigWitness.toRpc({
|
|
271
|
+
...witness,
|
|
272
|
+
approvals: [
|
|
273
|
+
{
|
|
274
|
+
...nested,
|
|
275
|
+
witness: {
|
|
276
|
+
...nested.witness,
|
|
277
|
+
approvals: Array.from(
|
|
278
|
+
{ length: 9 },
|
|
279
|
+
() => nested.witness.approvals[0]!,
|
|
280
|
+
),
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
}),
|
|
285
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
286
|
+
`[MultisigWitness.InvalidWitnessError: Invalid multisig witness: approval count exceeds 8.]`,
|
|
287
|
+
)
|
|
288
|
+
})
|
|
289
|
+
})
|