@feelyourprotocol/vm 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 (115) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +583 -0
  3. package/dist/cjs/bloom/index.d.ts +29 -0
  4. package/dist/cjs/bloom/index.d.ts.map +1 -0
  5. package/dist/cjs/bloom/index.js +76 -0
  6. package/dist/cjs/bloom/index.js.map +1 -0
  7. package/dist/cjs/buildBlock.d.ts +118 -0
  8. package/dist/cjs/buildBlock.d.ts.map +1 -0
  9. package/dist/cjs/buildBlock.js +363 -0
  10. package/dist/cjs/buildBlock.js.map +1 -0
  11. package/dist/cjs/constructors.d.ts +9 -0
  12. package/dist/cjs/constructors.d.ts.map +1 -0
  13. package/dist/cjs/constructors.js +75 -0
  14. package/dist/cjs/constructors.js.map +1 -0
  15. package/dist/cjs/emitEVMProfile.d.ts +9 -0
  16. package/dist/cjs/emitEVMProfile.d.ts.map +1 -0
  17. package/dist/cjs/emitEVMProfile.js +130 -0
  18. package/dist/cjs/emitEVMProfile.js.map +1 -0
  19. package/dist/cjs/index.d.ts +11 -0
  20. package/dist/cjs/index.d.ts.map +1 -0
  21. package/dist/cjs/index.js +36 -0
  22. package/dist/cjs/index.js.map +1 -0
  23. package/dist/cjs/package.json +3 -0
  24. package/dist/cjs/params.d.ts +3 -0
  25. package/dist/cjs/params.d.ts.map +1 -0
  26. package/dist/cjs/params.js +105 -0
  27. package/dist/cjs/params.js.map +1 -0
  28. package/dist/cjs/requests.d.ts +11 -0
  29. package/dist/cjs/requests.d.ts.map +1 -0
  30. package/dist/cjs/requests.js +208 -0
  31. package/dist/cjs/requests.js.map +1 -0
  32. package/dist/cjs/runBlock.d.ts +35 -0
  33. package/dist/cjs/runBlock.d.ts.map +1 -0
  34. package/dist/cjs/runBlock.js +797 -0
  35. package/dist/cjs/runBlock.js.map +1 -0
  36. package/dist/cjs/runFrameTx.d.ts +18 -0
  37. package/dist/cjs/runFrameTx.d.ts.map +1 -0
  38. package/dist/cjs/runFrameTx.js +313 -0
  39. package/dist/cjs/runFrameTx.js.map +1 -0
  40. package/dist/cjs/runTx.d.ts +18 -0
  41. package/dist/cjs/runTx.d.ts.map +1 -0
  42. package/dist/cjs/runTx.js +900 -0
  43. package/dist/cjs/runTx.js.map +1 -0
  44. package/dist/cjs/types.d.ts +452 -0
  45. package/dist/cjs/types.d.ts.map +1 -0
  46. package/dist/cjs/types.js +3 -0
  47. package/dist/cjs/types.js.map +1 -0
  48. package/dist/cjs/vm.d.ts +75 -0
  49. package/dist/cjs/vm.d.ts.map +1 -0
  50. package/dist/cjs/vm.js +111 -0
  51. package/dist/cjs/vm.js.map +1 -0
  52. package/dist/esm/bloom/index.d.ts +29 -0
  53. package/dist/esm/bloom/index.d.ts.map +1 -0
  54. package/dist/esm/bloom/index.js +72 -0
  55. package/dist/esm/bloom/index.js.map +1 -0
  56. package/dist/esm/buildBlock.d.ts +118 -0
  57. package/dist/esm/buildBlock.d.ts.map +1 -0
  58. package/dist/esm/buildBlock.js +358 -0
  59. package/dist/esm/buildBlock.js.map +1 -0
  60. package/dist/esm/constructors.d.ts +9 -0
  61. package/dist/esm/constructors.d.ts.map +1 -0
  62. package/dist/esm/constructors.js +72 -0
  63. package/dist/esm/constructors.js.map +1 -0
  64. package/dist/esm/emitEVMProfile.d.ts +9 -0
  65. package/dist/esm/emitEVMProfile.d.ts.map +1 -0
  66. package/dist/esm/emitEVMProfile.js +127 -0
  67. package/dist/esm/emitEVMProfile.js.map +1 -0
  68. package/dist/esm/index.d.ts +11 -0
  69. package/dist/esm/index.d.ts.map +1 -0
  70. package/dist/esm/index.js +11 -0
  71. package/dist/esm/index.js.map +1 -0
  72. package/dist/esm/package.json +3 -0
  73. package/dist/esm/params.d.ts +3 -0
  74. package/dist/esm/params.d.ts.map +1 -0
  75. package/dist/esm/params.js +102 -0
  76. package/dist/esm/params.js.map +1 -0
  77. package/dist/esm/requests.d.ts +11 -0
  78. package/dist/esm/requests.d.ts.map +1 -0
  79. package/dist/esm/requests.js +204 -0
  80. package/dist/esm/requests.js.map +1 -0
  81. package/dist/esm/runBlock.d.ts +35 -0
  82. package/dist/esm/runBlock.d.ts.map +1 -0
  83. package/dist/esm/runBlock.js +790 -0
  84. package/dist/esm/runBlock.js.map +1 -0
  85. package/dist/esm/runFrameTx.d.ts +18 -0
  86. package/dist/esm/runFrameTx.d.ts.map +1 -0
  87. package/dist/esm/runFrameTx.js +310 -0
  88. package/dist/esm/runFrameTx.js.map +1 -0
  89. package/dist/esm/runTx.d.ts +18 -0
  90. package/dist/esm/runTx.d.ts.map +1 -0
  91. package/dist/esm/runTx.js +896 -0
  92. package/dist/esm/runTx.js.map +1 -0
  93. package/dist/esm/types.d.ts +452 -0
  94. package/dist/esm/types.d.ts.map +1 -0
  95. package/dist/esm/types.js +2 -0
  96. package/dist/esm/types.js.map +1 -0
  97. package/dist/esm/vm.d.ts +75 -0
  98. package/dist/esm/vm.d.ts.map +1 -0
  99. package/dist/esm/vm.js +107 -0
  100. package/dist/esm/vm.js.map +1 -0
  101. package/dist/tsconfig.prod.cjs.tsbuildinfo +1 -0
  102. package/dist/tsconfig.prod.esm.tsbuildinfo +1 -0
  103. package/package.json +117 -0
  104. package/src/bloom/index.ts +83 -0
  105. package/src/buildBlock.ts +470 -0
  106. package/src/constructors.ts +91 -0
  107. package/src/emitEVMProfile.ts +151 -0
  108. package/src/index.ts +10 -0
  109. package/src/params.ts +104 -0
  110. package/src/requests.ts +293 -0
  111. package/src/runBlock.ts +1022 -0
  112. package/src/runFrameTx.ts +411 -0
  113. package/src/runTx.ts +1203 -0
  114. package/src/types.ts +511 -0
  115. package/src/vm.ts +147 -0
