ox 1.8.8 → 1.8.10
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 +37 -0
- package/dist/core/AbiEvent.d.ts.map +1 -1
- package/dist/core/AbiEvent.js +32 -10
- package/dist/core/AbiEvent.js.map +1 -1
- package/dist/core/Hex.d.ts +13 -1
- package/dist/core/Hex.d.ts.map +1 -1
- package/dist/core/Hex.js +5 -3
- package/dist/core/Hex.js.map +1 -1
- package/dist/core/RpcTransport.d.ts.map +1 -1
- package/dist/core/RpcTransport.js +21 -14
- package/dist/core/RpcTransport.js.map +1 -1
- package/dist/core/Signature.d.ts +1 -1
- package/dist/core/Signature.d.ts.map +1 -1
- package/dist/core/Signature.js +3 -2
- package/dist/core/Signature.js.map +1 -1
- package/dist/core/TypedData.js +1 -1
- package/dist/core/TypedData.js.map +1 -1
- package/dist/core/internal/abiItem.d.ts.map +1 -1
- package/dist/core/internal/abiItem.js +45 -19
- package/dist/core/internal/abiItem.js.map +1 -1
- package/dist/tempo/AccountConfig.d.ts +46 -3
- package/dist/tempo/AccountConfig.d.ts.map +1 -1
- package/dist/tempo/AccountConfig.js +58 -9
- package/dist/tempo/AccountConfig.js.map +1 -1
- package/dist/tempo/AccountSimulation.d.ts +6 -2
- package/dist/tempo/AccountSimulation.d.ts.map +1 -1
- package/dist/tempo/AccountSimulation.js +15 -5
- package/dist/tempo/AccountSimulation.js.map +1 -1
- package/dist/tempo/KeyAuthorization.d.ts +14 -9
- package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/tempo/KeyAuthorization.js +8 -12
- package/dist/tempo/KeyAuthorization.js.map +1 -1
- package/dist/tempo/TransactionRequest.d.ts +3 -3
- package/dist/tempo/TransactionRequest.d.ts.map +1 -1
- package/dist/tempo/TransactionRequest.js +10 -6
- package/dist/tempo/TransactionRequest.js.map +1 -1
- package/dist/zod/tempo/AccountSimulation.d.ts +4 -0
- package/dist/zod/tempo/AccountSimulation.d.ts.map +1 -1
- package/dist/zod/tempo/AccountSimulation.js +2 -0
- package/dist/zod/tempo/AccountSimulation.js.map +1 -1
- package/dist/zod/tempo/KeyAuthorization.d.ts +5 -5
- package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/zod/tempo/KeyAuthorization.js +10 -13
- package/dist/zod/tempo/KeyAuthorization.js.map +1 -1
- package/dist/zod/tempo/RpcSchemaTempo.d.ts +16 -8
- package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/dist/zod/tempo/Transaction.d.ts +6 -6
- package/dist/zod/tempo/TransactionRequest.d.ts +23 -9
- package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
- package/dist/zod/tempo/TransactionRequest.js +13 -9
- package/dist/zod/tempo/TransactionRequest.js.map +1 -1
- package/package.json +1 -1
- package/src/core/AbiEvent.ts +61 -22
- package/src/core/Hex.ts +17 -3
- package/src/core/RpcTransport.ts +20 -16
- package/src/core/Signature.ts +5 -2
- package/src/core/TypedData.ts +1 -1
- package/src/core/_test/AbiEvent.test.ts +232 -1
- package/src/core/_test/AbiItem.test.ts +342 -0
- package/src/core/_test/Hex.test.ts +31 -0
- package/src/core/_test/RpcTransport.test.ts +48 -0
- package/src/core/_test/Signature.test.ts +42 -1
- package/src/core/_test/TypedData.test.ts +29 -0
- package/src/core/internal/abiItem.ts +59 -25
- package/src/tempo/AccountConfig.test-d.ts +10 -0
- package/src/tempo/AccountConfig.test.ts +143 -10
- package/src/tempo/AccountConfig.ts +80 -9
- package/src/tempo/AccountOperation.test.ts +32 -32
- package/src/tempo/AccountSimulation.test.ts +43 -0
- package/src/tempo/AccountSimulation.ts +19 -5
- package/src/tempo/KeyAuthorization.test.ts +91 -32
- package/src/tempo/KeyAuthorization.ts +16 -39
- package/src/tempo/RpcSchemaTempo.test-d.ts +2 -2
- package/src/tempo/TransactionRequest.test-d.ts +5 -5
- package/src/tempo/TransactionRequest.test.ts +11 -9
- package/src/tempo/TransactionRequest.ts +13 -9
- package/src/version.ts +1 -1
- package/src/zod/tempo/AccountSimulation.ts +2 -0
- package/src/zod/tempo/KeyAuthorization.ts +28 -46
- package/src/zod/tempo/TransactionRequest.ts +17 -9
- package/src/zod/tempo/_test/AccountOperation.test.ts +1 -1
- package/src/zod/tempo/_test/KeyAuthorization.test.ts +5 -5
- package/src/zod/tempo/_test/TransactionRequest.test.ts +12 -7
|
@@ -242,6 +242,54 @@ describe('fromHttp', () => {
|
|
|
242
242
|
|
|
243
243
|
await server.close()
|
|
244
244
|
})
|
|
245
|
+
|
|
246
|
+
test('behavior: caller signal without `AbortSignal.any`', async () => {
|
|
247
|
+
const server = await createHttpServer((_, res) => {
|
|
248
|
+
res.end(JSON.stringify({ id: 0, jsonrpc: '2.0', result: 'wagmi' }))
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
const transport = RpcTransport.fromHttp(server.url, {
|
|
252
|
+
timeout: 50,
|
|
253
|
+
fetchOptions: {
|
|
254
|
+
signal: new AbortController().signal,
|
|
255
|
+
},
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
const descriptor = Object.getOwnPropertyDescriptor(AbortSignal, 'any')!
|
|
259
|
+
Object.defineProperty(AbortSignal, 'any', {
|
|
260
|
+
configurable: true,
|
|
261
|
+
value: undefined,
|
|
262
|
+
})
|
|
263
|
+
try {
|
|
264
|
+
await expect(
|
|
265
|
+
transport.request({ method: 'eth_accounts' }),
|
|
266
|
+
).resolves.toMatchInlineSnapshot(`"wagmi"`)
|
|
267
|
+
} finally {
|
|
268
|
+
Object.defineProperty(AbortSignal, 'any', descriptor)
|
|
269
|
+
await server.close()
|
|
270
|
+
}
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
test('behavior: timeout covers a response body that stalls after its headers', async () => {
|
|
274
|
+
const server = await createHttpServer(async (_, res) => {
|
|
275
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
276
|
+
res.write('{"id":0,"jsonrpc":"2.0",')
|
|
277
|
+
await setTimeout(200)
|
|
278
|
+
res.end('"result":"wagmi"}')
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
const transport = RpcTransport.fromHttp(server.url, {
|
|
282
|
+
timeout: 50,
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
await expect(() =>
|
|
286
|
+
transport.request({ method: 'eth_accounts' }),
|
|
287
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
288
|
+
`[Promise.TimeoutError: Operation timed out.]`,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
await server.close()
|
|
292
|
+
})
|
|
245
293
|
})
|
|
246
294
|
|
|
247
295
|
test('exports', () => {
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Bytes,
|
|
3
|
+
Hex,
|
|
4
|
+
Secp256k1,
|
|
5
|
+
Signature,
|
|
6
|
+
Solidity,
|
|
7
|
+
TxEnvelopeEip1559,
|
|
8
|
+
} from 'ox'
|
|
2
9
|
import { describe, expect, test } from 'vp/test'
|
|
3
10
|
|
|
4
11
|
describe('assert', () => {
|
|
@@ -488,6 +495,40 @@ describe('serialize', () => {
|
|
|
488
495
|
`"0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8"`,
|
|
489
496
|
)
|
|
490
497
|
})
|
|
498
|
+
|
|
499
|
+
test('behavior: `r` and `s` shorter than 32 bytes', () => {
|
|
500
|
+
const signature = Signature.toHex({
|
|
501
|
+
r: '0x1cdb3cbf94c7059e0e16d2ba046daef144d77384995a66f379194b638fc07e',
|
|
502
|
+
s: '0xc3d1ccd2c334876b9f8ef0ba3725c30dd5e1def225df36efa406d881ed610b',
|
|
503
|
+
yParity: 1,
|
|
504
|
+
})
|
|
505
|
+
expect(signature).toMatchInlineSnapshot(
|
|
506
|
+
`"0x001cdb3cbf94c7059e0e16d2ba046daef144d77384995a66f379194b638fc07e00c3d1ccd2c334876b9f8ef0ba3725c30dd5e1def225df36efa406d881ed610b1c"`,
|
|
507
|
+
)
|
|
508
|
+
expect(Hex.size(signature)).toMatchInlineSnapshot(`65`)
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
test('behavior: short `s` round-trips through recovery', () => {
|
|
512
|
+
const payload =
|
|
513
|
+
'0x44caf484852acee09d52b2802dba26d3c8ab3d28c7abcc1398b87898115633ec'
|
|
514
|
+
const signature = Signature.fromHex(
|
|
515
|
+
Signature.toHex({
|
|
516
|
+
r: '0xdf1cdb3cbf94c7059e0e16d2ba046daef144d77384995a66f379194b638fc07e',
|
|
517
|
+
s: '0xc3d1ccd2c334876b9f8ef0ba3725c30dd5e1def225df36efa406d881ed610b',
|
|
518
|
+
yParity: 1,
|
|
519
|
+
}),
|
|
520
|
+
)
|
|
521
|
+
expect(signature).toMatchInlineSnapshot(`
|
|
522
|
+
{
|
|
523
|
+
"r": "0xdf1cdb3cbf94c7059e0e16d2ba046daef144d77384995a66f379194b638fc07e",
|
|
524
|
+
"s": "0x00c3d1ccd2c334876b9f8ef0ba3725c30dd5e1def225df36efa406d881ed610b",
|
|
525
|
+
"yParity": 1,
|
|
526
|
+
}
|
|
527
|
+
`)
|
|
528
|
+
expect(
|
|
529
|
+
Secp256k1.recoverAddress({ payload, signature }),
|
|
530
|
+
).toMatchInlineSnapshot(`"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"`)
|
|
531
|
+
})
|
|
491
532
|
})
|
|
492
533
|
|
|
493
534
|
describe('toBytes', () => {
|
|
@@ -791,6 +791,23 @@ describe('extractEip712DomainTypes', () => {
|
|
|
791
791
|
test('empty', () => {
|
|
792
792
|
expect(TypedData.extractEip712DomainTypes({})).toMatchInlineSnapshot('[]')
|
|
793
793
|
})
|
|
794
|
+
|
|
795
|
+
test('empty version', () => {
|
|
796
|
+
expect(
|
|
797
|
+
TypedData.extractEip712DomainTypes({ name: 'Ether Mail', version: '' }),
|
|
798
|
+
).toMatchInlineSnapshot(`
|
|
799
|
+
[
|
|
800
|
+
{
|
|
801
|
+
"name": "name",
|
|
802
|
+
"type": "string",
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
"name": "version",
|
|
806
|
+
"type": "string",
|
|
807
|
+
},
|
|
808
|
+
]
|
|
809
|
+
`)
|
|
810
|
+
})
|
|
794
811
|
})
|
|
795
812
|
|
|
796
813
|
describe('getSignPayload', () => {
|
|
@@ -849,6 +866,18 @@ describe('getSignPayload', () => {
|
|
|
849
866
|
)
|
|
850
867
|
})
|
|
851
868
|
|
|
869
|
+
test('domain: empty version', () => {
|
|
870
|
+
expect(
|
|
871
|
+
TypedData.getSignPayload({
|
|
872
|
+
...typedData.complex,
|
|
873
|
+
domain: { name: 'Ether Mail', version: '' },
|
|
874
|
+
primaryType: 'Mail',
|
|
875
|
+
}),
|
|
876
|
+
).toMatchInlineSnapshot(
|
|
877
|
+
`"0xa437e12ee2fd4391429d9714be617951b7e69fb61dc520ad6fb8230d03b7508c"`,
|
|
878
|
+
)
|
|
879
|
+
})
|
|
880
|
+
|
|
852
881
|
test('domain: bigint chainId produces same hash as number chainId', () => {
|
|
853
882
|
const typedDataWithNumberChainId = {
|
|
854
883
|
domain: {
|
|
@@ -4,6 +4,7 @@ import type * as AbiItem from '../AbiItem.js'
|
|
|
4
4
|
import type * as AbiParameters from '../AbiParameters.js'
|
|
5
5
|
import * as Address from '../Address.js'
|
|
6
6
|
import * as Errors from '../Errors.js'
|
|
7
|
+
import { getArrayComponents } from './abiParameters.js'
|
|
7
8
|
import type {
|
|
8
9
|
Compute,
|
|
9
10
|
IsNever,
|
|
@@ -570,20 +571,6 @@ const integerTypeRegex =
|
|
|
570
571
|
*/
|
|
571
572
|
const fixedBytesTypeRegex = /^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/
|
|
572
573
|
|
|
573
|
-
/**
|
|
574
|
-
* Fixed-length (`<type>[M]`) / dynamic (`<type>[]`) array regex.
|
|
575
|
-
*
|
|
576
|
-
* @internal
|
|
577
|
-
*/
|
|
578
|
-
const arrayTypeRegex = /[a-z]+[1-9]{0,3}(\[[0-9]{0,}\])+$/
|
|
579
|
-
|
|
580
|
-
/**
|
|
581
|
-
* Strips a single trailing `[...]` array suffix from a type.
|
|
582
|
-
*
|
|
583
|
-
* @internal
|
|
584
|
-
*/
|
|
585
|
-
const arraySuffixStripRegex = /(\[[0-9]{0,}\])$/
|
|
586
|
-
|
|
587
574
|
/** @internal */
|
|
588
575
|
export function isArgOfType(
|
|
589
576
|
arg: unknown,
|
|
@@ -604,9 +591,12 @@ export function isArgOfType(
|
|
|
604
591
|
if (abiParameterType === 'tuple' && 'components' in abiParameter)
|
|
605
592
|
return Object.values(abiParameter.components).every(
|
|
606
593
|
(component, index) => {
|
|
607
|
-
return
|
|
608
|
-
|
|
609
|
-
|
|
594
|
+
return (
|
|
595
|
+
argType === 'object' &&
|
|
596
|
+
isArgOfType(
|
|
597
|
+
getComponentValue(arg, component, index),
|
|
598
|
+
component as AbiParameters.Parameter,
|
|
599
|
+
)
|
|
610
600
|
)
|
|
611
601
|
},
|
|
612
602
|
)
|
|
@@ -617,14 +607,17 @@ export function isArgOfType(
|
|
|
617
607
|
if (fixedBytesTypeRegex.test(abiParameterType))
|
|
618
608
|
return argType === 'string' || arg instanceof Uint8Array
|
|
619
609
|
|
|
620
|
-
|
|
610
|
+
// Fixed-length (`<type>[M]`) and dynamic (`<type>[]`) arrays.
|
|
611
|
+
const arrayComponents = getArrayComponents(abiParameterType)
|
|
612
|
+
if (arrayComponents) {
|
|
613
|
+
const [length, type] = arrayComponents
|
|
621
614
|
return (
|
|
622
615
|
Array.isArray(arg) &&
|
|
616
|
+
(length === null || arg.length === length) &&
|
|
623
617
|
arg.every((x: unknown) =>
|
|
624
618
|
isArgOfType(x, {
|
|
625
619
|
...abiParameter,
|
|
626
|
-
|
|
627
|
-
type: abiParameterType.replace(arraySuffixStripRegex, ''),
|
|
620
|
+
type,
|
|
628
621
|
} as AbiParameters.Parameter),
|
|
629
622
|
)
|
|
630
623
|
)
|
|
@@ -649,19 +642,49 @@ export function getAmbiguousTypes(
|
|
|
649
642
|
parameterIndex++
|
|
650
643
|
) {
|
|
651
644
|
const sourceParameter = sourceParameters[parameterIndex]!
|
|
652
|
-
const targetParameter = targetParameters[parameterIndex]
|
|
645
|
+
const targetParameter = targetParameters[parameterIndex]
|
|
646
|
+
if (!targetParameter) continue
|
|
653
647
|
|
|
654
648
|
if (
|
|
655
649
|
sourceParameter.type === 'tuple' &&
|
|
656
650
|
targetParameter.type === 'tuple' &&
|
|
657
651
|
'components' in sourceParameter &&
|
|
658
652
|
'components' in targetParameter
|
|
659
|
-
)
|
|
660
|
-
|
|
653
|
+
) {
|
|
654
|
+
const value = args[parameterIndex]
|
|
655
|
+
const types = getAmbiguousTypes(
|
|
661
656
|
sourceParameter.components,
|
|
662
657
|
targetParameter.components,
|
|
663
|
-
(
|
|
658
|
+
sourceParameter.components.map((component, index) =>
|
|
659
|
+
getComponentValue(value, component, index),
|
|
660
|
+
),
|
|
661
|
+
)
|
|
662
|
+
if (types) return types
|
|
663
|
+
continue
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const sourceArray = getArrayComponents(sourceParameter.type)
|
|
667
|
+
const targetArray = getArrayComponents(targetParameter.type)
|
|
668
|
+
if (sourceArray && targetArray) {
|
|
669
|
+
// Every item matches both element types, so the first item (or `undefined` for an empty array) decides.
|
|
670
|
+
const types = getAmbiguousTypes(
|
|
671
|
+
[
|
|
672
|
+
{
|
|
673
|
+
...sourceParameter,
|
|
674
|
+
type: sourceArray[1],
|
|
675
|
+
} as AbiParameters.Parameter,
|
|
676
|
+
],
|
|
677
|
+
[
|
|
678
|
+
{
|
|
679
|
+
...targetParameter,
|
|
680
|
+
type: targetArray[1],
|
|
681
|
+
} as AbiParameters.Parameter,
|
|
682
|
+
],
|
|
683
|
+
[(args[parameterIndex] as readonly unknown[] | undefined)?.[0]],
|
|
664
684
|
)
|
|
685
|
+
if (types) return types
|
|
686
|
+
continue
|
|
687
|
+
}
|
|
665
688
|
|
|
666
689
|
const sourceType = sourceParameter.type
|
|
667
690
|
const targetType = targetParameter.type
|
|
@@ -679,10 +702,21 @@ export function getAmbiguousTypes(
|
|
|
679
702
|
(sourceType === 'address' && targetType === 'bytes') ||
|
|
680
703
|
(sourceType === 'bytes' && targetType === 'address')
|
|
681
704
|
)
|
|
682
|
-
|
|
705
|
+
// An `undefined` value comes from an empty array, which matches both types.
|
|
706
|
+
ambiguous = arg === undefined || Address.validate(arg, { strict: false })
|
|
683
707
|
|
|
684
708
|
if (ambiguous) return [sourceType, targetType]
|
|
685
709
|
}
|
|
686
710
|
|
|
687
711
|
return
|
|
688
712
|
}
|
|
713
|
+
|
|
714
|
+
// Reads a tuple component's value by index from arrays and by name from objects, like `AbiParameters.encode`.
|
|
715
|
+
function getComponentValue(
|
|
716
|
+
arg: unknown,
|
|
717
|
+
component: AbiParameters.Parameter,
|
|
718
|
+
index: number,
|
|
719
|
+
): unknown {
|
|
720
|
+
if (Array.isArray(arg)) return arg[index]
|
|
721
|
+
return (arg as Record<string, unknown> | undefined)?.[component.name!]
|
|
722
|
+
}
|
|
@@ -22,6 +22,16 @@ test('from returns a complete configuration', () => {
|
|
|
22
22
|
expectTypeOf(zero.version).toEqualTypeOf<0n>()
|
|
23
23
|
})
|
|
24
24
|
|
|
25
|
+
test('fromUpgrade returns a version-one configuration', () => {
|
|
26
|
+
const config = AccountConfig.fromUpgrade(
|
|
27
|
+
'0x3333333333333333333333333333333333333333',
|
|
28
|
+
input,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
expectTypeOf(config).toMatchTypeOf<AccountConfig.Config>()
|
|
32
|
+
expectTypeOf(config.version).toEqualTypeOf<1n>()
|
|
33
|
+
})
|
|
34
|
+
|
|
25
35
|
test('update returns the next complete configuration', () => {
|
|
26
36
|
const config = AccountConfig.update(AccountConfig.from(input), {
|
|
27
37
|
owners: input.owners,
|
|
@@ -12,6 +12,13 @@ const config = AccountConfig.from({
|
|
|
12
12
|
})
|
|
13
13
|
const account = AccountConfig.getAddress(config, { factory })
|
|
14
14
|
|
|
15
|
+
// Vectors from Tempo `crates/primitives/tests/configurable_account.rs`
|
|
16
|
+
// (`configurable_account_hashes_follow_vectors_and_documented_layout`).
|
|
17
|
+
const tempo = {
|
|
18
|
+
account: '0x91847576f406d0842ad7c1a0c97c22a122e64278',
|
|
19
|
+
factory: '0x9999999999999999999999999999999999999999',
|
|
20
|
+
} as const
|
|
21
|
+
|
|
15
22
|
describe('from', () => {
|
|
16
23
|
test('behavior: normalizes an initial configuration', () => {
|
|
17
24
|
expect(
|
|
@@ -98,10 +105,103 @@ describe('fromRpc/toRpc', () => {
|
|
|
98
105
|
})
|
|
99
106
|
})
|
|
100
107
|
|
|
108
|
+
describe('fromUpgrade', () => {
|
|
109
|
+
const root = '0x3333333333333333333333333333333333333333'
|
|
110
|
+
|
|
111
|
+
test('example: derives the migrated config', () => {
|
|
112
|
+
// The root key may also be listed as an owner.
|
|
113
|
+
const config = AccountConfig.fromUpgrade(root, {
|
|
114
|
+
owners: [
|
|
115
|
+
{ owner: root, weight: 1 },
|
|
116
|
+
{ owner: owner_1, weight: 1 },
|
|
117
|
+
],
|
|
118
|
+
threshold: 2,
|
|
119
|
+
})
|
|
120
|
+
expect({
|
|
121
|
+
commitment: AccountConfig.getCommitment(config),
|
|
122
|
+
config,
|
|
123
|
+
}).toMatchInlineSnapshot(`
|
|
124
|
+
{
|
|
125
|
+
"commitment": "0xabca6d8f3a14b99ecaef378042e6a2ffa4f231125517e073cfbd3bcd69ac6ea1",
|
|
126
|
+
"config": {
|
|
127
|
+
"owners": [
|
|
128
|
+
{
|
|
129
|
+
"owner": "0x1111111111111111111111111111111111111111",
|
|
130
|
+
"weight": 1,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"owner": "0x3333333333333333333333333333333333333333",
|
|
134
|
+
"weight": 1,
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
"salt": "0x42b3dad8fa6c40b50e6dbbbdaaace72d6c04250c16c1e263a52355983a30eb56",
|
|
138
|
+
"threshold": 2,
|
|
139
|
+
"version": 1n,
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
`)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('behavior: binds the salt to the account', () => {
|
|
146
|
+
const options = {
|
|
147
|
+
owners: [{ owner: owner_1, weight: 1 }],
|
|
148
|
+
threshold: 1,
|
|
149
|
+
} as const
|
|
150
|
+
expect(AccountConfig.fromUpgrade(root, options).salt).not.toBe(
|
|
151
|
+
AccountConfig.fromUpgrade(owner_2, options).salt,
|
|
152
|
+
)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('error: rejects an invalid account', () => {
|
|
156
|
+
expect(() =>
|
|
157
|
+
AccountConfig.fromUpgrade('0x1234', {
|
|
158
|
+
owners: [{ owner: owner_1, weight: 1 }],
|
|
159
|
+
threshold: 1,
|
|
160
|
+
}),
|
|
161
|
+
).toThrowErrorMatchingInlineSnapshot(`
|
|
162
|
+
[Address.InvalidAddressError: Address "0x1234" is invalid.
|
|
163
|
+
|
|
164
|
+
Details: Address is not a 20 byte (40 hexadecimal character) value.]
|
|
165
|
+
`)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test('error: rejects an invalid config', () => {
|
|
169
|
+
expect(() =>
|
|
170
|
+
AccountConfig.fromUpgrade(root, {
|
|
171
|
+
owners: [{ owner: owner_1, weight: 1 }],
|
|
172
|
+
threshold: 2,
|
|
173
|
+
}),
|
|
174
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
175
|
+
`[AccountConfig.InvalidConfigError: Invalid account config: threshold exceeds total owner weight.]`,
|
|
176
|
+
)
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
|
|
101
180
|
describe('getAddress', () => {
|
|
181
|
+
test('example: matches the Tempo vectors', () => {
|
|
182
|
+
expect({
|
|
183
|
+
maxOwners: AccountConfig.getAddress(
|
|
184
|
+
{
|
|
185
|
+
owners: Array.from({ length: AccountConfig.maxOwners }, (_, i) => ({
|
|
186
|
+
owner: Hex.padLeft(Hex.fromNumber(i + 1), 20),
|
|
187
|
+
weight: 1,
|
|
188
|
+
})),
|
|
189
|
+
threshold: 8,
|
|
190
|
+
},
|
|
191
|
+
{ factory: tempo.factory },
|
|
192
|
+
),
|
|
193
|
+
single: AccountConfig.getAddress(config, { factory: tempo.factory }),
|
|
194
|
+
}).toMatchInlineSnapshot(`
|
|
195
|
+
{
|
|
196
|
+
"maxOwners": "0x7a4279bb3f1b8b54d703484317f64eae6814e827",
|
|
197
|
+
"single": "0x87c346a9029d675f296db533b12e146439cf20d5",
|
|
198
|
+
}
|
|
199
|
+
`)
|
|
200
|
+
})
|
|
201
|
+
|
|
102
202
|
test('example: matches the frozen CREATE2 vector', () => {
|
|
103
203
|
expect(account).toMatchInlineSnapshot(
|
|
104
|
-
`"
|
|
204
|
+
`"0x2318ebe50d1d476ec10854bfd949f96ea7737b68"`,
|
|
105
205
|
)
|
|
106
206
|
expect(account).not.toBe('0x8820d1497eeaf4f68e00b2cfc00a2f3b1dbb00da')
|
|
107
207
|
})
|
|
@@ -119,7 +219,7 @@ describe('getAddress', () => {
|
|
|
119
219
|
},
|
|
120
220
|
{ factory },
|
|
121
221
|
),
|
|
122
|
-
).toMatchInlineSnapshot(`"
|
|
222
|
+
).toMatchInlineSnapshot(`"0x1baa996a4f142d99999aa22f1cb1eb97eed2a24a"`)
|
|
123
223
|
})
|
|
124
224
|
|
|
125
225
|
test('behavior: is stable and binds the salt', () => {
|
|
@@ -134,8 +234,8 @@ describe('getAddress', () => {
|
|
|
134
234
|
),
|
|
135
235
|
}).toMatchInlineSnapshot(`
|
|
136
236
|
{
|
|
137
|
-
"repeated": "
|
|
138
|
-
"salted": "
|
|
237
|
+
"repeated": "0x2318ebe50d1d476ec10854bfd949f96ea7737b68",
|
|
238
|
+
"salted": "0x70d2e44032514493b68641ce5182cc878baa51f0",
|
|
139
239
|
}
|
|
140
240
|
`)
|
|
141
241
|
})
|
|
@@ -156,6 +256,18 @@ describe('getAddress', () => {
|
|
|
156
256
|
})
|
|
157
257
|
|
|
158
258
|
describe('getCommitment', () => {
|
|
259
|
+
test('example: matches the Tempo vectors', () => {
|
|
260
|
+
expect({
|
|
261
|
+
current: AccountConfig.getCommitment({ ...config, version: 1n }),
|
|
262
|
+
initial: AccountConfig.getCommitment(config),
|
|
263
|
+
}).toMatchInlineSnapshot(`
|
|
264
|
+
{
|
|
265
|
+
"current": "0xee570a0f26f17b41cdf0952a04ee01ea23495b0c0a59f9c904b0d60cb76453c3",
|
|
266
|
+
"initial": "0x79cc3a5fcbe18cb8cbbd2b41393750ccf3bbc20533a846755767fa10068ae01d",
|
|
267
|
+
}
|
|
268
|
+
`)
|
|
269
|
+
})
|
|
270
|
+
|
|
159
271
|
test('example: matches the frozen initial and current vectors', () => {
|
|
160
272
|
expect({
|
|
161
273
|
current: AccountConfig.getCommitment({ ...config, version: 1n }),
|
|
@@ -163,20 +275,40 @@ describe('getCommitment', () => {
|
|
|
163
275
|
initial: AccountConfig.getCommitment(config),
|
|
164
276
|
}).toMatchInlineSnapshot(`
|
|
165
277
|
{
|
|
166
|
-
"current": "
|
|
167
|
-
"currentNumber": "
|
|
168
|
-
"initial": "
|
|
278
|
+
"current": "0xee570a0f26f17b41cdf0952a04ee01ea23495b0c0a59f9c904b0d60cb76453c3",
|
|
279
|
+
"currentNumber": "0xee570a0f26f17b41cdf0952a04ee01ea23495b0c0a59f9c904b0d60cb76453c3",
|
|
280
|
+
"initial": "0x79cc3a5fcbe18cb8cbbd2b41393750ccf3bbc20533a846755767fa10068ae01d",
|
|
169
281
|
}
|
|
170
282
|
`)
|
|
171
283
|
})
|
|
172
284
|
})
|
|
173
285
|
|
|
174
286
|
describe('getSignPayload', () => {
|
|
287
|
+
test('example: matches the Tempo vectors', () => {
|
|
288
|
+
expect({
|
|
289
|
+
current: AccountConfig.getSignPayload({
|
|
290
|
+
account: tempo.account,
|
|
291
|
+
config: { version: 1n },
|
|
292
|
+
payload,
|
|
293
|
+
}),
|
|
294
|
+
initial: AccountConfig.getSignPayload({
|
|
295
|
+
account: tempo.account,
|
|
296
|
+
config,
|
|
297
|
+
payload,
|
|
298
|
+
}),
|
|
299
|
+
}).toMatchInlineSnapshot(`
|
|
300
|
+
{
|
|
301
|
+
"current": "0x285c850cb2803beb10f2f212dac73784a1a03b3991ba79081b24bfa91beb9182",
|
|
302
|
+
"initial": "0xe6fa513b5ab32da6f116443f13191096bce77b4850f4d51f3aa436fa7650c964",
|
|
303
|
+
}
|
|
304
|
+
`)
|
|
305
|
+
})
|
|
306
|
+
|
|
175
307
|
test('example: matches the frozen version-0 vector', () => {
|
|
176
308
|
expect(
|
|
177
309
|
AccountConfig.getSignPayload({ account, config, payload }),
|
|
178
310
|
).toMatchInlineSnapshot(
|
|
179
|
-
`"
|
|
311
|
+
`"0x32144254ec42f7a37b97e3a843667b527479f933320be553265e6594dc44ea87"`,
|
|
180
312
|
)
|
|
181
313
|
})
|
|
182
314
|
|
|
@@ -324,8 +456,8 @@ describe('assert/validate', () => {
|
|
|
324
456
|
valid: AccountConfig.validate(boundary),
|
|
325
457
|
}).toMatchInlineSnapshot(`
|
|
326
458
|
{
|
|
327
|
-
"account": "
|
|
328
|
-
"commitment": "
|
|
459
|
+
"account": "0x8400988285be93cba9391d445e473601633e5d62",
|
|
460
|
+
"commitment": "0xa9282ca5b73ac63b7e27f49cafa6ab13bd2aa79fc45267cf27342db73150cd46",
|
|
329
461
|
"rlpHash": "0xbaf0d030add91caaa10815d2e99c942f1e39b0d199216973781adb4fc1af6955",
|
|
330
462
|
"rlpLength": 1145,
|
|
331
463
|
"valid": true,
|
|
@@ -495,6 +627,7 @@ test('exports', () => {
|
|
|
495
627
|
"from",
|
|
496
628
|
"fromRpc",
|
|
497
629
|
"fromTuple",
|
|
630
|
+
"fromUpgrade",
|
|
498
631
|
"getAddress",
|
|
499
632
|
"getCommitment",
|
|
500
633
|
"getSignPayload",
|
|
@@ -33,17 +33,25 @@ export const signatureTypeByte = '0x05' as const
|
|
|
33
33
|
export const zeroSalt = `0x${'00'.repeat(32)}` as const
|
|
34
34
|
|
|
35
35
|
/** Domain prefix for the configurable account address derivation. */
|
|
36
|
-
const accountDomain = 'tempo:
|
|
36
|
+
const accountDomain = 'tempo:configurable-account:account'
|
|
37
37
|
|
|
38
38
|
/** Domain prefix for account configuration commitments. */
|
|
39
|
-
const configDomain = 'tempo:
|
|
39
|
+
const configDomain = 'tempo:configurable-account:config'
|
|
40
40
|
|
|
41
|
-
/**
|
|
42
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Keccak-256 of the account proxy creation code.
|
|
43
|
+
*
|
|
44
|
+
* Placeholder until [TIP-1110](https://tips.sh/1110) fixes the canonical
|
|
45
|
+
* account proxy, which will change every derived address.
|
|
46
|
+
*/
|
|
47
|
+
const accountProxyInitCodeHash =
|
|
43
48
|
'0x583cc63a2e37f645b43eac911b1a6d6de08b83abdc308c61364edda8cfc3bd37'
|
|
44
49
|
|
|
45
50
|
/** Domain prefix for configurable account owner approvals. */
|
|
46
|
-
const signatureDomain = 'tempo:
|
|
51
|
+
const signatureDomain = 'tempo:configurable-account:signature'
|
|
52
|
+
|
|
53
|
+
/** Domain prefix for the salt of an account migrated in place. */
|
|
54
|
+
const upgradeDomain = 'tempo:configurable-account:upgrade'
|
|
47
55
|
|
|
48
56
|
/**
|
|
49
57
|
* Complete account configuration witness.
|
|
@@ -347,12 +355,75 @@ export function fromTuple(tuple: Tuple): Config {
|
|
|
347
355
|
return config
|
|
348
356
|
}
|
|
349
357
|
|
|
358
|
+
/**
|
|
359
|
+
* Derives the configuration an existing account installs when it migrates in
|
|
360
|
+
* place with the `upgradeAccount` precompile call
|
|
361
|
+
* ([TIP-1113](https://tips.sh/1113)).
|
|
362
|
+
*
|
|
363
|
+
* The account keeps its address. Its salt is
|
|
364
|
+
* `keccak256("tempo:configurable-account:upgrade" || account)` and its version
|
|
365
|
+
* is `1n`. Owners are sorted into canonical order and the config is validated.
|
|
366
|
+
* The account itself may be listed as an owner.
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
* ```ts twoslash
|
|
370
|
+
* import { AccountConfig } from 'ox/tempo'
|
|
371
|
+
*
|
|
372
|
+
* const config = AccountConfig.fromUpgrade(
|
|
373
|
+
* '0x3333333333333333333333333333333333333333',
|
|
374
|
+
* {
|
|
375
|
+
* owners: [
|
|
376
|
+
* {
|
|
377
|
+
* owner: '0x1111111111111111111111111111111111111111',
|
|
378
|
+
* weight: 1
|
|
379
|
+
* }
|
|
380
|
+
* ],
|
|
381
|
+
* threshold: 1
|
|
382
|
+
* }
|
|
383
|
+
* )
|
|
384
|
+
* // config.version === 1n
|
|
385
|
+
* ```
|
|
386
|
+
*
|
|
387
|
+
* @param account - The account migrating in place.
|
|
388
|
+
* @param options - The owners and threshold to install.
|
|
389
|
+
* @returns The installed account config.
|
|
390
|
+
*/
|
|
391
|
+
export function fromUpgrade<numberType = number>(
|
|
392
|
+
account: Address.Address,
|
|
393
|
+
options: fromUpgrade.Options<numberType>,
|
|
394
|
+
): Config<1n, numberType> {
|
|
395
|
+
Address.assert(account)
|
|
396
|
+
return from({
|
|
397
|
+
owners: options.owners,
|
|
398
|
+
salt: Hash.keccak256(Hex.concat(Hex.fromString(upgradeDomain), account)),
|
|
399
|
+
threshold: options.threshold,
|
|
400
|
+
version: 1n,
|
|
401
|
+
})
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export declare namespace fromUpgrade {
|
|
405
|
+
type Options<numberType = number> = {
|
|
406
|
+
/** Weighted owners to install. Sorted into canonical order. */
|
|
407
|
+
owners: readonly Owner<numberType>[]
|
|
408
|
+
/** Minimum total owner weight required for authorization. */
|
|
409
|
+
threshold: numberType
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
type ErrorType =
|
|
413
|
+
| Address.assert.ErrorType
|
|
414
|
+
| assert.ErrorType
|
|
415
|
+
| Hash.keccak256.ErrorType
|
|
416
|
+
| Hex.concat.ErrorType
|
|
417
|
+
| Hex.fromString.ErrorType
|
|
418
|
+
| Errors.GlobalErrorType
|
|
419
|
+
}
|
|
420
|
+
|
|
350
421
|
/**
|
|
351
422
|
* Derives the stable configurable account address.
|
|
352
423
|
*
|
|
353
424
|
* The initial config is hashed into a CREATE2 salt using fixed-width
|
|
354
425
|
* big-endian fields, not RLP. The account uses the chain-configured recovery
|
|
355
|
-
* factory and
|
|
426
|
+
* factory and account proxy init-code hash.
|
|
356
427
|
*
|
|
357
428
|
* The address is derived once from the initial version-0 config. Config
|
|
358
429
|
* updates do not change it.
|
|
@@ -403,7 +474,7 @@ export function getAddress(
|
|
|
403
474
|
),
|
|
404
475
|
)
|
|
405
476
|
const account = ContractAddress.fromCreate2({
|
|
406
|
-
bytecodeHash:
|
|
477
|
+
bytecodeHash: accountProxyInitCodeHash,
|
|
407
478
|
from: options.factory,
|
|
408
479
|
salt: accountSalt,
|
|
409
480
|
})
|
|
@@ -436,7 +507,7 @@ export declare namespace getAddress {
|
|
|
436
507
|
* Computes the commitment for an account configuration.
|
|
437
508
|
*
|
|
438
509
|
* The commitment uses raw fixed-width fields, not RLP or ABI encoding:
|
|
439
|
-
* `keccak256("tempo:
|
|
510
|
+
* `keccak256("tempo:configurable-account:config" || salt || uint64be(version) || uint8(threshold) || uint8(owners.length) || owners)`.
|
|
440
511
|
*
|
|
441
512
|
* @example
|
|
442
513
|
* ```ts twoslash
|
|
@@ -487,7 +558,7 @@ export declare namespace getCommitment {
|
|
|
487
558
|
/**
|
|
488
559
|
* Computes the digest a configurable account owner approves (signs).
|
|
489
560
|
*
|
|
490
|
-
* `keccak256("tempo:
|
|
561
|
+
* `keccak256("tempo:configurable-account:signature" || inner_digest || account || uint64be(version))`,
|
|
491
562
|
* where `inner_digest` is the transaction sign payload
|
|
492
563
|
* ({@link ox#TxEnvelopeTempo.(getSignPayload:function)}).
|
|
493
564
|
*
|