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, Common: Add EIP-3670 (EOF - Code Validation) #1743

Merged
merged 5 commits into from
Feb 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 15 additions & 0 deletions packages/common/src/eips/3670.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "EIP-3670",
"number": 3670,
"comment": "EOF - Code Validation",
"url": "https://eips.ethereum.org/EIPS/eip-3670",
"status": "Review",
"minimumHardfork": "london",
"requiredEIPs": [
3540
],
"gasConfig": {},
"gasPrices": {},
"vm": {},
"pow": {}
}
1 change: 1 addition & 0 deletions packages/common/src/eips/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const EIPs: eipsType = {
3541: require('./3541.json'),
3554: require('./3554.json'),
3607: require('./3607.json'),
3670: require('./3670.json'),
3675: require('./3675.json'),
3855: require('./3855.json'),
4345: require('./4345.json'),
Expand Down
24 changes: 21 additions & 3 deletions packages/vm/src/evm/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import TxContext from './txContext'
import Message from './message'
import EEI from './eei'
// eslint-disable-next-line
import { eof1CodeAnalysis, short } from './opcodes/util'
import { eof1CodeAnalysis, eof1ValidOpcodes, short } from './opcodes/util'
import { Log } from './types'
import { default as Interpreter, InterpreterOpts, RunState } from './interpreter'

Expand Down Expand Up @@ -372,6 +372,7 @@ export default class EVM {
if (this._vm.DEBUG) {
debug(`Start bytecode processing...`)
}

let result = await this.runInterpreter(message)

// fee for size of the return value
Expand Down Expand Up @@ -407,12 +408,29 @@ export default class EVM {
if (!this._vm._common.isActivatedEIP(3540)) {
result = { ...result, ...INVALID_BYTECODE_RESULT(message.gasLimit) }
}
// EIP-3540 EOF1 checks
if (!eof1CodeAnalysis(result.returnValue)) {
// EIP-3540 EOF1 header check
const eof1CodeAnalysisResults = eof1CodeAnalysis(result.returnValue)
if (!eof1CodeAnalysisResults?.code) {
result = {
...result,
...INVALID_BYTECODE_RESULT(message.gasLimit),
}
} else if (this._vm._common.isActivatedEIP(3670)) {
// EIP-3670 EOF1 code check
const codeStart = eof1CodeAnalysisResults.data > 0 ? 10 : 7
holgerd77 marked this conversation as resolved.
Show resolved Hide resolved
// The start of the code section of an EOF1 compliant contract will either be
// index 7 (if no data section is present) or index 10 (if a data section is present)
// in the bytecode of the contract
if (
!eof1ValidOpcodes(
result.returnValue.slice(codeStart, codeStart + eof1CodeAnalysisResults.code)
)
) {
result = {
...result,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch is not covered by tests

...INVALID_BYTECODE_RESULT(message.gasLimit),
}
}
}
} else {
result.gasUsed = totalGas
Expand Down
4 changes: 2 additions & 2 deletions packages/vm/src/evm/opcodes/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,7 @@ export const handlers: Map<number, OpHandler> = new Map([
},
],
// 0x5b: JUMPDEST
[0x5b, function () { }],
[0x5b, function () {}],
// 0x5c: BEGINSUB
[
0x5c,
Expand Down Expand Up @@ -825,7 +825,7 @@ export const handlers: Map<number, OpHandler> = new Map([
trap(ERROR.OUT_OF_RANGE)
}
const loaded = bufferToBigInt(
runState.eei.getCode().slice(runState.programCounter, runState.programCounter + numToPush)
runState.code.slice(runState.programCounter, runState.programCounter + numToPush)
)
runState.programCounter += numToPush
runState.stack.push(loaded)
Expand Down
32 changes: 31 additions & 1 deletion packages/vm/src/evm/opcodes/util.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Common from '@ethereumjs/common'
import { keccak256, setLengthRight, setLengthLeft, bigIntToBuffer } from 'ethereumjs-util'
import { handlers } from '.'
import { ERROR, VmError } from './../../exceptions'
import { RunState } from './../interpreter'

Expand Down Expand Up @@ -334,9 +335,38 @@ export const eof1CodeAnalysis = (container: Buffer) => {
}
if (container.length !== computedContainerSize) {
// Scanned code does not match length of contract byte code

return
}
return sectionSizes
}
}

export const eof1ValidOpcodes = (code: Buffer) => {
// EIP-3670 - validate all opcodes
const opcodes = new Set(handlers.keys())
opcodes.add(0xfe) // Add INVALID opcode to set

let x = 0
while (x < code.length) {
const opcode = code[x]
x++
if (!opcodes.has(opcode)) {
// No invalid/undefined opcodes
return false
}
if (opcode >= 0x60 && opcode <= 0x7f) {
// Skip data block following push
x += opcode - 0x5f
if (x > code.length - 1) {
// Push blocks mmust not exceed end of code section
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: spelling mistake mmust -> must

return false
}
}
}
const terminatingOpcodes = new Set([0x00, 0xd3, 0xfd, 0xfe, 0xff])
holgerd77 marked this conversation as resolved.
Show resolved Hide resolved
// Per EIP-3670, the final opcode of a code section must be STOP, RETURN, REVERT, INVALID, or SELFDESTRUCT
if (!terminatingOpcodes.has(code[code.length - 1])) {
return false
}
return true
}
2 changes: 1 addition & 1 deletion packages/vm/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ export default class VM extends AsyncEventEmitter {
if (opts.common) {
// Supported EIPs
const supportedEIPs = [
1559, 2315, 2537, 2565, 2718, 2929, 2930, 3198, 3529, 3540, 3541, 3607, 3855,
1559, 2315, 2537, 2565, 2718, 2929, 2930, 3198, 3529, 3540, 3541, 3607, 3670, 3855,
]
for (const eip of opts.common.eips()) {
if (!supportedEIPs.includes(eip)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ import VM from '../../../src'
import Common, { Chain, Hardfork } from '@ethereumjs/common'
import { FeeMarketEIP1559Transaction } from '@ethereumjs/tx'
import { Address, BN, privateToAddress } from 'ethereumjs-util'

const pkey = Buffer.from('20'.repeat(32), 'hex')
const GWEI = new BN('1000000000')
const sender = new Address(privateToAddress(pkey))

tape('EIP 3540 tests', (t) => {
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.London, eips: [3540, 3541] })
//const commonNoEIP3541 = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.London, eips: [] })

const common = new Common({
chain: Chain.Mainnet,
hardfork: Hardfork.London,
eips: [3540, 3541],
})
t.test('invalid object formats', async (st) => {
const vm = new VM({ common })
const account = await vm.stateManager.getAccount(sender)
Expand Down Expand Up @@ -93,7 +94,7 @@ tape('EIP 3540 tests', (t) => {
await vm.stateManager.putAccount(sender, account)

let tx = FeeMarketEIP1559Transaction.fromTxData({
data: '0x67EF0001010001000060005260206007F3',
data: '0x67EF0001010001000060005260086018F3',
gasLimit: 1000000,
maxFeePerGas: 7,
nonce: 0,
Expand All @@ -103,8 +104,8 @@ tape('EIP 3540 tests', (t) => {
let code = await vm.stateManager.getContractCode(created!)
st.ok(code.length > 0, 'code section with no data section')
tx = FeeMarketEIP1559Transaction.fromTxData({
data: '0x6CEF00010100010000020001AA0060005260206007F3',
gasLimit: 1000000,
data: '0x6BEF00010100010200010000AA600052600C6014F3',
gasLimit: 100000000,
maxFeePerGas: 7,
nonce: 1,
}).sign(pkey)
Expand Down
61 changes: 61 additions & 0 deletions packages/vm/tests/api/EIPs/eip-3670-eof-code-validation.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import tape from 'tape'
import VM from '../../../src'
import Common, { Chain, Hardfork } from '@ethereumjs/common'
import { FeeMarketEIP1559Transaction } from '@ethereumjs/tx'
import { Address, BN, privateToAddress } from 'ethereumjs-util'
import { eof1ValidOpcodes } from '../../../src/evm/opcodes'
const pkey = Buffer.from('20'.repeat(32), 'hex')
const GWEI = new BN('1000000000')
const sender = new Address(privateToAddress(pkey))

tape('EIP 3670 tests', (t) => {
const common = new Common({
chain: Chain.Mainnet,
hardfork: Hardfork.London,
eips: [3540, 3541, 3670],
})

t.test('eof1ValidOpcodes() tests', (st) => {
st.ok(eof1ValidOpcodes(Buffer.from([0])), 'valid -- STOP ')
st.notOk(eof1ValidOpcodes(Buffer.from([0xaa])), 'invalid -- AA -- undefined opcode')
st.ok(eof1ValidOpcodes(Buffer.from([0x60, 0xaa, 0])), 'valid - PUSH1 AA STOP')
st.notOk(
eof1ValidOpcodes(Buffer.from([0x7f, 0xaa, 0])),
'invalid -- PUSH32 AA STOP -- truncated push'
)
st.notOk(
eof1ValidOpcodes(Buffer.from([0x61, 0xaa, 0])),
'invalid -- PUSH2 AA STOP -- truncated push'
)
st.notOk(eof1ValidOpcodes(Buffer.from([0x30])), 'invalid -- ADDRESS -- invalid terminal opcode')
st.end()
})
t.test('valid contract code transactions', async (st) => {
const vm = new VM({ common })
const account = await vm.stateManager.getAccount(sender)
const balance = GWEI.muln(21000).muln(10000000)
account.balance = balance
await vm.stateManager.putAccount(sender, account)

let tx = FeeMarketEIP1559Transaction.fromTxData({
data: '0x67EF0001010001000060005260086018F3',
gasLimit: 1000000,
maxFeePerGas: 7,
nonce: 0,
}).sign(pkey)
let result = await vm.runTx({ tx })
let created = result.createdAddress
let code = await vm.stateManager.getContractCode(created!)
st.ok(code.length > 0, 'code section with no data section')
tx = FeeMarketEIP1559Transaction.fromTxData({
data: '0x6BEF00010100010200010000AA600052600C6014F3',
gasLimit: 100000000,
maxFeePerGas: 7,
nonce: 1,
}).sign(pkey)
result = await vm.runTx({ tx })
created = result.createdAddress
code = await vm.stateManager.getContractCode(created!)
st.ok(code.length > 0, 'code section with data section')
})
})