@feelyourprotocol/statemanager 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.
Files changed (169) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +331 -0
  3. package/dist/cjs/cache/account.d.ts +85 -0
  4. package/dist/cjs/cache/account.d.ts.map +1 -0
  5. package/dist/cjs/cache/account.js +252 -0
  6. package/dist/cjs/cache/account.js.map +1 -0
  7. package/dist/cjs/cache/cache.d.ts +23 -0
  8. package/dist/cjs/cache/cache.d.ts.map +1 -0
  9. package/dist/cjs/cache/cache.js +31 -0
  10. package/dist/cjs/cache/cache.js.map +1 -0
  11. package/dist/cjs/cache/caches.d.ts +19 -0
  12. package/dist/cjs/cache/caches.d.ts.map +1 -0
  13. package/dist/cjs/cache/caches.js +107 -0
  14. package/dist/cjs/cache/caches.js.map +1 -0
  15. package/dist/cjs/cache/code.d.ts +87 -0
  16. package/dist/cjs/cache/code.d.ts.map +1 -0
  17. package/dist/cjs/cache/code.js +258 -0
  18. package/dist/cjs/cache/code.js.map +1 -0
  19. package/dist/cjs/cache/index.d.ts +7 -0
  20. package/dist/cjs/cache/index.d.ts.map +1 -0
  21. package/dist/cjs/cache/index.js +23 -0
  22. package/dist/cjs/cache/index.js.map +1 -0
  23. package/dist/cjs/cache/originalStorageCache.d.ts +21 -0
  24. package/dist/cjs/cache/originalStorageCache.d.ts.map +1 -0
  25. package/dist/cjs/cache/originalStorageCache.js +52 -0
  26. package/dist/cjs/cache/originalStorageCache.js.map +1 -0
  27. package/dist/cjs/cache/storage.d.ts +101 -0
  28. package/dist/cjs/cache/storage.d.ts.map +1 -0
  29. package/dist/cjs/cache/storage.js +337 -0
  30. package/dist/cjs/cache/storage.js.map +1 -0
  31. package/dist/cjs/cache/types.d.ts +36 -0
  32. package/dist/cjs/cache/types.d.ts.map +1 -0
  33. package/dist/cjs/cache/types.js +8 -0
  34. package/dist/cjs/cache/types.js.map +1 -0
  35. package/dist/cjs/index.d.ts +8 -0
  36. package/dist/cjs/index.d.ts.map +1 -0
  37. package/dist/cjs/index.js +24 -0
  38. package/dist/cjs/index.js.map +1 -0
  39. package/dist/cjs/merkleStateManager.d.ts +260 -0
  40. package/dist/cjs/merkleStateManager.d.ts.map +1 -0
  41. package/dist/cjs/merkleStateManager.js +616 -0
  42. package/dist/cjs/merkleStateManager.js.map +1 -0
  43. package/dist/cjs/package.json +3 -0
  44. package/dist/cjs/proof/index.d.ts +3 -0
  45. package/dist/cjs/proof/index.d.ts.map +1 -0
  46. package/dist/cjs/proof/index.js +19 -0
  47. package/dist/cjs/proof/index.js.map +1 -0
  48. package/dist/cjs/proof/merkle.d.ts +40 -0
  49. package/dist/cjs/proof/merkle.d.ts.map +1 -0
  50. package/dist/cjs/proof/merkle.js +182 -0
  51. package/dist/cjs/proof/merkle.js.map +1 -0
  52. package/dist/cjs/proof/rpc.d.ts +10 -0
  53. package/dist/cjs/proof/rpc.d.ts.map +1 -0
  54. package/dist/cjs/proof/rpc.js +20 -0
  55. package/dist/cjs/proof/rpc.js.map +1 -0
  56. package/dist/cjs/rpcStateManager.d.ts +162 -0
  57. package/dist/cjs/rpcStateManager.d.ts.map +1 -0
  58. package/dist/cjs/rpcStateManager.js +313 -0
  59. package/dist/cjs/rpcStateManager.js.map +1 -0
  60. package/dist/cjs/simpleStateManager.d.ts +54 -0
  61. package/dist/cjs/simpleStateManager.d.ts.map +1 -0
  62. package/dist/cjs/simpleStateManager.js +125 -0
  63. package/dist/cjs/simpleStateManager.js.map +1 -0
  64. package/dist/cjs/statefulBinaryTreeStateManager.d.ts +69 -0
  65. package/dist/cjs/statefulBinaryTreeStateManager.d.ts.map +1 -0
  66. package/dist/cjs/statefulBinaryTreeStateManager.js +576 -0
  67. package/dist/cjs/statefulBinaryTreeStateManager.js.map +1 -0
  68. package/dist/cjs/types.d.ts +92 -0
  69. package/dist/cjs/types.d.ts.map +1 -0
  70. package/dist/cjs/types.js +3 -0
  71. package/dist/cjs/types.js.map +1 -0
  72. package/dist/cjs/util.d.ts +4 -0
  73. package/dist/cjs/util.d.ts.map +1 -0
  74. package/dist/cjs/util.js +21 -0
  75. package/dist/cjs/util.js.map +1 -0
  76. package/dist/esm/cache/account.d.ts +85 -0
  77. package/dist/esm/cache/account.d.ts.map +1 -0
  78. package/dist/esm/cache/account.js +248 -0
  79. package/dist/esm/cache/account.js.map +1 -0
  80. package/dist/esm/cache/cache.d.ts +23 -0
  81. package/dist/esm/cache/cache.d.ts.map +1 -0
  82. package/dist/esm/cache/cache.js +27 -0
  83. package/dist/esm/cache/cache.js.map +1 -0
  84. package/dist/esm/cache/caches.d.ts +19 -0
  85. package/dist/esm/cache/caches.d.ts.map +1 -0
  86. package/dist/esm/cache/caches.js +103 -0
  87. package/dist/esm/cache/caches.js.map +1 -0
  88. package/dist/esm/cache/code.d.ts +87 -0
  89. package/dist/esm/cache/code.d.ts.map +1 -0
  90. package/dist/esm/cache/code.js +254 -0
  91. package/dist/esm/cache/code.js.map +1 -0
  92. package/dist/esm/cache/index.d.ts +7 -0
  93. package/dist/esm/cache/index.d.ts.map +1 -0
  94. package/dist/esm/cache/index.js +7 -0
  95. package/dist/esm/cache/index.js.map +1 -0
  96. package/dist/esm/cache/originalStorageCache.d.ts +21 -0
  97. package/dist/esm/cache/originalStorageCache.d.ts.map +1 -0
  98. package/dist/esm/cache/originalStorageCache.js +48 -0
  99. package/dist/esm/cache/originalStorageCache.js.map +1 -0
  100. package/dist/esm/cache/storage.d.ts +101 -0
  101. package/dist/esm/cache/storage.d.ts.map +1 -0
  102. package/dist/esm/cache/storage.js +333 -0
  103. package/dist/esm/cache/storage.js.map +1 -0
  104. package/dist/esm/cache/types.d.ts +36 -0
  105. package/dist/esm/cache/types.d.ts.map +1 -0
  106. package/dist/esm/cache/types.js +5 -0
  107. package/dist/esm/cache/types.js.map +1 -0
  108. package/dist/esm/index.d.ts +8 -0
  109. package/dist/esm/index.d.ts.map +1 -0
  110. package/dist/esm/index.js +8 -0
  111. package/dist/esm/index.js.map +1 -0
  112. package/dist/esm/merkleStateManager.d.ts +260 -0
  113. package/dist/esm/merkleStateManager.d.ts.map +1 -0
  114. package/dist/esm/merkleStateManager.js +612 -0
  115. package/dist/esm/merkleStateManager.js.map +1 -0
  116. package/dist/esm/package.json +3 -0
  117. package/dist/esm/proof/index.d.ts +3 -0
  118. package/dist/esm/proof/index.d.ts.map +1 -0
  119. package/dist/esm/proof/index.js +3 -0
  120. package/dist/esm/proof/index.js.map +1 -0
  121. package/dist/esm/proof/merkle.d.ts +40 -0
  122. package/dist/esm/proof/merkle.d.ts.map +1 -0
  123. package/dist/esm/proof/merkle.js +175 -0
  124. package/dist/esm/proof/merkle.js.map +1 -0
  125. package/dist/esm/proof/rpc.d.ts +10 -0
  126. package/dist/esm/proof/rpc.d.ts.map +1 -0
  127. package/dist/esm/proof/rpc.js +17 -0
  128. package/dist/esm/proof/rpc.js.map +1 -0
  129. package/dist/esm/rpcStateManager.d.ts +162 -0
  130. package/dist/esm/rpcStateManager.d.ts.map +1 -0
  131. package/dist/esm/rpcStateManager.js +308 -0
  132. package/dist/esm/rpcStateManager.js.map +1 -0
  133. package/dist/esm/simpleStateManager.d.ts +54 -0
  134. package/dist/esm/simpleStateManager.d.ts.map +1 -0
  135. package/dist/esm/simpleStateManager.js +121 -0
  136. package/dist/esm/simpleStateManager.js.map +1 -0
  137. package/dist/esm/statefulBinaryTreeStateManager.d.ts +69 -0
  138. package/dist/esm/statefulBinaryTreeStateManager.d.ts.map +1 -0
  139. package/dist/esm/statefulBinaryTreeStateManager.js +572 -0
  140. package/dist/esm/statefulBinaryTreeStateManager.js.map +1 -0
  141. package/dist/esm/types.d.ts +92 -0
  142. package/dist/esm/types.d.ts.map +1 -0
  143. package/dist/esm/types.js +2 -0
  144. package/dist/esm/types.js.map +1 -0
  145. package/dist/esm/util.d.ts +4 -0
  146. package/dist/esm/util.d.ts.map +1 -0
  147. package/dist/esm/util.js +18 -0
  148. package/dist/esm/util.js.map +1 -0
  149. package/dist/tsconfig.prod.cjs.tsbuildinfo +1 -0
  150. package/dist/tsconfig.prod.esm.tsbuildinfo +1 -0
  151. package/package.json +74 -0
  152. package/src/cache/account.ts +277 -0
  153. package/src/cache/cache.ts +35 -0
  154. package/src/cache/caches.ts +125 -0
  155. package/src/cache/code.ts +277 -0
  156. package/src/cache/index.ts +6 -0
  157. package/src/cache/originalStorageCache.ts +57 -0
  158. package/src/cache/storage.ts +369 -0
  159. package/src/cache/types.ts +38 -0
  160. package/src/index.ts +7 -0
  161. package/src/merkleStateManager.ts +737 -0
  162. package/src/proof/index.ts +2 -0
  163. package/src/proof/merkle.ts +264 -0
  164. package/src/proof/rpc.ts +24 -0
  165. package/src/rpcStateManager.ts +381 -0
  166. package/src/simpleStateManager.ts +154 -0
  167. package/src/statefulBinaryTreeStateManager.ts +789 -0
  168. package/src/types.ts +103 -0
  169. package/src/util.ts +28 -0
