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] group opcodes based upon hardfork #798

Merged
merged 1 commit into from
Jun 30, 2020
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
68 changes: 48 additions & 20 deletions packages/vm/lib/evm/opcodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface OpcodeList {
[code: number]: Opcode
}

// Base opcode list. The opcode list is extended in future hardforks
const opcodes: OpcodeList = createOpcodes({
// 0x0 range - arithmetic ops
// name, baseCost, async
Expand Down Expand Up @@ -63,9 +64,6 @@ const opcodes: OpcodeList = createOpcodes({
0x18: { name: 'XOR', fee: 3, isAsync: false },
0x19: { name: 'NOT', fee: 3, isAsync: false },
0x1a: { name: 'BYTE', fee: 3, isAsync: false },
0x1b: { name: 'SHL', fee: 3, isAsync: false },
0x1c: { name: 'SHR', fee: 3, isAsync: false },
0x1d: { name: 'SAR', fee: 3, isAsync: false },

// 0x20 range - crypto
0x20: { name: 'SHA3', fee: 30, isAsync: false },
Expand All @@ -84,9 +82,6 @@ const opcodes: OpcodeList = createOpcodes({
0x3a: { name: 'GASPRICE', fee: 2, isAsync: false },
0x3b: { name: 'EXTCODESIZE', fee: 700, isAsync: true },
0x3c: { name: 'EXTCODECOPY', fee: 700, isAsync: true },
0x3d: { name: 'RETURNDATASIZE', fee: 2, isAsync: true },
0x3e: { name: 'RETURNDATACOPY', fee: 3, isAsync: true },
0x3f: { name: 'EXTCODEHASH', fee: 400, isAsync: true },

// '0x40' range - block operations
0x40: { name: 'BLOCKHASH', fee: 20, isAsync: true },
Expand Down Expand Up @@ -189,23 +184,52 @@ const opcodes: OpcodeList = createOpcodes({
0xf1: { name: 'CALL', fee: 700, isAsync: true },
0xf2: { name: 'CALLCODE', fee: 700, isAsync: true },
0xf3: { name: 'RETURN', fee: 0, isAsync: false },
0xf4: { name: 'DELEGATECALL', fee: 700, isAsync: true },
0xf5: { name: 'CREATE2', fee: 32000, isAsync: true },
0xfa: { name: 'STATICCALL', fee: 700, isAsync: true },
0xfd: { name: 'REVERT', fee: 0, isAsync: false },

// '0x70', range - other
0xfe: { name: 'INVALID', fee: 0, isAsync: false },
0xff: { name: 'SELFDESTRUCT', fee: 5000, isAsync: true },
})

const istanbulOpcodes: OpcodeList = createOpcodes({
0x31: { name: 'BALANCE', fee: 700, isAsync: true },
0x3f: { name: 'EXTCODEHASH', fee: 700, isAsync: true },
0x46: { name: 'CHAINID', fee: 2, isAsync: false },
0x47: { name: 'SELFBALANCE', fee: 5, isAsync: false },
0x54: { name: 'SLOAD', fee: 800, isAsync: true },
})
// Array of hard forks in order. These changes are repeatedly applied to `opcodes` until the hard fork is in the future based upon the common
// TODO: All gas price changes should be moved to common
const hardforkOpcodes = [
{
hardforkName: 'homestead',
opcodes: createOpcodes({
0xf4: { name: 'DELEGATECALL', fee: 700, isAsync: true }, // EIP 7
}),
},
{
hardforkName: 'byzantium',
opcodes: createOpcodes({
0xfd: { name: 'REVERT', fee: 0, isAsync: false }, // EIP 140
0xfa: { name: 'STATICCALL', fee: 700, isAsync: true }, // EIP 214
0x3d: { name: 'RETURNDATASIZE', fee: 2, isAsync: true }, // EIP 211
0x3e: { name: 'RETURNDATACOPY', fee: 3, isAsync: true }, // EIP 211
}),
},
{
hardforkName: 'constantinople',
opcodes: createOpcodes({
0x1b: { name: 'SHL', fee: 3, isAsync: false }, // EIP 145
0x1c: { name: 'SHR', fee: 3, isAsync: false }, // EIP 145
0x1d: { name: 'SAR', fee: 3, isAsync: false }, // EIP 145
0x3f: { name: 'EXTCODEHASH', fee: 400, isAsync: true }, // EIP 1052
0xf5: { name: 'CREATE2', fee: 32000, isAsync: true }, // EIP 1014
}),
},
{
hardforkName: 'istanbul',
opcodes: createOpcodes({
0x31: { name: 'BALANCE', fee: 700, isAsync: true }, // gas price change, EIP 1884
0x3f: { name: 'EXTCODEHASH', fee: 700, isAsync: true }, // gas price change, EIP 1884
0x46: { name: 'CHAINID', fee: 2, isAsync: false }, // EIP 1344
0x47: { name: 'SELFBALANCE', fee: 5, isAsync: false }, // EIP 1884
0x54: { name: 'SLOAD', fee: 800, isAsync: true }, // gas price change, EIP 1884
}),
},
]

/**
* Convert basic opcode info dictonary into complete OpcodeList instance.
Expand Down Expand Up @@ -260,9 +284,13 @@ function getFullname(code: number, name: string): string {
* @returns {OpcodeList} Opcodes dictionary object.
*/
export function getOpcodesForHF(common: Common): OpcodeList {
if (common.gteHardfork('istanbul')) {
return { ...opcodes, ...istanbulOpcodes }
} else {
return { ...opcodes }
let opcodeBuilder = { ...opcodes }

for (let fork = 0; fork < hardforkOpcodes.length; fork++) {
if (common.gteHardfork(hardforkOpcodes[fork].hardforkName)) {
opcodeBuilder = { ...opcodeBuilder, ...hardforkOpcodes[fork].opcodes }
}
}

return opcodeBuilder
}
34 changes: 34 additions & 0 deletions packages/vm/tests/api/runCall.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const BN = require('bn.js')
const VM = require('../../dist/index').default
const { createAccount } = require('./utils')
const { keccak256, padToEven } = require('ethereumjs-util')
const Common = require('@ethereumjs/common').default

// Non-protected Create2Address generator. Does not check if buffers have the right padding. Returns a 32-byte buffer which contains the address.
function create2address(sourceAddress, codeHash, salt) {
Expand Down Expand Up @@ -76,4 +77,37 @@ tape('Constantinople: EIP-1014 CREATE2 creates the right contract address', asyn
t.end()
})

tape('Byzantium cannot access Constantinople opcodes', async (t) => {
t.plan(2)
// setup the accounts for this test
const caller = Buffer.from('00000000000000000000000000000000000000ee', 'hex') // caller addres
const contractAddress = Buffer.from('00000000000000000000000000000000000000ff', 'hex') // contract address
// setup the vm
const vmByzantium = new VM({ chain: 'mainnet', hardfork: 'byzantium'})
const vmConstantinople = new VM({ chain: 'mainnet', hardfork: 'constantinople'})
const code = "600160011B00"
/*
code: remarks: (top of the stack is at the zero index)
PUSH1 0x01
PUSH1 0x01
SHL
STOP
*/

await vmByzantium.stateManager.putContractCode(contractAddress, Buffer.from(code, 'hex')) // setup the contract code
await vmConstantinople.stateManager.putContractCode(contractAddress, Buffer.from(code, 'hex')) // setup the contract code

const runCallArgs = {
caller: caller, // call address
gasLimit: new BN(0xffffffffff), // ensure we pass a lot of gas, so we do not run out of gas
to: contractAddress, // call to the contract address
}

const byzantiumResult = await vmByzantium.runCall(runCallArgs)
const constantinopleResult = await vmConstantinople.runCall(runCallArgs)

t.assert(byzantiumResult.execResult.exceptionError && byzantiumResult.execResult.exceptionError.error === 'invalid opcode', 'byzantium cannot accept constantinople opcodes (SHL)')
t.assert(!constantinopleResult.execResult.exceptionError, 'constantinople can access the SHL opcode')

t.end()
})