diff --git a/.github/workflows/vm-pr.yml b/.github/workflows/vm-pr.yml index b143aebbae..e6dd99f1ad 100644 --- a/.github/workflows/vm-pr.yml +++ b/.github/workflows/vm-pr.yml @@ -51,7 +51,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - fork: ['Petersburg', 'Istanbul', 'MuirGlacier'] + fork: ['Petersburg', 'Istanbul', 'MuirGlacier', 'Homestead', 'SpuriousDragon', 'TangerineWhistle', 'Byzantium'] fail-fast: false steps: - uses: actions/setup-node@v1 @@ -84,6 +84,8 @@ jobs: matrix: # This is the most fair division among 1 directory vs. everything else args: ['--excludeDir=stTimeConsuming', '--dir=GeneralStateTests/stTimeConsuming'] + # Run specific fork tests + fork: ['Homestead', 'Istanbul'] fail-fast: false steps: - uses: actions/setup-node@v1 @@ -108,4 +110,4 @@ jobs: - run: npm run build:vm working-directory: ${{github.workspace}} - - run: npm run test:blockchain -- ${{ matrix.args }} + - run: npm run test:blockchain -- ${{ matrix.args }} --fork=${{ matrix.fork }} diff --git a/package-lock.json b/package-lock.json index 6bd102b593..e1e4ca8cd1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4847,9 +4847,9 @@ } }, "ethereumjs-util": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.0.3.tgz", - "integrity": "sha512-uLQsGPOwsRxe50WV1Dybh5N8zXDz4ev7wP49LKX9kr28I5TmcDILPgpKK/BFe5zYSfRGEeo+hPT7W3tjghYLuA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.0.4.tgz", + "integrity": "sha512-isldtbCn9fdnhBPxedMNbFkNWVZ8ZdQvKRDSrdflame/AycAPKMer+vEpndpBxYIB3qxN6bd3Gh1YCQW9LDkCQ==", "requires": { "@types/bn.js": "^4.11.3", "bn.js": "^5.1.2", @@ -6222,9 +6222,9 @@ } }, "highlight.js": { - "version": "9.18.2", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.2.tgz", - "integrity": "sha512-tglpEIoxZR+CTZw7ivuOZJ2wEowySSPAmBv+BQnXzezJ937wQwWnqJ8BmpQAdi3Lgf3ltlCkhT2bb6IWVZrO9Q==" + "version": "9.18.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.3.tgz", + "integrity": "sha512-zBZAmhSupHIl5sITeMqIJnYCDfAEc3Gdkqj65wC1lpI468MMQeeQkhcIAvk+RylAkxrCcI9xy9piHiXeQ1BdzQ==" }, "hmac-drbg": { "version": "1.0.1", diff --git a/packages/account/package.json b/packages/account/package.json index 74de4f24b7..fabff61d73 100644 --- a/packages/account/package.json +++ b/packages/account/package.json @@ -36,7 +36,7 @@ }, "homepage": "https://github.com/ethereumjs/ethereumjs-vm/tree/master/packages/account#synopsis", "dependencies": { - "ethereumjs-util": "^7.0.2", + "ethereumjs-util": "^7.0.4", "rlp": "^2.2.3", "safe-buffer": "^5.1.1" }, diff --git a/packages/block/package.json b/packages/block/package.json index dca57b91bd..398fd10b90 100644 --- a/packages/block/package.json +++ b/packages/block/package.json @@ -42,7 +42,7 @@ "@ethereumjs/common": "^1.5.1", "@ethereumjs/tx": "^2.1.2", "@types/bn.js": "^4.11.6", - "ethereumjs-util": "^7.0.2", + "ethereumjs-util": "^7.0.4", "merkle-patricia-tree": "^4.0.0" }, "devDependencies": { diff --git a/packages/blockchain/package-lock.json b/packages/blockchain/package-lock.json index 60e8581c52..9f280ace9c 100644 --- a/packages/blockchain/package-lock.json +++ b/packages/blockchain/package-lock.json @@ -1,21 +1,23 @@ { - "name": "@ethereumjs/blockchain", - "version": "4.0.3", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "requires": { - "lodash": "^4.17.14" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - } - } + "name": "@ethereumjs/blockchain", + "version": "4.0.3", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": { + "lodash": "^4.17.14" + }, + "dependencies": { + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + } + } + } + } } diff --git a/packages/blockchain/package.json b/packages/blockchain/package.json index 5794c28811..56e4de3220 100644 --- a/packages/blockchain/package.json +++ b/packages/blockchain/package.json @@ -40,7 +40,7 @@ "@ethereumjs/common": "^1.5.1", "@ethereumjs/ethash": "^1.0.0", "async": "^2.6.1", - "ethereumjs-util": "^7.0.2", + "ethereumjs-util": "^7.0.4", "flow-stoplight": "^1.0.0", "level-mem": "^3.0.1", "lru-cache": "^5.1.1", diff --git a/packages/ethash/package-lock.json b/packages/ethash/package-lock.json index 28d9cf5e93..f8b6c69d6a 100644 --- a/packages/ethash/package-lock.json +++ b/packages/ethash/package-lock.json @@ -1,21 +1,23 @@ { - "name": "@ethereumjs/ethash", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "requires": { - "lodash": "^4.17.14" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - } - } + "name": "@ethereumjs/ethash", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": { + "lodash": "^4.17.14" + }, + "dependencies": { + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + } + } + } + } } diff --git a/packages/ethash/package.json b/packages/ethash/package.json index 6cab8c4dac..168f05356a 100644 --- a/packages/ethash/package.json +++ b/packages/ethash/package.json @@ -36,7 +36,7 @@ "@types/levelup": "^4.3.0", "async": "^2.1.2", "buffer-xor": "^2.0.1", - "ethereumjs-util": "^7.0.2", + "ethereumjs-util": "^7.0.4", "miller-rabin": "^4.0.0" }, "devDependencies": { diff --git a/packages/tx/package.json b/packages/tx/package.json index b41908059d..687df0e167 100644 --- a/packages/tx/package.json +++ b/packages/tx/package.json @@ -32,7 +32,7 @@ "license": "MPL-2.0", "dependencies": { "@ethereumjs/common": "^1.5.1", - "ethereumjs-util": "^7.0.2" + "ethereumjs-util": "^7.0.4" }, "devDependencies": { "@ethereumjs/config-nyc": "^1.1.1", diff --git a/packages/vm/lib/evm/memory.ts b/packages/vm/lib/evm/memory.ts index 42ee69c0a7..843ba86c4b 100644 --- a/packages/vm/lib/evm/memory.ts +++ b/packages/vm/lib/evm/memory.ts @@ -54,12 +54,18 @@ export default class Memory { * @param size - How many bytes to read */ read(offset: number, size: number): Buffer { - const loaded = this._store.slice(offset, offset + size) - // Fill the remaining length with zeros - for (let i = loaded.length; i < size; i++) { - loaded[i] = 0 + const returnBuffer = Buffer.allocUnsafe(size) + // Copy the stored "buffer" from memory into the return Buffer + + const loaded = Buffer.from(this._store.slice(offset, offset + size)) + returnBuffer.fill(loaded, 0, loaded.length) + + if (loaded.length < size) { + // fill the remaining part of the Buffer with zeros + returnBuffer.fill(0, loaded.length, size) } - return Buffer.from(loaded) + + return returnBuffer } } diff --git a/packages/vm/lib/evm/opFns.ts b/packages/vm/lib/evm/opFns.ts index 1dde4208ae..f123d92c88 100644 --- a/packages/vm/lib/evm/opFns.ts +++ b/packages/vm/lib/evm/opFns.ts @@ -607,7 +607,11 @@ export const handlers: { [k: string]: OpHandler } = { subMemUsage(runState, offset, length) let gasLimit = new BN(runState.eei.getGasLeft()) - gasLimit = maxCallGas(gasLimit, runState.eei.getGasLeft()) + gasLimit = maxCallGas( + gasLimit, + runState.eei.getGasLeft(), + runState._common.gteHardfork('tangerineWhistle'), + ) let data = Buffer.alloc(0) if (!length.isZero()) { @@ -630,7 +634,7 @@ export const handlers: { [k: string]: OpHandler } = { new BN(runState._common.param('gasPrices', 'sha3Word')).imul(divCeil(length, new BN(32))), ) let gasLimit = new BN(runState.eei.getGasLeft()) - gasLimit = maxCallGas(gasLimit, runState.eei.getGasLeft()) + gasLimit = maxCallGas(gasLimit, runState.eei.getGasLeft(), true) // CREATE2 is only available after TW (Constantinople introduced this opcode) let data = Buffer.alloc(0) if (!length.isZero()) { @@ -666,7 +670,6 @@ export const handlers: { [k: string]: OpHandler } = { if (!value.isZero()) { runState.eei.useGas(new BN(runState._common.param('gasPrices', 'callValueTransfer'))) } - gasLimit = maxCallGas(gasLimit, runState.eei.getGasLeft()) let data = Buffer.alloc(0) if (!inLength.isZero()) { @@ -685,6 +688,16 @@ export const handlers: { [k: string]: OpHandler } = { runState.eei.useGas(new BN(runState._common.param('gasPrices', 'callNewAccount'))) } + gasLimit = maxCallGas( + gasLimit, + runState.eei.getGasLeft(), + runState._common.gteHardfork('tangerineWhistle'), + ) + // note that TW or later this cannot happen (it could have ran out of gas prior to getting here though) + if (gasLimit.gt(runState.eei.getGasLeft())) { + trap(ERROR.OUT_OF_GAS) + } + if (!value.isZero()) { // TODO: Don't use private attr directly runState.eei._gasLeft.iaddn(runState._common.param('gasPrices', 'callStipend')) @@ -713,7 +726,15 @@ export const handlers: { [k: string]: OpHandler } = { if (!value.isZero()) { runState.eei.useGas(new BN(runState._common.param('gasPrices', 'callValueTransfer'))) } - gasLimit = maxCallGas(gasLimit, runState.eei.getGasLeft()) + gasLimit = maxCallGas( + gasLimit, + runState.eei.getGasLeft(), + runState._common.gteHardfork('tangerineWhistle'), + ) + // note that TW or later this cannot happen (it could have ran out of gas prior to getting here though) + if (gasLimit.gt(runState.eei.getGasLeft())) { + trap(ERROR.OUT_OF_GAS) + } if (!value.isZero()) { // TODO: Don't use private attr directly runState.eei._gasLeft.iaddn(runState._common.param('gasPrices', 'callStipend')) @@ -737,7 +758,15 @@ export const handlers: { [k: string]: OpHandler } = { subMemUsage(runState, inOffset, inLength) subMemUsage(runState, outOffset, outLength) - gasLimit = maxCallGas(gasLimit, runState.eei.getGasLeft()) + gasLimit = maxCallGas( + gasLimit, + runState.eei.getGasLeft(), + runState._common.gteHardfork('tangerineWhistle'), + ) + // note that TW or later this cannot happen (it could have ran out of gas prior to getting here though) + if (gasLimit.gt(runState.eei.getGasLeft())) { + trap(ERROR.OUT_OF_GAS) + } let data = Buffer.alloc(0) if (!inLength.isZero()) { @@ -756,7 +785,7 @@ export const handlers: { [k: string]: OpHandler } = { subMemUsage(runState, inOffset, inLength) subMemUsage(runState, outOffset, outLength) - gasLimit = maxCallGas(gasLimit, runState.eei.getGasLeft()) + gasLimit = maxCallGas(gasLimit, runState.eei.getGasLeft(), true) // we set TW or later to true here, as STATICCALL was available from Byzantium (which is after TW) let data = Buffer.alloc(0) if (!inLength.isZero()) { @@ -807,8 +836,8 @@ export const handlers: { [k: string]: OpHandler } = { deductGas = true } } - } else { - // Pre EIP-161 (Spurious Dragon) gas semantics + } else if (runState._common.gteHardfork('tangerineWhistle')) { + // Pre EIP-150 (Tangerine Whistle) gas semantics const exists = await runState.stateManager.accountExists(selfdestructToAddressBuf) if (!exists) { deductGas = true @@ -893,9 +922,24 @@ function jumpIsValid(runState: RunState, dest: number): boolean { return runState.validJumps.indexOf(dest) !== -1 } -function maxCallGas(gasLimit: BN, gasLeft: BN): BN { - const gasAllowed = gasLeft.sub(gasLeft.divn(64)) - return gasLimit.gt(gasAllowed) ? gasAllowed : gasLimit +// returns the max call gas which we want to use for the gas limit +// note that pre-TW there was no hard limit. if you tried sending more gas, your CALLs (or equivalent) went OOG +// + +/** + * Returns an overflow-safe slice of an array. It right-pads + * the data with zeros to `length`. + * @param {BN} gasLimit - requested gas Limit + * @param {BN} gasLeft - current gas left + * @param {boolean} isTangerineWhistleOrLater - set to true if we are on TW or later (this converts this to the identify function if it is false) + */ +function maxCallGas(gasLimit: BN, gasLeft: BN, isTangerineWhistleOrLater: boolean): BN { + if (isTangerineWhistleOrLater) { + const gasAllowed = gasLeft.sub(gasLeft.divn(64)) + return gasLimit.gt(gasAllowed) ? gasAllowed : gasLimit + } else { + return gasLimit + } } async function getContractStorage(runState: RunState, address: Buffer, key: Buffer) { diff --git a/packages/vm/lib/index.ts b/packages/vm/lib/index.ts index 4500104625..ceaece0fab 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 = [ + 'homestead', 'tangerineWhistle', 'spuriousDragon', 'byzantium', diff --git a/packages/vm/package.json b/packages/vm/package.json index 10b54c257f..4e181507f7 100644 --- a/packages/vm/package.json +++ b/packages/vm/package.json @@ -14,12 +14,12 @@ "coverage:test": "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=TangerineWhistle && 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=TangerineWhistle", + "test:state:allForks": "npm run test:state -- --fork=Homestead && npm run test:state -- --fork=TangerineWhistle && 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=Homestead && npm run test:state -- --fork=TangerineWhistle && 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", - "test:blockchain:allForks": "node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain --fork=TangerineWhistle && node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain --fork=SpuriousDragon && node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain --fork=Byzantium && node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain --fork=Constantinople && node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain --fork=Petersburg && node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain --fork=Istanbul && node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain --fork=MuirGlacier", + "test:blockchain:allForks": "node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain --fork=Homestead && node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain --fork=TangerineWhistle && node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain --fork=SpuriousDragon && node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain --fork=Byzantium && node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain --fork=Constantinople && node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain --fork=Petersburg && node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain --fork=Istanbul && node -r ts-node/register --stack-size=1500 ./tests/tester --blockchain --fork=MuirGlacier", "test:API": "tape -r ts-node/register --stack-size=1500 ./tests/api/**/*.js", "test:API:browser": "npm run build && karma start karma.conf.js", "test": "echo \"[INFO] Generic test cmd not used. See package.json for more specific test run cmds.\"", @@ -49,7 +49,7 @@ "@ethereumjs/blockchain": "^4.0.3", "@ethereumjs/common": "^1.5.1", "@ethereumjs/tx": "^2.1.2", - "ethereumjs-util": "^7.0.2", + "ethereumjs-util": "^7.0.4", "functional-red-black-tree": "^1.0.1", "merkle-patricia-tree": "^4.0.0", "rustbn.js": "~0.2.0", diff --git a/packages/vm/tests/tester.js b/packages/vm/tests/tester.js index 9c058ee9ec..364ad1e0a6 100755 --- a/packages/vm/tests/tester.js +++ b/packages/vm/tests/tester.js @@ -4,6 +4,7 @@ const argv = require('minimist')(process.argv.slice(2)) const tape = require('tape') const config = require('./config') const testLoader = require('./testLoader') +const Common = require('@ethereumjs/common').default function runTests() { let name @@ -96,8 +97,8 @@ function runTests() { // Tests for HFs before Istanbul have been moved under `LegacyTests/Constantinople`: // https://github.com/ethereum/tests/releases/tag/v7.0.0-beta.1 - // TODO: Replace with Common.lteHardfork('Istanbul') - if (testGetterArgs.forkConfig !== 'Istanbul') { + const common = new Common('mainnet', FORK_CONFIG_VM) + if (!common.gteHardfork('istanbul')) { name = 'LegacyTests/Constantinople/'.concat(name) } testLoader.getTestsFromArgs(