@@ -0,0 +1,2 @@
1
+ export * from './merkle.ts'
2
+ export * from './rpc.ts'
@@ -0,0 +1,264 @@
1
+ import {
2
+ createMPTFromProof,
3
+ createMerkleProof,
4
+ updateMPTFromMerkleProof,
5
+ verifyMerkleProof,
6
+ } from '@feelyourprotocol/mpt'
7
+ import { RLP } from '@feelyourprotocol/rlp'
8
+ import {
9
+ EthereumJSErrorWithoutCode,
10
+ KECCAK256_NULL,
11
+ KECCAK256_NULL_S,
12
+ KECCAK256_RLP,
13
+ KECCAK256_RLP_S,
14
+ bigIntToHex,
15
+ bytesToHex,
16
+ createAccountFromRLP,
17
+ createAddressFromString,
18
+ equalsBytes,
19
+ hexToBytes,
20
+ setLengthLeft,
21
+ unpadBytes,
22
+ } from '@feelyourprotocol/util'
23
+
24
+ import { MerkleStateManager } from '../merkleStateManager.ts'
25
+
26
+ import type { Proof, StorageProof } from '@feelyourprotocol/common'
27
+ import type { Address, PrefixedHexString } from '@feelyourprotocol/util'
28
+ import type { MerkleStateManagerOpts } from '../index.ts'
29
+
30
+ /**
31
+ * Get an EIP-1186 proof
32
+ * @param address address to get proof of
33
+ * @param storageSlots storage slots to get proof of
34
+ */
35
+ export async function getMerkleStateProof(
36
+ sm: MerkleStateManager,
37
+ address: Address,
38
+ storageSlots: Uint8Array[] = [],
39
+ ): Promise<Proof> {
40
+ await sm['flush']()
41
+ const account = await sm.getAccount(address)
42
+ if (!account) {
43
+ const returnValue: Proof = {
44
+ address: address.toString(),
45
+ balance: '0x0',
46
+ codeHash: KECCAK256_NULL_S,
47
+ nonce: '0x0',
48
+ storageHash: KECCAK256_RLP_S,
49
+ accountProof: (await createMerkleProof(sm['_trie'], address.bytes)).map((p) => bytesToHex(p)),
50
+ storageProof: [],
51
+ }
52
+ return returnValue
53
+ }
54
+ const accountProof: PrefixedHexString[] = (
55
+ await createMerkleProof(sm['_trie'], address.bytes)
56
+ ).map((p) => bytesToHex(p))
57
+ const storageProof: StorageProof[] = []
58
+ const storageTrie = sm['_getStorageTrie'](address, account)
59
+
60
+ for (const storageKey of storageSlots) {
61
+ const proof = (await createMerkleProof(storageTrie, storageKey)).map((p) => bytesToHex(p))
62
+ const value = bytesToHex(await sm.getStorage(address, storageKey))
63
+ const proofItem: StorageProof = {
64
+ key: bytesToHex(storageKey),
65
+ value: value === '0x' ? '0x0' : value, // Return '0x' values as '0x0' since this is a JSON RPC response
66
+ proof,
67
+ }
68
+ storageProof.push(proofItem)
69
+ }
70
+
71
+ const returnValue: Proof = {
72
+ address: address.toString(),
73
+ balance: bigIntToHex(account.balance),
74
+ codeHash: bytesToHex(account.codeHash),
75
+ nonce: bigIntToHex(account.nonce),
76
+ storageHash: bytesToHex(account.storageRoot),
77
+ accountProof,
78
+ storageProof,
79
+ }
80
+ return returnValue
81
+ }
82
+
83
+ /**
84
+ * Adds a storage proof to the state manager
85
+ * @param storageProof The storage proof
86
+ * @param storageHash The root hash of the storage trie
87
+ * @param address The address
88
+ * @param safe Whether or not to verify if the reported roots match the current storage root
89
+ */
90
+ export async function addMerkleStateStorageProof(
91
+ sm: MerkleStateManager,
92
+ storageProof: StorageProof[],
93
+ storageHash: PrefixedHexString,
94
+ address: Address,
95
+ safe: boolean = false,
96
+ ) {
97
+ const trie = sm['_getStorageTrie'](address)
98
+ trie.root(hexToBytes(storageHash))
99
+ for (let i = 0; i < storageProof.length; i++) {
100
+ await updateMPTFromMerkleProof(
101
+ trie,
102
+ storageProof[i].proof.map((e) => hexToBytes(e)),
103
+ safe,
104
+ )
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Create a StateManager and initialize this with proof(s) gotten previously from getProof
110
+ * This generates a (partial) StateManager where one can retrieve all items from the proof
111
+ * @param proof Either a proof retrieved from `getProof`, or an array of those proofs
112
+ * @param safe Whether or not to verify that the roots of the proof items match the reported roots
113
+ * @param opts a dictionary of StateManager opts
114
+ * @returns A new MerkleStateManager with elements from the given proof included in its backing state trie
115
+ */
116
+ export async function fromMerkleStateProof(
117
+ proof: Proof | Proof[],
118
+ safe: boolean = false,
119
+ opts: MerkleStateManagerOpts = {},
120
+ ): Promise<MerkleStateManager> {
121
+ if (Array.isArray(proof)) {
122
+ if (proof.length === 0) {
123
+ return new MerkleStateManager(opts)
124
+ } else {
125
+ const trie =
126
+ opts.trie ??
127
+ (await createMPTFromProof(
128
+ proof[0].accountProof.map((e) => hexToBytes(e)),
129
+ { useKeyHashing: true },
130
+ ))
131
+ const sm = new MerkleStateManager({ ...opts, trie })
132
+ const address = createAddressFromString(proof[0].address)
133
+ await addMerkleStateStorageProof(
134
+ sm,
135
+ proof[0].storageProof,
136
+ proof[0].storageHash,
137
+ address,
138
+ safe,
139
+ )
140
+ for (let i = 1; i < proof.length; i++) {
141
+ const proofItem = proof[i]
142
+ await addMerkleStateProofData(sm, proofItem, true)
143
+ }
144
+ await sm.flush() // TODO verify if this is necessary
145
+ return sm
146
+ }
147
+ } else {
148
+ return fromMerkleStateProof([proof], safe, opts)
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Add proof(s) into an already existing trie
154
+ * @param proof The proof(s) retrieved from `getProof`
155
+ * @param verifyRoot verify that all proof root nodes match statemanager's stateroot - should be
156
+ * set to `false` when constructing a state manager where the underlying trie has proof nodes from different state roots
157
+ */
158
+ export async function addMerkleStateProofData(
159
+ sm: MerkleStateManager,
160
+ proof: Proof | Proof[],
161
+ safe: boolean = false,
162
+ ) {
163
+ if (Array.isArray(proof)) {
164
+ for (let i = 0; i < proof.length; i++) {
165
+ await updateMPTFromMerkleProof(
166
+ sm['_trie'],
167
+ proof[i].accountProof.map((e) => hexToBytes(e)),
168
+ safe,
169
+ )
170
+ await addMerkleStateStorageProof(
171
+ sm,
172
+ proof[i].storageProof,
173
+ proof[i].storageHash,
174
+ createAddressFromString(proof[i].address),
175
+ safe,
176
+ )
177
+ }
178
+ } else {
179
+ await addMerkleStateProofData(sm, [proof], safe)
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Verify an EIP-1186 proof. Throws if proof is invalid, otherwise returns true.
185
+ * @param proof the proof to prove
186
+ */
187
+ export async function verifyMerkleStateProof(
188
+ sm: MerkleStateManager,
189
+ proof: Proof,
190
+ ): Promise<boolean> {
191
+ const key = hexToBytes(proof.address)
192
+ const accountProof = proof.accountProof.map((rlpString: PrefixedHexString) =>
193
+ hexToBytes(rlpString),
194
+ )
195
+
196
+ // This returns the account if the proof is valid.
197
+ // Verify that it matches the reported account.
198
+ const value = await verifyMerkleProof(key, accountProof, {
199
+ useKeyHashing: true,
200
+ })
201
+
202
+ if (value === null) {
203
+ // Verify that the account is empty in the proof.
204
+ const emptyBytes = new Uint8Array(0)
205
+ const notEmptyErrorMsg = 'Invalid proof provided: account is not empty'
206
+ const nonce = unpadBytes(hexToBytes(proof.nonce))
207
+ if (!equalsBytes(nonce, emptyBytes)) {
208
+ throw EthereumJSErrorWithoutCode(`${notEmptyErrorMsg} (nonce is not zero)`)
209
+ }
210
+ const balance = unpadBytes(hexToBytes(proof.balance))
211
+ if (!equalsBytes(balance, emptyBytes)) {
212
+ throw EthereumJSErrorWithoutCode(`${notEmptyErrorMsg} (balance is not zero)`)
213
+ }
214
+ const storageHash = hexToBytes(proof.storageHash)
215
+ if (!equalsBytes(storageHash, KECCAK256_RLP)) {
216
+ throw EthereumJSErrorWithoutCode(
217
+ `${notEmptyErrorMsg} (storageHash does not equal KECCAK256_RLP)`,
218
+ )
219
+ }
220
+ const codeHash = hexToBytes(proof.codeHash)
221
+ if (!equalsBytes(codeHash, KECCAK256_NULL)) {
222
+ throw EthereumJSErrorWithoutCode(
223
+ `${notEmptyErrorMsg} (codeHash does not equal KECCAK256_NULL)`,
224
+ )
225
+ }
226
+ } else {
227
+ const account = createAccountFromRLP(value)
228
+ const { nonce, balance, storageRoot, codeHash } = account
229
+ const invalidErrorMsg = 'Invalid proof provided:'
230
+ if (nonce !== BigInt(proof.nonce)) {
231
+ throw EthereumJSErrorWithoutCode(`${invalidErrorMsg} nonce does not match`)
232
+ }
233
+ if (balance !== BigInt(proof.balance)) {
234
+ throw EthereumJSErrorWithoutCode(`${invalidErrorMsg} balance does not match`)
235
+ }
236
+ if (!equalsBytes(storageRoot, hexToBytes(proof.storageHash))) {
237
+ throw EthereumJSErrorWithoutCode(`${invalidErrorMsg} storageHash does not match`)
238
+ }
239
+ if (!equalsBytes(codeHash, hexToBytes(proof.codeHash))) {
240
+ throw EthereumJSErrorWithoutCode(`${invalidErrorMsg} codeHash does not match`)
241
+ }
242
+ }
243
+
244
+ for (const stProof of proof.storageProof) {
245
+ const storageProof = stProof.proof.map((value: PrefixedHexString) => hexToBytes(value))
246
+ const storageValue = setLengthLeft(hexToBytes(stProof.value), 32)
247
+ const storageKey = hexToBytes(stProof.key)
248
+ const proofValue = await verifyMerkleProof(storageKey, storageProof, {
249
+ useKeyHashing: true,
250
+ })
251
+ const reportedValue = setLengthLeft(
252
+ RLP.decode(proofValue ?? new Uint8Array(0)) as Uint8Array,
253
+ 32,
254
+ )
255
+ if (!equalsBytes(reportedValue, storageValue)) {
256
+ throw EthereumJSErrorWithoutCode(
257
+ `Reported trie value does not match storage, key: ${stProof.key}, reported: ${bytesToHex(
258
+ reportedValue,
259
+ )}, actual: ${bytesToHex(storageValue)}`,
260
+ )
261
+ }
262
+ }
263
+ return true
264
+ }
@@ -0,0 +1,24 @@
1
+ import { bytesToHex, fetchFromProvider } from '@feelyourprotocol/util'
2
+
3
+ import type { Address } from '@feelyourprotocol/util'
4
+ import type { Proof, RPCStateManager } from '../index.ts'
5
+
6
+ /**
7
+ * Get an EIP-1186 proof from the provider
8
+ * @param address address to get proof of
9
+ * @param storageSlots storage slots to get proof of
10
+ * @returns an EIP-1186 formatted proof
11
+ */
12
+ export async function getRPCStateProof(
13
+ sm: RPCStateManager,
14
+ address: Address,
15
+ storageSlots: Uint8Array[] = [],
16
+ ): Promise<Proof> {
17
+ if (sm['DEBUG']) sm['_debug'](`retrieving proof from provider for ${address.toString()}`)
18
+ const proof = await fetchFromProvider(sm['_provider'], {
19
+ method: 'eth_getProof',
20
+ params: [address.toString(), storageSlots.map(bytesToHex), sm['_blockTag']],
21
+ })
22
+
23
+ return proof
24
+ }
@@ -0,0 +1,381 @@
1
+ import { Common, Mainnet } from '@feelyourprotocol/common'
2
+ import { RLP } from '@feelyourprotocol/rlp'
3
+ import {
4
+ Account,
5
+ EthereumJSErrorWithoutCode,
6
+ bigIntToHex,
7
+ bytesToHex,
8
+ createAccount,
9
+ createAccountFromRLP,
10
+ equalsBytes,
11
+ fetchFromProvider,
12
+ hexToBytes,
13
+ intToHex,
14
+ isDebugEnabled,
15
+ toBytes,
16
+ } from '@feelyourprotocol/util'
17
+ import { keccak_256 } from '@noble/hashes/sha3.js'
18
+ import debugDefault from 'debug'
19
+
20
+ import { Caches, OriginalStorageCache } from './cache/index.ts'
21
+ import { modifyAccountFields } from './util.ts'
22
+
23
+ import type { AccountFields, StateManagerInterface, StorageDump } from '@feelyourprotocol/common'
24
+ import type { Address } from '@feelyourprotocol/util'
25
+ import type { Debugger } from 'debug'
26
+ import type { RPCStateManagerOpts } from './index.ts'
27
+
28
+ const KECCAK256_RLP_EMPTY_ACCOUNT = RLP.encode(new Account().serialize()).slice(2)
29
+
30
+ export class RPCStateManager implements StateManagerInterface {
31
+ protected _provider: string
32
+ protected _caches: Caches
33
+ protected _blockTag: string
34
+ originalStorageCache: OriginalStorageCache
35
+ protected _debug: Debugger
36
+ protected DEBUG: boolean
37
+ private keccakFunction: Function
38
+ public readonly common: Common
39
+
40
+ constructor(opts: RPCStateManagerOpts) {
41
+ // Skip DEBUG calls unless 'ethjs' included in environmental DEBUG variables
42
+ this.DEBUG = isDebugEnabled('ethjs')
43
+
44
+ this._debug = debugDefault('statemanager:rpc')
45
+ if (typeof opts.provider === 'string' && opts.provider.startsWith('http')) {
46
+ this._provider = opts.provider
47
+ } else {
48
+ throw EthereumJSErrorWithoutCode(`valid RPC provider url required; got ${opts.provider}`)
49
+ }
50
+
51
+ this._blockTag = opts.blockTag === 'earliest' ? opts.blockTag : bigIntToHex(opts.blockTag)
52
+
53
+ this._caches = new Caches({ storage: { size: 100000 }, code: { size: 100000 } })
54
+
55
+ this.originalStorageCache = new OriginalStorageCache(this.getStorage.bind(this))
56
+ this.common = opts.common ?? new Common({ chain: Mainnet })
57
+ this.keccakFunction = opts.common?.customCrypto.keccak256 ?? keccak_256
58
+ }
59
+
60
+ /**
61
+ * Note that the returned statemanager will share the same JSONRPCProvider as the original
62
+ *
63
+ * @returns RPCStateManager
64
+ */
65
+ shallowCopy(): RPCStateManager {
66
+ const newState = new RPCStateManager({
67
+ provider: this._provider,
68
+ blockTag: BigInt(this._blockTag),
69
+ })
70
+ newState._caches = new Caches({ storage: { size: 100000 } })
71
+
72
+ return newState
73
+ }
74
+
75
+ /**
76
+ * Sets the new block tag used when querying the provider and clears the
77
+ * internal cache.
78
+ * @param blockTag - the new block tag to use when querying the provider
79
+ */
80
+ setBlockTag(blockTag: bigint | 'earliest'): void {
81
+ this._blockTag = blockTag === 'earliest' ? blockTag : bigIntToHex(blockTag)
82
+ this.clearCaches()
83
+ if (this.DEBUG) this._debug(`setting block tag to ${this._blockTag}`)
84
+ }
85
+
86
+ /**
87
+ * Clears the internal cache so all accounts, contract code, and storage slots will
88
+ * initially be retrieved from the provider
89
+ */
90
+ clearCaches(): void {
91
+ this._caches.clear()
92
+ }
93
+
94
+ /**
95
+ * Gets the code corresponding to the provided `address`.
96
+ * @param address - Address to get the `code` for
97
+ * @returns {Promise<Uint8Array>} - Resolves with the code corresponding to the provided address.
98
+ * Returns an empty `Uint8Array` if the account has no associated code.
99
+ */
100
+ async getCode(address: Address): Promise<Uint8Array> {
101
+ let codeBytes = this._caches.code?.get(address)?.code
102
+ if (codeBytes !== undefined) return codeBytes
103
+ const code = await fetchFromProvider(this._provider, {
104
+ method: 'eth_getCode',
105
+ params: [address.toString(), this._blockTag],
106
+ })
107
+ codeBytes = toBytes(code)
108
+ this._caches.code?.put(address, codeBytes)
109
+ return codeBytes
110
+ }
111
+
112
+ async getCodeSize(address: Address): Promise<number> {
113
+ const contractCode = await this.getCode(address)
114
+ return contractCode.length
115
+ }
116
+
117
+ /**
118
+ * Adds `value` to the state trie as code, and sets `codeHash` on the account
119
+ * corresponding to `address` to reference this.
120
+ * @param address - Address of the `account` to add the `code` for
121
+ * @param value - The value of the `code`
122
+ */
123
+ async putCode(address: Address, value: Uint8Array): Promise<void> {
124
+ // Store contract code in the cache
125
+ this._caches.code?.put(address, value)
126
+ }
127
+
128
+ /**
129
+ * Gets the storage value associated with the provided `address` and `key`. This method returns
130
+ * the shortest representation of the stored value.
131
+ * @param address - Address of the account to get the storage for
132
+ * @param key - Key in the account's storage to get the value for. Must be 32 bytes long.
133
+ * @returns {Uint8Array} - The storage value for the account
134
+ * corresponding to the provided address at the provided key.
135
+ * If this does not exist an empty `Uint8Array` is returned.
136
+ */
137
+ async getStorage(address: Address, key: Uint8Array): Promise<Uint8Array> {
138
+ // Check storage slot in cache
139
+ if (key.length !== 32) {
140
+ throw EthereumJSErrorWithoutCode('Storage key must be 32 bytes long')
141
+ }
142
+
143
+ let value = this._caches.storage?.get(address, key)
144
+ if (value !== undefined) {
145
+ return value
146
+ }
147
+
148
+ // Retrieve storage slot from provider if not found in cache
149
+ const storage = await fetchFromProvider(this._provider, {
150
+ method: 'eth_getStorageAt',
151
+ params: [address.toString(), bytesToHex(key), this._blockTag],
152
+ })
153
+ value = toBytes(storage)
154
+
155
+ await this.putStorage(address, key, value)
156
+ return value
157
+ }
158
+
159
+ /**
160
+ * Adds value to the cache for the `account`
161
+ * corresponding to `address` at the provided `key`.
162
+ * @param address - Address to set a storage value for
163
+ * @param key - Key to set the value at. Must be 32 bytes long.
164
+ * @param value - Value to set at `key` for account corresponding to `address`.
165
+ * Cannot be more than 32 bytes. Leading zeros are stripped.
166
+ * If it is empty or filled with zeros, deletes the value.
167
+ */
168
+ async putStorage(address: Address, key: Uint8Array, value: Uint8Array): Promise<void> {
169
+ this._caches.storage?.put(address, key, value)
170
+ }
171
+
172
+ /**
173
+ * Clears all storage entries for the account corresponding to `address`.
174
+ * @param address - Address to clear the storage of
175
+ */
176
+ async clearStorage(address: Address): Promise<void> {
177
+ this._caches.storage?.clearStorage(address)
178
+ }
179
+
180
+ /**
181
+ * Dumps the RLP-encoded storage values for an `account` specified by `address`.
182
+ * @param address - The address of the `account` to return storage for
183
+ * @returns {Promise<StorageDump>} - The state of the account as an `Object` map.
184
+ * Keys are the storage keys, values are the storage values as strings.
185
+ * Both are represented as `0x` prefixed hex strings.
186
+ */
187
+ dumpStorage(address: Address): Promise<StorageDump> {
188
+ const storageMap = this._caches.storage?.dump(address)
189
+ const dump: StorageDump = {}
190
+ if (storageMap !== undefined) {
191
+ for (const slot of storageMap) {
192
+ dump[slot[0]] = bytesToHex(slot[1])
193
+ }
194
+ }
195
+ return Promise.resolve(dump)
196
+ }
197
+
198
+ /**
199
+ * Gets the account associated with `address` or `undefined` if account does not exist
200
+ * @param address - Address of the `account` to get
201
+ */
202
+ async getAccount(address: Address): Promise<Account | undefined> {
203
+ const elem = this._caches.account?.get(address)
204
+ if (elem !== undefined) {
205
+ return elem.accountRLP !== undefined ? createAccountFromRLP(elem.accountRLP) : undefined
206
+ }
207
+
208
+ const accountFromProvider = await this.getAccountFromProvider(address)
209
+ const account =
210
+ equalsBytes(accountFromProvider.codeHash, new Uint8Array(32)) ||
211
+ equalsBytes(accountFromProvider.serialize(), KECCAK256_RLP_EMPTY_ACCOUNT)
212
+ ? undefined
213
+ : createAccountFromRLP(accountFromProvider.serialize())
214
+
215
+ this._caches.account?.put(address, account)
216
+
217
+ return account
218
+ }
219
+
220
+ /**
221
+ * Retrieves an account from the provider and stores in the local trie
222
+ * @param address Address of account to be retrieved from provider
223
+ * @private
224
+ */
225
+ async getAccountFromProvider(address: Address): Promise<Account> {
226
+ if (this.DEBUG) this._debug(`retrieving account data from ${address.toString()} from provider`)
227
+ const accountData = await fetchFromProvider(this._provider, {
228
+ method: 'eth_getProof',
229
+ params: [address.toString(), [] as any, this._blockTag],
230
+ })
231
+ const account = createAccount({
232
+ balance: BigInt(accountData.balance),
233
+ nonce: BigInt(accountData.nonce),
234
+ codeHash: toBytes(accountData.codeHash),
235
+ storageRoot: toBytes(accountData.storageHash),
236
+ })
237
+ return account
238
+ }
239
+
240
+ /**
241
+ * Saves an account into state under the provided `address`.
242
+ * @param address - Address under which to store `account`
243
+ * @param account - The account to store
244
+ */
245
+ async putAccount(address: Address, account: Account | undefined): Promise<void> {
246
+ if (this.DEBUG) {
247
+ this._debug(
248
+ `Save account address=${address} nonce=${account?.nonce} balance=${
249
+ account?.balance
250
+ } contract=${account && account.isContract() ? 'yes' : 'no'} empty=${
251
+ account && account.isEmpty() ? 'yes' : 'no'
252
+ }`,
253
+ )
254
+ }
255
+ if (account !== undefined) {
256
+ this._caches.account!.put(address, account)
257
+ } else {
258
+ this._caches.account!.del(address)
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Gets the account associated with `address`, modifies the given account
264
+ * fields, then saves the account into state. Account fields can include
265
+ * `nonce`, `balance`, `storageRoot`, and `codeHash`.
266
+ * @param address - Address of the account to modify
267
+ * @param accountFields - Object containing account fields and values to modify
268
+ */
269
+ async modifyAccountFields(address: Address, accountFields: AccountFields): Promise<void> {
270
+ if (this.DEBUG) {
271
+ this._debug(`modifying account fields for ${address.toString()}`)
272
+ this._debug(
273
+ JSON.stringify(
274
+ accountFields,
275
+ (k, v) => {
276
+ if (k === 'nonce') return v.toString()
277
+ return v
278
+ },
279
+ 2,
280
+ ),
281
+ )
282
+ }
283
+ await modifyAccountFields(this, address, accountFields)
284
+ }
285
+
286
+ /**
287
+ * Deletes an account from state under the provided `address`.
288
+ * @param address - Address of the account which should be deleted
289
+ */
290
+ async deleteAccount(address: Address) {
291
+ if (this.DEBUG) {
292
+ this._debug(`deleting account corresponding to ${address.toString()}`)
293
+ }
294
+ this._caches.account?.del(address)
295
+ }
296
+
297
+ /**
298
+ * Returns the applied key for a given address
299
+ * Used for saving preimages
300
+ * @param address - The address to return the applied key
301
+ * @returns {Uint8Array} - The applied key (e.g. hashed address)
302
+ */
303
+ getAppliedKey(address: Uint8Array): Uint8Array {
304
+ return this.keccakFunction(address)
305
+ }
306
+
307
+ /**
308
+ * Checkpoints the current state of the StateManager instance.
309
+ * State changes that follow can then be committed by calling
310
+ * `commit` or `reverted` by calling rollback.
311
+ */
312
+ async checkpoint(): Promise<void> {
313
+ this._caches.checkpoint()
314
+ }
315
+
316
+ /**
317
+ * Commits the current change-set to the instance since the
318
+ * last call to checkpoint.
319
+ *
320
+ * Partial implementation, called from the subclass.
321
+ */
322
+ async commit(): Promise<void> {
323
+ // setup cache checkpointing
324
+ this._caches.account?.commit()
325
+ }
326
+
327
+ /**
328
+ * Reverts the current change-set to the instance since the
329
+ * last call to checkpoint.
330
+ *
331
+ * Partial implementation , called from the subclass.
332
+ */
333
+ async revert(): Promise<void> {
334
+ this._caches.revert()
335
+ }
336
+
337
+ async flush(): Promise<void> {
338
+ this._caches.account?.flush()
339
+ }
340
+
341
+ /**
342
+ * @deprecated This method is not used by the RPC State Manager and is a stub required by the State Manager interface
343
+ */
344
+ getStateRoot = async () => {
345
+ return new Uint8Array(32)
346
+ }
347
+
348
+ /**
349
+ * @deprecated This method is not used by the RPC State Manager and is a stub required by the State Manager interface
350
+ */
351
+ setStateRoot = async (_root: Uint8Array) => {}
352
+
353
+ /**
354
+ * @deprecated This method is not used by the RPC State Manager and is a stub required by the State Manager interface
355
+ */
356
+ hasStateRoot = () => {
357
+ throw EthereumJSErrorWithoutCode('function not implemented')
358
+ }
359
+ }
360
+
361
+ export class RPCBlockChain {
362
+ readonly provider: string
363
+ constructor(provider: string) {
364
+ if (provider === undefined || provider === '')
365
+ throw EthereumJSErrorWithoutCode('provider URL is required')
366
+ this.provider = provider
367
+ }
368
+ async getBlock(blockId: number) {
369
+ const block = await fetchFromProvider(this.provider, {
370
+ method: 'eth_getBlockByNumber',
371
+ params: [intToHex(blockId), false],
372
+ })
373
+ return {
374
+ hash: () => hexToBytes(block.hash),
375
+ }
376
+ }
377
+
378
+ shallowCopy() {
379
+ return this
380
+ }
381
+ }