@feelyourprotocol/util 8141.0.0
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/LICENSE +373 -0
- package/README.md +297 -0
- package/dist/cjs/account.d.ts +165 -0
- package/dist/cjs/account.d.ts.map +1 -0
- package/dist/cjs/account.js +530 -0
- package/dist/cjs/account.js.map +1 -0
- package/dist/cjs/address.d.ts +67 -0
- package/dist/cjs/address.d.ts.map +1 -0
- package/dist/cjs/address.js +136 -0
- package/dist/cjs/address.js.map +1 -0
- package/dist/cjs/authorization.d.ts +41 -0
- package/dist/cjs/authorization.d.ts.map +1 -0
- package/dist/cjs/authorization.js +135 -0
- package/dist/cjs/authorization.js.map +1 -0
- package/dist/cjs/bal.d.ts +129 -0
- package/dist/cjs/bal.d.ts.map +1 -0
- package/dist/cjs/bal.js +529 -0
- package/dist/cjs/bal.js.map +1 -0
- package/dist/cjs/binaryTree.d.ts +148 -0
- package/dist/cjs/binaryTree.d.ts.map +1 -0
- package/dist/cjs/binaryTree.js +240 -0
- package/dist/cjs/binaryTree.js.map +1 -0
- package/dist/cjs/blobs.d.ts +76 -0
- package/dist/cjs/blobs.d.ts.map +1 -0
- package/dist/cjs/blobs.js +175 -0
- package/dist/cjs/blobs.js.map +1 -0
- package/dist/cjs/bytes.d.ts +291 -0
- package/dist/cjs/bytes.d.ts.map +1 -0
- package/dist/cjs/bytes.js +606 -0
- package/dist/cjs/bytes.js.map +1 -0
- package/dist/cjs/constants.d.ts +91 -0
- package/dist/cjs/constants.d.ts.map +1 -0
- package/dist/cjs/constants.js +97 -0
- package/dist/cjs/constants.js.map +1 -0
- package/dist/cjs/db.d.ts +65 -0
- package/dist/cjs/db.d.ts.map +1 -0
- package/dist/cjs/db.js +14 -0
- package/dist/cjs/db.js.map +1 -0
- package/dist/cjs/env.d.ts +9 -0
- package/dist/cjs/env.d.ts.map +1 -0
- package/dist/cjs/env.js +13 -0
- package/dist/cjs/env.js.map +1 -0
- package/dist/cjs/errors.d.ts +3 -0
- package/dist/cjs/errors.d.ts.map +1 -0
- package/dist/cjs/errors.js +19 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/helpers.d.ts +21 -0
- package/dist/cjs/helpers.d.ts.map +1 -0
- package/dist/cjs/helpers.js +50 -0
- package/dist/cjs/helpers.js.map +1 -0
- package/dist/cjs/index.d.ts +67 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +93 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/internal.d.ts +72 -0
- package/dist/cjs/internal.d.ts.map +1 -0
- package/dist/cjs/internal.js +182 -0
- package/dist/cjs/internal.js.map +1 -0
- package/dist/cjs/kzg.d.ts +14 -0
- package/dist/cjs/kzg.d.ts.map +1 -0
- package/dist/cjs/kzg.js +3 -0
- package/dist/cjs/kzg.js.map +1 -0
- package/dist/cjs/lock.d.ts +15 -0
- package/dist/cjs/lock.d.ts.map +1 -0
- package/dist/cjs/lock.js +45 -0
- package/dist/cjs/lock.js.map +1 -0
- package/dist/cjs/mapDB.d.ts +17 -0
- package/dist/cjs/mapDB.d.ts.map +1 -0
- package/dist/cjs/mapDB.js +46 -0
- package/dist/cjs/mapDB.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/provider.d.ts +46 -0
- package/dist/cjs/provider.d.ts.map +1 -0
- package/dist/cjs/provider.js +84 -0
- package/dist/cjs/provider.js.map +1 -0
- package/dist/cjs/request.d.ts +20 -0
- package/dist/cjs/request.d.ts.map +1 -0
- package/dist/cjs/request.js +35 -0
- package/dist/cjs/request.js.map +1 -0
- package/dist/cjs/signature.d.ts +47 -0
- package/dist/cjs/signature.d.ts.map +1 -0
- package/dist/cjs/signature.js +147 -0
- package/dist/cjs/signature.js.map +1 -0
- package/dist/cjs/tasks.d.ts +32 -0
- package/dist/cjs/tasks.d.ts.map +1 -0
- package/dist/cjs/tasks.js +51 -0
- package/dist/cjs/tasks.js.map +1 -0
- package/dist/cjs/types.d.ts +64 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +78 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/units.d.ts +22 -0
- package/dist/cjs/units.d.ts.map +1 -0
- package/dist/cjs/units.js +51 -0
- package/dist/cjs/units.js.map +1 -0
- package/dist/cjs/withdrawal.d.ts +72 -0
- package/dist/cjs/withdrawal.d.ts.map +1 -0
- package/dist/cjs/withdrawal.js +93 -0
- package/dist/cjs/withdrawal.js.map +1 -0
- package/dist/esm/account.d.ts +165 -0
- package/dist/esm/account.d.ts.map +1 -0
- package/dist/esm/account.js +505 -0
- package/dist/esm/account.js.map +1 -0
- package/dist/esm/address.d.ts +67 -0
- package/dist/esm/address.d.ts.map +1 -0
- package/dist/esm/address.js +125 -0
- package/dist/esm/address.js.map +1 -0
- package/dist/esm/authorization.d.ts +41 -0
- package/dist/esm/authorization.d.ts.map +1 -0
- package/dist/esm/authorization.js +126 -0
- package/dist/esm/authorization.js.map +1 -0
- package/dist/esm/bal.d.ts +129 -0
- package/dist/esm/bal.d.ts.map +1 -0
- package/dist/esm/bal.js +522 -0
- package/dist/esm/bal.js.map +1 -0
- package/dist/esm/binaryTree.d.ts +148 -0
- package/dist/esm/binaryTree.d.ts.map +1 -0
- package/dist/esm/binaryTree.js +226 -0
- package/dist/esm/binaryTree.js.map +1 -0
- package/dist/esm/blobs.d.ts +76 -0
- package/dist/esm/blobs.d.ts.map +1 -0
- package/dist/esm/blobs.js +163 -0
- package/dist/esm/blobs.js.map +1 -0
- package/dist/esm/bytes.d.ts +291 -0
- package/dist/esm/bytes.d.ts.map +1 -0
- package/dist/esm/bytes.js +562 -0
- package/dist/esm/bytes.js.map +1 -0
- package/dist/esm/constants.d.ts +91 -0
- package/dist/esm/constants.d.ts.map +1 -0
- package/dist/esm/constants.js +94 -0
- package/dist/esm/constants.js.map +1 -0
- package/dist/esm/db.d.ts +65 -0
- package/dist/esm/db.d.ts.map +1 -0
- package/dist/esm/db.js +11 -0
- package/dist/esm/db.js.map +1 -0
- package/dist/esm/env.d.ts +9 -0
- package/dist/esm/env.d.ts.map +1 -0
- package/dist/esm/env.js +9 -0
- package/dist/esm/env.js.map +1 -0
- package/dist/esm/errors.d.ts +3 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +14 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/helpers.d.ts +21 -0
- package/dist/esm/helpers.d.ts.map +1 -0
- package/dist/esm/helpers.js +43 -0
- package/dist/esm/helpers.js.map +1 -0
- package/dist/esm/index.d.ts +67 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +67 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal.d.ts +72 -0
- package/dist/esm/internal.d.ts.map +1 -0
- package/dist/esm/internal.js +170 -0
- package/dist/esm/internal.js.map +1 -0
- package/dist/esm/kzg.d.ts +14 -0
- package/dist/esm/kzg.d.ts.map +1 -0
- package/dist/esm/kzg.js +2 -0
- package/dist/esm/kzg.js.map +1 -0
- package/dist/esm/lock.d.ts +15 -0
- package/dist/esm/lock.d.ts.map +1 -0
- package/dist/esm/lock.js +41 -0
- package/dist/esm/lock.js.map +1 -0
- package/dist/esm/mapDB.d.ts +17 -0
- package/dist/esm/mapDB.d.ts.map +1 -0
- package/dist/esm/mapDB.js +42 -0
- package/dist/esm/mapDB.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/provider.d.ts +46 -0
- package/dist/esm/provider.d.ts.map +1 -0
- package/dist/esm/provider.js +79 -0
- package/dist/esm/provider.js.map +1 -0
- package/dist/esm/request.d.ts +20 -0
- package/dist/esm/request.d.ts.map +1 -0
- package/dist/esm/request.js +30 -0
- package/dist/esm/request.js.map +1 -0
- package/dist/esm/signature.d.ts +47 -0
- package/dist/esm/signature.d.ts.map +1 -0
- package/dist/esm/signature.js +137 -0
- package/dist/esm/signature.js.map +1 -0
- package/dist/esm/tasks.d.ts +32 -0
- package/dist/esm/tasks.d.ts.map +1 -0
- package/dist/esm/tasks.js +47 -0
- package/dist/esm/tasks.js.map +1 -0
- package/dist/esm/types.d.ts +64 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +71 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/units.d.ts +22 -0
- package/dist/esm/units.d.ts.map +1 -0
- package/dist/esm/units.js +46 -0
- package/dist/esm/units.js.map +1 -0
- package/dist/esm/withdrawal.d.ts +72 -0
- package/dist/esm/withdrawal.d.ts.map +1 -0
- package/dist/esm/withdrawal.js +86 -0
- package/dist/esm/withdrawal.js.map +1 -0
- package/dist/tsconfig.prod.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.prod.esm.tsbuildinfo +1 -0
- package/package.json +116 -0
- package/src/account.ts +630 -0
- package/src/address.ts +158 -0
- package/src/authorization.ts +180 -0
- package/src/bal.ts +761 -0
- package/src/binaryTree.ts +353 -0
- package/src/blobs.ts +209 -0
- package/src/bytes.ts +659 -0
- package/src/constants.ts +125 -0
- package/src/db.ts +86 -0
- package/src/env.ts +9 -0
- package/src/errors.ts +28 -0
- package/src/helpers.ts +46 -0
- package/src/index.ts +88 -0
- package/src/internal.ts +212 -0
- package/src/kzg.ts +24 -0
- package/src/lock.ts +42 -0
- package/src/mapDB.ts +57 -0
- package/src/provider.ts +109 -0
- package/src/request.ts +48 -0
- package/src/signature.ts +202 -0
- package/src/tasks.ts +59 -0
- package/src/types.ts +177 -0
- package/src/units.ts +56 -0
- package/src/withdrawal.ts +133 -0
package/src/bal.ts
ADDED
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
import { RLP } from '@feelyourprotocol/rlp'
|
|
2
|
+
import { keccak_256 } from '@noble/hashes/sha3.js'
|
|
3
|
+
import {
|
|
4
|
+
bigIntToBytes,
|
|
5
|
+
bigIntToHex,
|
|
6
|
+
bytesToHex,
|
|
7
|
+
bytesToInt,
|
|
8
|
+
hexToBigInt,
|
|
9
|
+
hexToBytes,
|
|
10
|
+
} from './bytes.ts'
|
|
11
|
+
import { SYSTEM_ADDRESS } from './constants.ts'
|
|
12
|
+
import { padToEven } from './internal.ts'
|
|
13
|
+
import type { PrefixedHexString } from './types.ts'
|
|
14
|
+
|
|
15
|
+
// Base types which can be used for JSON, internal representation and raw format.
|
|
16
|
+
type BALAddressHex = PrefixedHexString // bytes20
|
|
17
|
+
type BALStorageKeyBytes = Uint8Array // uint256
|
|
18
|
+
type BALStorageKeyHex = PrefixedHexString // uint256
|
|
19
|
+
type BALStorageValueBytes = Uint8Array // uint256
|
|
20
|
+
type BALStorageValueHex = PrefixedHexString // uint256 as hex
|
|
21
|
+
type BALAccessIndexNumber = number // uint16
|
|
22
|
+
type BALAccessIndexHex = PrefixedHexString // uint16 as hex d
|
|
23
|
+
type BALBalanceBigInt = bigint // uint256 as bigint
|
|
24
|
+
type BALBalanceHex = PrefixedHexString // uint256 as hex
|
|
25
|
+
type BALNonceBigInt = bigint // uint64 as bigint
|
|
26
|
+
type BALNonceHex = PrefixedHexString // uint64 as hex
|
|
27
|
+
type BALByteCodeBytes = Uint8Array // bytes
|
|
28
|
+
type BALByteCodeHex = PrefixedHexString // bytes as hex
|
|
29
|
+
|
|
30
|
+
// Change types which can be used for internal representation and raw format.
|
|
31
|
+
type BALRawStorageChange = [BALAccessIndexNumber, BALStorageValueBytes]
|
|
32
|
+
type BALRawBalanceChange = [BALAccessIndexNumber, BALBalanceHex]
|
|
33
|
+
type BALRawNonceChange = [BALAccessIndexNumber, BALNonceHex]
|
|
34
|
+
type BALRawCodeChange = [BALAccessIndexNumber, BALByteCodeBytes]
|
|
35
|
+
type BALRawSlotChanges = [BALStorageKeyHex, BALRawStorageChange[]]
|
|
36
|
+
|
|
37
|
+
// Core data format for the raw format.
|
|
38
|
+
type BALRawAccountChanges = [
|
|
39
|
+
BALAddressHex,
|
|
40
|
+
BALRawSlotChanges[],
|
|
41
|
+
BALStorageKeyHex[],
|
|
42
|
+
BALRawBalanceChange[],
|
|
43
|
+
BALRawNonceChange[],
|
|
44
|
+
BALRawCodeChange[],
|
|
45
|
+
]
|
|
46
|
+
type BALRawBlockAccessList = BALRawAccountChanges[]
|
|
47
|
+
|
|
48
|
+
// Internal representation of the access list.
|
|
49
|
+
export type Accesses = Record<
|
|
50
|
+
BALAddressHex,
|
|
51
|
+
{
|
|
52
|
+
nonceChanges: Map<BALAccessIndexNumber, BALNonceHex>
|
|
53
|
+
balanceChanges: Map<BALAccessIndexNumber, BALBalanceHex>
|
|
54
|
+
codeChanges: BALRawCodeChange[]
|
|
55
|
+
storageChanges: Record<BALStorageKeyHex, BALRawStorageChange[]>
|
|
56
|
+
storageReads: Set<BALStorageKeyHex>
|
|
57
|
+
}
|
|
58
|
+
>
|
|
59
|
+
|
|
60
|
+
// JSON representation types (all numeric values as hex strings for JSON serialization)
|
|
61
|
+
// JSON change types
|
|
62
|
+
interface BALJSONBalanceChange {
|
|
63
|
+
blockAccessIndex: BALAccessIndexHex
|
|
64
|
+
postBalance: BALBalanceHex
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface BALJSONNonceChange {
|
|
68
|
+
blockAccessIndex: BALAccessIndexHex
|
|
69
|
+
postNonce: BALNonceHex
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface BALJSONCodeChange {
|
|
73
|
+
blockAccessIndex: BALAccessIndexHex
|
|
74
|
+
newCode: BALByteCodeHex
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface BALJSONStorageChange {
|
|
78
|
+
blockAccessIndex: BALAccessIndexHex
|
|
79
|
+
postValue: BALStorageValueHex
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface BALJSONSlotChanges {
|
|
83
|
+
slot: BALStorageKeyHex
|
|
84
|
+
slotChanges: BALJSONStorageChange[]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// JSON representation of account changes
|
|
88
|
+
interface BALJSONAccountChanges {
|
|
89
|
+
address: BALAddressHex
|
|
90
|
+
balanceChanges: BALJSONBalanceChange[]
|
|
91
|
+
nonceChanges: BALJSONNonceChange[]
|
|
92
|
+
codeChanges: BALJSONCodeChange[]
|
|
93
|
+
storageChanges: BALJSONSlotChanges[]
|
|
94
|
+
storageReads: BALStorageKeyHex[]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Top level JSON type
|
|
98
|
+
export type BALJSONBlockAccessList = BALJSONAccountChanges[]
|
|
99
|
+
|
|
100
|
+
// Re-export JSON types for external use
|
|
101
|
+
export type {
|
|
102
|
+
BALJSONAccountChanges,
|
|
103
|
+
BALJSONStorageChange,
|
|
104
|
+
BALJSONSlotChanges,
|
|
105
|
+
BALJSONBalanceChange,
|
|
106
|
+
BALJSONNonceChange,
|
|
107
|
+
BALJSONCodeChange,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Structural helper class for block level access lists
|
|
112
|
+
*
|
|
113
|
+
* EXPERIMENTAL: DO NOT USE IN PRODUCTION!
|
|
114
|
+
*/
|
|
115
|
+
export class BlockLevelAccessList {
|
|
116
|
+
public accesses: Accesses
|
|
117
|
+
public blockAccessIndex: number
|
|
118
|
+
private checkpoints: { accesses: Accesses; blockAccessIndex: number }[] = []
|
|
119
|
+
// Track original (pre-transaction) balances for net-zero detection
|
|
120
|
+
private originalBalances: Map<BALAddressHex, bigint> = new Map()
|
|
121
|
+
// Track original code at the start of each blockAccessIndex for each address
|
|
122
|
+
// Key format: `${address}-${blockAccessIndex}`
|
|
123
|
+
private originalCodesAtIndex: Map<string, Uint8Array> = new Map()
|
|
124
|
+
constructor(accesses: Accesses = {}) {
|
|
125
|
+
this.accesses = accesses
|
|
126
|
+
this.blockAccessIndex = 0
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Serializes the block level access list to RLP.
|
|
131
|
+
*
|
|
132
|
+
* @returns the RLP encoded block level access list
|
|
133
|
+
*/
|
|
134
|
+
public serialize(): Uint8Array {
|
|
135
|
+
return RLP.encode(this.raw())
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* This hash is used in the block header
|
|
140
|
+
*
|
|
141
|
+
* @returns the hash of the serialized block level access list
|
|
142
|
+
*/
|
|
143
|
+
public hash(): Uint8Array {
|
|
144
|
+
return keccak_256(this.serialize())
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
public checkpoint(): void {
|
|
148
|
+
this.checkpoints.push({
|
|
149
|
+
accesses: this.cloneAccesses(this.accesses),
|
|
150
|
+
blockAccessIndex: this.blockAccessIndex,
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
public commit(): void {
|
|
155
|
+
if (this.checkpoints.length > 0) {
|
|
156
|
+
this.checkpoints.pop()
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public revert(): void {
|
|
161
|
+
const snapshot = this.checkpoints.pop()
|
|
162
|
+
if (!snapshot) {
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
const current = this.accesses
|
|
166
|
+
this.accesses = snapshot.accesses
|
|
167
|
+
this.blockAccessIndex = snapshot.blockAccessIndex
|
|
168
|
+
|
|
169
|
+
// Preserve address touches and storage reads across reverts.
|
|
170
|
+
// EIP-7928: When storage writes are reverted, the slot keys MUST still
|
|
171
|
+
// appear in storageReads since the slots were accessed (SSTORE reads
|
|
172
|
+
// the current value for gas calculation).
|
|
173
|
+
for (const [address, access] of Object.entries(current)) {
|
|
174
|
+
if (this.accesses[address as BALAddressHex] === undefined) {
|
|
175
|
+
// Collect both explicit reads and slots that were written (but will be reverted)
|
|
176
|
+
const allReads = new Set(access.storageReads)
|
|
177
|
+
for (const slot of Object.keys(access.storageChanges)) {
|
|
178
|
+
allReads.add(slot as BALStorageKeyHex)
|
|
179
|
+
}
|
|
180
|
+
this.accesses[address as BALAddressHex] = {
|
|
181
|
+
nonceChanges: new Map(),
|
|
182
|
+
balanceChanges: new Map(),
|
|
183
|
+
codeChanges: [],
|
|
184
|
+
storageChanges: {},
|
|
185
|
+
storageReads: allReads,
|
|
186
|
+
}
|
|
187
|
+
continue
|
|
188
|
+
}
|
|
189
|
+
const target = this.accesses[address as BALAddressHex]
|
|
190
|
+
// Preserve explicit storageReads
|
|
191
|
+
for (const slot of access.storageReads) {
|
|
192
|
+
target.storageReads.add(slot)
|
|
193
|
+
}
|
|
194
|
+
// EIP-7928: Convert reverted storageChanges to storageReads
|
|
195
|
+
for (const slot of Object.keys(access.storageChanges)) {
|
|
196
|
+
// Only add to reads if not already in the target's storageChanges
|
|
197
|
+
// (a successful write subsumes a read)
|
|
198
|
+
if (target.storageChanges[slot as BALStorageKeyHex] === undefined) {
|
|
199
|
+
target.storageReads.add(slot as BALStorageKeyHex)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private cloneAccesses(accesses: Accesses): Accesses {
|
|
206
|
+
const cloned: Accesses = {}
|
|
207
|
+
for (const [address, access] of Object.entries(accesses)) {
|
|
208
|
+
const storageChanges: Record<BALStorageKeyHex, BALRawStorageChange[]> = {}
|
|
209
|
+
for (const [slot, changes] of Object.entries(access.storageChanges)) {
|
|
210
|
+
storageChanges[slot as BALStorageKeyHex] = changes.map(
|
|
211
|
+
([index, value]) => [index, value] as BALRawStorageChange,
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
cloned[address as BALAddressHex] = {
|
|
215
|
+
nonceChanges: new Map(access.nonceChanges),
|
|
216
|
+
balanceChanges: new Map(access.balanceChanges),
|
|
217
|
+
codeChanges: access.codeChanges.map(([index, code]) => [index, code] as BALRawCodeChange),
|
|
218
|
+
storageChanges,
|
|
219
|
+
storageReads: new Set(access.storageReads),
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return cloned
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Returns the raw block level access list with values
|
|
227
|
+
* correctly sorted.
|
|
228
|
+
*
|
|
229
|
+
* @returns the raw block level access list
|
|
230
|
+
*/
|
|
231
|
+
public raw(): BALRawBlockAccessList {
|
|
232
|
+
const bal: BALRawBlockAccessList = []
|
|
233
|
+
|
|
234
|
+
for (const address of Object.keys(this.accesses)
|
|
235
|
+
.sort()
|
|
236
|
+
.filter((address) =>
|
|
237
|
+
shouldIncludeAddress(address as BALAddressHex, this.accesses[address as BALAddressHex]),
|
|
238
|
+
)) {
|
|
239
|
+
const data = this.accesses[address as BALAddressHex]
|
|
240
|
+
|
|
241
|
+
// Format storage changes: [slot, [[index, value], ...]]
|
|
242
|
+
// Normalize slot keys for canonical RLP encoding (0 -> empty bytes)
|
|
243
|
+
const storageChanges = (
|
|
244
|
+
Object.entries(data.storageChanges) as [BALStorageKeyHex, BALRawStorageChange[]][]
|
|
245
|
+
)
|
|
246
|
+
.sort((a, b) => compareLexicographicHexOrBytes(a[0], b[0]))
|
|
247
|
+
.map(([slot, changes]) => [
|
|
248
|
+
normalizeHexForRLP(slot),
|
|
249
|
+
changes
|
|
250
|
+
.sort((a, b) => a[0] - b[0])
|
|
251
|
+
.map(
|
|
252
|
+
([index, value]) =>
|
|
253
|
+
[index, normalizeBytesForRLPQuantity(value)] as BALRawStorageChange,
|
|
254
|
+
),
|
|
255
|
+
])
|
|
256
|
+
|
|
257
|
+
// Normalize storage reads for canonical RLP encoding (0 -> empty bytes)
|
|
258
|
+
const storageReads = Array.from(data.storageReads)
|
|
259
|
+
.map(normalizeHexForRLP)
|
|
260
|
+
.sort((a, b) => compareLexicographicHexOrBytes(a, b))
|
|
261
|
+
|
|
262
|
+
const balanceChanges = Array.from(data.balanceChanges.entries())
|
|
263
|
+
.sort(([a], [b]) => a - b)
|
|
264
|
+
.map(
|
|
265
|
+
([index, balance]) => [index, normalizeQuantityHexForRLP(balance)] as BALRawBalanceChange,
|
|
266
|
+
)
|
|
267
|
+
const nonceChanges = Array.from(data.nonceChanges.entries())
|
|
268
|
+
.sort(([a], [b]) => a - b)
|
|
269
|
+
.map(([index, nonce]) => [index, normalizeQuantityHexForRLP(nonce)] as BALRawNonceChange)
|
|
270
|
+
const codeChanges = [...data.codeChanges].sort(([a], [b]) => a - b)
|
|
271
|
+
bal.push([
|
|
272
|
+
address as BALAddressHex,
|
|
273
|
+
storageChanges,
|
|
274
|
+
storageReads,
|
|
275
|
+
balanceChanges,
|
|
276
|
+
nonceChanges,
|
|
277
|
+
codeChanges,
|
|
278
|
+
] as BALRawAccountChanges)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return bal
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
public addAddress(address: BALAddressHex): void {
|
|
285
|
+
if (this.accesses[address] !== undefined) {
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
this.accesses[address] = {
|
|
289
|
+
storageChanges: {},
|
|
290
|
+
storageReads: new Set(),
|
|
291
|
+
balanceChanges: new Map(),
|
|
292
|
+
nonceChanges: new Map(),
|
|
293
|
+
codeChanges: [],
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
public addStorageWrite(
|
|
298
|
+
address: BALAddressHex,
|
|
299
|
+
storageKey: BALStorageKeyBytes,
|
|
300
|
+
value: BALStorageValueBytes,
|
|
301
|
+
blockAccessIndex: BALAccessIndexNumber,
|
|
302
|
+
originalValue?: BALStorageValueBytes,
|
|
303
|
+
): void {
|
|
304
|
+
const strippedKey = normalizeStorageKeyHex(bytesToHex(stripLeadingZeros(storageKey)))
|
|
305
|
+
const strippedValue = stripLeadingZeros(value)
|
|
306
|
+
const strippedOriginal = originalValue ? stripLeadingZeros(originalValue) : undefined
|
|
307
|
+
const isZeroWrite = strippedValue.length === 0
|
|
308
|
+
|
|
309
|
+
// EIP-7928: Check if this is a no-op write (value equals pre-transaction value)
|
|
310
|
+
// No-op writes should be recorded as reads, not changes.
|
|
311
|
+
// Note: Both empty arrays (zero values) compare equal via bytesToHex
|
|
312
|
+
let isNoOp = false
|
|
313
|
+
if (strippedOriginal !== undefined) {
|
|
314
|
+
// We have original value - compare properly
|
|
315
|
+
isNoOp = bytesToHex(strippedValue) === bytesToHex(strippedOriginal)
|
|
316
|
+
} else if (isZeroWrite) {
|
|
317
|
+
// No original value provided and writing zero - likely a no-op for system contracts
|
|
318
|
+
// reading empty slots. Treat as read for safety.
|
|
319
|
+
isNoOp = true
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Only no-op writes (writing same value as original) are treated as reads
|
|
323
|
+
// EIP-7928: Zeroing a slot (pre-value exists, post-value is zero) IS a write
|
|
324
|
+
if (isNoOp) {
|
|
325
|
+
// EIP-7928: If a slot is written back to its original value (net-zero change),
|
|
326
|
+
// it should appear in storageReads, not storageChanges.
|
|
327
|
+
// This handles nested calls where intermediate frames write different values
|
|
328
|
+
// but the final value equals the original.
|
|
329
|
+
if (this.accesses[address] !== undefined) {
|
|
330
|
+
// Remove any existing storageChanges for this slot since final == original
|
|
331
|
+
delete this.accesses[address].storageChanges[strippedKey]
|
|
332
|
+
}
|
|
333
|
+
this.addStorageRead(address, storageKey)
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
if (this.accesses[address] === undefined) {
|
|
337
|
+
this.addAddress(address)
|
|
338
|
+
}
|
|
339
|
+
if (this.accesses[address].storageChanges[strippedKey] === undefined) {
|
|
340
|
+
this.accesses[address].storageChanges[strippedKey] = []
|
|
341
|
+
}
|
|
342
|
+
// For zero values, strippedValue is empty - this is correct for RLP encoding
|
|
343
|
+
this.accesses[address].storageChanges[strippedKey].push([blockAccessIndex, strippedValue])
|
|
344
|
+
// Per EIP-7928: A successful storage write subsumes any prior read of the same slot.
|
|
345
|
+
// Remove the slot from storageReads since it's now in storageChanges.
|
|
346
|
+
this.accesses[address].storageReads.delete(strippedKey)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
public addStorageRead(address: BALAddressHex, storageKey: BALStorageKeyBytes): void {
|
|
350
|
+
if (this.accesses[address] === undefined) {
|
|
351
|
+
this.addAddress(address)
|
|
352
|
+
}
|
|
353
|
+
const strippedKey = normalizeStorageKeyHex(bytesToHex(stripLeadingZeros(storageKey)))
|
|
354
|
+
// Per EIP-7928: Don't add to storageReads if the slot was already written.
|
|
355
|
+
// A write subsumes any reads of the same slot.
|
|
356
|
+
if (this.accesses[address].storageChanges[strippedKey] === undefined) {
|
|
357
|
+
this.accesses[address].storageReads.add(strippedKey)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
public addBalanceChange(
|
|
362
|
+
address: BALAddressHex,
|
|
363
|
+
balance: BALBalanceBigInt,
|
|
364
|
+
blockAccessIndex: BALAccessIndexNumber,
|
|
365
|
+
originalBalance?: BALBalanceBigInt,
|
|
366
|
+
): void {
|
|
367
|
+
if (this.accesses[address] === undefined) {
|
|
368
|
+
this.addAddress(address)
|
|
369
|
+
}
|
|
370
|
+
// EIP-7928: Track the original (pre-transaction) balance for net-zero detection
|
|
371
|
+
// Only set if not already tracked (first call wins)
|
|
372
|
+
if (originalBalance !== undefined && !this.originalBalances.has(address)) {
|
|
373
|
+
this.originalBalances.set(address, originalBalance)
|
|
374
|
+
}
|
|
375
|
+
this.accesses[address].balanceChanges.set(
|
|
376
|
+
blockAccessIndex,
|
|
377
|
+
padToEvenHex(bytesToHex(stripLeadingZeros(bigIntToBytes(balance)))),
|
|
378
|
+
)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* EIP-7928: Remove balance changes for addresses where final balance equals first balance.
|
|
383
|
+
* Call this at the end of each transaction to clean up net-zero balance changes.
|
|
384
|
+
*/
|
|
385
|
+
public cleanupNetZeroBalanceChanges(): void {
|
|
386
|
+
for (const [address, originalBalance] of this.originalBalances.entries()) {
|
|
387
|
+
const access = this.accesses[address]
|
|
388
|
+
if (access === undefined || access.balanceChanges.size === 0) {
|
|
389
|
+
continue
|
|
390
|
+
}
|
|
391
|
+
// Get the final balance (last entry in the balanceChanges map)
|
|
392
|
+
const entries = Array.from(access.balanceChanges.values())
|
|
393
|
+
const finalBalanceHex = entries[entries.length - 1]
|
|
394
|
+
const finalBalance =
|
|
395
|
+
finalBalanceHex === '0x' ? BigInt(0) : BigInt(`0x${finalBalanceHex.replace(/^0x/, '')}`)
|
|
396
|
+
|
|
397
|
+
// EIP-7928: If final balance == original balance, remove all balanceChanges
|
|
398
|
+
// for the current blockAccessIndex only, but keep prior tx entries.
|
|
399
|
+
if (finalBalance === originalBalance) {
|
|
400
|
+
access.balanceChanges.delete(this.blockAccessIndex)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
// Clear the tracking map for the next transaction
|
|
404
|
+
this.originalBalances.clear()
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
public addNonceChange(
|
|
408
|
+
address: BALAddressHex,
|
|
409
|
+
nonce: BALNonceBigInt,
|
|
410
|
+
blockAccessIndex: BALAccessIndexNumber,
|
|
411
|
+
): void {
|
|
412
|
+
if (this.accesses[address] === undefined) {
|
|
413
|
+
this.addAddress(address)
|
|
414
|
+
}
|
|
415
|
+
this.accesses[address].nonceChanges.set(blockAccessIndex, padToEvenHex(bigIntToHex(nonce)))
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
public addCodeChange(
|
|
419
|
+
address: BALAddressHex,
|
|
420
|
+
code: BALByteCodeBytes,
|
|
421
|
+
blockAccessIndex: BALAccessIndexNumber,
|
|
422
|
+
originalCode?: BALByteCodeBytes,
|
|
423
|
+
): void {
|
|
424
|
+
if (this.accesses[address] === undefined) {
|
|
425
|
+
this.addAddress(address)
|
|
426
|
+
}
|
|
427
|
+
const codeChanges = this.accesses[address].codeChanges
|
|
428
|
+
|
|
429
|
+
// Track the original code at the start of this blockAccessIndex
|
|
430
|
+
const trackingKey = `${address}-${blockAccessIndex}`
|
|
431
|
+
if (!this.originalCodesAtIndex.has(trackingKey) && originalCode !== undefined) {
|
|
432
|
+
this.originalCodesAtIndex.set(trackingKey, originalCode)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Get the original code at the start of this blockAccessIndex
|
|
436
|
+
const originalCodeAtIndex = this.originalCodesAtIndex.get(trackingKey)
|
|
437
|
+
|
|
438
|
+
// Check if there's already a code change at this blockAccessIndex
|
|
439
|
+
const existingIndex = codeChanges.findIndex(([idx]) => idx === blockAccessIndex)
|
|
440
|
+
if (existingIndex !== -1) {
|
|
441
|
+
// Check if the new code equals the original code at start of this blockAccessIndex
|
|
442
|
+
// If so, remove the entry (net-zero change within this blockAccessIndex)
|
|
443
|
+
if (
|
|
444
|
+
originalCodeAtIndex !== undefined &&
|
|
445
|
+
bytesToHex(code) === bytesToHex(originalCodeAtIndex)
|
|
446
|
+
) {
|
|
447
|
+
codeChanges.splice(existingIndex, 1)
|
|
448
|
+
} else {
|
|
449
|
+
// Update the existing entry with the new code
|
|
450
|
+
codeChanges[existingIndex] = [blockAccessIndex, code]
|
|
451
|
+
}
|
|
452
|
+
} else {
|
|
453
|
+
// Add new entry, but only if code is actually different from originalCode
|
|
454
|
+
if (originalCode !== undefined && bytesToHex(code) === bytesToHex(originalCode)) {
|
|
455
|
+
// No actual change, don't record
|
|
456
|
+
return
|
|
457
|
+
}
|
|
458
|
+
codeChanges.push([blockAccessIndex, code])
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* EIP-7928: For selfdestructed accounts, drop all state changes while
|
|
464
|
+
* preserving read footprints. Any storageChanges are converted to storageReads.
|
|
465
|
+
*
|
|
466
|
+
* Per EIP-7928: "if the account had a positive balance pre-transaction,
|
|
467
|
+
* the balance change to zero MUST be recorded."
|
|
468
|
+
*/
|
|
469
|
+
/**
|
|
470
|
+
* Converts the internal representation to the JSON format (BALJSONBlockAccessList).
|
|
471
|
+
* Inverse of createBlockLevelAccessListFromJSON().
|
|
472
|
+
*/
|
|
473
|
+
public toJSON(): BALJSONBlockAccessList {
|
|
474
|
+
const result: BALJSONBlockAccessList = []
|
|
475
|
+
|
|
476
|
+
for (const [address, access] of Object.entries(this.accesses)
|
|
477
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
478
|
+
.filter(([address, access]) => shouldIncludeAddress(address as BALAddressHex, access))) {
|
|
479
|
+
const storageChanges: BALJSONSlotChanges[] = (
|
|
480
|
+
Object.entries(access.storageChanges) as [BALStorageKeyHex, BALRawStorageChange[]][]
|
|
481
|
+
)
|
|
482
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
483
|
+
.map(([slot, changes]) => ({
|
|
484
|
+
slot,
|
|
485
|
+
slotChanges: changes
|
|
486
|
+
.sort((a, b) => a[0] - b[0])
|
|
487
|
+
.map(([index, value]) => ({
|
|
488
|
+
blockAccessIndex: indexToHex(index),
|
|
489
|
+
postValue: padToEvenHex(bytesToHex(value)),
|
|
490
|
+
})),
|
|
491
|
+
}))
|
|
492
|
+
|
|
493
|
+
const storageReads: BALStorageKeyHex[] = Array.from(access.storageReads).sort((a, b) =>
|
|
494
|
+
Number(
|
|
495
|
+
(a === '0x' ? 0n : hexToBigInt(a as `0x${string}`)) -
|
|
496
|
+
(b === '0x' ? 0n : hexToBigInt(b as `0x${string}`)),
|
|
497
|
+
),
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
const balanceChanges: BALJSONBalanceChange[] = Array.from(access.balanceChanges.entries())
|
|
501
|
+
.sort(([a], [b]) => a - b)
|
|
502
|
+
.map(([index, balance]) => ({
|
|
503
|
+
blockAccessIndex: indexToHex(index),
|
|
504
|
+
postBalance: balance,
|
|
505
|
+
}))
|
|
506
|
+
|
|
507
|
+
const nonceChanges: BALJSONNonceChange[] = Array.from(access.nonceChanges.entries())
|
|
508
|
+
.sort(([a], [b]) => a - b)
|
|
509
|
+
.map(([index, nonce]) => ({
|
|
510
|
+
blockAccessIndex: indexToHex(index),
|
|
511
|
+
postNonce: nonce,
|
|
512
|
+
}))
|
|
513
|
+
|
|
514
|
+
const codeChanges: BALJSONCodeChange[] = access.codeChanges.map(([index, code]) => ({
|
|
515
|
+
blockAccessIndex: indexToHex(index),
|
|
516
|
+
newCode: bytesToHex(code),
|
|
517
|
+
}))
|
|
518
|
+
|
|
519
|
+
result.push({
|
|
520
|
+
address: address as BALAddressHex,
|
|
521
|
+
nonceChanges,
|
|
522
|
+
balanceChanges,
|
|
523
|
+
codeChanges,
|
|
524
|
+
storageChanges,
|
|
525
|
+
storageReads,
|
|
526
|
+
})
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return result
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
public cleanupSelfdestructed(addresses: Array<BALAddressHex>): void {
|
|
533
|
+
for (const address of addresses) {
|
|
534
|
+
const access = this.accesses[address]
|
|
535
|
+
if (access === undefined) {
|
|
536
|
+
continue
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Convert any storageChanges into storageReads
|
|
540
|
+
for (const slot of Object.keys(access.storageChanges)) {
|
|
541
|
+
access.storageReads.add(slot as BALStorageKeyHex)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
access.storageChanges = {}
|
|
545
|
+
access.nonceChanges.clear()
|
|
546
|
+
access.codeChanges = []
|
|
547
|
+
|
|
548
|
+
// EIP-7928: If the account had a positive pre-transaction balance,
|
|
549
|
+
// the balance change to zero MUST be recorded.
|
|
550
|
+
// The balance change to 0 is already added during SELFDESTRUCT execution.
|
|
551
|
+
// We only clear balance changes if pre-transaction balance was 0 (no actual change).
|
|
552
|
+
const originalBalance = this.originalBalances.get(address)
|
|
553
|
+
if (originalBalance === undefined || originalBalance === BigInt(0)) {
|
|
554
|
+
// Pre-transaction balance was 0 or unknown - clear balance changes
|
|
555
|
+
// (0 -> 0 is no change, so nothing to record)
|
|
556
|
+
access.balanceChanges.clear()
|
|
557
|
+
}
|
|
558
|
+
// If originalBalance > 0, keep the balance changes (which should show balance = 0)
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function compareLexicographicHexOrBytes(
|
|
564
|
+
a: PrefixedHexString | Uint8Array,
|
|
565
|
+
b: PrefixedHexString | Uint8Array,
|
|
566
|
+
): number {
|
|
567
|
+
const aBytes = a instanceof Uint8Array ? a : hexToBytes(a)
|
|
568
|
+
const bBytes = b instanceof Uint8Array ? b : hexToBytes(b)
|
|
569
|
+
const minLength = Math.min(aBytes.length, bBytes.length)
|
|
570
|
+
for (let i = 0; i < minLength; i++) {
|
|
571
|
+
if (aBytes[i] < bBytes[i]) return -1
|
|
572
|
+
if (aBytes[i] > bBytes[i]) return 1
|
|
573
|
+
}
|
|
574
|
+
if (aBytes.length < bBytes.length) return -1
|
|
575
|
+
if (aBytes.length > bBytes.length) return 1
|
|
576
|
+
return 0
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
export function createBlockLevelAccessList(): BlockLevelAccessList {
|
|
580
|
+
return new BlockLevelAccessList()
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
export function createBlockLevelAccessListFromJSON(
|
|
584
|
+
json: BALJSONBlockAccessList,
|
|
585
|
+
): BlockLevelAccessList {
|
|
586
|
+
const bal = new BlockLevelAccessList()
|
|
587
|
+
|
|
588
|
+
for (const account of json) {
|
|
589
|
+
bal.addAddress(account.address)
|
|
590
|
+
const access = bal.accesses[account.address]
|
|
591
|
+
|
|
592
|
+
for (const slotChange of account.storageChanges) {
|
|
593
|
+
const normalizedSlot = normalizeStorageKeyHex(slotChange.slot)
|
|
594
|
+
if (access.storageChanges[normalizedSlot] === undefined) {
|
|
595
|
+
access.storageChanges[normalizedSlot] = []
|
|
596
|
+
}
|
|
597
|
+
for (const change of slotChange.slotChanges) {
|
|
598
|
+
access.storageChanges[normalizedSlot].push([
|
|
599
|
+
parseInt(change.blockAccessIndex, 16),
|
|
600
|
+
hexToBytes(change.postValue),
|
|
601
|
+
])
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
for (const slot of account.storageReads) {
|
|
606
|
+
access.storageReads.add(normalizeStorageKeyHex(slot))
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
for (const change of account.balanceChanges) {
|
|
610
|
+
access.balanceChanges.set(
|
|
611
|
+
parseInt(change.blockAccessIndex, 16),
|
|
612
|
+
padToEvenHex(change.postBalance),
|
|
613
|
+
)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
for (const change of account.nonceChanges) {
|
|
617
|
+
access.nonceChanges.set(parseInt(change.blockAccessIndex, 16), padToEvenHex(change.postNonce))
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
for (const change of account.codeChanges) {
|
|
621
|
+
access.codeChanges.push([parseInt(change.blockAccessIndex, 16), hexToBytes(change.newCode)])
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return bal
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Normalizes a quantity-like hex string for canonical RLP encoding.
|
|
630
|
+
* Integer fields in the BAL use minimal big-endian encoding, so leading zero bytes
|
|
631
|
+
* are stripped and zero is encoded as empty bytes.
|
|
632
|
+
*/
|
|
633
|
+
function normalizeHexForRLP(hex: PrefixedHexString): PrefixedHexString | Uint8Array {
|
|
634
|
+
const stripped = hex.slice(2).replace(/^0+/, '')
|
|
635
|
+
if (stripped === '') {
|
|
636
|
+
return Uint8Array.from([])
|
|
637
|
+
}
|
|
638
|
+
return `0x${padToEven(stripped)}` as PrefixedHexString
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function normalizeBytesForRLPQuantity(bytes: Uint8Array): Uint8Array {
|
|
642
|
+
return stripLeadingZeros(bytes)
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function normalizeQuantityHexForRLP(hex: PrefixedHexString): PrefixedHexString {
|
|
646
|
+
const stripped = hex.slice(2).replace(/^0+/, '')
|
|
647
|
+
if (stripped === '') {
|
|
648
|
+
return '0x'
|
|
649
|
+
}
|
|
650
|
+
return `0x${padToEven(stripped)}`
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
export function createBlockLevelAccessListFromRLP(rlp: Uint8Array): BlockLevelAccessList {
|
|
654
|
+
const decoded = RLP.decode(rlp) as Array<
|
|
655
|
+
[
|
|
656
|
+
Uint8Array, // address
|
|
657
|
+
Array<[Uint8Array, Array<[Uint8Array, Uint8Array]>]>, // storage changes
|
|
658
|
+
Uint8Array[], // storage reads
|
|
659
|
+
Array<[Uint8Array, Uint8Array]>, // balance changes
|
|
660
|
+
Array<[Uint8Array, Uint8Array]>, // nonce changes
|
|
661
|
+
Array<[Uint8Array, Uint8Array]>, // code changes
|
|
662
|
+
]
|
|
663
|
+
>
|
|
664
|
+
|
|
665
|
+
const bal = new BlockLevelAccessList()
|
|
666
|
+
|
|
667
|
+
for (const account of decoded) {
|
|
668
|
+
const [
|
|
669
|
+
addressBytes,
|
|
670
|
+
storageChangesRaw,
|
|
671
|
+
storageReadsRaw,
|
|
672
|
+
balanceChangesRaw,
|
|
673
|
+
nonceChangesRaw,
|
|
674
|
+
codeChangesRaw,
|
|
675
|
+
] = account
|
|
676
|
+
const address = bytesToHex(addressBytes) as BALAddressHex
|
|
677
|
+
bal.addAddress(address)
|
|
678
|
+
const access = bal.accesses[address]
|
|
679
|
+
|
|
680
|
+
for (const [slotBytes, slotChangesRaw] of storageChangesRaw) {
|
|
681
|
+
const slot = normalizeStorageKeyHex(bytesToHex(slotBytes))
|
|
682
|
+
if (access.storageChanges[slot] === undefined) {
|
|
683
|
+
access.storageChanges[slot] = []
|
|
684
|
+
}
|
|
685
|
+
for (const [indexBytes, valueBytes] of slotChangesRaw) {
|
|
686
|
+
access.storageChanges[slot].push([bytesToInt(indexBytes), valueBytes])
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
for (const slotBytes of storageReadsRaw) {
|
|
691
|
+
access.storageReads.add(normalizeStorageKeyHex(bytesToHex(slotBytes)))
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
for (const [indexBytes, balanceBytes] of balanceChangesRaw) {
|
|
695
|
+
access.balanceChanges.set(
|
|
696
|
+
bytesToInt(indexBytes),
|
|
697
|
+
padToEvenHex(bytesToHex(balanceBytes)) as BALBalanceHex,
|
|
698
|
+
)
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
for (const [indexBytes, nonceBytes] of nonceChangesRaw) {
|
|
702
|
+
access.nonceChanges.set(
|
|
703
|
+
bytesToInt(indexBytes),
|
|
704
|
+
padToEvenHex(bytesToHex(nonceBytes)) as BALNonceHex,
|
|
705
|
+
)
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
for (const [indexBytes, codeBytes] of codeChangesRaw) {
|
|
709
|
+
access.codeChanges.push([bytesToInt(indexBytes), codeBytes])
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return bal
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function stripLeadingZeros(bytes: Uint8Array): Uint8Array {
|
|
717
|
+
let first = bytes[0]
|
|
718
|
+
while (bytes.length > 0 && first.toString() === '0') {
|
|
719
|
+
bytes = bytes.slice(1)
|
|
720
|
+
first = bytes[0]
|
|
721
|
+
}
|
|
722
|
+
return bytes
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function padToEvenHex(hex: PrefixedHexString): PrefixedHexString {
|
|
726
|
+
return `0x${padToEven(hex.slice(2))}`
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Normalizes a storage key hex string to ensure consistent even-length representation.
|
|
731
|
+
* - "0x" (empty bytes) is kept as is
|
|
732
|
+
* - "0x0" becomes "0x00"
|
|
733
|
+
* - Any odd-length hex is padded to even (e.g., "0x1" → "0x01")
|
|
734
|
+
*/
|
|
735
|
+
function normalizeStorageKeyHex(hex: PrefixedHexString): BALStorageKeyHex {
|
|
736
|
+
const stripped = hex.slice(2)
|
|
737
|
+
// Empty string "0x" stays as is
|
|
738
|
+
if (stripped === '') {
|
|
739
|
+
return '0x' as BALStorageKeyHex
|
|
740
|
+
}
|
|
741
|
+
// Pad to even length (handles "0x0" → "0x00", "0x1" → "0x01", etc.)
|
|
742
|
+
return `0x${padToEven(stripped)}` as BALStorageKeyHex
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function shouldIncludeAddress(address: BALAddressHex, access: Accesses[BALAddressHex]): boolean {
|
|
746
|
+
if (address !== SYSTEM_ADDRESS) {
|
|
747
|
+
return true
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return (
|
|
751
|
+
access.storageReads.size > 0 ||
|
|
752
|
+
Object.keys(access.storageChanges).length > 0 ||
|
|
753
|
+
access.balanceChanges.size > 0 ||
|
|
754
|
+
access.nonceChanges.size > 0 ||
|
|
755
|
+
access.codeChanges.length > 0
|
|
756
|
+
)
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function indexToHex(index: BALAccessIndexNumber): BALAccessIndexHex {
|
|
760
|
+
return padToEvenHex(`0x${index.toString(16)}`) as BALAccessIndexHex
|
|
761
|
+
}
|