Skip to content

Commit

Permalink
VM: fix several internal todos (#1375)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanio authored Jul 25, 2021
1 parent 008a4c3 commit 8b358df
Showing 11 changed files with 306 additions and 275 deletions.
1 change: 1 addition & 0 deletions packages/vm/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)

- Update internal `common` usage to new Chain & Hardfork enums, PR [#1363](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1363)
- Add tests for wrong transactions, PR [#1374](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1374)
- Fix several internal todos, PR [#1375](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1375)

**Dependencies, CI and Docs**

23 changes: 18 additions & 5 deletions packages/vm/src/evm/eei.ts
Original file line number Diff line number Diff line change
@@ -90,7 +90,6 @@ export default class EEI {
* @param context - Usage context for debugging
* @throws if out of gas
*/
// eslint-disable-next-line no-unused-vars
useGas(amount: BN, context?: string): void {
this._gasLeft.isub(amount)
if (this._evm._vm.DEBUG) {
@@ -107,7 +106,6 @@ export default class EEI {
* @param amount - Amount of gas refunded
* @param context - Usage context for debugging
*/
// eslint-disable-next-line no-unused-vars
refundGas(amount: BN, context?: string): void {
if (this._evm._vm.DEBUG) {
debugGas(`${context ? context + ': ' : ''}refund ${amount} gas (-> ${this._evm._refund})`)
@@ -120,7 +118,6 @@ export default class EEI {
* @param amount - Amount to subtract from gas refunds
* @param context - Usage context for debugging
*/
// eslint-disable-next-line no-unused-vars
subRefund(amount: BN, context?: string): void {
if (this._evm._vm.DEBUG) {
debugGas(`${context ? context + ': ' : ''}sub gas refund ${amount} (-> ${this._evm._refund})`)
@@ -132,6 +129,17 @@ export default class EEI {
}
}

/**
* Increments the internal gasLeft counter. Used for adding callStipend.
* @param amount - Amount to add
*/
addStipend(amount: BN): void {
if (this._evm._vm.DEBUG) {
debugGas(`add stipend ${amount} (-> ${this._gasLeft})`)
}
this._gasLeft.iadd(amount)
}

/**
* Returns address of currently executing account.
*/
@@ -355,9 +363,14 @@ export default class EEI {
/**
* Loads a 256-bit value to memory from persistent storage.
* @param key - Storage key
* @param original - If true, return the original storage value (default: false)
*/
async storageLoad(key: Buffer): Promise<Buffer> {
return this._state.getContractStorage(this._env.address, key)
async storageLoad(key: Buffer, original = false): Promise<Buffer> {
if (original) {
return this._state.getOriginalContractStorage(this._env.address, key)
} else {
return this._state.getContractStorage(this._env.address, key)
}
}

/**
12 changes: 4 additions & 8 deletions packages/vm/src/evm/interpreter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { debug as createDebugLogger } from 'debug'
import { Account, Address, BN } from 'ethereumjs-util'
import Common from '@ethereumjs/common'
import { StateManager } from '../state/index'
import { ERROR, VmError } from '../exceptions'
import Memory from './memory'
@@ -23,7 +22,6 @@ export interface RunState {
code: Buffer
validJumps: number[]
validJumpSubs: number[]
_common: Common
stateManager: StateManager
eei: EEI
}
@@ -68,10 +66,10 @@ export default class Interpreter {
_eei: EEI

// Opcode debuggers (e.g. { 'push': [debug Object], 'sstore': [debug Object], ...})
private opDebuggers: any = {}
private opDebuggers: { [key: string]: (debug: string) => void } = {}

constructor(vm: any, eei: EEI) {
this._vm = vm // TODO: remove when not needed
this._vm = vm
this._state = vm.stateManager
this._eei = eei
this._runState = {
@@ -85,8 +83,6 @@ export default class Interpreter {
code: Buffer.alloc(0),
validJumps: [],
validJumpSubs: [],
// TODO: Replace with EEI methods
_common: this._vm._common,
stateManager: this._state,
eei: this._eei,
}
@@ -153,9 +149,9 @@ export default class Interpreter {
// Execute opcode handler
const opFn = this.getOpHandler(opInfo)
if (opInfo.isAsync) {
await (<AsyncOpHandler>opFn).apply(null, [this._runState])
await (opFn as AsyncOpHandler).apply(null, [this._runState, this._vm._common])
} else {
opFn.apply(null, [this._runState])
opFn.apply(null, [this._runState, this._vm._common])
}
}

46 changes: 27 additions & 19 deletions packages/vm/src/evm/opcodes/EIP1283.ts
Original file line number Diff line number Diff line change
@@ -1,81 +1,89 @@
import Common from '@ethereumjs/common'
import { BN } from 'ethereumjs-util'
import { RunState } from './../interpreter'

/**
* Adjusts gas usage and refunds of SStore ops per EIP-1283 (Constantinople)
*
* @param {RunState} runState
* @param {any} found
* @param {Buffer} currentStorage
* @param {Buffer} originalStorage
* @param {Buffer} value
* @param {Common} common
*/
export function updateSstoreGasEIP1283(runState: RunState, found: any, value: Buffer) {
const { original, current } = found
if (current.equals(value)) {
export function updateSstoreGasEIP1283(
runState: RunState,
currentStorage: Buffer,
originalStorage: Buffer,
value: Buffer,
common: Common
) {
if (currentStorage.equals(value)) {
// If current value equals new value (this is a no-op), 200 gas is deducted.
runState.eei.useGas(
new BN(runState._common.param('gasPrices', 'netSstoreNoopGas')),
new BN(common.param('gasPrices', 'netSstoreNoopGas')),
'EIP-1283 -> netSstoreNoopGas'
)
return
}
// If current value does not equal new value
if (original.equals(current)) {
if (originalStorage.equals(currentStorage)) {
// If original value equals current value (this storage slot has not been changed by the current execution context)
if (original.length === 0) {
if (originalStorage.length === 0) {
// If original value is 0, 20000 gas is deducted.
return runState.eei.useGas(
new BN(runState._common.param('gasPrices', 'netSstoreInitGas')),
new BN(common.param('gasPrices', 'netSstoreInitGas')),
'EIP-1283 -> netSstoreInitGas'
)
}
if (value.length === 0) {
// If new value is 0, add 15000 gas to refund counter.
runState.eei.refundGas(
new BN(runState._common.param('gasPrices', 'netSstoreClearRefund')),
new BN(common.param('gasPrices', 'netSstoreClearRefund')),
'EIP-1283 -> netSstoreClearRefund'
)
}
// Otherwise, 5000 gas is deducted.
return runState.eei.useGas(
new BN(runState._common.param('gasPrices', 'netSstoreCleanGas')),
new BN(common.param('gasPrices', 'netSstoreCleanGas')),
'EIP-1283 -> netSstoreCleanGas'
)
}
// If original value does not equal current value (this storage slot is dirty), 200 gas is deducted. Apply both of the following clauses.
if (original.length !== 0) {
if (originalStorage.length !== 0) {
// If original value is not 0
if (current.length === 0) {
if (currentStorage.length === 0) {
// If current value is 0 (also means that new value is not 0), remove 15000 gas from refund counter. We can prove that refund counter will never go below 0.
runState.eei.subRefund(
new BN(runState._common.param('gasPrices', 'netSstoreClearRefund')),
new BN(common.param('gasPrices', 'netSstoreClearRefund')),
'EIP-1283 -> netSstoreClearRefund'
)
} else if (value.length === 0) {
// If new value is 0 (also means that current value is not 0), add 15000 gas to refund counter.
runState.eei.refundGas(
new BN(runState._common.param('gasPrices', 'netSstoreClearRefund')),
new BN(common.param('gasPrices', 'netSstoreClearRefund')),
'EIP-1283 -> netSstoreClearRefund'
)
}
}
if (original.equals(value)) {
if (originalStorage.equals(value)) {
// If original value equals new value (this storage slot is reset)
if (original.length === 0) {
if (originalStorage.length === 0) {
// If original value is 0, add 19800 gas to refund counter.
runState.eei.refundGas(
new BN(runState._common.param('gasPrices', 'netSstoreResetClearRefund')),
new BN(common.param('gasPrices', 'netSstoreResetClearRefund')),
'EIP-1283 -> netSstoreResetClearRefund'
)
} else {
// Otherwise, add 4800 gas to refund counter.
runState.eei.refundGas(
new BN(runState._common.param('gasPrices', 'netSstoreResetRefund')),
new BN(common.param('gasPrices', 'netSstoreResetRefund')),
'EIP-1283 -> netSstoreResetRefund'
)
}
}
return runState.eei.useGas(
new BN(runState._common.param('gasPrices', 'netSstoreDirtyGas')),
new BN(common.param('gasPrices', 'netSstoreDirtyGas')),
'EIP-1283 -> netSstoreDirtyGas'
)
}
57 changes: 32 additions & 25 deletions packages/vm/src/evm/opcodes/EIP2200.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Common from '@ethereumjs/common'
import { BN } from 'ethereumjs-util'
import { RunState } from './../interpreter'
import { ERROR } from '../../exceptions'
@@ -8,82 +9,88 @@ import { trap } from './util'
* Adjusts gas usage and refunds of SStore ops per EIP-2200 (Istanbul)
*
* @param {RunState} runState
* @param {any} found
* @param {Buffer} currentStorage
* @param {Buffer} originalStorage
* @param {Buffer} value
* @param {Common} common
*/
export function updateSstoreGasEIP2200(runState: RunState, found: any, value: Buffer, key: Buffer) {
const { original, current } = found
export function updateSstoreGasEIP2200(
runState: RunState,
currentStorage: Buffer,
originalStorage: Buffer,
value: Buffer,
key: Buffer,
common: Common
) {
// Fail if not enough gas is left
if (
runState.eei.getGasLeft().lten(runState._common.param('gasPrices', 'sstoreSentryGasEIP2200'))
) {
if (runState.eei.getGasLeft().lten(common.param('gasPrices', 'sstoreSentryGasEIP2200'))) {
trap(ERROR.OUT_OF_GAS)
}

// Noop
if (current.equals(value)) {
const sstoreNoopCost = runState._common.param('gasPrices', 'sstoreNoopGasEIP2200')
if (currentStorage.equals(value)) {
const sstoreNoopCost = common.param('gasPrices', 'sstoreNoopGasEIP2200')
return runState.eei.useGas(
new BN(adjustSstoreGasEIP2929(runState, key, sstoreNoopCost, 'noop')),
new BN(adjustSstoreGasEIP2929(runState, key, sstoreNoopCost, 'noop', common)),
'EIP-2200 -> sstoreNoopGasEIP2200'
)
}
if (original.equals(current)) {
if (originalStorage.equals(currentStorage)) {
// Create slot
if (original.length === 0) {
if (originalStorage.length === 0) {
return runState.eei.useGas(
new BN(runState._common.param('gasPrices', 'sstoreInitGasEIP2200')),
new BN(common.param('gasPrices', 'sstoreInitGasEIP2200')),
'EIP-2200 -> sstoreInitGasEIP2200'
)
}
// Delete slot
if (value.length === 0) {
runState.eei.refundGas(
new BN(runState._common.param('gasPrices', 'sstoreClearRefundEIP2200')),
new BN(common.param('gasPrices', 'sstoreClearRefundEIP2200')),
'EIP-2200 -> sstoreClearRefundEIP2200'
)
}
// Write existing slot
return runState.eei.useGas(
new BN(runState._common.param('gasPrices', 'sstoreCleanGasEIP2200')),
new BN(common.param('gasPrices', 'sstoreCleanGasEIP2200')),
'EIP-2200 -> sstoreCleanGasEIP2200'
)
}
if (original.length > 0) {
if (current.length === 0) {
if (originalStorage.length > 0) {
if (currentStorage.length === 0) {
// Recreate slot
runState.eei.subRefund(
new BN(runState._common.param('gasPrices', 'sstoreClearRefundEIP2200')),
new BN(common.param('gasPrices', 'sstoreClearRefundEIP2200')),
'EIP-2200 -> sstoreClearRefundEIP2200'
)
} else if (value.length === 0) {
// Delete slot
runState.eei.refundGas(
new BN(runState._common.param('gasPrices', 'sstoreClearRefundEIP2200')),
new BN(common.param('gasPrices', 'sstoreClearRefundEIP2200')),
'EIP-2200 -> sstoreClearRefundEIP2200'
)
}
}
if (original.equals(value)) {
if (original.length === 0) {
if (originalStorage.equals(value)) {
if (originalStorage.length === 0) {
// Reset to original non-existent slot
const sstoreInitRefund = runState._common.param('gasPrices', 'sstoreInitRefundEIP2200')
const sstoreInitRefund = common.param('gasPrices', 'sstoreInitRefundEIP2200')
runState.eei.refundGas(
new BN(adjustSstoreGasEIP2929(runState, key, sstoreInitRefund, 'initRefund')),
new BN(adjustSstoreGasEIP2929(runState, key, sstoreInitRefund, 'initRefund', common)),
'EIP-2200 -> initRefund'
)
} else {
// Reset to original existing slot
const sstoreCleanRefund = runState._common.param('gasPrices', 'sstoreCleanRefundEIP2200')
const sstoreCleanRefund = common.param('gasPrices', 'sstoreCleanRefundEIP2200')
runState.eei.refundGas(
new BN(adjustSstoreGasEIP2929(runState, key, sstoreCleanRefund, 'cleanRefund')),
new BN(adjustSstoreGasEIP2929(runState, key, sstoreCleanRefund, 'cleanRefund', common)),
'EIP-2200 -> cleanRefund'
)
}
}
// Dirty update
return runState.eei.useGas(
new BN(runState._common.param('gasPrices', 'sstoreDirtyGasEIP2200')),
new BN(common.param('gasPrices', 'sstoreDirtyGasEIP2200')),
'EIP-2200 -> sstoreDirtyGasEIP2200'
)
}
Loading

0 comments on commit 8b358df

Please sign in to comment.