Skip to content

Instantly share code, notes, and snippets.

@lispc
Created July 28, 2022 02:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lispc/7d575feb1e21f0a75054f335c7ef6418 to your computer and use it in GitHub Desktop.
Save lispc/7d575feb1e21f0a75054f335c7ef6418 to your computer and use it in GitHub Desktop.
700lines fork
diff --git a/src/evm/eei.ts b/src/evm/eei.ts
index 9793588..12d545b 100644
--- a/src/evm/eei.ts
+++ b/src/evm/eei.ts
@@ -1,5 +1,5 @@
import { debug as createDebugLogger } from 'debug'
-import { Account, Address, BN, MAX_UINT64 } from 'ethereumjs-util'
+import { Account, Address, BN, MAX_UINT64, toBuffer } from 'ethereumjs-util'
import { Block } from '@ethereumjs/block'
import Blockchain from '@ethereumjs/blockchain'
import Common, { ConsensusAlgorithm } from '@ethereumjs/common'
@@ -8,6 +8,10 @@ import { VmError, ERROR } from '../exceptions'
import Message from './message'
import EVM, { EVMResult } from './evm'
import { Log } from './types'
+const ethers = require('ethers')
+
+const ADDRESS_SYSTEM = '0x0000000000000000000000000000000000000000'
+const STATE_ROOT_STORAGE_POS = 0
const debugGas = createDebugLogger('vm:eei:gas')
@@ -53,6 +57,13 @@ export interface RunResult {
selfdestruct: { [k: string]: Buffer }
}
+/**
+ * Result for object of a CALL
+ */
+export interface BaseCallResult {
+ returnCode: BN
+ results?: EVMResult
+}
/**
* External interface made available to EVM bytecode. Modeled after
* the ewasm EEI [spec](https://github.com/ewasm/design/blob/master/eth_interface.md).
@@ -351,6 +362,25 @@ export default class EEI {
return new BN(block.hash())
}
+ /**
+ * Returns Gets the hash of one of the 256 most recent complete blocks.
+ * @param num - Number of block
+ */
+ async getBatchHash(num: BN): Promise<BN> {
+ const stateRootPos = ethers.utils.solidityKeccak256(
+ ['uint256', 'uint256'],
+ [Number(num), STATE_ROOT_STORAGE_POS]
+ )
+ const hash = await this._state.getContractStorage(
+ new Address(toBuffer(ADDRESS_SYSTEM)),
+ toBuffer(stateRootPos)
+ )
+ if (!hash || hash.length === 0) {
+ return new BN(0)
+ }
+ return new BN(hash)
+ }
+
/**
* Store 256-bit a value in memory to persistent storage.
*/
@@ -406,7 +436,30 @@ export default class EEI {
* @param toAddress - Beneficiary address
*/
async selfDestruct(toAddress: Address): Promise<void> {
- return this._selfDestruct(toAddress)
+ // return this._selfDestruct(toAddress)
+ return this._customSelfDestruct(toAddress)
+ }
+
+ async _customSelfDestruct(toAddress: Address): Promise<void> {
+ // only add to refund if this is the first selfdestruct for the address
+ if (!this._result.selfdestruct[this._env.address.buf.toString('hex')]) {
+ this.refundGas(new BN(this._common.param('gasPrices', 'selfdestructRefund')))
+ }
+
+ // clear contract bytecode
+ await this._state.putContractCode(this._env.address, toBuffer('0x'))
+
+ // Add to beneficiary balance
+ const toAccount = await this._state.getAccount(toAddress)
+ toAccount.balance.iadd(this._env.contract.balance)
+ await this._state.putAccount(toAddress, toAccount)
+
+ // Subtract from contract balance
+ const account = await this._state.getAccount(this._env.address)
+ account.balance = new BN(0)
+ await this._state.putAccount(this._env.address, account)
+
+ trap(ERROR.STOP)
}
async _selfDestruct(toAddress: Address): Promise<void> {
@@ -449,7 +502,7 @@ export default class EEI {
/**
* Sends a message with arbitrary data to a given address path.
*/
- async call(gasLimit: BN, address: Address, value: BN, data: Buffer): Promise<BN> {
+ async call(gasLimit: BN, address: Address, value: BN, data: Buffer): Promise<BaseCallResult> {
const msg = new Message({
caller: this._env.address,
gasLimit,
@@ -466,7 +519,7 @@ export default class EEI {
/**
* Message-call into this account with an alternative account's code.
*/
- async callCode(gasLimit: BN, address: Address, value: BN, data: Buffer): Promise<BN> {
+ async callCode(gasLimit: BN, address: Address, value: BN, data: Buffer): Promise<BaseCallResult> {
const msg = new Message({
caller: this._env.address,
gasLimit,
@@ -486,7 +539,12 @@ export default class EEI {
* state modifications. This includes log, create, selfdestruct and call with
* a non-zero value.
*/
- async callStatic(gasLimit: BN, address: Address, value: BN, data: Buffer): Promise<BN> {
+ async callStatic(
+ gasLimit: BN,
+ address: Address,
+ value: BN,
+ data: Buffer
+ ): Promise<BaseCallResult> {
const msg = new Message({
caller: this._env.address,
gasLimit,
@@ -504,7 +562,12 @@ export default class EEI {
* Message-call into this account with an alternative account’s code, but
* persisting the current values for sender and value.
*/
- async callDelegate(gasLimit: BN, address: Address, value: BN, data: Buffer): Promise<BN> {
+ async callDelegate(
+ gasLimit: BN,
+ address: Address,
+ value: BN,
+ data: Buffer
+ ): Promise<BaseCallResult> {
const msg = new Message({
caller: this._env.caller,
gasLimit,
@@ -520,7 +583,7 @@ export default class EEI {
return this._baseCall(msg)
}
- async _baseCall(msg: Message): Promise<BN> {
+ async _baseCall(msg: Message): Promise<BaseCallResult> {
const selfdestruct = { ...this._result.selfdestruct }
msg.selfdestruct = selfdestruct
@@ -532,7 +595,9 @@ export default class EEI {
this._env.depth >= this._common.param('vm', 'stackLimit') ||
(msg.delegatecall !== true && this._env.contract.balance.lt(msg.value))
) {
- return new BN(0)
+ return {
+ returnCode: new BN(0),
+ }
}
const results = await this._evm.executeMessage(msg)
@@ -560,13 +625,21 @@ export default class EEI {
this._env.contract = account
}
- return this._getReturnCode(results)
+ return {
+ returnCode: this._getReturnCode(results),
+ results,
+ }
}
/**
* Creates a new contract with a given value.
*/
- async create(gasLimit: BN, value: BN, data: Buffer, salt: Buffer | null = null): Promise<BN> {
+ async create(
+ gasLimit: BN,
+ value: BN,
+ data: Buffer,
+ salt: Buffer | null = null
+ ): Promise<BaseCallResult> {
const selfdestruct = { ...this._result.selfdestruct }
const msg = new Message({
caller: this._env.address,
@@ -586,12 +659,16 @@ export default class EEI {
this._env.depth >= this._common.param('vm', 'stackLimit') ||
(msg.delegatecall !== true && this._env.contract.balance.lt(msg.value))
) {
- return new BN(0)
+ return {
+ returnCode: new BN(0),
+ }
}
// EIP-2681 check
if (this._env.contract.nonce.gte(MAX_UINT64)) {
- return new BN(0)
+ return {
+ returnCode: new BN(0),
+ }
}
this._env.contract.nonce.iaddn(1)
await this._state.putAccount(this._env.address, this._env.contract)
@@ -623,18 +700,24 @@ export default class EEI {
this._env.contract = account
if (results.createdAddress) {
// push the created address to the stack
- return new BN(results.createdAddress.buf)
+ return {
+ returnCode: new BN(results.createdAddress.buf),
+ results,
+ }
}
}
- return this._getReturnCode(results)
+ return {
+ returnCode: this._getReturnCode(results),
+ results,
+ }
}
/**
* Creates a new contract with a given value. Generates
* a deterministic address via CREATE2 rules.
*/
- async create2(gasLimit: BN, value: BN, data: Buffer, salt: Buffer): Promise<BN> {
+ async create2(gasLimit: BN, value: BN, data: Buffer, salt: Buffer): Promise<BaseCallResult> {
return this.create(gasLimit, value, data, salt)
}
diff --git a/src/evm/evm.ts b/src/evm/evm.ts
index f4cd15f..1dd27fb 100644
--- a/src/evm/evm.ts
+++ b/src/evm/evm.ts
@@ -18,7 +18,12 @@ import EEI from './eei'
// eslint-disable-next-line
import { short } from './opcodes/util'
import { Log } from './types'
-import { default as Interpreter, InterpreterOpts, RunState } from './interpreter'
+import {
+ default as Interpreter,
+ InterpreterOpts,
+ RunState,
+ SimpleInterpreterStep,
+} from './interpreter'
const debug = createDebugLogger('vm:evm')
const debugGas = createDebugLogger('vm:evm:gas')
@@ -39,6 +44,10 @@ export interface EVMResult {
* Contains the results from running the code, if any, as described in {@link runCode}
*/
execResult: ExecResult
+ /**
+ * Array of evm steps to process the tx bytecode
+ */
+ evmSteps?: SimpleInterpreterStep[]
}
/**
@@ -74,6 +83,10 @@ export interface ExecResult {
* Total amount of gas to be refunded from all nested calls.
*/
gasRefund?: BN
+ /**
+ * Array of evm steps to process the tx bytecode
+ */
+ evmSteps?: SimpleInterpreterStep[]
}
export interface NewContractEvent {
@@ -290,6 +303,7 @@ export default class EVM {
}
return {
+ evmSteps: result.evmSteps,
gasUsed: result.gasUsed,
execResult: result,
}
@@ -454,6 +468,7 @@ export default class EVM {
}
return {
+ evmSteps: result.evmSteps,
gasUsed: result.gasUsed,
createdAddress: message.to,
execResult: result,
@@ -510,6 +525,7 @@ export default class EVM {
...result,
...eei._env,
},
+ evmSteps: interpreterRes.evmSteps,
exceptionError: interpreterRes.exceptionError,
gas: eei._gasLeft,
gasUsed,
diff --git a/src/evm/interpreter.ts b/src/evm/interpreter.ts
index 327c33c..cd72dc5 100644
--- a/src/evm/interpreter.ts
+++ b/src/evm/interpreter.ts
@@ -31,6 +31,7 @@ export interface RunState {
export interface InterpreterResult {
runState?: RunState
exceptionError?: VmError
+ evmSteps?: SimpleInterpreterStep[]
}
export interface InterpreterStep {
@@ -54,6 +55,25 @@ export interface InterpreterStep {
codeAddress: Address
}
+export interface SimpleInterpreterStep {
+ pc: number
+ opcode: {
+ name: string
+ fee: number
+ dynamicFee?: BN
+ isAsync: boolean
+ }
+ gasLeft: BN
+ gasRefund: BN
+ stack: BN[]
+ returnStack: BN[]
+ depth: number
+ memory: Buffer
+ memoryWordCount: BN
+ codeAddress: Address
+ callOpcodes?: SimpleInterpreterStep[]
+}
+
/**
* Parses and executes EVM bytecode.
*/
@@ -62,6 +82,7 @@ export default class Interpreter {
_state: StateManager
_runState: RunState
_eei: EEI
+ _evmStepAux: SimpleInterpreterStep | null = null
// Opcode debuggers (e.g. { 'push': [debug Object], 'sstore': [debug Object], ...})
private opDebuggers: { [key: string]: (debug: string) => void } = {}
@@ -84,11 +105,13 @@ export default class Interpreter {
eei: this._eei,
shouldDoJumpAnalysis: true,
}
+ this._evmStepAux = null
}
async run(code: Buffer, opts: InterpreterOpts = {}): Promise<InterpreterResult> {
this._runState.code = code
this._runState.programCounter = opts.pc ?? this._runState.programCounter
+ const evmSteps = []
// Check that the programCounter is in range
const pc = this._runState.programCounter
@@ -111,8 +134,21 @@ export default class Interpreter {
this._runState.opCode = opCode
try {
- await this.runStep()
+ const interpreterStep = await this.runStep()
+ //Store a copy of the object
+ evmSteps.push(JSON.parse(JSON.stringify(interpreterStep)))
+ //If has extra steps from a call, add them to the array
+ if (interpreterStep.callOpcodes) {
+ interpreterStep.callOpcodes.forEach(function (step) {
+ evmSteps.push(JSON.parse(JSON.stringify(step)))
+ })
+ }
} catch (e: any) {
+ //Add evmStepAux to steps array
+ if (this._evmStepAux) {
+ evmSteps.push(JSON.parse(JSON.stringify(this._evmStepAux)))
+ this._evmStepAux = null
+ }
// re-throw on non-VM errors
if (!('errorType' in e && e.errorType === 'VmError')) {
throw e
@@ -128,6 +164,7 @@ export default class Interpreter {
return {
runState: this._runState,
exceptionError: err,
+ evmSteps,
}
}
@@ -135,7 +172,7 @@ export default class Interpreter {
* Executes the opcode to which the program counter is pointing,
* reducing its base gas cost, and increments the program counter.
*/
- async runStep(): Promise<void> {
+ async runStep(): Promise<SimpleInterpreterStep> {
const opInfo = this.lookupOpInfo(this._runState.opCode)
const gas = new BN(opInfo.fee)
@@ -150,10 +187,17 @@ export default class Interpreter {
await dynamicGasHandler(this._runState, gas, this._vm._common)
}
- if (this._vm.listenerCount('step') > 0 || this._vm.DEBUG) {
- // Only run this stepHook function if there is an event listener (e.g. test runner)
- // or if the vm is running in debug mode (to display opcode debug logs)
- await this._runStepHook(gas, gasLimitClone)
+ const simpleInterpreterStep = await this._runStepHook(gas, gasLimitClone)
+
+ // Add aux evm step for revert/invalid opcodes
+ if (
+ opInfo.name === 'STOP' ||
+ opInfo.name === 'INVALID' ||
+ opInfo.name === 'SELFDESTRUCT' ||
+ opInfo.name === 'REVERT'
+ ) {
+ // Copy step to aux to use it in case of reverted tx
+ this._evmStepAux = simpleInterpreterStep
}
// Check for invalid opcode
@@ -168,11 +212,23 @@ export default class Interpreter {
// Execute opcode handler
const opFn = this.getOpHandler(opInfo)
+ let fnRes: any
if (opInfo.isAsync) {
- await (opFn as AsyncOpHandler).apply(null, [this._runState, this._vm._common])
+ fnRes = await (opFn as AsyncOpHandler).apply(null, [this._runState, this._vm._common])
} else {
- opFn.apply(null, [this._runState, this._vm._common])
+ fnRes = opFn.apply(null, [this._runState, this._vm._common])
}
+ // If is a CALL, append the call opcodes to the interpreter object
+ if (
+ ['CALL', 'STATICCALL', 'DELEGATECALL', 'CALLCODE', 'CREATE', 'CREATE2'].includes(
+ opInfo.name
+ ) &&
+ fnRes
+ ) {
+ simpleInterpreterStep.callOpcodes = fnRes.evmSteps
+ }
+
+ return simpleInterpreterStep
}
/**
@@ -190,7 +246,7 @@ export default class Interpreter {
return this._vm._opcodes.get(op) ?? this._vm._opcodes.get(0xfe)
}
- async _runStepHook(dynamicFee: BN, gasLeft: BN): Promise<void> {
+ async _runStepHook(dynamicFee: BN, gasLeft: BN): Promise<SimpleInterpreterStep> {
const opcode = this.lookupOpInfo(this._runState.opCode)
const eventObj: InterpreterStep = {
pc: this._runState.programCounter,
@@ -213,6 +269,24 @@ export default class Interpreter {
codeAddress: this._eei._env.codeAddress,
}
+ const simpleInterpreterStep: SimpleInterpreterStep = {
+ pc: this._runState.programCounter,
+ gasLeft,
+ gasRefund: this._eei._evm._refund,
+ opcode: {
+ name: opcode.fullName,
+ fee: opcode.fee,
+ dynamicFee,
+ isAsync: opcode.isAsync,
+ },
+ stack: this._runState.stack._store,
+ returnStack: this._runState.returnStack._store,
+ depth: this._eei._env.depth,
+ memory: this._runState.memory._store, // Return underlying array for backwards-compatibility
+ memoryWordCount: this._runState.memoryWordCount,
+ codeAddress: this._eei._env.codeAddress,
+ }
+
if (this._vm.DEBUG) {
// Create opTrace for debug functionality
let hexStack = []
@@ -260,7 +334,12 @@ export default class Interpreter {
* @property {BN} memoryWordCount current size of memory in words
* @property {Address} codeAddress the address of the code which is currently being ran (this differs from `address` in a `DELEGATECALL` and `CALLCODE` call)
*/
- return this._vm._emit('step', eventObj)
+ if (this._vm.listenerCount('step') > 0 || this._vm.DEBUG) {
+ // Only run this stepHook function if there is an event listener (e.g. test runner)
+ // or if the vm is running in debug mode (to display opcode debug logs)
+ this._vm._emit('step', eventObj)
+ }
+ return simpleInterpreterStep
}
// Returns all valid jump and jumpsub destinations.
diff --git a/src/evm/opcodes/functions.ts b/src/evm/opcodes/functions.ts
index d94a37a..07220f7 100644
--- a/src/evm/opcodes/functions.ts
+++ b/src/evm/opcodes/functions.ts
@@ -1,13 +1,5 @@
import Common from '@ethereumjs/common'
-import {
- Address,
- BN,
- keccak256,
- setLengthRight,
- TWO_POW256,
- MAX_INTEGER,
- KECCAK256_NULL,
-} from 'ethereumjs-util'
+import { Address, BN, keccak256, setLengthRight, TWO_POW256, MAX_INTEGER } from 'ethereumjs-util'
import {
addressToBuffer,
describeLocation,
@@ -19,7 +11,7 @@ import {
} from './util'
import { ERROR } from '../../exceptions'
import { RunState } from './../interpreter'
-
+const { smtUtils } = require('@polygon-hermez/zkevm-commonjs')
export interface SyncOpHandler {
(runState: RunState, common: Common): void
}
@@ -516,20 +508,14 @@ export const handlers: Map<number, OpHandler> = new Map([
0x3f,
async function (runState) {
const addressBN = runState.stack.pop()
- const address = new Address(addressToBuffer(addressBN))
- const empty = await runState.eei.isAccountEmpty(address)
- if (empty) {
- runState.stack.push(new BN(0))
- return
- }
-
const code = await runState.eei.getExternalCode(addressBN)
if (code.length === 0) {
- runState.stack.push(new BN(KECCAK256_NULL))
+ runState.stack.push(new BN(0))
return
}
-
- runState.stack.push(new BN(keccak256(code)))
+ // Use linear poseidon hash
+ const lpCode = await smtUtils.hashContractBytecode(code.toString('hex'))
+ runState.stack.push(new BN(lpCode.slice(2), 16))
},
],
// 0x3d: RETURNDATASIZE
@@ -567,15 +553,7 @@ export const handlers: Map<number, OpHandler> = new Map([
0x40,
async function (runState) {
const number = runState.stack.pop()
-
- const diff = runState.eei.getBlockNumber().sub(number)
- // block lookups must be within the past 256 blocks
- if (diff.gtn(256) || diff.lten(0)) {
- runState.stack.push(new BN(0))
- return
- }
-
- const hash = await runState.eei.getBlockHash(number)
+ const hash = await runState.eei.getBatchHash(number)
runState.stack.push(hash)
},
],
@@ -880,7 +858,8 @@ export const handlers: Map<number, OpHandler> = new Map([
}
const ret = await runState.eei.create(gasLimit, value, data)
- runState.stack.push(ret)
+ runState.stack.push(ret.returnCode)
+ return ret.results
},
],
// 0xf5: CREATE2
@@ -907,7 +886,8 @@ export const handlers: Map<number, OpHandler> = new Map([
data,
salt.toArrayLike(Buffer, 'be', 32)
)
- runState.stack.push(ret)
+ runState.stack.push(ret.returnCode)
+ return ret.results
},
],
// 0xf1: CALL
@@ -929,7 +909,8 @@ export const handlers: Map<number, OpHandler> = new Map([
const ret = await runState.eei.call(gasLimit, toAddress, value, data)
// Write return data to memory
writeCallOutput(runState, outOffset, outLength)
- runState.stack.push(ret)
+ runState.stack.push(ret.returnCode)
+ return ret.results
},
],
// 0xf2: CALLCODE
@@ -951,7 +932,8 @@ export const handlers: Map<number, OpHandler> = new Map([
const ret = await runState.eei.callCode(gasLimit, toAddress, value, data)
// Write return data to memory
writeCallOutput(runState, outOffset, outLength)
- runState.stack.push(ret)
+ runState.stack.push(ret.returnCode)
+ return ret.results
},
],
// 0xf4: DELEGATECALL
@@ -974,7 +956,8 @@ export const handlers: Map<number, OpHandler> = new Map([
const ret = await runState.eei.callDelegate(gasLimit, toAddress, value, data)
// Write return data to memory
writeCallOutput(runState, outOffset, outLength)
- runState.stack.push(ret)
+ runState.stack.push(ret.returnCode)
+ return ret.results
},
],
// 0x06: STATICCALL
@@ -997,7 +980,8 @@ export const handlers: Map<number, OpHandler> = new Map([
const ret = await runState.eei.callStatic(gasLimit, toAddress, value, data)
// Write return data to memory
writeCallOutput(runState, outOffset, outLength)
- runState.stack.push(ret)
+ runState.stack.push(ret.returnCode)
+ return ret.results
},
],
// 0xf3: RETURN
diff --git a/src/index.ts b/src/index.ts
index 796f80c..18409ee 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,4 +1,4 @@
-import { SecureTrie as Trie } from 'merkle-patricia-tree'
+import { CheckpointTrie as Trie } from 'merkle-patricia-tree'
import { Account, Address, BNLike } from 'ethereumjs-util'
import Blockchain from '@ethereumjs/blockchain'
import Common, { Chain } from '@ethereumjs/common'
@@ -71,7 +71,7 @@ export interface VMOpts {
*/
stateManager?: StateManager
/**
- * A {@link SecureTrie} instance for the state tree (ignored if stateManager is passed)
+ * A {@link CheckpointTrie} instance for the state tree (ignored if stateManager is passed)
* @deprecated - will be removed in next major version release
*/
state?: Trie
diff --git a/src/state/baseStateManager.ts b/src/state/baseStateManager.ts
index 738994c..ce711e0 100644
--- a/src/state/baseStateManager.ts
+++ b/src/state/baseStateManager.ts
@@ -28,6 +28,7 @@ export abstract class BaseStateManager {
_touched: Set<AddressHex>
_touchedStack: Set<AddressHex>[]
+ _customTouched: Set<AddressHex>
_originalStorageCache: Map<AddressHex, Map<AddressHex, Buffer>>
// EIP-2929 address/storage trackers.
@@ -67,6 +68,7 @@ export abstract class BaseStateManager {
this._common = common
this._touched = new Set()
+ this._customTouched = new Set()
this._touchedStack = []
this._originalStorageCache = new Map()
@@ -129,6 +131,7 @@ export abstract class BaseStateManager {
*/
touchAccount(address: Address): void {
this._touched.add(address.buf.toString('hex'))
+ this._customTouched.add(address.buf.toString('hex'))
}
abstract putContractCode(address: Address, value: Buffer): Promise<void>
diff --git a/src/state/stateManager.ts b/src/state/stateManager.ts
index 9142cc9..f63505e 100644
--- a/src/state/stateManager.ts
+++ b/src/state/stateManager.ts
@@ -1,4 +1,4 @@
-import { SecureTrie as Trie } from 'merkle-patricia-tree'
+import { CheckpointTrie as Trie } from 'merkle-patricia-tree'
import {
Account,
Address,
@@ -45,7 +45,7 @@ export interface DefaultStateManagerOpts {
*/
common?: Common
/**
- * A {@link SecureTrie} instance
+ * A {@link CheckpointTrie} instance
*/
trie?: Trie
}
@@ -115,9 +115,10 @@ export default class DefaultStateManager extends BaseStateManager implements Sta
async putContractCode(address: Address, value: Buffer): Promise<void> {
const codeHash = keccak256(value)
- if (codeHash.equals(KECCAK256_NULL)) {
- return
- }
+ // Commented to allow deleting contract bytecode directly
+ // if (codeHash.equals(KECCAK256_NULL)) {
+ // return
+ // }
await this._trie.db.put(codeHash, value)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment