Skip to content

Commit

Permalink
Merge pull request #1124 from ethereumjs/fix-eip2929
Browse files Browse the repository at this point in the history
Fix EIP2929
  • Loading branch information
jochem-brouwer authored Feb 23, 2021
2 parents fa84b93 + d67e8bd commit 93c1110
Show file tree
Hide file tree
Showing 22 changed files with 515 additions and 137 deletions.
9 changes: 6 additions & 3 deletions .github/workflows/vm-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ jobs:
strategy:
matrix:
fork: [
'MuirGlacier',
'Istanbul'
'Berlin',
'Istanbul',
'MuirGlacier'
]
fail-fast: false
steps:
Expand Down Expand Up @@ -137,7 +138,9 @@ jobs:
# Tests were splitted with --dir and --excludeDir to balance execution times below the 9min mark.
args: [
'--fork=Istanbul --dir=GeneralStateTests/stTimeConsuming --expected-test-amount=15561',
'--fork=Istanbul --excludeDir=stTimeConsuming --expected-test-amount=19817'
'--fork=Istanbul --excludeDir=stTimeConsuming --expected-test-amount=19817',
'--fork=Berlin --dir=GeneralStateTests/stTimeConsuming',
'--fork=Berlin --excludeDir=stTimeConsuming'
]
fail-fast: false
steps:
Expand Down
60 changes: 60 additions & 0 deletions packages/common/src/eips/2929.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,66 @@
"warmstorageread": {
"v": 100,
"d": "Gas cost of reading storage locations which have already loaded 'cold'"
},
"sstoreCleanGasEIP2200": {
"v": 2900,
"d": "Once per SSTORE operation from clean non-zero to something else"
},
"sstoreNoopGasEIP2200": {
"v": 100,
"d": "Once per SSTORE operation if the value doesn't change"
},
"sstoreDirtyGasEIP2200": {
"v": 100,
"d": "Once per SSTORE operation if a dirty value is changed"
},
"sstoreInitRefundEIP2200": {
"v": 19900,
"d": "Once per SSTORE operation for resetting to the original zero value"
},
"sstoreCleanRefundEIP2200": {
"v": 4900,
"d": "Once per SSTORE operation for resetting to the original non-zero value"
},
"call": {
"v": 0,
"d": "Base fee of the CALL opcode"
},
"callcode": {
"v": 0,
"d": "Base fee of the CALLCODE opcode"
},
"delegatecall": {
"v": 0,
"d": "Base fee of the DELEGATECALL opcode"
},
"staticcall": {
"v": 0,
"d": "Base fee of the STATICCALL opcode"
},
"balance": {
"v": 0,
"d": "Base fee of the BALANCE opcode"
},
"extcodesize": {
"v": 0,
"d": "Base fee of the EXTCODESIZE opcode"
},
"extcodecopy": {
"v": 0,
"d": "Base fee of the EXTCODECOPY opcode"
},
"extcodehash": {
"v": 0,
"d": "Base fee of the EXTCODEHASH opcode"
},
"sload": {
"v": 0,
"d": "Base fee of the SLOAD opcode"
},
"sstore": {
"v": 0,
"d": "Base fee of the SSTORE opcode"
}
},
"vm": {},
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/hardforks/berlin.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"comment": "HF targeted for July 2020 following the Muir Glacier HF",
"url": "https://eips.ethereum.org/EIPS/eip-2070",
"status": "Draft",
"eips": [ 2315 ]
"eips": [ 2315, 2565, 2929 ]
}
7 changes: 7 additions & 0 deletions packages/vm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ The following loggers are currently available:
| `vm:tx:gas` | Transaction gas logger |
| `vm:evm` | EVM control flow, CALL or CREATE message execution |
| `vm:evm:gas` | EVM gas logger |
| `vm:eei:gas` | EEI gas logger |
| `vm:state`| StateManager logger |
| `vm:ops` | Opcode traces |
| `vm:ops:[Lower-case opcode name]` | Traces on a specific opcode |
Expand All @@ -231,6 +232,12 @@ Run all loggers currently available:
DEBUG=vm:*,vm:*:* ts-node test.ts
```

Run only the gas loggers:

```shell
DEBUG=vm:*:gas ts-node test.ts
```

Excluding the state logger:

```shell
Expand Down
19 changes: 14 additions & 5 deletions packages/vm/lib/evm/eei.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { debug as createDebugLogger } from 'debug'
import { Account, Address, BN } from 'ethereumjs-util'
import { Block } from '@ethereumjs/block'
import Blockchain from '@ethereumjs/blockchain'
Expand All @@ -8,6 +9,8 @@ import Message from './message'
import EVM, { EVMResult } from './evm'
import { Log } from './types'

