@ark-us/evm2wasm
Version:
This is a JS protope of a EVM to eWASM transcompiler
176 lines (154 loc) • 5.76 kB
JavaScript
const argv = require('minimist')(process.argv.slice(2))
const tape = require('tape')
const ethUtil = require('ethereumjs-util')
const testing = require('ethereumjs-testing')
const Kernel = require('ewasm-kernel')
const Environment = require('ewasm-kernel/environment.js')
const Address = require('ewasm-kernel/deps/address.js')
const U256 = require('ewasm-kernel/deps/u256.js')
const evm2wasm = require('../index.js')
const Interface = require('ewasm-kernel/EVMimports')
const DebugInterface = require('ewasm-kernel/debugInterface')
const skipList = [
// slow performance tests
'loop-mul',
'loop-add-10M',
'loop-divadd-10M',
'loop-divadd-unr100-10M',
'loop-exp-16b-100k',
'loop-exp-1b-1M',
'loop-exp-2b-100k',
'loop-exp-32b-100k',
'loop-exp-4b-100k',
'loop-exp-8b-100k',
'loop-exp-nop-1M',
'loop-mulmod-2M',
// these failures might be due to evm2wasm
// TODO: fix these
'JDfromStorageDynamicJump0_jumpdest0',
'JDfromStorageDynamicJump0_jumpdest2',
'JDfromStorageDynamicJumpi1',
'JDfromStorageDynamicJumpiAfterStop',
// failures probably due to broken ewasm-kernel
'coinbase',
'calldatacopy_DataIndexTooHigh',
'calldatacopy_DataIndexTooHigh_return',
'201503102037PYTHON',
'201503102148PYTHON',
'201503102300PYTHON',
'201503120547PYTHON',
'CallToPrecompiledContract'
]
async function runner (testName, testData, t) {
const code = Buffer.from(testData.exec.code.slice(2), 'hex')
const {
buffer: evm
} = await evm2wasm.evm2wasm(code, {
stackTrace: argv.trace,
useAsyncAPI: true,
testName: testName,
inlineOps: true
})
const environment = setupEnviroment(testData)
let instance
let vmExceptionErr = null
try {
const kernel = new Kernel({
code: evm,
interfaces: [Interface, DebugInterface]
})
instance = await kernel.run(environment)
} catch (e) {
vmExceptionErr = e
} finally {
await checkResults(testData, t, instance, environment, vmExceptionErr)
}
}
function setupEnviroment (testData) {
const env = new Environment()
env.gasLeft = parseInt(testData.exec.gas.slice(2), 16)
env.callData = new Uint8Array(Buffer.from(testData.exec.data.slice(2), 'hex'))
env.gasPrice = ethUtil.bufferToInt(Buffer.from(testData.exec.gasPrice.slice(2), 'hex'))
env.address = new Address(testData.exec.address)
env.caller = new Address(testData.exec.caller)
env.origin = new Address(testData.exec.origin)
env.value = new U256(testData.exec.value)
env.callValue = new U256(testData.exec.value)
env.code = new Uint8Array(Buffer.from(testData.exec.code.slice(2), 'hex'))
// setup block
env.block.header.number = testData.env.currentNumber
env.block.header.coinbase = Buffer.from(testData.env.currentCoinbase.slice(2), 'hex')
env.block.header.difficulty = testData.env.currentDifficulty
env.block.header.gasLimit = Buffer.from(testData.env.currentGasLimit.slice(2), 'hex')
env.block.header.number = Buffer.from(testData.env.currentNumber.slice(2), 'hex')
env.block.header.timestamp = Buffer.from(testData.env.currentTimestamp.slice(2), 'hex')
env.state = {}
for (let address in testData.pre) {
const testAccount = testData.pre[address]
let envAccount = {
code: testAccount.code,
balance: testAccount.balance,
storage: testAccount.storage
}
env.state[address] = envAccount
}
return env
}
async function checkResults (testData, t, instance, environment, vmExceptionErr) {
// https://github.com/ethereum/tests/wiki/VM-Tests
// > It is generally expected that the test implementer will read env, exec
// and pre then check their results against gas, logs, out, post and
// callcreates. If an exception is expected, then latter sections are absent
// in the test.
if ((testData.gas && testData.out && testData.post)) {
if (vmExceptionErr) {
t.fail('should not have VM exception')
console.log('vmExceptionErr:', vmExceptionErr)
}
} else {
// if any of the expected fields are missing then a VM exception is expected
t.true(vmExceptionErr, 'should have VM exception')
}
if (testData.gas) {
t.equals(ethUtil.intToHex(environment.gasLeft), testData.gas, 'should have the correct gas')
}
// check return value
if (testData.out) {
t.equals(Buffer.from(environment.returnValue).toString('hex'), testData.out.slice(2), 'return value')
}
// check storage
if (testData.post) {
const expectedAccount = testData.post[testData.exec.address]
// TODO: check all accounts
if (expectedAccount) {
const expectedStorage = expectedAccount.storage
if (expectedStorage) {
for (let key in expectedStorage) {
const keyHex = (new U256(key)).toString(16)
// pad values to get consistent hex strings for comparison
let expectedValue = ethUtil.setLengthLeft(ethUtil.toBuffer(expectedStorage[key]), 32)
expectedValue = '0x' + expectedValue.toString('hex')
let actualValue = environment.state[testData.exec.address]['storage'][keyHex]
if (actualValue) {
actualValue = '0x' + ethUtil.setLengthLeft(ethUtil.toBuffer(actualValue), 32).toString('hex')
} else {
actualValue = '0x' + ethUtil.setLengthLeft(ethUtil.toBuffer(0), 32).toString('hex')
}
t.equals(actualValue, expectedValue, `should have correct storage value at key ${key}`)
}
}
}
}
}
let testGetterArgs = {}
testGetterArgs.skipVM = skipList
tape('VMTESTS', t => {
testing.getTestsFromArgs('VMTests', (fileName, testName, tests) => {
t.comment(fileName + ' ' + testName)
return runner(testName, tests, t).catch(err => {
t.fail(err)
})
}, testGetterArgs).then(() => {
t.end()
})
})