@@ -0,0 +1,91 @@
1
+ import { Common, Mainnet } from '@feelyourprotocol/common'
2
+ import { EVMMockBlockchain, createEVM, getActivePrecompiles } from '@feelyourprotocol/evm'
3
+ import { MerkleStateManager } from '@feelyourprotocol/statemanager'
4
+ import {
5
+ Account,
6
+ Address,
7
+ EthereumJSErrorWithoutCode,
8
+ createAccount,
9
+ unprefixedHexToBytes,
10
+ } from '@feelyourprotocol/util'
11
+
12
+ import { VM } from './vm.ts'
13
+
14
+ import type { VMOpts } from './types.ts'
15
+
16
+ /**
17
+ * VM async constructor. Creates engine instance and initializes it.
18
+ *
19
+ * @param opts VM engine constructor options
20
+ */
21
+ export async function createVM(opts: VMOpts = {}): Promise<VM> {
22
+ // Save if a `StateManager` was passed (for activatePrecompiles)
23
+ const didPassStateManager = opts.stateManager !== undefined
24
+
25
+ // Add common, SM, blockchain, EVM here
26
+ if (opts.common === undefined) {
27
+ opts.common = new Common({ chain: Mainnet })
28
+ }
29
+
30
+ if (opts.stateManager === undefined) {
31
+ opts.stateManager = new MerkleStateManager({
32
+ common: opts.common,
33
+ })
34
+ }
35
+
36
+ if (opts.blockchain === undefined) {
37
+ opts.blockchain = new EVMMockBlockchain()
38
+ }
39
+
40
+ if (opts.profilerOpts !== undefined) {
41
+ const profilerOpts = opts.profilerOpts
42
+ if (profilerOpts.reportAfterBlock === true && profilerOpts.reportAfterTx === true) {
43
+ throw EthereumJSErrorWithoutCode(
44
+ 'Cannot have `reportProfilerAfterBlock` and `reportProfilerAfterTx` set to `true` at the same time',
45
+ )
46
+ }
47
+ }
48
+
49
+ if (opts.evm !== undefined && opts.evmOpts !== undefined) {
50
+ throw EthereumJSErrorWithoutCode('the evm and evmOpts options cannot be used in conjunction')
51
+ }
52
+
53
+ if (opts.evm === undefined) {
54
+ let enableProfiler = false
55
+ if (opts.profilerOpts?.reportAfterBlock === true || opts.profilerOpts?.reportAfterTx === true) {
56
+ enableProfiler = true
57
+ }
58
+ const evmOpts = opts.evmOpts ?? {}
59
+ opts.evm = await createEVM({
60
+ common: opts.common,
61
+ stateManager: opts.stateManager,
62
+ blockchain: opts.blockchain,
63
+ profiler: {
64
+ enabled: enableProfiler,
65
+ },
66
+ ...evmOpts,
67
+ })
68
+ }
69
+
70
+ if (opts.activatePrecompiles === true && !didPassStateManager) {
71
+ await opts.evm.journal.checkpoint()
72
+ // put 1 wei in each of the precompiles in order to make the accounts non-empty and thus not have them deduct `callNewAccount` gas.
73
+ for (const [addressStr] of getActivePrecompiles(opts.common)) {
74
+ const address = new Address(unprefixedHexToBytes(addressStr))
75
+ let account = await opts.evm.stateManager.getAccount(address)
76
+ // Only do this if it is not overridden in genesis
77
+ // Note: in the case that custom genesis has storage fields, this is preserved
78
+ if (account === undefined) {
79
+ account = new Account()
80
+ const newAccount = createAccount({
81
+ balance: 1,
82
+ storageRoot: account.storageRoot,
83
+ })
84
+ await opts.evm.stateManager.putAccount(address, newAccount)
85
+ }
86
+ }
87
+ await opts.evm.journal.commit()
88
+ }
89
+
90
+ return new VM(opts)
91
+ }
@@ -0,0 +1,151 @@
1
+ import type { EVMPerformanceLogOutput } from '@feelyourprotocol/evm'
2
+
3
+ /**
4
+ * Emit EVM profile logs
5
+ * @param logs
6
+ * @param profileTitle
7
+ * @hidden
8
+ */
9
+ export function emitEVMProfile(logs: EVMPerformanceLogOutput[], profileTitle: string) {
10
+ if (logs.length === 0) {
11
+ return
12
+ }
13
+
14
+ // Track total calls / time (ms) / gas
15
+
16
+ let calls = 0
17
+ let totalMs = 0
18
+ let totalGas = 0
19
+
20
+ // Order of columns to report (see `EVMPerformanceLogOutput` type)
21
+
22
+ const colOrder: (keyof EVMPerformanceLogOutput)[] = [
23
+ 'tag',
24
+ 'calls',
25
+ 'avgTimePerCall',
26
+ 'totalTime',
27
+ 'staticGasUsed',
28
+ 'dynamicGasUsed',
29
+ 'gasUsed',
30
+ 'staticGas',
31
+ 'millionGasPerSecond',
32
+ 'blocksPerSlot',
33
+ ]
34
+
35
+ // The name of this column to report (saves space)
36
+ const colNames = [
37
+ 'tag',
38
+ 'calls',
39
+ 'ms/call',
40
+ 'total (ms)',
41
+ 'sgas',
42
+ 'dgas',
43
+ 'total (s+d)',
44
+ 'static fee',
45
+ 'Mgas/s',
46
+ 'BpS',
47
+ ]
48
+
49
+ // Special padStr method which inserts whitespace left and right
50
+ // This ensures that there is at least one whitespace between the columns (denoted by pipe `|` chars)
51
+ function padStr(str: string | number, leftpad: number) {
52
+ return ' ' + str.toString().padStart(leftpad, ' ') + ' '
53
+ }
54
+ // Returns the string length of this column. Used to calculate how big the header / footer should be
55
+ function strLen(str: string | number) {
56
+ return padStr(str, 0).length - 2
57
+ }
58
+
59
+ // Step one: calculate the length of each column
60
+ const colLength: number[] = []
61
+
62
+ for (const entry of logs) {
63
+ let ins = 0
64
+ colLength[ins] = Math.max(colLength[ins] ?? 0, strLen(colNames[ins]))
65
+ for (const key of colOrder) {
66
+ if (entry[key] !== undefined) {
67
+ // If entry is available, max out the current column length (this will be the longest string of this column)
68
+ colLength[ins] = Math.max(colLength[ins] ?? 0, strLen(entry[key]!))
69
+ ins++
70
+ // In this switch statement update the total calls / time / gas used
71
+ switch (key) {
72
+ case 'calls':
73
+ calls += entry[key]
74
+ break
75
+ case 'totalTime':
76
+ totalMs += entry[key]
77
+ break
78
+ case 'gasUsed':
79
+ totalGas += entry[key]
80
+ break
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ // Ensure that the column names also fit on the column length
87
+ for (const i in colLength) {
88
+ colLength[i] = Math.max(colLength[i] ?? 0, strLen(colNames[i]))
89
+ }
90
+
91
+ // Calculate the total header length
92
+ // This is done by summing all columns together, plus adding three extra chars per column (two whitespace, one pipe)
93
+ // Remove the final pipe character since this is included in the header string (so subtract one)
94
+ const headerLength = colLength.reduce((pv, cv) => pv + cv, 0) + colLength.length * 3 - 1
95
+
96
+ const blockGasLimit = 30_000_000 // Block gas limit
97
+ const slotTime = 12000 // Time in milliseconds (!) per slot
98
+
99
+ // Normalize constant to check if execution time is above one block per slot (>=1) or not (<1)
100
+ const bpsNormalizer = blockGasLimit / slotTime
101
+
102
+ const avgGas = totalGas / totalMs // Gas per millisecond
103
+ const mGasSAvg = Math.round(avgGas) / 1e3
104
+ const bpSAvg = Math.round((avgGas / bpsNormalizer) * 1e3) / 1e3
105
+
106
+ // Write the profile title
107
+ // eslint-disable-next-line
108
+ console.log('+== ' + profileTitle + ' ==+')
109
+ // Write the summary of this profile
110
+ // eslint-disable-next-line
111
+ console.log(
112
+ `+== Calls: ${calls}, Total time: ${
113
+ Math.round(totalMs * 1e3) / 1e3
114
+ }ms, Total gas: ${totalGas}, MGas/s: ${mGasSAvg}, Blocks per Slot (BpS): ${bpSAvg} ==+`,
115
+ )
116
+
117
+ // Generate and write the header
118
+ const header = '|' + '-'.repeat(headerLength) + '|'
119
+ // eslint-disable-next-line
120
+ console.log(header)
121
+
122
+ // Write the columns
123
+ let str = ''
124
+ for (const i in colLength) {
125
+ str += '|' + padStr(colNames[i], colLength[i])
126
+ }
127
+ str += '|'
128
+
129
+ // eslint-disable-next-line
130
+ console.log(str)
131
+
132
+ // Write each profile entry
133
+ for (const entry of logs) {
134
+ let str = ''
135
+ let i = 0
136
+ for (const key of colOrder) {
137
+ if (entry[key] !== undefined) {
138
+ str += '|' + padStr(entry[key]!, colLength[i])
139
+ i++
140
+ }
141
+ }
142
+ str += '|'
143
+ // eslint-disable-next-line
144
+ console.log(str)
145
+ }
146
+
147
+ // Finally, write the footer
148
+ const footer = '+' + '-'.repeat(headerLength) + '+'
149
+ // eslint-disable-next-line
150
+ console.log(footer)
151
+ }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export { Bloom } from './bloom/index.ts'
2
+ export { BlockBuilder, BuildStatus } from './buildBlock.ts'
3
+ export { buildBlock } from './buildBlock.ts'
4
+ export * from './constructors.ts'
5
+ export * from './params.ts'
6
+ export { encodeReceipt } from './runBlock.ts'
7
+ export { runBlock } from './runBlock.ts'
8
+ export { runTx } from './runTx.ts'
9
+ export * from './types.ts'
10
+ export { VM } from './vm.ts'
package/src/params.ts ADDED
@@ -0,0 +1,104 @@
1
+ import type { ParamsDict } from '@feelyourprotocol/common'
2
+ import { SYSTEM_ADDRESS } from '@feelyourprotocol/util'
3
+
4
+ export const paramsVM: ParamsDict = {
5
+ /**
6
+ * Frontier/Chainstart
7
+ */
8
+ 1: {
9
+ // gasConfig
10
+ maxRefundQuotient: 2, // Maximum refund quotient; max tx refund is min(tx.gasUsed/maxRefundQuotient, tx.gasRefund)
11
+ blobGasPerBlob: 0,
12
+ maxBlobGasPerBlock: 0,
13
+ targetBlobGasPerBlock: 0,
14
+ // pow
15
+ minerReward: '5000000000000000000', // the amount a miner get rewarded for mining a block
16
+ },
17
+ /**
18
+ . * Byzantium HF Meta EIP
19
+ . */
20
+ 609: {
21
+ // pow
22
+ minerReward: '3000000000000000000', // the amount a miner get rewarded for mining a block
23
+ },
24
+ /**
25
+ . * Constantinople HF Meta EIP
26
+ . */
27
+ 1013: {
28
+ // pow
29
+ minerReward: '2000000000000000000', // The amount a miner gets rewarded for mining a block
30
+ },
31
+ /**
32
+ . * Fee market change for ETH 1.0 chain
33
+ . */
34
+ 1559: {
35
+ // gasConfig
36
+ elasticityMultiplier: 2, // Maximum block gas target elasticity
37
+ initialBaseFee: 1000000000, // Initial base fee on first EIP1559 block
38
+ },
39
+ /**
40
+ * Save historical block hashes in state (Verkle related usage, UNSTABLE)
41
+ */
42
+ 2935: {
43
+ // config
44
+ historyStorageAddress: '0x0000F90827F1C53A10CB7A02335B175320002935', // The address where the historical blockhashes are stored
45
+ historyServeWindow: 8191, // The amount of blocks to be served by the historical blockhash contract
46
+ systemAddress: SYSTEM_ADDRESS, // The system address
47
+ },
48
+ /**
49
+ . * Reduction in refunds
50
+ . */
51
+ 3529: {
52
+ // gasConfig
53
+ maxRefundQuotient: 5, // Maximum refund quotient; max tx refund is min(tx.gasUsed/maxRefundQuotient, tx.gasRefund)
54
+ },
55
+ /**
56
+ . * Shard Blob Transactions
57
+ . */
58
+ 4844: {
59
+ // gasConfig
60
+ targetBlobGasPerBlock: 393216, // The target blob gas consumed per block
61
+ blobGasPerBlob: 131072, // The base fee for blob gas per blob
62
+ maxBlobGasPerBlock: 786432, // The max blob gas allowable per block
63
+ blobGasPriceUpdateFraction: 3338477, // The denominator used in the exponential when calculating a blob gas price
64
+ // gasPrices
65
+ minBlobGas: 1, // The minimum fee per blob gas
66
+ },
67
+ /**
68
+ . * Beacon block root in the EVM
69
+ . */
70
+ 4788: {
71
+ // config
72
+ historicalRootsLength: 8191, // The modulo parameter of the beaconroot ring buffer in the beaconroot stateful precompile
73
+ },
74
+ /**
75
+ * Execution layer triggerable withdrawals (experimental)
76
+ */
77
+ 7002: {
78
+ // config
79
+ systemAddress: SYSTEM_ADDRESS, // The system address to perform operations on the withdrawal requests predeploy address
80
+ // See: https://github.com/ethereum/EIPs/pull/8934/files
81
+ withdrawalRequestPredeployAddress: '0x00000961EF480EB55E80D19AD83579A64C007002', // Address of the validator excess address
82
+ systemCallGasLimit: 30_000_000, // EIP-7002 system call gas limit
83
+ },
84
+
85
+ /**
86
+ * Increase the MAX_EFFECTIVE_BALANCE -> Execution layer triggered consolidations (experimental)
87
+ */
88
+ 7251: {
89
+ // config
90
+ systemAddress: SYSTEM_ADDRESS, // The system address to perform operations on the consolidation requests predeploy address
91
+ // See: https://github.com/ethereum/EIPs/pull/8934/files
92
+ consolidationRequestPredeployAddress: '0x0000BBDDC7CE488642FB579F8B00F3A590007251', // Address of the consolidations contract
93
+ systemCallGasLimit: 30_000_000, // EIP-7251 system call gas limit
94
+ },
95
+ /**
96
+ . * Shard Blob Transactions
97
+ . */
98
+ 7691: {
99
+ // gasConfig
100
+ targetBlobGasPerBlock: 786432, // The target blob gas consumed per block
101
+ maxBlobGasPerBlock: 1179648, // The max blob gas allowable per block
102
+ blobGasPriceUpdateFraction: 5007716, // The denominator used in the exponential when calculating a blob gas price
103
+ },
104
+ }
@@ -0,0 +1,293 @@
1
+ import { Mainnet } from '@feelyourprotocol/common'
2
+ import type { BlockLevelAccessList, PrefixedHexString } from '@feelyourprotocol/util'
3
+ import {
4
+ CLRequest,
5
+ CLRequestType,
6
+ EthereumJSErrorWithoutCode,
7
+ bigIntToAddressBytes,
8
+ bigIntToBytes,
9
+ bytesToBigInt,
10
+ bytesToHex,
11
+ concatBytes,
12
+ createAddressFromString,
13
+ setLengthLeft,
14
+ } from '@feelyourprotocol/util'
15
+
16
+ import type { RunTxResult } from './types.ts'
17
+ import type { VM } from './vm.ts'
18
+
19
+ const DEPOSIT_TOPIC = '0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5'
20
+ const PUBKEY_OFFSET = BigInt(160)
21
+ const WITHDRAWAL_CREDENTIALS_OFFSET = BigInt(256)
22
+ const AMOUNT_OFFSET = BigInt(320)
23
+ const SIGNATURE_OFFSET = BigInt(384)
24
+ const INDEX_OFFSET = BigInt(512)
25
+ const PUBKEY_SIZE = BigInt(48)
26
+ const WITHDRAWAL_CREDENTIALS_SIZE = BigInt(32)
27
+ const AMOUNT_SIZE = BigInt(8)
28
+ const SIGNATURE_SIZE = BigInt(96)
29
+ const INDEX_SIZE = BigInt(8)
30
+ const LOG_SIZE = 576
31
+ const LOG_LAYOUT_MISMATCH = 'invalid deposit log: unsupported data layout'
32
+
33
+ function cloneSystemAccessEntry(vm: VM, systemAddressHex: PrefixedHexString) {
34
+ const access = vm.evm.blockLevelAccessList?.accesses[systemAddressHex]
35
+ if (access === undefined) {
36
+ return undefined
37
+ }
38
+
39
+ const storageChanges: typeof access.storageChanges = {}
40
+ for (const [slot, changes] of Object.entries(access.storageChanges)) {
41
+ storageChanges[slot as PrefixedHexString] = changes.map(
42
+ ([index, value]) => [index, value] as const,
43
+ )
44
+ }
45
+
46
+ return {
47
+ nonceChanges: new Map(access.nonceChanges),
48
+ balanceChanges: new Map(access.balanceChanges),
49
+ codeChanges: access.codeChanges.map(([index, code]) => [index, code] as const),
50
+ storageChanges,
51
+ storageReads: new Set(access.storageReads),
52
+ }
53
+ }
54
+
55
+ function restoreSystemAccessEntry(
56
+ vm: VM,
57
+ systemAddressHex: PrefixedHexString,
58
+ snapshot: ReturnType<typeof cloneSystemAccessEntry>,
59
+ ) {
60
+ const bal = vm.evm.blockLevelAccessList
61
+ if (bal === undefined) {
62
+ return
63
+ }
64
+
65
+ if (snapshot === undefined) {
66
+ delete bal.accesses[systemAddressHex]
67
+ return
68
+ }
69
+
70
+ bal.accesses[systemAddressHex] = snapshot as BlockLevelAccessList['accesses'][PrefixedHexString]
71
+ }
72
+
73
+ /**
74
+ * This helper method generates a list of all CL requests that can be included in a pending block
75
+ * @param vm VM instance (used in deriving partial withdrawal requests)
76
+ * @param txResults (used in deriving deposit requests)
77
+ * @returns a list of CL requests in ascending order by type
78
+ */
79
+ export const accumulateRequests = async (
80
+ vm: VM,
81
+ txResults: RunTxResult[],
82
+ ): Promise<CLRequest<CLRequestType>[]> => {
83
+ const requests: CLRequest<CLRequestType>[] = []
84
+ const common = vm.common
85
+
86
+ if (common.isActivatedEIP(6110)) {
87
+ const depositContractAddress =
88
+ vm.common['_chainParams'].depositContractAddress ?? Mainnet.depositContractAddress
89
+ if (depositContractAddress === undefined)
90
+ throw EthereumJSErrorWithoutCode('deposit contract address required with EIP 6110')
91
+ const depositsRequest = accumulateDepositsRequest(depositContractAddress, txResults)
92
+ requests.push(depositsRequest)
93
+ }
94
+
95
+ if (common.isActivatedEIP(7002)) {
96
+ const withdrawalsRequest = await accumulateWithdrawalsRequest(vm)
97
+ requests.push(withdrawalsRequest)
98
+ }
99
+
100
+ if (common.isActivatedEIP(7251)) {
101
+ const consolidationsRequest = await accumulateConsolidationsRequest(vm)
102
+ requests.push(consolidationsRequest)
103
+ }
104
+
105
+ // requests are already type byte ordered by construction
106
+ return requests
107
+ }
108
+
109
+ const accumulateWithdrawalsRequest = async (
110
+ vm: VM,
111
+ ): Promise<CLRequest<typeof CLRequestType.Withdrawal>> => {
112
+ // Partial withdrawals logic
113
+ const addressBytes = setLengthLeft(
114
+ bigIntToBytes(vm.common.param('withdrawalRequestPredeployAddress')),
115
+ 20,
116
+ )
117
+ const withdrawalsAddress = createAddressFromString(bytesToHex(addressBytes))
118
+
119
+ const systemAddressBytes = bigIntToAddressBytes(vm.common.param('systemAddress'))
120
+ const systemAddress = createAddressFromString(bytesToHex(systemAddressBytes))
121
+ const systemAccount = await vm.stateManager.getAccount(systemAddress)
122
+
123
+ const originalAccount = await vm.stateManager.getAccount(withdrawalsAddress)
124
+
125
+ if (originalAccount === undefined) {
126
+ return new CLRequest(CLRequestType.Withdrawal, new Uint8Array())
127
+ }
128
+
129
+ const systemAddressHex = systemAddress.toString()
130
+ const balSnapshot = cloneSystemAccessEntry(vm, systemAddressHex)
131
+ const results = await vm.evm.runCall({
132
+ caller: systemAddress,
133
+ gasLimit: vm.common.param('systemCallGasLimit'),
134
+ to: withdrawalsAddress,
135
+ })
136
+ restoreSystemAccessEntry(vm, systemAddressHex, balSnapshot)
137
+
138
+ if (systemAccount === undefined) {
139
+ await vm.stateManager.deleteAccount(systemAddress)
140
+ } else {
141
+ await vm.stateManager.putAccount(systemAddress, systemAccount)
142
+ }
143
+
144
+ const resultsBytes = results.execResult.returnValue
145
+ return new CLRequest(CLRequestType.Withdrawal, resultsBytes)
146
+ }
147
+
148
+ const accumulateConsolidationsRequest = async (
149
+ vm: VM,
150
+ ): Promise<CLRequest<typeof CLRequestType.Consolidation>> => {
151
+ // Partial withdrawals logic
152
+ const addressBytes = setLengthLeft(
153
+ bigIntToBytes(vm.common.param('consolidationRequestPredeployAddress')),
154
+ 20,
155
+ )
156
+ const consolidationsAddress = createAddressFromString(bytesToHex(addressBytes))
157
+
158
+ const systemAddressBytes = bigIntToAddressBytes(vm.common.param('systemAddress'))
159
+ const systemAddress = createAddressFromString(bytesToHex(systemAddressBytes))
160
+ const systemAccount = await vm.stateManager.getAccount(systemAddress)
161
+
162
+ const originalAccount = await vm.stateManager.getAccount(consolidationsAddress)
163
+
164
+ if (originalAccount === undefined) {
165
+ return new CLRequest(CLRequestType.Consolidation, new Uint8Array(0))
166
+ }
167
+
168
+ const systemAddressHex = systemAddress.toString()
169
+ const balSnapshot = cloneSystemAccessEntry(vm, systemAddressHex)
170
+ const results = await vm.evm.runCall({
171
+ caller: systemAddress,
172
+ gasLimit: vm.common.param('systemCallGasLimit'),
173
+ to: consolidationsAddress,
174
+ })
175
+ restoreSystemAccessEntry(vm, systemAddressHex, balSnapshot)
176
+
177
+ if (systemAccount === undefined) {
178
+ await vm.stateManager.deleteAccount(systemAddress)
179
+ } else {
180
+ await vm.stateManager.putAccount(systemAddress, systemAccount)
181
+ }
182
+
183
+ const resultsBytes = results.execResult.returnValue
184
+ return new CLRequest(CLRequestType.Consolidation, resultsBytes)
185
+ }
186
+
187
+ const accumulateDepositsRequest = (
188
+ depositContractAddress: string,
189
+ txResults: RunTxResult[],
190
+ ): CLRequest<typeof CLRequestType.Deposit> => {
191
+ let resultsBytes = new Uint8Array(0)
192
+ const depositContractAddressLowerCase = depositContractAddress.toLowerCase()
193
+ for (const [_, tx] of txResults.entries()) {
194
+ for (let i = 0; i < tx.receipt.logs.length; i++) {
195
+ const log = tx.receipt.logs[i]
196
+ const [address, topics, data] = log
197
+ if (
198
+ topics.length > 0 &&
199
+ bytesToHex(topics[0]) === DEPOSIT_TOPIC &&
200
+ depositContractAddressLowerCase === bytesToHex(address).toLowerCase()
201
+ ) {
202
+ const { pubkey, withdrawalCredentials, amount, signature, index } = parseDepositLog(data)
203
+ const depositRequestBytes = concatBytes(
204
+ pubkey,
205
+ withdrawalCredentials,
206
+ amount,
207
+ signature,
208
+ index,
209
+ )
210
+
211
+ resultsBytes = concatBytes(resultsBytes, depositRequestBytes)
212
+ }
213
+ }
214
+ }
215
+
216
+ return new CLRequest(CLRequestType.Deposit, resultsBytes)
217
+ }
218
+
219
+ function parseDepositLog(requestData: Uint8Array) {
220
+ if (requestData.length !== LOG_SIZE) {
221
+ throw EthereumJSErrorWithoutCode(LOG_LAYOUT_MISMATCH)
222
+ }
223
+ // Extracts validator pubkey, withdrawal credential, deposit amount, signature,
224
+ // and validator index from Deposit Event log.
225
+ // The event fields are non-indexed so contained in one byte array (log[2]) so parsing is as follows:
226
+ // 1. Read the first 32 bytes to get the starting position of the first field.
227
+ // 2. Continue reading the byte array in 32 byte increments to get all the field starting positions
228
+ // 3. Read 32 bytes starting with the first field position to get the size of the first field
229
+ // 4. Read the bytes from first field position + 32 + the size of the first field to get the first field value
230
+ // 5. Repeat steps 3-4 for each field
231
+ const pubKeyIdxBigInt = bytesToBigInt(requestData.slice(0, 32))
232
+ const withdrawalCreditsIdxBigInt = bytesToBigInt(requestData.slice(32, 64))
233
+ const amountIdxBigInt = bytesToBigInt(requestData.slice(64, 96))
234
+ const sigIdxBigInt = bytesToBigInt(requestData.slice(96, 128))
235
+ const indexIdxBigInt = bytesToBigInt(requestData.slice(128, 160))
236
+
237
+ if (
238
+ pubKeyIdxBigInt !== PUBKEY_OFFSET ||
239
+ withdrawalCreditsIdxBigInt !== WITHDRAWAL_CREDENTIALS_OFFSET ||
240
+ amountIdxBigInt !== AMOUNT_OFFSET ||
241
+ sigIdxBigInt !== SIGNATURE_OFFSET ||
242
+ indexIdxBigInt !== INDEX_OFFSET
243
+ ) {
244
+ throw EthereumJSErrorWithoutCode(LOG_LAYOUT_MISMATCH)
245
+ }
246
+
247
+ const pubKeyIdx = Number(pubKeyIdxBigInt)
248
+ const withdrawalCreditsIdx = Number(withdrawalCreditsIdxBigInt)
249
+ const amountIdx = Number(amountIdxBigInt)
250
+ const sigIdx = Number(sigIdxBigInt)
251
+ const indexIdx = Number(indexIdxBigInt)
252
+
253
+ const pubKeySizeBigInt = bytesToBigInt(requestData.slice(pubKeyIdx, pubKeyIdx + 32))
254
+ const withdrawalCreditsSizeBigInt = bytesToBigInt(
255
+ requestData.slice(withdrawalCreditsIdx, withdrawalCreditsIdx + 32),
256
+ )
257
+ const amountSizeBigInt = bytesToBigInt(requestData.slice(amountIdx, amountIdx + 32))
258
+ const sigSizeBigInt = bytesToBigInt(requestData.slice(sigIdx, sigIdx + 32))
259
+ const indexSizeBigInt = bytesToBigInt(requestData.slice(indexIdx, indexIdx + 32))
260
+
261
+ if (
262
+ pubKeySizeBigInt !== PUBKEY_SIZE ||
263
+ withdrawalCreditsSizeBigInt !== WITHDRAWAL_CREDENTIALS_SIZE ||
264
+ amountSizeBigInt !== AMOUNT_SIZE ||
265
+ sigSizeBigInt !== SIGNATURE_SIZE ||
266
+ indexSizeBigInt !== INDEX_SIZE
267
+ ) {
268
+ throw EthereumJSErrorWithoutCode(LOG_LAYOUT_MISMATCH)
269
+ }
270
+
271
+ const pubKeySize = Number(pubKeySizeBigInt)
272
+ const withdrawalCreditsSize = Number(withdrawalCreditsSizeBigInt)
273
+ const amountSize = Number(amountSizeBigInt)
274
+ const sigSize = Number(sigSizeBigInt)
275
+ const indexSize = Number(indexSizeBigInt)
276
+
277
+ const pubkey = requestData.slice(pubKeyIdx + 32, pubKeyIdx + 32 + pubKeySize)
278
+ const withdrawalCredentials = requestData.slice(
279
+ withdrawalCreditsIdx + 32,
280
+ withdrawalCreditsIdx + 32 + withdrawalCreditsSize,
281
+ )
282
+ const amount = requestData.slice(amountIdx + 32, amountIdx + 32 + amountSize)
283
+ const signature = requestData.slice(sigIdx + 32, sigIdx + 32 + sigSize)
284
+ const index = requestData.slice(indexIdx + 32, indexIdx + 32 + indexSize)
285
+
286
+ return {
287
+ pubkey,
288
+ withdrawalCredentials,
289
+ amount,
290
+ signature,
291
+ index,
292
+ }
293
+ }