const debugGas = createDebugLogger('vm:eei:gas')

function trap(err: ERROR) {
throw new VmError(err)
}
Expand Down Expand Up @@ -84,10 +87,12 @@ export default class EEI {
/**
* Subtracts an amount from the gas counter.
* @param amount - Amount of gas to consume
* @param context - Usage context for debugging
* @throws if out of gas
*/
useGas(amount: BN): void {
useGas(amount: BN, context?: string): void {
this._gasLeft.isub(amount)
debugGas(`${context ? context + ': ' : ''}used ${amount} gas (-> ${this._gasLeft})`)
if (this._gasLeft.ltn(0)) {
this._gasLeft = new BN(0)
trap(ERROR.OUT_OF_GAS)
Expand All @@ -97,16 +102,20 @@ export default class EEI {
/**
* Adds a positive amount to the gas counter.
* @param amount - Amount of gas refunded
* @param context - Usage context for debugging
*/
refundGas(amount: BN): void {
refundGas(amount: BN, context?: string): void {
debugGas(`${context ? context + ': ' : ''}refund ${amount} gas (-> ${this._evm._refund})`)
this._evm._refund.iadd(amount)
}

/**
* Reduces amount of gas to be refunded by a positive value.
* @param amount - Amount to subtract from gas refunds
* @param context - Usage context for debugging
*/
subRefund(amount: BN): void {
subRefund(amount: BN, context?: string): void {
debugGas(`${context ? context + ': ' : ''}sub gas refund ${amount} (-> ${this._evm._refund})`)
this._evm._refund.isub(amount)
if (this._evm._refund.ltn(0)) {
this._evm._refund = new BN(0)
Expand Down Expand Up @@ -499,7 +508,7 @@ export default class EEI {
}

// this should always be safe
this.useGas(results.gasUsed)
this.useGas(results.gasUsed, 'CALL, STATICCALL, DELEGATECALL, CALLCODE')

// Set return value
if (
Expand Down Expand Up @@ -556,7 +565,7 @@ export default class EEI {
}

// this should always be safe
this.useGas(results.gasUsed)
this.useGas(results.gasUsed, 'CREATE')

// Set return buffer in case revert happened
if (
Expand Down
6 changes: 6 additions & 0 deletions packages/vm/lib/evm/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ export default class EVM {
async executeMessage(message: Message): Promise<EVMResult> {
await this._vm._emit('beforeMessage', message)

if (!message.to && this._vm._common.isActivatedEIP(2929)) {
message.code = message.data
;(<any>this._state).addWarmedAddress((await this._generateAddress(message)).buf)
}

await this._state.checkpoint()
debug('-'.repeat(100))
debug(`message checkpoint`)
Expand Down Expand Up @@ -258,6 +263,7 @@ export default class EVM {
message.to = await this._generateAddress(message)
debug(`Generated CREATE contract address ${message.to.toString()}`)
let toAccount = await this._state.getAccount(message.to)

// Check for collision
if ((toAccount.nonce && toAccount.nonce.gtn(0)) || !toAccount.codeHash.equals(KECCAK256_NULL)) {
debug(`Returning on address collision`)
Expand Down
29 changes: 4 additions & 25 deletions packages/vm/lib/evm/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { ERROR, VmError } from '../exceptions'
import Memory from './memory'
import Stack from './stack'
import EEI from './eei'
import { precompiles } from './precompiles'
import { Opcode, handlers as opHandlers, OpHandler, AsyncOpHandler } from './opcodes'

export interface InterpreterOpts {
Expand All @@ -27,8 +26,6 @@ export interface RunState {
_common: Common
stateManager: StateManager
eei: EEI
accessedAddresses: Set<string>
accessedStorage: Map<string, Set<string>>
}

export interface InterpreterResult {
Expand Down Expand Up @@ -91,8 +88,6 @@ export default class Interpreter {
_common: this._vm._common,
stateManager: this._state,
eei: this._eei,
accessedAddresses: new Set(),
accessedStorage: new Map(),
}
}

Expand All @@ -103,8 +98,6 @@ export default class Interpreter {
const valid = this._getValidJumpDests(code)
this._runState.validJumps = valid.jumps
this._runState.validJumpSubs = valid.jumpSubs
this._initAccessedAddresses()
this._runState.accessedStorage.clear()

// Check that the programCounter is in range
const pc = this._runState.programCounter
Expand Down Expand Up @@ -149,7 +142,7 @@ export default class Interpreter {
}

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

Expand Down Expand Up @@ -205,7 +198,6 @@ export default class Interpreter {
})

const name = eventObj.opcode.name
const nameLC = name.toLowerCase()
const opTrace = {
pc: eventObj.pc,
op: name,
Expand All @@ -215,10 +207,10 @@ export default class Interpreter {
depth: eventObj.depth,
}

if (!(nameLC in this.opDebuggers)) {
this.opDebuggers[nameLC] = createDebugLogger(`vm:ops:${nameLC}`)
if (!(name in this.opDebuggers)) {
this.opDebuggers[name] = createDebugLogger(`vm:ops:${name}`)
}
this.opDebuggers[nameLC](JSON.stringify(opTrace))
this.opDebuggers[name](JSON.stringify(opTrace))

/**
* The `step` event for trace output
Expand Down Expand Up @@ -264,17 +256,4 @@ export default class Interpreter {

return { jumps, jumpSubs }
}

// Populates accessedAddresses with 'pre-warmed' addresses. Includes
// tx.origin, `this` (e.g the address of the code being executed), and
// all the precompiles. (EIP 2929)
_initAccessedAddresses() {
this._runState.accessedAddresses.clear()
this._runState.accessedAddresses.add(this._eei._env.origin.toString())
this._runState.accessedAddresses.add(this._eei.getAddress().toString())

for (const address of Object.keys(precompiles)) {
this._runState.accessedAddresses.add(`0x${address}`)
}
}
}
43 changes: 34 additions & 9 deletions packages/vm/lib/evm/opcodes/EIP1283.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,45 +12,70 @@ export function updateSstoreGasEIP1283(runState: RunState, found: any, value: Bu
const { original, current } = found
if (current.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')))
runState.eei.useGas(
new BN(runState._common.param('gasPrices', 'netSstoreNoopGas')),
'EIP-1283 -> netSstoreNoopGas'
)
return
}
// If current value does not equal new value
if (original.equals(current)) {
// If original value equals current value (this storage slot has not been changed by the current execution context)
if (original.length === 0) {
// If original value is 0, 20000 gas is deducted.
return runState.eei.useGas(new BN(runState._common.param('gasPrices', 'netSstoreInitGas')))
return runState.eei.useGas(
new BN(runState._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')))
runState.eei.refundGas(
new BN(runState._common.param('gasPrices', 'netSstoreClearRefund')),
'EIP-1283 -> netSstoreClearRefund'
)
}
// Otherwise, 5000 gas is deducted.
return runState.eei.useGas(new BN(runState._common.param('gasPrices', 'netSstoreCleanGas')))
return runState.eei.useGas(
new BN(runState._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 original value is not 0
if (current.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')))
runState.eei.subRefund(
new BN(runState._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')))
runState.eei.refundGas(
new BN(runState._common.param('gasPrices', 'netSstoreClearRefund')),
'EIP-1283 -> netSstoreClearRefund'
)
}
}
if (original.equals(value)) {
// If original value equals new value (this storage slot is reset)
if (original.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(runState._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')))
runState.eei.refundGas(
new BN(runState._common.param('gasPrices', 'netSstoreResetRefund')),
'EIP-1283 -> netSstoreResetRefund'
)
}
}
return runState.eei.useGas(new BN(runState._common.param('gasPrices', 'netSstoreDirtyGas')))
return runState.eei.useGas(
new BN(runState._common.param('gasPrices', 'netSstoreDirtyGas')),
'EIP-1283 -> netSstoreDirtyGas'
)
}
Loading

0 comments on commit 93c1110

Please sign in to comment.