@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,411 @@
1
+ /**
2
+ * EIP-8141 Frame Transaction Execution
3
+ *
4
+ * Implements the frame execution loop and default code for EOA accounts.
5
+ * The EVM holds a FrameExecutionContext = { tx, state } during execution.
6
+ */
7
+
8
+ import { ENTRY_POINT_ADDRESS, FRAME_MODE } from '@feelyourprotocol/evm'
9
+ import type { ExecResult, FrameExecutionState, FrameResult, ParsedFrame } from '@feelyourprotocol/evm'
10
+ import { RLP } from '@feelyourprotocol/rlp'
11
+ import type { FrameBytes, FrameEIP8141Tx } from '@feelyourprotocol/tx'
12
+ import {
13
+ Account,
14
+ Address,
15
+ BIGINT_0,
16
+ BIGINT_1,
17
+ EthereumJSErrorWithoutCode,
18
+ bytesToBigInt,
19
+ bytesToHex,
20
+ concatBytes,
21
+ ecrecover,
22
+ equalsBytes,
23
+ hexToBytes,
24
+ publicToAddress,
25
+ } from '@feelyourprotocol/util'
26
+ import { keccak_256 } from '@noble/hashes/sha3.js'
27
+
28
+ import type { Block } from '@feelyourprotocol/block'
29
+ import type { RunTxOpts, RunTxResult } from './types.ts'
30
+ import type { VM } from './vm.ts'
31
+
32
+ const ECRECOVER_GAS = BigInt(3000)
33
+ const DEFAULT_CODE_OVERHEAD = BigInt(100)
34
+
35
+ /**
36
+ * Parse raw FrameBytes from the tx into ParsedFrame objects.
37
+ */
38
+ function parseFrames(tx: FrameEIP8141Tx): ParsedFrame[] {
39
+ return tx.frames.map((f: FrameBytes) => {
40
+ const modeVal = f[0].length === 0 ? 0 : Number(bytesToBigInt(f[0]))
41
+ return {
42
+ mode: modeVal & 0xff,
43
+ target: f[1].length === 0 ? null : new Address(f[1]),
44
+ gasLimit: f[2].length === 0 ? BIGINT_0 : bytesToBigInt(f[2]),
45
+ data: f[3],
46
+ }
47
+ })
48
+ }
49
+
50
+ /**
51
+ * Execute an EIP-8141 frame transaction through the frame execution loop.
52
+ *
53
+ * Gas cost deduction and nonce increment are handled by APPROVE (or the default
54
+ * code equivalent), not by the VM upfront.
55
+ */
56
+ export async function runFrameTransaction(
57
+ vm: VM,
58
+ tx: FrameEIP8141Tx,
59
+ gasPrice: bigint,
60
+ blobGasPrice: bigint,
61
+ intrinsicGas: bigint,
62
+ block: Block | undefined,
63
+ _opts: RunTxOpts,
64
+ ): Promise<RunTxResult> {
65
+ const evm = vm.evm
66
+ const caller = tx.getSenderAddress()
67
+ const parsedFrames = parseFrames(tx)
68
+
69
+ const blobCount = tx.blobVersionedHashes.length
70
+ const blobGasPerBlob = blobCount > 0 ? vm.common.param('blobGasPerBlob') : BIGINT_0
71
+ const totalBlobGas = blobGasPerBlob * BigInt(blobCount)
72
+ const totalGasCost = tx.gasLimit * gasPrice
73
+ const totalBlobGasCost = totalBlobGas * blobGasPrice
74
+
75
+ const state: FrameExecutionState = {
76
+ parsedFrames,
77
+ currentFrameIndex: 0,
78
+ senderApproved: false,
79
+ payerApproved: false,
80
+ approveCalledInCurrentFrame: false,
81
+ frameResults: [],
82
+ totalGasCost,
83
+ totalBlobGasCost,
84
+ }
85
+
86
+ evm.frameExecutionContext = { tx, state }
87
+
88
+ let totalFrameGasUsed = BIGINT_0
89
+ const entryPoint = new Address(hexToBytes(ENTRY_POINT_ADDRESS))
90
+ const allLogs: any[] = []
91
+ let txInvalid = false
92
+ let invalidReason = ''
93
+
94
+ try {
95
+ for (let i = 0; i < parsedFrames.length; i++) {
96
+ state.currentFrameIndex = i
97
+ state.approveCalledInCurrentFrame = false
98
+ const frame = parsedFrames[i]
99
+
100
+ if (frame.mode === FRAME_MODE.SENDER && !state.senderApproved) {
101
+ txInvalid = true
102
+ invalidReason = 'SENDER frame executed before sender_approved'
103
+ break
104
+ }
105
+
106
+ const frameTarget = frame.target ?? caller
107
+ const frameCaller = frame.mode === FRAME_MODE.SENDER ? caller : entryPoint
108
+
109
+ const code = await vm.stateManager.getCode(frameTarget)
110
+ const hasCode = code.length > 0
111
+
112
+ let frameResult: FrameResult
113
+
114
+ if (!hasCode) {
115
+ frameResult = await runDefaultCode(
116
+ vm,
117
+ tx,
118
+ state,
119
+ frame,
120
+ frameCaller,
121
+ frameTarget,
122
+ gasPrice,
123
+ block,
124
+ )
125
+ } else {
126
+ const result = await evm.runCall({
127
+ block,
128
+ gasPrice,
129
+ caller: frameCaller,
130
+ gasLimit: frame.gasLimit,
131
+ to: frameTarget,
132
+ value: BIGINT_0,
133
+ data: frame.data,
134
+ origin: frameCaller,
135
+ blobVersionedHashes: tx.blobVersionedHashes.map(bytesToHex),
136
+ })
137
+
138
+ const reverted = result.execResult.exceptionError !== undefined
139
+ frameResult = {
140
+ status: reverted ? 0 : 1,
141
+ gasUsed: result.execResult.executionGasUsed,
142
+ returnValue: result.execResult.returnValue,
143
+ }
144
+ if (result.execResult.logs) {
145
+ allLogs.push(...result.execResult.logs)
146
+ }
147
+ }
148
+
149
+ state.frameResults.push(frameResult)
150
+ totalFrameGasUsed += frameResult.gasUsed
151
+
152
+ if (frame.mode === FRAME_MODE.VERIFY && !state.approveCalledInCurrentFrame) {
153
+ txInvalid = true
154
+ invalidReason = 'VERIFY frame did not call APPROVE'
155
+ break
156
+ }
157
+ }
158
+
159
+ if (!txInvalid && !state.payerApproved) {
160
+ txInvalid = true
161
+ invalidReason = 'payer_approved not set after executing all frames'
162
+ }
163
+ } finally {
164
+ evm.frameExecutionContext = undefined
165
+ }
166
+
167
+ if (txInvalid) {
168
+ throw EthereumJSErrorWithoutCode(`EIP-8141 frame transaction invalid: ${invalidReason}`)
169
+ }
170
+
171
+ const totalGasSpent = intrinsicGas + totalFrameGasUsed
172
+
173
+ const result: RunTxResult = {
174
+ execResult: {
175
+ executionGasUsed: totalFrameGasUsed,
176
+ exceptionError: undefined,
177
+ gas: BIGINT_0,
178
+ gasUsed: totalFrameGasUsed,
179
+ returnValue: new Uint8Array(0),
180
+ logs: allLogs.length > 0 ? allLogs : undefined,
181
+ runState: undefined as any,
182
+ } as ExecResult,
183
+ totalGasSpent,
184
+ amountSpent: BIGINT_0,
185
+ receipt: undefined as any,
186
+ bloom: undefined as any,
187
+ gasRefund: BIGINT_0,
188
+ blockGasSpent: totalGasSpent,
189
+ } as RunTxResult
190
+
191
+ return result
192
+ }
193
+
194
+ /**
195
+ * Default code execution for EOA accounts (no deployed code).
196
+ */
197
+ async function runDefaultCode(
198
+ vm: VM,
199
+ tx: FrameEIP8141Tx,
200
+ execState: FrameExecutionState,
201
+ frame: ParsedFrame,
202
+ _frameCaller: Address,
203
+ frameTarget: Address,
204
+ gasPrice: bigint,
205
+ block: Block | undefined,
206
+ ): Promise<FrameResult> {
207
+ if (frame.mode === FRAME_MODE.VERIFY) {
208
+ return runDefaultCodeVerify(vm, tx, execState, frame, frameTarget)
209
+ }
210
+ if (frame.mode === FRAME_MODE.SENDER) {
211
+ return runDefaultCodeSender(vm, tx, execState, frame, frameTarget, gasPrice, block)
212
+ }
213
+ return { status: 0, gasUsed: frame.gasLimit, returnValue: new Uint8Array(0) }
214
+ }
215
+
216
+ /**
217
+ * Default code VERIFY mode: ECDSA signature verification + APPROVE.
218
+ */
219
+ async function runDefaultCodeVerify(
220
+ vm: VM,
221
+ tx: FrameEIP8141Tx,
222
+ execState: FrameExecutionState,
223
+ frame: ParsedFrame,
224
+ frameTarget: Address,
225
+ ): Promise<FrameResult> {
226
+ const gasUsed = ECRECOVER_GAS + DEFAULT_CODE_OVERHEAD
227
+ if (frame.gasLimit < gasUsed) {
228
+ return { status: 0, gasUsed: frame.gasLimit, returnValue: new Uint8Array(0) }
229
+ }
230
+
231
+ if (!equalsBytes(frameTarget.bytes, tx.sender.bytes)) {
232
+ return { status: 0, gasUsed, returnValue: new Uint8Array(0) }
233
+ }
234
+
235
+ if (frame.data.length < 1) {
236
+ return { status: 0, gasUsed, returnValue: new Uint8Array(0) }
237
+ }
238
+
239
+ const firstByte = frame.data[0]
240
+ const scope = (firstByte >> 4) & 0xf
241
+ const sigType = firstByte & 0xf
242
+
243
+ if (sigType === 0x0) {
244
+ if (frame.data.length !== 66) {
245
+ return { status: 0, gasUsed, returnValue: new Uint8Array(0) }
246
+ }
247
+ const v = frame.data[1]
248
+ const r = frame.data.slice(2, 34)
249
+ const s = frame.data.slice(34, 66)
250
+ const dataWithoutSig = frame.data.slice(0, 1)
251
+ const sigHash = tx.getHashedMessageToSign()
252
+ const keccakFn = vm.common.customCrypto.keccak256 ?? keccak_256
253
+ const hash = keccakFn(concatBytes(sigHash, dataWithoutSig))
254
+
255
+ let recoveredAddress: Address
256
+ try {
257
+ const pubKey = ecrecover(hash, BigInt(v), r, s)
258
+ recoveredAddress = new Address(publicToAddress(pubKey))
259
+ } catch {
260
+ return { status: 0, gasUsed, returnValue: new Uint8Array(0) }
261
+ }
262
+
263
+ if (!equalsBytes(recoveredAddress.bytes, frameTarget.bytes)) {
264
+ return { status: 0, gasUsed, returnValue: new Uint8Array(0) }
265
+ }
266
+ } else {
267
+ return { status: 0, gasUsed, returnValue: new Uint8Array(0) }
268
+ }
269
+
270
+ const approveResult = await executeApprove(vm, tx, execState, frameTarget, scope)
271
+ if (!approveResult) {
272
+ return { status: 0, gasUsed, returnValue: new Uint8Array(0) }
273
+ }
274
+
275
+ execState.approveCalledInCurrentFrame = true
276
+ return { status: 1, gasUsed, returnValue: new Uint8Array(0) }
277
+ }
278
+
279
+ /**
280
+ * Default code SENDER mode: decode RLP calls and execute them.
281
+ */
282
+ async function runDefaultCodeSender(
283
+ vm: VM,
284
+ tx: FrameEIP8141Tx,
285
+ _execState: FrameExecutionState,
286
+ frame: ParsedFrame,
287
+ frameTarget: Address,
288
+ gasPrice: bigint,
289
+ block: Block | undefined,
290
+ ): Promise<FrameResult> {
291
+ if (frame.data.length < 1) {
292
+ return { status: 0, gasUsed: frame.gasLimit, returnValue: new Uint8Array(0) }
293
+ }
294
+
295
+ const firstNibble = (frame.data[0] >> 4) & 0xf
296
+ if (firstNibble !== 0x0) {
297
+ return { status: 0, gasUsed: frame.gasLimit, returnValue: new Uint8Array(0) }
298
+ }
299
+
300
+ if (!equalsBytes(frameTarget.bytes, tx.sender.bytes)) {
301
+ return { status: 0, gasUsed: frame.gasLimit, returnValue: new Uint8Array(0) }
302
+ }
303
+
304
+ const rlpData = frame.data.slice(1)
305
+ let calls: any[]
306
+ try {
307
+ const decoded = RLP.decode(rlpData)
308
+ calls = decoded as any[]
309
+ } catch {
310
+ return { status: 0, gasUsed: frame.gasLimit, returnValue: new Uint8Array(0) }
311
+ }
312
+
313
+ let remainingGas = frame.gasLimit
314
+ let totalGasUsed = BIGINT_0
315
+ const allLogs: any[] = []
316
+
317
+ for (const call of calls) {
318
+ if (!Array.isArray(call) || call.length < 3) {
319
+ return { status: 0, gasUsed: frame.gasLimit, returnValue: new Uint8Array(0) }
320
+ }
321
+ const [callTargetBytes, callValueBytes, callDataBytes] = call as [
322
+ Uint8Array,
323
+ Uint8Array,
324
+ Uint8Array,
325
+ ]
326
+ const callTarget = new Address(callTargetBytes)
327
+ const callValue = callValueBytes.length === 0 ? BIGINT_0 : bytesToBigInt(callValueBytes)
328
+ const callData = callDataBytes ?? new Uint8Array(0)
329
+
330
+ const result = await vm.evm.runCall({
331
+ block,
332
+ gasPrice,
333
+ caller: tx.sender,
334
+ gasLimit: remainingGas,
335
+ to: callTarget,
336
+ value: callValue,
337
+ data: callData,
338
+ origin: tx.sender,
339
+ })
340
+
341
+ const callGasUsed = result.execResult.executionGasUsed
342
+ totalGasUsed += callGasUsed
343
+ remainingGas = remainingGas > callGasUsed ? remainingGas - callGasUsed : BIGINT_0
344
+
345
+ if (result.execResult.logs) {
346
+ allLogs.push(...result.execResult.logs)
347
+ }
348
+
349
+ if (result.execResult.exceptionError) {
350
+ return { status: 0, gasUsed: frame.gasLimit, returnValue: new Uint8Array(0) }
351
+ }
352
+ }
353
+
354
+ return {
355
+ status: 1,
356
+ gasUsed: totalGasUsed,
357
+ returnValue: new Uint8Array(0),
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Execute APPROVE logic directly (for default code, not via opcode).
363
+ * Returns true on success, false if conditions fail.
364
+ */
365
+ async function executeApprove(
366
+ vm: VM,
367
+ tx: FrameEIP8141Tx,
368
+ execState: FrameExecutionState,
369
+ target: Address,
370
+ scope: number,
371
+ ): Promise<boolean> {
372
+ const stateManager = vm.stateManager
373
+
374
+ if (scope === 0x0) {
375
+ if (execState.senderApproved) return false
376
+ if (!equalsBytes(target.bytes, tx.sender.bytes)) return false
377
+ execState.senderApproved = true
378
+ return true
379
+ }
380
+
381
+ if (scope === 0x1) {
382
+ if (execState.payerApproved) return false
383
+ if (!execState.senderApproved) return false
384
+ let account = await stateManager.getAccount(target)
385
+ if (account === undefined) account = new Account()
386
+ if (account.balance < execState.totalGasCost + execState.totalBlobGasCost) return false
387
+ account.nonce += BIGINT_1
388
+ account.balance -= execState.totalGasCost + execState.totalBlobGasCost
389
+ await vm.evm.journal.putAccount(target, account)
390
+ execState.payerApproved = true
391
+ execState.payer = target
392
+ return true
393
+ }
394
+
395
+ if (scope === 0x2) {
396
+ if (execState.senderApproved || execState.payerApproved) return false
397
+ if (!equalsBytes(target.bytes, tx.sender.bytes)) return false
398
+ let account = await stateManager.getAccount(target)
399
+ if (account === undefined) account = new Account()
400
+ if (account.balance < execState.totalGasCost + execState.totalBlobGasCost) return false
401
+ execState.senderApproved = true
402
+ account.nonce += BIGINT_1
403
+ account.balance -= execState.totalGasCost + execState.totalBlobGasCost
404
+ await vm.evm.journal.putAccount(target, account)
405
+ execState.payerApproved = true
406
+ execState.payer = target
407
+ return true
408
+ }
409
+
410
+ return false
411
+ }