Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VM: report dynamic gas values in fee field of step event #1364

Merged
merged 22 commits into from
Aug 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4409615
vm: codes: add dynamicGas property
jochem-brouwer Jul 14, 2021
87e6e26
vm: create no-op dynamic gas handlers
jochem-brouwer Jul 18, 2021
51c2d23
vm: first batch of dynamic gas up to 0x3f
jochem-brouwer Jul 31, 2021
a09c281
vm: add other opcodes to gas map
jochem-brouwer Jul 19, 2021
a2210a0
vm: move errors to gas map
jochem-brouwer Jul 19, 2021
43e04fb
vm: fix memory dynamic gas bug
jochem-brouwer Jul 19, 2021
d7f05d1
vm: fix gas bugs caused by not considering base fee
jochem-brouwer Jul 19, 2021
b78827e
vm: fix message call gas related bugs, clone current gas left
jochem-brouwer Jul 19, 2021
c17c19c
add typedoc for peek
ryanio Jul 20, 2021
723a17b
simplify the 2929 state manager castings in runTx
ryanio Jul 27, 2021
a4a4add
add changelog entry
ryanio Jul 27, 2021
9edf9a0
vm: add EIP1283 tests
jochem-brouwer Jul 28, 2021
4a6404d
vm: split non-eip2929 and eip2929 gas costs
jochem-brouwer Jul 28, 2021
f3fb920
vm: fix gas costs
jochem-brouwer Jul 29, 2021
2d58847
vm: add early-hardfork coverage
jochem-brouwer Jul 30, 2021
ba821ef
vm: clarify pre-Constantinople SSTORE gas
jochem-brouwer Jul 30, 2021
804a0ad
run coverage for all state and blockchain tests, remove redundant ist…
ryanio Jul 30, 2021
6276969
vm: fix CALLCODE gas
jochem-brouwer Aug 1, 2021
f01f69f
vm: remove TODO in interpreter
jochem-brouwer Aug 20, 2021
cb3a1f2
update defaultCost to BN, cast 2929 statemanager to simplify use syntax
ryanio Aug 6, 2021
44b4ac3
use underscore for unused variables, simplify types since they can be…
ryanio Aug 6, 2021
3264d5b
vm: fix browser tests + fix rebase
jochem-brouwer Aug 20, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/vm-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,4 @@ jobs:

# Re-apply git stash to prepare for saving back to cache.
# Avoids exit code 1 by checking if there are changes to be stashed first
- run: STASH_LIST=`git stash list` && [ ! -z $STASH_LIST ] && git stash apply || echo "No files to stash-apply. Skipping…"
- run: STASH_LIST=`git stash list` && [ ! -z $STASH_LIST ] && git stash apply || echo "No files to stash-apply. Skipping…"
2 changes: 1 addition & 1 deletion .github/workflows/vm-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -228,4 +228,4 @@ jobs:

