@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.
- package/LICENSE +373 -0
- package/README.md +583 -0
- package/dist/cjs/bloom/index.d.ts +29 -0
- package/dist/cjs/bloom/index.d.ts.map +1 -0
- package/dist/cjs/bloom/index.js +76 -0
- package/dist/cjs/bloom/index.js.map +1 -0
- package/dist/cjs/buildBlock.d.ts +118 -0
- package/dist/cjs/buildBlock.d.ts.map +1 -0
- package/dist/cjs/buildBlock.js +363 -0
- package/dist/cjs/buildBlock.js.map +1 -0
- package/dist/cjs/constructors.d.ts +9 -0
- package/dist/cjs/constructors.d.ts.map +1 -0
- package/dist/cjs/constructors.js +75 -0
- package/dist/cjs/constructors.js.map +1 -0
- package/dist/cjs/emitEVMProfile.d.ts +9 -0
- package/dist/cjs/emitEVMProfile.d.ts.map +1 -0
- package/dist/cjs/emitEVMProfile.js +130 -0
- package/dist/cjs/emitEVMProfile.js.map +1 -0
- package/dist/cjs/index.d.ts +11 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +36 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/params.d.ts +3 -0
- package/dist/cjs/params.d.ts.map +1 -0
- package/dist/cjs/params.js +105 -0
- package/dist/cjs/params.js.map +1 -0
- package/dist/cjs/requests.d.ts +11 -0
- package/dist/cjs/requests.d.ts.map +1 -0
- package/dist/cjs/requests.js +208 -0
- package/dist/cjs/requests.js.map +1 -0
- package/dist/cjs/runBlock.d.ts +35 -0
- package/dist/cjs/runBlock.d.ts.map +1 -0
- package/dist/cjs/runBlock.js +797 -0
- package/dist/cjs/runBlock.js.map +1 -0
- package/dist/cjs/runFrameTx.d.ts +18 -0
- package/dist/cjs/runFrameTx.d.ts.map +1 -0
- package/dist/cjs/runFrameTx.js +313 -0
- package/dist/cjs/runFrameTx.js.map +1 -0
- package/dist/cjs/runTx.d.ts +18 -0
- package/dist/cjs/runTx.d.ts.map +1 -0
- package/dist/cjs/runTx.js +900 -0
- package/dist/cjs/runTx.js.map +1 -0
- package/dist/cjs/types.d.ts +452 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +3 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/vm.d.ts +75 -0
- package/dist/cjs/vm.d.ts.map +1 -0
- package/dist/cjs/vm.js +111 -0
- package/dist/cjs/vm.js.map +1 -0
- package/dist/esm/bloom/index.d.ts +29 -0
- package/dist/esm/bloom/index.d.ts.map +1 -0
- package/dist/esm/bloom/index.js +72 -0
- package/dist/esm/bloom/index.js.map +1 -0
- package/dist/esm/buildBlock.d.ts +118 -0
- package/dist/esm/buildBlock.d.ts.map +1 -0
- package/dist/esm/buildBlock.js +358 -0
- package/dist/esm/buildBlock.js.map +1 -0
- package/dist/esm/constructors.d.ts +9 -0
- package/dist/esm/constructors.d.ts.map +1 -0
- package/dist/esm/constructors.js +72 -0
- package/dist/esm/constructors.js.map +1 -0
- package/dist/esm/emitEVMProfile.d.ts +9 -0
- package/dist/esm/emitEVMProfile.d.ts.map +1 -0
- package/dist/esm/emitEVMProfile.js +127 -0
- package/dist/esm/emitEVMProfile.js.map +1 -0
- package/dist/esm/index.d.ts +11 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +11 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/params.d.ts +3 -0
- package/dist/esm/params.d.ts.map +1 -0
- package/dist/esm/params.js +102 -0
- package/dist/esm/params.js.map +1 -0
- package/dist/esm/requests.d.ts +11 -0
- package/dist/esm/requests.d.ts.map +1 -0
- package/dist/esm/requests.js +204 -0
- package/dist/esm/requests.js.map +1 -0
- package/dist/esm/runBlock.d.ts +35 -0
- package/dist/esm/runBlock.d.ts.map +1 -0
- package/dist/esm/runBlock.js +790 -0
- package/dist/esm/runBlock.js.map +1 -0
- package/dist/esm/runFrameTx.d.ts +18 -0
- package/dist/esm/runFrameTx.d.ts.map +1 -0
- package/dist/esm/runFrameTx.js +310 -0
- package/dist/esm/runFrameTx.js.map +1 -0
- package/dist/esm/runTx.d.ts +18 -0
- package/dist/esm/runTx.d.ts.map +1 -0
- package/dist/esm/runTx.js +896 -0
- package/dist/esm/runTx.js.map +1 -0
- package/dist/esm/types.d.ts +452 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/vm.d.ts +75 -0
- package/dist/esm/vm.d.ts.map +1 -0
- package/dist/esm/vm.js +107 -0
- package/dist/esm/vm.js.map +1 -0
- package/dist/tsconfig.prod.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.prod.esm.tsbuildinfo +1 -0
- package/package.json +117 -0
- package/src/bloom/index.ts +83 -0
- package/src/buildBlock.ts +470 -0
- package/src/constructors.ts +91 -0
- package/src/emitEVMProfile.ts +151 -0
- package/src/index.ts +10 -0
- package/src/params.ts +104 -0
- package/src/requests.ts +293 -0
- package/src/runBlock.ts +1022 -0
- package/src/runFrameTx.ts +411 -0
- package/src/runTx.ts +1203 -0
- package/src/types.ts +511 -0
- 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
|
+
}
|
package/src/requests.ts
ADDED
|
@@ -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
|
+
}
|