diff --git a/packages/vm/lib/evm/evm.ts b/packages/vm/lib/evm/evm.ts index f2afa09323..88acdae009 100644 --- a/packages/vm/lib/evm/evm.ts +++ b/packages/vm/lib/evm/evm.ts @@ -255,10 +255,18 @@ export default class EVM { totalGas = totalGas.add(returnFee) } - // if not enough gas + // Check for SpuriousDragon EIP-170 code size limit + let allowedCodeSize = true + if ( + this._vm._common.gteHardfork('spuriousDragon') && + result.returnValue.length > this._vm._common.param('vm', 'maxCodeSize') + ) { + allowedCodeSize = false + } + // If enough gas and allowed code size if ( totalGas.lte(message.gasLimit) && - (this._vm.allowUnlimitedContractSize || result.returnValue.length <= 24576) + (this._vm.allowUnlimitedContractSize || allowedCodeSize) ) { result.gasUsed = totalGas } else { diff --git a/packages/vm/lib/evm/opFns.ts b/packages/vm/lib/evm/opFns.ts index 85be450d5c..33af20f40e 100644 --- a/packages/vm/lib/evm/opFns.ts +++ b/packages/vm/lib/evm/opFns.ts @@ -401,9 +401,15 @@ export const handlers: { [k: string]: OpHandler } = { runState.stack.push(new BN(keccak256(code))) }, RETURNDATASIZE: function (runState: RunState) { + if (!runState._common.gteHardfork('byzantium')) { + trap(ERROR.INVALID_OPCODE) + } runState.stack.push(runState.eei.getReturnDataSize()) }, RETURNDATACOPY: function (runState: RunState) { + if (!runState._common.gteHardfork('byzantium')) { + trap(ERROR.INVALID_OPCODE) + } let [memOffset, returnDataOffset, length] = runState.stack.popN(3) if (returnDataOffset.add(length).gt(runState.eei.getReturnDataSize())) { @@ -769,6 +775,9 @@ export const handlers: { [k: string]: OpHandler } = { runState.stack.push(ret) }, STATICCALL: async function (runState: RunState) { + if (!runState._common.gteHardfork('byzantium')) { + trap(ERROR.INVALID_OPCODE) + } const value = new BN(0) let [gasLimit, toAddress, inOffset, inLength, outOffset, outLength] = runState.stack.popN(6) const toAddressBuf = addressToBuffer(toAddress) @@ -797,6 +806,9 @@ export const handlers: { [k: string]: OpHandler } = { runState.eei.finish(returnData) }, REVERT: function (runState: RunState) { + if (!runState._common.gteHardfork('byzantium')) { + trap(ERROR.INVALID_OPCODE) + } const [offset, length] = runState.stack.popN(2) subMemUsage(runState, offset, length) let returnData = Buffer.alloc(0) diff --git a/packages/vm/lib/index.ts b/packages/vm/lib/index.ts index dd33afa716..537926b93a 100644 --- a/packages/vm/lib/index.ts +++ b/packages/vm/lib/index.ts @@ -110,6 +110,7 @@ export default class VM extends AsyncEventEmitter { const chain = opts.chain ? opts.chain : 'mainnet' const hardfork = opts.hardfork ? opts.hardfork : 'petersburg' const supportedHardforks = [ + 'spuriousDragon', 'byzantium', 'constantinople', 'petersburg', diff --git a/packages/vm/lib/runBlock.ts b/packages/vm/lib/runBlock.ts index 59fadd7062..d9d1233782 100644 --- a/packages/vm/lib/runBlock.ts +++ b/packages/vm/lib/runBlock.ts @@ -44,7 +44,7 @@ export interface RunBlockResult { /** * Receipts generated for transactions in the block */ - receipts: TxReceipt[] + receipts: (PreByzantiumTxReceipt | PostByzantiumTxReceipt)[] /** * Results of executing the transactions in the block */ @@ -52,13 +52,9 @@ export interface RunBlockResult { } /** - * Receipt generated for a transaction + * Abstract interface with common transaction receipt fields */ -export interface TxReceipt { - /** - * Status of transaction, `1` if successful, `0` if an exception occured - */ - status: 0 | 1 +interface TxReceipt { /** * Gas used */ @@ -73,6 +69,28 @@ export interface TxReceipt { logs: any[] } +/** + * Pre-Byzantium receipt type with a field + * for the intermediary state root + */ +export interface PreByzantiumTxReceipt extends TxReceipt { + /** + * Intermediary state root + */ + stateRoot: Buffer +} + +/** + * Receipt type for Byzantium and beyond replacing the intermediary + * state root field with a status code field (EIP-658) + */ +export interface PostByzantiumTxReceipt extends TxReceipt { + /** + * Status of transaction, `1` if successful, `0` if an exception occured + */ + status: 0 | 1 +} + /** * @ignore */ @@ -219,12 +237,28 @@ async function applyTransactions(this: VM, block: any, opts: RunBlockOpts) { // Combine blooms via bitwise OR bloom.or(txRes.bloom) - const txReceipt: TxReceipt = { - status: txRes.execResult.exceptionError ? 0 : 1, // Receipts have a 0 as status on error + const abstractTxReceipt: TxReceipt = { gasUsed: gasUsed.toArrayLike(Buffer), bitvector: txRes.bloom.bitvector, logs: txRes.execResult.logs || [], } + let txReceipt + if (this._common.gteHardfork('byzantium')) { + txReceipt = { + status: txRes.execResult.exceptionError ? 0 : 1, // Receipts have a 0 as status on error + ...abstractTxReceipt, + } as PostByzantiumTxReceipt + } else { + // This is just using a dummy place holder for the state root right now. + // Giving the correct intermediary state root would need a too depp intervention + // into the current checkpointing mechanism which hasn't been considered + // to be worth it on a HF backport, 2020-06-26 + txReceipt = { + stateRoot: Buffer.alloc(32), + ...abstractTxReceipt, + } as PreByzantiumTxReceipt + } + receipts.push(txReceipt) // Add receipt to trie to later calculate receipt root diff --git a/packages/vm/package.json b/packages/vm/package.json index 737d041236..544c148bcd 100644 --- a/packages/vm/package.json +++ b/packages/vm/package.json @@ -14,8 +14,8 @@ "coverage:test": "npm run build && tape './tests/api/**/*.js' ./tests/tester.js --state --dist", "docs:build": "typedoc --options typedoc.js", "test:state": "ts-node ./tests/tester --state", - "test:state:allForks": "npm run test:state -- --fork=Byzantium && npm run test:state -- --fork=Constantinople && npm run test:state -- --fork=Petersburg && npm run test:state -- --fork=Istanbul && npm run test:state -- --fork=MuirGlacier", - "test:state:selectedForks": "npm run test:state -- --fork=Petersburg && npm run test:state -- --fork=Istanbul && npm run test:state -- --fork=MuirGlacier", + "test:state:allForks": "npm run test:state -- --fork=SpuriousDragon && npm run test:state -- --fork=Byzantium && npm run test:state -- --fork=Constantinople && npm run test:state -- --fork=Petersburg && npm run test:state -- --fork=Istanbul && npm run test:state -- --fork=MuirGlacier", + "test:state:selectedForks": "npm run test:state -- --fork=Petersburg && npm run test:state -- --fork=SpuriousDragon", "test:state:slow": "npm run test:state -- --runSkipped=slow", "test:buildIntegrity": "npm run test:state -- --test='stackOverflow'", "test:blockchain": "node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain", diff --git a/packages/vm/tests/api/runBlock.js b/packages/vm/tests/api/runBlock.js index a49f583df6..92582e1595 100644 --- a/packages/vm/tests/api/runBlock.js +++ b/packages/vm/tests/api/runBlock.js @@ -145,3 +145,32 @@ tape('should run valid block', async (t) => { t.end() }) + +async function runWithHf(hardfork) { + const vm = setupVM({ hardfork: hardfork }) + const suite = setup(vm) + + const block = new Block(util.rlp.decode(suite.data.blocks[0].rlp)) + + await setupPreConditions(suite.vm.stateManager._trie, suite.data) + + let res = await suite.p.runBlock({ + block, + generate: true, + skipBlockValidation: true, + }) + return res +} + +tape('should return correct HF receipts', async (t) => { + let res = await runWithHf('byzantium') + t.equal(res.receipts[0].status, 1, 'should return correct post-Byzantium receipt format') + + res = await runWithHf('spuriousDragon') + t.deepEqual( + res.receipts[0].stateRoot, + Buffer.alloc(32), + 'should return correct pre-Byzantium receipt format') + + t.end() +}) \ No newline at end of file diff --git a/packages/vm/tests/config.js b/packages/vm/tests/config.js index 2c3bca1979..78cdc89427 100644 --- a/packages/vm/tests/config.js +++ b/packages/vm/tests/config.js @@ -119,11 +119,17 @@ const SKIP_VM = [ * @returns {String} Either an alias of the forkConfig param, or the forkConfig param itself */ function getRequiredForkConfigAlias(forkConfig) { + // SpuriousDragon is named EIP158 (attention: misleading name) + // in the client-independent consensus test suite + if (String(forkConfig).match(/^spuriousDragon$/i)) { + return 'EIP158' + } // Run the Istanbul tests for MuirGlacier since there are no dedicated tests if (String(forkConfig).match(/^muirGlacier/i)) { return 'Istanbul' } - // Petersburg is named ConstantinopleFix in the client-independent consensus test suite + // Petersburg is named ConstantinopleFix + // in the client-independent consensus test suite if (String(forkConfig).match(/^petersburg$/i)) { return 'ConstantinopleFix' } diff --git a/packages/vm/tests/tester.js b/packages/vm/tests/tester.js index 3041a023c6..991d44de24 100755 --- a/packages/vm/tests/tester.js +++ b/packages/vm/tests/tester.js @@ -15,7 +15,7 @@ function runTests() { const FORK_CONFIG = (argv.fork || config.DEFAULT_FORK_CONFIG) const FORK_CONFIG_TEST_SUITE = config.getRequiredForkConfigAlias(FORK_CONFIG) - // Istanbul -> istanbul, MuirGlacier -> muirGlacier + // Examples: Istanbul -> istanbul, MuirGlacier -> muirGlacier const FORK_CONFIG_VM = FORK_CONFIG.charAt(0).toLowerCase() + FORK_CONFIG.substring(1) /**