# Re-apply git stash to prepare for saving back to cache.
# Avoids exit code 1 by checking if there are changes to be stashed first
- run: STASH_LIST=`git stash list` && [ ! -z $STASH_LIST ] && git stash apply || echo "No files to stash-apply. Skipping…"
- run: STASH_LIST=`git stash list` && [ ! -z $STASH_LIST ] && git stash apply || echo "No files to stash-apply. Skipping…"
1 change: 1 addition & 0 deletions packages/vm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Bug fix release to reverse StateManager interface breaking change. The method `m
**New Features**

- StateManager: Added `modifyAccountFields` method to simplify the `getAccount` -> modify fields -> `putAccount` pattern, PR [#1369](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1369)
- Report dynamic gas values in `fee` field of `step` event, PR [#1364](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1364)

**Bug Fixes**

Expand Down
2 changes: 1 addition & 1 deletion packages/vm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,4 @@
"bugs": {
"url": "https://github.com/ethereumjs/ethereumjs-monorepo/issues?q=is%3Aissue+label%3A%22package%3A+vm%22"
}
}
}
28 changes: 22 additions & 6 deletions packages/vm/src/evm/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Memory from './memory'
import Stack from './stack'
import EEI from './eei'
import { Opcode, handlers as opHandlers, OpHandler, AsyncOpHandler } from './opcodes'
import { dynamicGasHandlers } from './opcodes/gas'

export interface InterpreterOpts {
pc?: number
Expand All @@ -24,6 +25,7 @@ export interface RunState {
validJumpSubs: number[]
stateManager: StateManager
eei: EEI
messageGasLimit?: BN // Cache value from `gas.ts` to save gas limit for a message call
}

export interface InterpreterResult {
Expand All @@ -44,7 +46,7 @@ export interface InterpreterStep {
memoryWordCount: BN
opcode: {
name: string
fee: number
fee: BN
isAsync: boolean
}
account: Account
Expand Down Expand Up @@ -107,7 +109,6 @@ export default class Interpreter {
while (this._runState.programCounter < this._runState.code.length) {
const opCode = this._runState.code[this._runState.programCounter]
this._runState.opCode = opCode
await this._runStepHook()

try {
await this.runStep()
Expand Down Expand Up @@ -136,13 +137,28 @@ export default class Interpreter {
*/
async runStep(): Promise<void> {
const opInfo = this.lookupOpInfo(this._runState.opCode)

const gas = new BN(opInfo.fee)
// clone the gas limit; call opcodes can add stipend,
// which makes it seem like the gas left increases
const gasLimitClone = this._eei.getGasLeft()

if (opInfo.dynamicGas) {
const dynamicGasHandler = dynamicGasHandlers.get(this._runState.opCode)!
// This function updates the gas BN in-place using `i*` methods
// It needs the base fee, for correct gas limit calculation for the CALL opcodes
await dynamicGasHandler(this._runState, gas, this._vm._common)
}

await this._runStepHook(gas, gasLimitClone)

// Check for invalid opcode
if (opInfo.name === 'INVALID') {
throw new VmError(ERROR.INVALID_OPCODE)
}

// Reduce opcode's base fee
this._eei.useGas(new BN(opInfo.fee), `${opInfo.name} (base fee)`)
this._eei.useGas(gas, `${opInfo.name} fee`)
// Advance program counter
this._runState.programCounter++

Expand Down Expand Up @@ -170,15 +186,15 @@ export default class Interpreter {
return this._vm._opcodes.get(op) ?? this._vm._opcodes.get(0xfe)
}

async _runStepHook(): Promise<void> {
async _runStepHook(fee: BN, gasLeft: BN): Promise<void> {
const opcode = this.lookupOpInfo(this._runState.opCode)
const eventObj: InterpreterStep = {
pc: this._runState.programCounter,
gasLeft: this._eei.getGasLeft(),
gasLeft,
gasRefund: this._eei._evm._refund,
opcode: {
name: opcode.fullName,
fee: opcode.fee,
fee,
isAsync: opcode.isAsync,
},
stack: this._runState.stack._store,
Expand Down
21 changes: 4 additions & 17 deletions packages/vm/src/evm/opcodes/EIP1283.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,14 @@ export function updateSstoreGasEIP1283(
) {
if (currentStorage.equals(value)) {
// If current value equals new value (this is a no-op), 200 gas is deducted.
runState.eei.useGas(
new BN(common.param('gasPrices', 'netSstoreNoopGas')),
'EIP-1283 -> netSstoreNoopGas'
)
return
return new BN(common.param('gasPrices', 'netSstoreNoopGas'))
}
// If current value does not equal new value
if (originalStorage.equals(currentStorage)) {
// If original value equals current value (this storage slot has not been changed by the current execution context)
if (originalStorage.length === 0) {
// If original value is 0, 20000 gas is deducted.
return runState.eei.useGas(
new BN(common.param('gasPrices', 'netSstoreInitGas')),
'EIP-1283 -> netSstoreInitGas'
)
return new BN(common.param('gasPrices', 'netSstoreInitGas'))
}
if (value.length === 0) {
// If new value is 0, add 15000 gas to refund counter.
Expand All @@ -44,10 +37,7 @@ export function updateSstoreGasEIP1283(
)
}
// Otherwise, 5000 gas is deducted.
return runState.eei.useGas(
new BN(common.param('gasPrices', 'netSstoreCleanGas')),
'EIP-1283 -> netSstoreCleanGas'
)
return new BN(common.param('gasPrices', '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 (originalStorage.length !== 0) {
Expand Down Expand Up @@ -82,8 +72,5 @@ export function updateSstoreGasEIP1283(
)
}
}
return runState.eei.useGas(
new BN(common.param('gasPrices', 'netSstoreDirtyGas')),
'EIP-1283 -> netSstoreDirtyGas'
)
return new BN(common.param('gasPrices', 'netSstoreDirtyGas'))
}
30 changes: 9 additions & 21 deletions packages/vm/src/evm/opcodes/EIP2200.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,13 @@ export function updateSstoreGasEIP2200(

// Noop
if (currentStorage.equals(value)) {
const sstoreNoopCost = common.param('gasPrices', 'sstoreNoopGasEIP2200')
return runState.eei.useGas(
new BN(adjustSstoreGasEIP2929(runState, key, sstoreNoopCost, 'noop', common)),
'EIP-2200 -> sstoreNoopGasEIP2200'
)
const sstoreNoopCost = new BN(common.param('gasPrices', 'sstoreNoopGasEIP2200'))
return adjustSstoreGasEIP2929(runState, key, sstoreNoopCost, 'noop', common)
}
if (originalStorage.equals(currentStorage)) {
// Create slot
if (originalStorage.length === 0) {
return runState.eei.useGas(
new BN(common.param('gasPrices', 'sstoreInitGasEIP2200')),
'EIP-2200 -> sstoreInitGasEIP2200'
)
return new BN(common.param('gasPrices', 'sstoreInitGasEIP2200'))
}
// Delete slot
if (value.length === 0) {
Expand All @@ -51,10 +45,7 @@ export function updateSstoreGasEIP2200(
)
}
// Write existing slot
return runState.eei.useGas(
new BN(common.param('gasPrices', 'sstoreCleanGasEIP2200')),
'EIP-2200 -> sstoreCleanGasEIP2200'
)
return new BN(common.param('gasPrices', 'sstoreCleanGasEIP2200'))
}
if (originalStorage.length > 0) {
if (currentStorage.length === 0) {
Expand All @@ -74,23 +65,20 @@ export function updateSstoreGasEIP2200(
if (originalStorage.equals(value)) {
if (originalStorage.length === 0) {
// Reset to original non-existent slot
const sstoreInitRefund = common.param('gasPrices', 'sstoreInitRefundEIP2200')
const sstoreInitRefund = new BN(common.param('gasPrices', 'sstoreInitRefundEIP2200'))
runState.eei.refundGas(
new BN(adjustSstoreGasEIP2929(runState, key, sstoreInitRefund, 'initRefund', common)),
adjustSstoreGasEIP2929(runState, key, sstoreInitRefund, 'initRefund', common),
'EIP-2200 -> initRefund'
)
} else {
// Reset to original existing slot
const sstoreCleanRefund = common.param('gasPrices', 'sstoreCleanRefundEIP2200')
const sstoreCleanRefund = new BN(common.param('gasPrices', 'sstoreCleanRefundEIP2200'))
runState.eei.refundGas(
new BN(adjustSstoreGasEIP2929(runState, key, sstoreCleanRefund, 'cleanRefund', common)),
adjustSstoreGasEIP2929(runState, key, sstoreCleanRefund, 'cleanRefund', common),
'EIP-2200 -> cleanRefund'
)
}
}
// Dirty update
return runState.eei.useGas(
new BN(common.param('gasPrices', 'sstoreDirtyGasEIP2200')),
'EIP-2200 -> sstoreDirtyGasEIP2200'
)
return new BN(common.param('gasPrices', 'sstoreDirtyGasEIP2200'))
}
63 changes: 27 additions & 36 deletions packages/vm/src/evm/opcodes/EIP2929.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,26 @@ export function accessAddressEIP2929(
common: Common,
chargeGas = true,
isSelfdestruct = false
) {
if (!common.isActivatedEIP(2929)) return
): BN {
if (!common.isActivatedEIP(2929)) return new BN(0)

const stateManager = runState.stateManager as EIP2929StateManager
const addressStr = address.buf

// Cold
if (!(<EIP2929StateManager>runState.stateManager).isWarmedAddress(addressStr)) {
// eslint-disable-next-line prettier/prettier
(<EIP2929StateManager>runState.stateManager).addWarmedAddress(addressStr)
if (!stateManager.isWarmedAddress(addressStr)) {
stateManager.addWarmedAddress(addressStr)

// CREATE, CREATE2 opcodes have the address warmed for free.
// selfdestruct beneficiary address reads are charged an *additional* cold access
if (chargeGas) {
runState.eei.useGas(
new BN(common.param('gasPrices', 'coldaccountaccess')),
'EIP-2929 -> coldaccountaccess'
)
return new BN(common.param('gasPrices', 'coldaccountaccess'))
}
// Warm: (selfdestruct beneficiary address reads are not charged when warm)
} else if (chargeGas && !isSelfdestruct) {
runState.eei.useGas(
new BN(common.param('gasPrices', 'warmstorageread')),
'EIP-2929 -> warmstorageread'
)
return new BN(common.param('gasPrices', 'warmstorageread'))
}
return new BN(0)
}

/**
Expand All @@ -59,59 +54,55 @@ export function accessStorageEIP2929(
key: Buffer,
isSstore: boolean,
common: Common
) {
if (!common.isActivatedEIP(2929)) return
): BN {
if (!common.isActivatedEIP(2929)) return new BN(0)

const stateManager = runState.stateManager as EIP2929StateManager
const address = runState.eei.getAddress().buf

const slotIsCold = !(<EIP2929StateManager>runState.stateManager).isWarmedStorage(address, key)
const slotIsCold = !stateManager.isWarmedStorage(address, key)

// Cold (SLOAD and SSTORE)
if (slotIsCold) {
// eslint-disable-next-line prettier/prettier
(<EIP2929StateManager>runState.stateManager).addWarmedStorage(address, key)
runState.eei.useGas(new BN(common.param('gasPrices', 'coldsload')), 'EIP-2929 -> coldsload')
stateManager.addWarmedStorage(address, key)
return new BN(common.param('gasPrices', 'coldsload'))
} else if (!isSstore) {
runState.eei.useGas(
new BN(common.param('gasPrices', 'warmstorageread')),
'EIP-2929 -> warmstorageread'
)
return new BN(common.param('gasPrices', 'warmstorageread'))
}
return new BN(0)
}

/**
* Adjusts cost of SSTORE_RESET_GAS or SLOAD (aka sstorenoop) (EIP-2200) downward when storage
* location is already warm
* @param {RunState} runState
* @param {Buffer} key storage slot
* @param {number} defaultCost SSTORE_RESET_GAS / SLOAD
* @param {string} costName parameter name ('reset' or 'noop')
* @param {BN} defaultCost SSTORE_RESET_GAS / SLOAD
* @param {string} costName parameter name ('noop')
* @param {Common} common
* @return {number} adjusted cost
* @return {BN} adjusted cost
*/
export function adjustSstoreGasEIP2929(
runState: RunState,
key: Buffer,
defaultCost: number,
defaultCost: BN,
costName: string,
common: Common
): number {
): BN {
if (!common.isActivatedEIP(2929)) return defaultCost

const stateManager = runState.stateManager as EIP2929StateManager
const address = runState.eei.getAddress().buf
const warmRead = common.param('gasPrices', 'warmstorageread')
const coldSload = common.param('gasPrices', 'coldsload')
const warmRead = new BN(common.param('gasPrices', 'warmstorageread'))
const coldSload = new BN(common.param('gasPrices', 'coldsload'))

if ((<EIP2929StateManager>runState.stateManager).isWarmedStorage(address, key)) {
if (stateManager.isWarmedStorage(address, key)) {
switch (costName) {
case 'reset':
return defaultCost - coldSload
case 'noop':
return warmRead
case 'initRefund':
return common.param('gasPrices', 'sstoreInitGasEIP2200') - warmRead
return new BN(common.param('gasPrices', 'sstoreInitGasEIP2200')).sub(warmRead)
case 'cleanRefund':
return common.param('gasPrices', 'sstoreReset') - coldSload - warmRead
return new BN(common.param('gasPrices', 'sstoreReset')).sub(coldSload).sub(warmRead)
}
}

Expand Down
Loading