ox 0.14.56 → 0.14.58
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 +35 -0
- package/_cjs/core/AbiEvent.js +16 -4
- package/_cjs/core/AbiEvent.js.map +1 -1
- package/_cjs/core/Hex.js +5 -3
- package/_cjs/core/Hex.js.map +1 -1
- package/_cjs/core/RpcTransport.js +32 -19
- package/_cjs/core/RpcTransport.js.map +1 -1
- package/_cjs/core/TypedData.js +1 -1
- package/_cjs/core/TypedData.js.map +1 -1
- package/_cjs/core/internal/abiItem.js +46 -14
- package/_cjs/core/internal/abiItem.js.map +1 -1
- package/_cjs/tempo/AccountConfig.js +16 -5
- package/_cjs/tempo/AccountConfig.js.map +1 -1
- package/_cjs/tempo/AccountSimulation.js +13 -4
- package/_cjs/tempo/AccountSimulation.js.map +1 -1
- package/_cjs/tempo/KeyAuthorization.js +2 -11
- package/_cjs/tempo/KeyAuthorization.js.map +1 -1
- package/_cjs/tempo/TransactionRequest.js +7 -5
- package/_cjs/tempo/TransactionRequest.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/core/AbiEvent.js +19 -4
- package/_esm/core/AbiEvent.js.map +1 -1
- package/_esm/core/Hex.js +5 -3
- package/_esm/core/Hex.js.map +1 -1
- package/_esm/core/RpcTransport.js +38 -19
- package/_esm/core/RpcTransport.js.map +1 -1
- package/_esm/core/TypedData.js +1 -1
- package/_esm/core/TypedData.js.map +1 -1
- package/_esm/core/internal/abiItem.js +51 -17
- package/_esm/core/internal/abiItem.js.map +1 -1
- package/_esm/tempo/AccountConfig.js +58 -9
- package/_esm/tempo/AccountConfig.js.map +1 -1
- package/_esm/tempo/AccountSimulation.js +15 -5
- package/_esm/tempo/AccountSimulation.js.map +1 -1
- package/_esm/tempo/KeyAuthorization.js +8 -12
- package/_esm/tempo/KeyAuthorization.js.map +1 -1
- package/_esm/tempo/TransactionRequest.js +7 -5
- package/_esm/tempo/TransactionRequest.js.map +1 -1
- package/_esm/version.js +1 -1
- package/_types/core/AbiEvent.d.ts.map +1 -1
- package/_types/core/Hex.d.ts +13 -1
- package/_types/core/Hex.d.ts.map +1 -1
- package/_types/core/RpcTransport.d.ts.map +1 -1
- package/_types/core/internal/abiItem.d.ts.map +1 -1
- package/_types/tempo/AccountConfig.d.ts +46 -3
- package/_types/tempo/AccountConfig.d.ts.map +1 -1
- package/_types/tempo/AccountSimulation.d.ts +6 -2
- package/_types/tempo/AccountSimulation.d.ts.map +1 -1
- package/_types/tempo/KeyAuthorization.d.ts +18 -13
- package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
- package/_types/tempo/TransactionRequest.d.ts +3 -3
- package/_types/tempo/TransactionRequest.d.ts.map +1 -1
- package/_types/version.d.ts +1 -1
- package/core/AbiEvent.ts +24 -3
- package/core/Hex.ts +17 -3
- package/core/RpcTransport.ts +40 -22
- package/core/TypedData.ts +1 -1
- package/core/internal/abiItem.ts +70 -21
- package/package.json +1 -1
- package/tempo/AccountConfig.test-d.ts +10 -0
- package/tempo/AccountConfig.test.ts +143 -10
- package/tempo/AccountConfig.ts +80 -9
- package/tempo/AccountOperation.test.ts +32 -32
- package/tempo/AccountSimulation.test.ts +44 -0
- package/tempo/AccountSimulation.ts +19 -5
- package/tempo/KeyAuthorization.test-d.ts +8 -18
- package/tempo/KeyAuthorization.test.ts +95 -39
- package/tempo/KeyAuthorization.ts +20 -43
- package/tempo/RpcSchemaTempo.test-d.ts +2 -2
- package/tempo/TransactionRequest.test-d.ts +3 -3
- package/tempo/TransactionRequest.test.ts +10 -8
- package/tempo/TransactionRequest.ts +10 -8
- package/version.ts +1 -1
package/core/RpcTransport.ts
CHANGED
|
@@ -95,8 +95,14 @@ export function fromHttp<
|
|
|
95
95
|
? await fetchOptions_(body_)
|
|
96
96
|
: fetchOptions_
|
|
97
97
|
|
|
98
|
-
const response = await promise.withTimeout(
|
|
99
|
-
({ signal }) => {
|
|
98
|
+
const { data, response } = await promise.withTimeout(
|
|
99
|
+
async ({ signal }) => {
|
|
100
|
+
// Abort on whichever signal fires first, so that a caller's
|
|
101
|
+
// `signal` does not disable the timeout.
|
|
102
|
+
const signals = [
|
|
103
|
+
fetchOptions?.signal,
|
|
104
|
+
timeout > 0 ? signal : null,
|
|
105
|
+
].filter((s): s is AbortSignal => Boolean(s))
|
|
100
106
|
const init: RequestInit = {
|
|
101
107
|
...fetchOptions,
|
|
102
108
|
body,
|
|
@@ -105,10 +111,40 @@ export function fromHttp<
|
|
|
105
111
|
...fetchOptions?.headers,
|
|
106
112
|
},
|
|
107
113
|
method: fetchOptions?.method ?? 'POST',
|
|
108
|
-
signal:
|
|
114
|
+
signal: (() => {
|
|
115
|
+
if (signals.length < 2) return signals[0] ?? null
|
|
116
|
+
// Runtimes without `AbortSignal.any` (e.g. Node < 18.17) keep
|
|
117
|
+
// only the caller's signal, as before.
|
|
118
|
+
if (typeof AbortSignal.any !== 'function') return signals[0]!
|
|
119
|
+
return AbortSignal.any(signals)
|
|
120
|
+
})(),
|
|
109
121
|
}
|
|
110
122
|
const request = new Request(url, init)
|
|
111
|
-
|
|
123
|
+
const response = await fetchFn(request)
|
|
124
|
+
|
|
125
|
+
// Read the body within the timeout too, so a response that stalls
|
|
126
|
+
// after its headers cannot hang the request.
|
|
127
|
+
const data = await (async () => {
|
|
128
|
+
if (
|
|
129
|
+
response.headers
|
|
130
|
+
.get('Content-Type')
|
|
131
|
+
?.startsWith('application/json')
|
|
132
|
+
)
|
|
133
|
+
return response.json()
|
|
134
|
+
return response.text().then((data) => {
|
|
135
|
+
try {
|
|
136
|
+
return JSON.parse(data || '{}')
|
|
137
|
+
} catch (_err) {
|
|
138
|
+
if (response.ok)
|
|
139
|
+
throw new MalformedResponseError({
|
|
140
|
+
response: data,
|
|
141
|
+
})
|
|
142
|
+
return { error: data }
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
})()
|
|
146
|
+
|
|
147
|
+
return { data, response }
|
|
112
148
|
},
|
|
113
149
|
{
|
|
114
150
|
timeout,
|
|
@@ -116,24 +152,6 @@ export function fromHttp<
|
|
|
116
152
|
},
|
|
117
153
|
)
|
|
118
154
|
|
|
119
|
-
const data = await (async () => {
|
|
120
|
-
if (
|
|
121
|
-
response.headers.get('Content-Type')?.startsWith('application/json')
|
|
122
|
-
)
|
|
123
|
-
return response.json()
|
|
124
|
-
return response.text().then((data) => {
|
|
125
|
-
try {
|
|
126
|
-
return JSON.parse(data || '{}')
|
|
127
|
-
} catch (_err) {
|
|
128
|
-
if (response.ok)
|
|
129
|
-
throw new MalformedResponseError({
|
|
130
|
-
response: data,
|
|
131
|
-
})
|
|
132
|
-
return { error: data }
|
|
133
|
-
}
|
|
134
|
-
})
|
|
135
|
-
})()
|
|
136
|
-
|
|
137
155
|
if (!response.ok)
|
|
138
156
|
throw new HttpError({
|
|
139
157
|
body,
|
package/core/TypedData.ts
CHANGED
|
@@ -393,7 +393,7 @@ export function extractEip712DomainTypes(
|
|
|
393
393
|
): Parameter[] {
|
|
394
394
|
return [
|
|
395
395
|
typeof domain?.name === 'string' && { name: 'name', type: 'string' },
|
|
396
|
-
domain?.version && { name: 'version', type: 'string' },
|
|
396
|
+
typeof domain?.version === 'string' && { name: 'version', type: 'string' },
|
|
397
397
|
(typeof domain?.chainId === 'number' ||
|
|
398
398
|
typeof domain?.chainId === 'bigint') && {
|
|
399
399
|
name: 'chainId',
|
package/core/internal/abiItem.ts
CHANGED
|
@@ -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,
|
|
@@ -466,8 +467,9 @@ export function normalizeSignature(signature: string): string {
|
|
|
466
467
|
|
|
467
468
|
// Ignore spaces
|
|
468
469
|
if (char === ' ') {
|
|
469
|
-
//
|
|
470
|
-
|
|
470
|
+
// A space directly after a `(` or `,` separator is leading whitespace.
|
|
471
|
+
// Otherwise, it ends the type, so we want to deactivate.
|
|
472
|
+
if (!current.endsWith(',') && !current.endsWith('(')) {
|
|
471
473
|
current = ''
|
|
472
474
|
active = false
|
|
473
475
|
}
|
|
@@ -508,9 +510,12 @@ export function isArgOfType(
|
|
|
508
510
|
if (abiParameterType === 'tuple' && 'components' in abiParameter)
|
|
509
511
|
return Object.values(abiParameter.components).every(
|
|
510
512
|
(component, index) => {
|
|
511
|
-
return
|
|
512
|
-
|
|
513
|
-
|
|
513
|
+
return (
|
|
514
|
+
argType === 'object' &&
|
|
515
|
+
isArgOfType(
|
|
516
|
+
getComponentValue(arg, component, index),
|
|
517
|
+
component as AbiParameters.Parameter,
|
|
518
|
+
)
|
|
514
519
|
)
|
|
515
520
|
},
|
|
516
521
|
)
|
|
@@ -530,15 +535,16 @@ export function isArgOfType(
|
|
|
530
535
|
return argType === 'string' || arg instanceof Uint8Array
|
|
531
536
|
|
|
532
537
|
// fixed-length (`<type>[M]`) and dynamic (`<type>[]`) arrays
|
|
533
|
-
|
|
534
|
-
if (
|
|
538
|
+
const arrayComponents = getArrayComponents(abiParameterType)
|
|
539
|
+
if (arrayComponents) {
|
|
540
|
+
const [length, type] = arrayComponents
|
|
535
541
|
return (
|
|
536
542
|
Array.isArray(arg) &&
|
|
543
|
+
(length === null || arg.length === length) &&
|
|
537
544
|
arg.every((x: unknown) =>
|
|
538
545
|
isArgOfType(x, {
|
|
539
546
|
...abiParameter,
|
|
540
|
-
|
|
541
|
-
type: abiParameterType.replace(/(\[[0-9]{0,}\])$/, ''),
|
|
547
|
+
type,
|
|
542
548
|
} as AbiParameters.Parameter),
|
|
543
549
|
)
|
|
544
550
|
)
|
|
@@ -557,32 +563,65 @@ export function getAmbiguousTypes(
|
|
|
557
563
|
): AbiParameters.Parameter['type'][] | undefined {
|
|
558
564
|
for (const parameterIndex in sourceParameters) {
|
|
559
565
|
const sourceParameter = sourceParameters[parameterIndex]!
|
|
560
|
-
const targetParameter = targetParameters[parameterIndex]
|
|
566
|
+
const targetParameter = targetParameters[parameterIndex]
|
|
567
|
+
if (!targetParameter) continue
|
|
561
568
|
|
|
562
569
|
if (
|
|
563
570
|
sourceParameter.type === 'tuple' &&
|
|
564
571
|
targetParameter.type === 'tuple' &&
|
|
565
572
|
'components' in sourceParameter &&
|
|
566
573
|
'components' in targetParameter
|
|
567
|
-
)
|
|
568
|
-
|
|
574
|
+
) {
|
|
575
|
+
const value = args[parameterIndex]
|
|
576
|
+
const types = getAmbiguousTypes(
|
|
569
577
|
sourceParameter.components,
|
|
570
578
|
targetParameter.components,
|
|
571
|
-
(
|
|
579
|
+
sourceParameter.components.map((component, index) =>
|
|
580
|
+
getComponentValue(value, component, index),
|
|
581
|
+
),
|
|
572
582
|
)
|
|
583
|
+
if (types) return types
|
|
584
|
+
continue
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const sourceArray = getArrayComponents(sourceParameter.type)
|
|
588
|
+
const targetArray = getArrayComponents(targetParameter.type)
|
|
589
|
+
if (sourceArray && targetArray) {
|
|
590
|
+
// Every item matches both element types, so the first item (or `undefined` for an empty array) decides.
|
|
591
|
+
const types = getAmbiguousTypes(
|
|
592
|
+
[
|
|
593
|
+
{
|
|
594
|
+
...sourceParameter,
|
|
595
|
+
type: sourceArray[1],
|
|
596
|
+
} as AbiParameters.Parameter,
|
|
597
|
+
],
|
|
598
|
+
[
|
|
599
|
+
{
|
|
600
|
+
...targetParameter,
|
|
601
|
+
type: targetArray[1],
|
|
602
|
+
} as AbiParameters.Parameter,
|
|
603
|
+
],
|
|
604
|
+
[(args[parameterIndex] as readonly unknown[] | undefined)?.[0]],
|
|
605
|
+
)
|
|
606
|
+
if (types) return types
|
|
607
|
+
continue
|
|
608
|
+
}
|
|
573
609
|
|
|
574
610
|
const types = [sourceParameter.type, targetParameter.type]
|
|
575
611
|
|
|
612
|
+
const value = args[parameterIndex]
|
|
613
|
+
|
|
576
614
|
const ambiguous = (() => {
|
|
577
615
|
if (types.includes('address') && types.includes('bytes20')) return true
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
return
|
|
584
|
-
|
|
585
|
-
|
|
616
|
+
// An `undefined` value comes from an empty array, which matches both types.
|
|
617
|
+
if (
|
|
618
|
+
types.includes('address') &&
|
|
619
|
+
(types.includes('string') || types.includes('bytes'))
|
|
620
|
+
)
|
|
621
|
+
return (
|
|
622
|
+
value === undefined ||
|
|
623
|
+
Address.validate(value as Address.Address, { strict: false })
|
|
624
|
+
)
|
|
586
625
|
return false
|
|
587
626
|
})()
|
|
588
627
|
|
|
@@ -591,3 +630,13 @@ export function getAmbiguousTypes(
|
|
|
591
630
|
|
|
592
631
|
return
|
|
593
632
|
}
|
|
633
|
+
|
|
634
|
+
// Reads a tuple component's value by index from arrays and by name from objects, like `AbiParameters.encode`.
|
|
635
|
+
function getComponentValue(
|
|
636
|
+
arg: unknown,
|
|
637
|
+
component: AbiParameters.Parameter,
|
|
638
|
+
index: number,
|
|
639
|
+
): unknown {
|
|
640
|
+
if (Array.isArray(arg)) return arg[index]
|
|
641
|
+
return (arg as Record<string, unknown> | undefined)?.[component.name!]
|
|
642
|
+
}
|
package/package.json
CHANGED
|
@@ -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(
|
|
@@ -99,10 +106,103 @@ describe('fromRpc/toRpc', () => {
|
|
|
99
106
|
})
|
|
100
107
|
})
|
|
101
108
|
|
|
109
|
+
describe('fromUpgrade', () => {
|
|
110
|
+
const root = '0x3333333333333333333333333333333333333333'
|
|
111
|
+
|
|
112
|
+
test('example: derives the migrated config', () => {
|
|
113
|
+
// The root key may also be listed as an owner.
|
|
114
|
+
const config = AccountConfig.fromUpgrade(root, {
|
|
115
|
+
owners: [
|
|
116
|
+
{ owner: root, weight: 1 },
|
|
117
|
+
{ owner: owner_1, weight: 1 },
|
|
118
|
+
],
|
|
119
|
+
threshold: 2,
|
|
120
|
+
})
|
|
121
|
+
expect({
|
|
122
|
+
commitment: AccountConfig.getCommitment(config),
|
|
123
|
+
config,
|
|
124
|
+
}).toMatchInlineSnapshot(`
|
|
125
|
+
{
|
|
126
|
+
"commitment": "0xabca6d8f3a14b99ecaef378042e6a2ffa4f231125517e073cfbd3bcd69ac6ea1",
|
|
127
|
+
"config": {
|
|
128
|
+
"owners": [
|
|
129
|
+
{
|
|
130
|
+
"owner": "0x1111111111111111111111111111111111111111",
|
|
131
|
+
"weight": 1,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"owner": "0x3333333333333333333333333333333333333333",
|
|
135
|
+
"weight": 1,
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
"salt": "0x42b3dad8fa6c40b50e6dbbbdaaace72d6c04250c16c1e263a52355983a30eb56",
|
|
139
|
+
"threshold": 2,
|
|
140
|
+
"version": 1n,
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
`)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test('behavior: binds the salt to the account', () => {
|
|
147
|
+
const options = {
|
|
148
|
+
owners: [{ owner: owner_1, weight: 1 }],
|
|
149
|
+
threshold: 1,
|
|
150
|
+
} as const
|
|
151
|
+
expect(AccountConfig.fromUpgrade(root, options).salt).not.toBe(
|
|
152
|
+
AccountConfig.fromUpgrade(owner_2, options).salt,
|
|
153
|
+
)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test('error: rejects an invalid account', () => {
|
|
157
|
+
expect(() =>
|
|
158
|
+
AccountConfig.fromUpgrade('0x1234', {
|
|
159
|
+
owners: [{ owner: owner_1, weight: 1 }],
|
|
160
|
+
threshold: 1,
|
|
161
|
+
}),
|
|
162
|
+
).toThrowErrorMatchingInlineSnapshot(`
|
|
163
|
+
[Address.InvalidAddressError: Address "0x1234" is invalid.
|
|
164
|
+
|
|
165
|
+
Details: Address is not a 20 byte (40 hexadecimal character) value.]
|
|
166
|
+
`)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test('error: rejects an invalid config', () => {
|
|
170
|
+
expect(() =>
|
|
171
|
+
AccountConfig.fromUpgrade(root, {
|
|
172
|
+
owners: [{ owner: owner_1, weight: 1 }],
|
|
173
|
+
threshold: 2,
|
|
174
|
+
}),
|
|
175
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
176
|
+
`[AccountConfig.InvalidConfigError: Invalid account config: threshold exceeds total owner weight.]`,
|
|
177
|
+
)
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
|
|
102
181
|
describe('getAddress', () => {
|
|
182
|
+
test('example: matches the Tempo vectors', () => {
|
|
183
|
+
expect({
|
|
184
|
+
maxOwners: AccountConfig.getAddress(
|
|
185
|
+
{
|
|
186
|
+
owners: Array.from({ length: AccountConfig.maxOwners }, (_, i) => ({
|
|
187
|
+
owner: Hex.padLeft(Hex.fromNumber(i + 1), 20),
|
|
188
|
+
weight: 1,
|
|
189
|
+
})),
|
|
190
|
+
threshold: 8,
|
|
191
|
+
},
|
|
192
|
+
{ factory: tempo.factory },
|
|
193
|
+
),
|
|
194
|
+
single: AccountConfig.getAddress(config, { factory: tempo.factory }),
|
|
195
|
+
}).toMatchInlineSnapshot(`
|
|
196
|
+
{
|
|
197
|
+
"maxOwners": "0x7a4279bb3f1b8b54d703484317f64eae6814e827",
|
|
198
|
+
"single": "0x87c346a9029d675f296db533b12e146439cf20d5",
|
|
199
|
+
}
|
|
200
|
+
`)
|
|
201
|
+
})
|
|
202
|
+
|
|
103
203
|
test('example: matches the frozen CREATE2 vector', () => {
|
|
104
204
|
expect(account).toMatchInlineSnapshot(
|
|
105
|
-
`"
|
|
205
|
+
`"0x2318ebe50d1d476ec10854bfd949f96ea7737b68"`,
|
|
106
206
|
)
|
|
107
207
|
expect(account).not.toBe('0x8820d1497eeaf4f68e00b2cfc00a2f3b1dbb00da')
|
|
108
208
|
})
|
|
@@ -120,7 +220,7 @@ describe('getAddress', () => {
|
|
|
120
220
|
},
|
|
121
221
|
{ factory },
|
|
122
222
|
),
|
|
123
|
-
).toMatchInlineSnapshot(`"
|
|
223
|
+
).toMatchInlineSnapshot(`"0x1baa996a4f142d99999aa22f1cb1eb97eed2a24a"`)
|
|
124
224
|
})
|
|
125
225
|
|
|
126
226
|
test('behavior: is stable and binds the salt', () => {
|
|
@@ -135,8 +235,8 @@ describe('getAddress', () => {
|
|
|
135
235
|
),
|
|
136
236
|
}).toMatchInlineSnapshot(`
|
|
137
237
|
{
|
|
138
|
-
"repeated": "
|
|
139
|
-
"salted": "
|
|
238
|
+
"repeated": "0x2318ebe50d1d476ec10854bfd949f96ea7737b68",
|
|
239
|
+
"salted": "0x70d2e44032514493b68641ce5182cc878baa51f0",
|
|
140
240
|
}
|
|
141
241
|
`)
|
|
142
242
|
})
|
|
@@ -157,6 +257,18 @@ describe('getAddress', () => {
|
|
|
157
257
|
})
|
|
158
258
|
|
|
159
259
|
describe('getCommitment', () => {
|
|
260
|
+
test('example: matches the Tempo vectors', () => {
|
|
261
|
+
expect({
|
|
262
|
+
current: AccountConfig.getCommitment({ ...config, version: 1n }),
|
|
263
|
+
initial: AccountConfig.getCommitment(config),
|
|
264
|
+
}).toMatchInlineSnapshot(`
|
|
265
|
+
{
|
|
266
|
+
"current": "0xee570a0f26f17b41cdf0952a04ee01ea23495b0c0a59f9c904b0d60cb76453c3",
|
|
267
|
+
"initial": "0x79cc3a5fcbe18cb8cbbd2b41393750ccf3bbc20533a846755767fa10068ae01d",
|
|
268
|
+
}
|
|
269
|
+
`)
|
|
270
|
+
})
|
|
271
|
+
|
|
160
272
|
test('example: matches the frozen initial and current vectors', () => {
|
|
161
273
|
expect({
|
|
162
274
|
current: AccountConfig.getCommitment({ ...config, version: 1n }),
|
|
@@ -164,20 +276,40 @@ describe('getCommitment', () => {
|
|
|
164
276
|
initial: AccountConfig.getCommitment(config),
|
|
165
277
|
}).toMatchInlineSnapshot(`
|
|
166
278
|
{
|
|
167
|
-
"current": "
|
|
168
|
-
"currentNumber": "
|
|
169
|
-
"initial": "
|
|
279
|
+
"current": "0xee570a0f26f17b41cdf0952a04ee01ea23495b0c0a59f9c904b0d60cb76453c3",
|
|
280
|
+
"currentNumber": "0xee570a0f26f17b41cdf0952a04ee01ea23495b0c0a59f9c904b0d60cb76453c3",
|
|
281
|
+
"initial": "0x79cc3a5fcbe18cb8cbbd2b41393750ccf3bbc20533a846755767fa10068ae01d",
|
|
170
282
|
}
|
|
171
283
|
`)
|
|
172
284
|
})
|
|
173
285
|
})
|
|
174
286
|
|
|
175
287
|
describe('getSignPayload', () => {
|
|
288
|
+
test('example: matches the Tempo vectors', () => {
|
|
289
|
+
expect({
|
|
290
|
+
current: AccountConfig.getSignPayload({
|
|
291
|
+
account: tempo.account,
|
|
292
|
+
config: { version: 1n },
|
|
293
|
+
payload,
|
|
294
|
+
}),
|
|
295
|
+
initial: AccountConfig.getSignPayload({
|
|
296
|
+
account: tempo.account,
|
|
297
|
+
config,
|
|
298
|
+
payload,
|
|
299
|
+
}),
|
|
300
|
+
}).toMatchInlineSnapshot(`
|
|
301
|
+
{
|
|
302
|
+
"current": "0x285c850cb2803beb10f2f212dac73784a1a03b3991ba79081b24bfa91beb9182",
|
|
303
|
+
"initial": "0xe6fa513b5ab32da6f116443f13191096bce77b4850f4d51f3aa436fa7650c964",
|
|
304
|
+
}
|
|
305
|
+
`)
|
|
306
|
+
})
|
|
307
|
+
|
|
176
308
|
test('example: matches the frozen version-0 vector', () => {
|
|
177
309
|
expect(
|
|
178
310
|
AccountConfig.getSignPayload({ account, config, payload }),
|
|
179
311
|
).toMatchInlineSnapshot(
|
|
180
|
-
`"
|
|
312
|
+
`"0x32144254ec42f7a37b97e3a843667b527479f933320be553265e6594dc44ea87"`,
|
|
181
313
|
)
|
|
182
314
|
})
|
|
183
315
|
|
|
@@ -325,8 +457,8 @@ describe('assert/validate', () => {
|
|
|
325
457
|
valid: AccountConfig.validate(boundary),
|
|
326
458
|
}).toMatchInlineSnapshot(`
|
|
327
459
|
{
|
|
328
|
-
"account": "
|
|
329
|
-
"commitment": "
|
|
460
|
+
"account": "0x8400988285be93cba9391d445e473601633e5d62",
|
|
461
|
+
"commitment": "0xa9282ca5b73ac63b7e27f49cafa6ab13bd2aa79fc45267cf27342db73150cd46",
|
|
330
462
|
"rlpHash": "0xbaf0d030add91caaa10815d2e99c942f1e39b0d199216973781adb4fc1af6955",
|
|
331
463
|
"rlpLength": 1145,
|
|
332
464
|
"valid": true,
|
|
@@ -496,6 +628,7 @@ test('exports', () => {
|
|
|
496
628
|
"from",
|
|
497
629
|
"fromRpc",
|
|
498
630
|
"fromTuple",
|
|
631
|
+
"fromUpgrade",
|
|
499
632
|
"getAddress",
|
|
500
633
|
"getCommitment",
|
|
501
634
|
"getSignPayload",
|
package/tempo/AccountConfig.ts
CHANGED
|
@@ -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.
|
|
@@ -335,12 +343,75 @@ export function fromTuple(tuple: Tuple): Config {
|
|
|
335
343
|
return config
|
|
336
344
|
}
|
|
337
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Derives the configuration an existing account installs when it migrates in
|
|
348
|
+
* place with the `upgradeAccount` precompile call
|
|
349
|
+
* ([TIP-1113](https://tips.sh/1113)).
|
|
350
|
+
*
|
|
351
|
+
* The account keeps its address. Its salt is
|
|
352
|
+
* `keccak256("tempo:configurable-account:upgrade" || account)` and its version
|
|
353
|
+
* is `1n`. Owners are sorted into canonical order and the config is validated.
|
|
354
|
+
* The account itself may be listed as an owner.
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* ```ts twoslash
|
|
358
|
+
* import { AccountConfig } from 'ox/tempo'
|
|
359
|
+
*
|
|
360
|
+
* const config = AccountConfig.fromUpgrade(
|
|
361
|
+
* '0x3333333333333333333333333333333333333333',
|
|
362
|
+
* {
|
|
363
|
+
* owners: [
|
|
364
|
+
* {
|
|
365
|
+
* owner: '0x1111111111111111111111111111111111111111',
|
|
366
|
+
* weight: 1
|
|
367
|
+
* }
|
|
368
|
+
* ],
|
|
369
|
+
* threshold: 1
|
|
370
|
+
* }
|
|
371
|
+
* )
|
|
372
|
+
* // config.version === 1n
|
|
373
|
+
* ```
|
|
374
|
+
*
|
|
375
|
+
* @param account - The account migrating in place.
|
|
376
|
+
* @param options - The owners and threshold to install.
|
|
377
|
+
* @returns The installed account config.
|
|
378
|
+
*/
|
|
379
|
+
export function fromUpgrade<numberType = number>(
|
|
380
|
+
account: Address.Address,
|
|
381
|
+
options: fromUpgrade.Options<numberType>,
|
|
382
|
+
): Config<1n, numberType> {
|
|
383
|
+
Address.assert(account)
|
|
384
|
+
return from({
|
|
385
|
+
owners: options.owners,
|
|
386
|
+
salt: Hash.keccak256(Hex.concat(Hex.fromString(upgradeDomain), account)),
|
|
387
|
+
threshold: options.threshold,
|
|
388
|
+
version: 1n,
|
|
389
|
+
})
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export declare namespace fromUpgrade {
|
|
393
|
+
type Options<numberType = number> = {
|
|
394
|
+
/** Weighted owners to install. Sorted into canonical order. */
|
|
395
|
+
owners: readonly Owner<numberType>[]
|
|
396
|
+
/** Minimum total owner weight required for authorization. */
|
|
397
|
+
threshold: numberType
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
type ErrorType =
|
|
401
|
+
| Address.assert.ErrorType
|
|
402
|
+
| assert.ErrorType
|
|
403
|
+
| Hash.keccak256.ErrorType
|
|
404
|
+
| Hex.concat.ErrorType
|
|
405
|
+
| Hex.fromString.ErrorType
|
|
406
|
+
| Errors.GlobalErrorType
|
|
407
|
+
}
|
|
408
|
+
|
|
338
409
|
/**
|
|
339
410
|
* Derives the stable configurable account address.
|
|
340
411
|
*
|
|
341
412
|
* The initial config is hashed into a CREATE2 salt using fixed-width
|
|
342
413
|
* big-endian fields, not RLP. The account uses the chain-configured recovery
|
|
343
|
-
* factory and
|
|
414
|
+
* factory and account proxy init-code hash.
|
|
344
415
|
*
|
|
345
416
|
* The address is derived once from the initial version-0 config. Config
|
|
346
417
|
* updates do not change it.
|
|
@@ -388,7 +459,7 @@ export function getAddress(
|
|
|
388
459
|
),
|
|
389
460
|
)
|
|
390
461
|
const account = ContractAddress.fromCreate2({
|
|
391
|
-
bytecodeHash:
|
|
462
|
+
bytecodeHash: accountProxyInitCodeHash,
|
|
392
463
|
from: options.factory,
|
|
393
464
|
salt: accountSalt,
|
|
394
465
|
})
|
|
@@ -421,7 +492,7 @@ export declare namespace getAddress {
|
|
|
421
492
|
* Computes the commitment for an account configuration.
|
|
422
493
|
*
|
|
423
494
|
* The commitment uses raw fixed-width fields, not RLP or ABI encoding:
|
|
424
|
-
* `keccak256("tempo:
|
|
495
|
+
* `keccak256("tempo:configurable-account:config" || salt || uint64be(version) || uint8(threshold) || uint8(owners.length) || owners)`.
|
|
425
496
|
*
|
|
426
497
|
* @example
|
|
427
498
|
* ```ts twoslash
|
|
@@ -469,7 +540,7 @@ export declare namespace getCommitment {
|
|
|
469
540
|
/**
|
|
470
541
|
* Computes the digest a configurable account owner approves (signs).
|
|
471
542
|
*
|
|
472
|
-
* `keccak256("tempo:
|
|
543
|
+
* `keccak256("tempo:configurable-account:signature" || inner_digest || account || uint64be(version))`,
|
|
473
544
|
* where `inner_digest` is the transaction sign payload
|
|
474
545
|
* ({@link ox#TxEnvelopeTempo.(getSignPayload:function)}).
|
|
475
546
|
*
|