Unverified Commit 0553a65a authored by yann300's avatar yann300 Committed by GitHub

Merge pull request #1192 from ethereum/fixDecodingCallData

Fix nested array calldata decoding
parents d0f0b7fe 2e8787ae
......@@ -52,27 +52,45 @@ export class RefType {
offset = parseInt(offset, 16)
return this.decodeFromMemoryInternal(offset, memory, cursor)
} else if (this.isInCallData()) {
calldata = calldata.length > 0 ? calldata[0] : '0x'
const ethersAbi = new ethers.utils.Interface(variableDetails.abi)
const fnSign = calldata.substr(0, 10)
const decodedData = ethersAbi.decodeFunctionData(ethersAbi.getFunction(fnSign), calldata)
let decodedValue = decodedData[variableDetails.name]
const isArray = Array.isArray(decodedValue)
if (isArray) {
decodedValue = decodedValue.map((el) => {
return {
value: el.toString(),
type: this.underlyingType.typeName
}
})
}
return this._decodeFromCallData(variableDetails, calldata)
} else {
return { error: '<decoding failed - no decoder for ' + this.location + '>', type: this.typeName }
}
}
_decodeFromCallData (variableDetails, calldata) {
calldata = calldata.length > 0 ? calldata[0] : '0x'
const ethersAbi = new ethers.utils.Interface(variableDetails.abi)
const fnSign = calldata.substr(0, 10)
const decodedData = ethersAbi.decodeFunctionData(ethersAbi.getFunction(fnSign), calldata)
const decodedValue = decodedData[variableDetails.name]
const isArray = Array.isArray(decodedValue)
if (isArray) {
return this._decodeCallDataArray(decodedValue, this)
}
return {
length: isArray ? '0x' + decodedValue.length.toString(16) : undefined,
value: decodedValue,
type: this.typeName
}
}
_decodeCallDataArray (value, type) {
const isArray = Array.isArray(value)
if (isArray) {
value = value.map((el) => {
return this._decodeCallDataArray(el, this.underlyingType)
})
return {
length: Array.isArray(decodedValue) ? '0x' + decodedValue.length.toString(16) : undefined,
value: decodedValue,
type: this.typeName
length: value.length.toString(16),
value: value,
type: type.typeName
}
} else {
return { error: '<decoding failed - no decoder for ' + this.location + '>', type: this.typeName }
return {
value: value.toString(),
type: (type.underlyingType && type.underlyingType.typeName) || type.typeName
}
}
}
......
'use strict'
module.exports = {
contract: `
pragma experimental ABIEncoderV2;
contract calldataLocal {
constructor () public {
}
function level11(uint8[1] calldata foo, string[2][1] calldata boo) public {
uint p = 45;
}
}
`}
......@@ -4,37 +4,40 @@ var compiler = require('solc')
var intLocal = require('./contracts/intLocal')
var miscLocal = require('./contracts/miscLocal')
var structArrayLocal = require('./contracts/structArrayLocal')
var calldataLocal = require('./contracts/calldata')
var vmCall = require('./vmCall')
var intLocalTest = require('./localsTests/int')
var miscLocalTest = require('./localsTests/misc')
var misc2LocalTest = require('./localsTests/misc2')
var structArrayLocalTest = require('./localsTests/structArray')
var calldataLocalTest = require('./localsTests/calldata')
var compilerInput = require('../helpers/compilerHelper').compilerInput
tape('solidity', function (t) {
t.test('local decoder', async function (st) {
var privateKey = Buffer.from('dae9801649ba2d95a21e688b56f77905e5667c44ce868ec83f82e838712a2c7a', 'hex')
var vm = await vmCall.initVM(st, privateKey)
test(st, vm, privateKey)
await test(st, vm, privateKey)
})
})
function test (st, vm, privateKey) {
async function test (st, vm, privateKey) {
var output = compiler.compile(compilerInput(intLocal.contract))
output = JSON.parse(output)
intLocalTest(st, vm, privateKey, output.contracts['test.sol']['intLocal'].evm.bytecode.object, output, function () {
output = compiler.compile(compilerInput(miscLocal.contract))
output = JSON.parse(output)
miscLocalTest(st, vm, privateKey, output.contracts['test.sol']['miscLocal'].evm.bytecode.object, output, function () {
output = compiler.compile(compilerInput(miscLocal.contract))
output = JSON.parse(output)
misc2LocalTest(st, vm, privateKey, output.contracts['test.sol']['miscLocal2'].evm.bytecode.object, output, function () {
output = compiler.compile(compilerInput(structArrayLocal.contract))
output = JSON.parse(output)
structArrayLocalTest(st, vm, privateKey, output.contracts['test.sol']['structArrayLocal'].evm.bytecode.object, output, function () {
st.end()
})
})
})
})
await intLocalTest(st, vm, privateKey, output.contracts['test.sol']['intLocal'].evm.bytecode.object, output)
output = compiler.compile(compilerInput(miscLocal.contract))
output = JSON.parse(output)
await miscLocalTest(st, vm, privateKey, output.contracts['test.sol']['miscLocal'].evm.bytecode.object, output)
output = compiler.compile(compilerInput(miscLocal.contract))
output = JSON.parse(output)
await misc2LocalTest(st, vm, privateKey, output.contracts['test.sol']['miscLocal2'].evm.bytecode.object, output)
output = compiler.compile(compilerInput(structArrayLocal.contract))
output = JSON.parse(output)
await structArrayLocalTest(st, vm, privateKey, output.contracts['test.sol']['structArrayLocal'].evm.bytecode.object, output)
output = compiler.compile(compilerInput(calldataLocal.contract))
output = JSON.parse(output)
await calldataLocalTest(st, vm, privateKey, output.contracts['test.sol']['calldataLocal'].evm.bytecode.object, output)
st.end()
}
'use strict'
import deepequal from 'deep-equal'
import { sendTx } from '../vmCall'
import { TraceManager } from '../../../src/trace/traceManager'
import { CodeManager } from '../../../src/code/codeManager'
import { SolidityProxy } from '../../../src/solidity-decoder/solidityProxy'
import { InternalCallTree } from '../../../src/solidity-decoder/internalCallTree'
import { EventManager } from '../../../src/eventManager'
import * as helper from './helper'
module.exports = async function (st, vm, privateKey, contractBytecode, compilationResult) {
let txHash
try {
let data = await sendTx(vm, { nonce: 0, privateKey: privateKey }, null, 0, contractBytecode)
const to = (data as any).result.createdAddress.toString()
// call to level11
data = await sendTx(vm, { nonce: 1, privateKey: privateKey }, to, 0, 'a372a595000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001520000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015400000000000000000000000000000000000000000000000000000000000000')
txHash = (data as any).hash
} catch (e) {
return st.fail(e)
}
return new Promise((resolve) => {
vm.web3.eth.getTransaction(txHash, function (error, tx) {
if (error) {
return st.fail(error)
}
var traceManager = new TraceManager({ web3: vm.web3 })
var codeManager = new CodeManager(traceManager)
codeManager.clear()
var solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager), getCode: codeManager.getCode.bind(codeManager) })
solidityProxy.reset(compilationResult)
var debuggerEvent = new EventManager()
var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true })
callTree.event.register('callTreeBuildFailed', (error) => {
st.fail(error)
})
callTree.event.register('callTreeNotReady', (reason) => {
st.fail(reason)
})
callTree.event.register('callTreeReady', (scopes, scopeStarts) => {
helper.decodeLocals(st, 140, traceManager, callTree, function (locals) {
try {
const expected = {"p":{"value":"45","type":"uint256"},"foo":{"length":"1","value":[{"value":"3","type":"uint8"}],"type":"uint8[1]"},"boo":{"length":"1","value":[{"length":"2","value":[{"value":"R","type":"string"},{"value":"T","type":"string"}],"type":"string[2]"}],"type":"string[2][1]"}}
st.deepEqual(locals, expected)
} catch (e) {
st.fail(e.message)
}
resolve({})
})
})
traceManager.resolveTrace(tx).then(() => {
debuggerEvent.trigger('newTraceLoaded', [traceManager.trace])
}).catch((error) => {
st.fail(error)
})
})
})
}
\ No newline at end of file
......@@ -8,66 +8,69 @@ import * as helper from './helper'
import { TraceManager } from '../../../src/trace/traceManager'
import { CodeManager } from '../../../src/code/codeManager'
module.exports = function (st, vm, privateKey, contractBytecode, compilationResult, cb) {
sendTx(vm, { nonce: 0, privateKey: privateKey }, null, 0, contractBytecode, function (error, txHash) {
if (error) {
return st.fail(error)
}
vm.web3.eth.getTransaction(txHash, function (error, tx) {
module.exports = function (st, vm, privateKey, contractBytecode, compilationResult) {
return new Promise((resolve) => {
sendTx(vm, { nonce: 0, privateKey: privateKey }, null, 0, contractBytecode, function (error, data) {
if (error) {
return st.fail(error)
}
tx.to = contractCreationToken('0')
var traceManager = new TraceManager({ web3: vm.web3 })
var codeManager = new CodeManager(traceManager)
codeManager.clear()
var solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager), getCode: codeManager.getCode.bind(codeManager) })
solidityProxy.reset(compilationResult)
var debuggerEvent = new EventManager()
var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true })
callTree.event.register('callTreeBuildFailed', (error) => {
st.fail(error)
})
callTree.event.register('callTreeReady', (scopes, scopeStarts) => {
helper.decodeLocals(st, 70, traceManager, callTree, function (locals) {
try {
st.equals(locals['boolFalse'].value, false)
st.equals(locals['boolTrue'].value, true)
st.equals(locals['testEnum'].value, 'three')
st.equals(locals['sender'].value, '0x4B0897B0513FDC7C541B6D9D7E929C4E5364D2DB')
st.equals(locals['_bytes1'].value, '0x99')
st.equals(locals['__bytes1'].value, '0x99')
st.equals(locals['__bytes2'].value, '0x99AB')
st.equals(locals['__bytes4'].value, '0x99FA0000')
st.equals(locals['__bytes6'].value, '0x990000000000')
st.equals(locals['__bytes7'].value, '0x99356700000000')
st.equals(locals['__bytes8'].value, '0x99ABD41700000000')
st.equals(locals['__bytes9'].value, '0x99156744AF00000000')
st.equals(locals['__bytes13'].value, '0x99123423425300000000000000')
st.equals(locals['__bytes16'].value, '0x99AFAD23432400000000000000000000')
st.equals(locals['__bytes24'].value, '0x99AFAD234324000000000000000000000000000000000000')
st.equals(locals['__bytes32'].value, '0x9999ABD41799ABD4170000000000000000000000000000000000000000000000')
st.equals(Object.keys(locals).length, 16)
} catch (e) {
st.fail(e.message)
}
const txHash = data.hash
vm.web3.eth.getTransaction(txHash, function (error, tx) {
if (error) {
return st.fail(error)
}
tx.to = contractCreationToken('0')
var traceManager = new TraceManager({ web3: vm.web3 })
var codeManager = new CodeManager(traceManager)
codeManager.clear()
var solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager), getCode: codeManager.getCode.bind(codeManager) })
solidityProxy.reset(compilationResult)
var debuggerEvent = new EventManager()
var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true })
callTree.event.register('callTreeBuildFailed', (error) => {
st.fail(error)
})
helper.decodeLocals(st, 7, traceManager, callTree, function (locals) {
try {
// st.equals(Object.keys(locals).length, 0)
st.equals(0, 0)
} catch (e) {
st.fail(e.message)
}
cb()
callTree.event.register('callTreeReady', (scopes, scopeStarts) => {
helper.decodeLocals(st, 70, traceManager, callTree, function (locals) {
try {
st.equals(locals['boolFalse'].value, false)
st.equals(locals['boolTrue'].value, true)
st.equals(locals['testEnum'].value, 'three')
st.equals(locals['sender'].value, '0x4B0897B0513FDC7C541B6D9D7E929C4E5364D2DB')
st.equals(locals['_bytes1'].value, '0x99')
st.equals(locals['__bytes1'].value, '0x99')
st.equals(locals['__bytes2'].value, '0x99AB')
st.equals(locals['__bytes4'].value, '0x99FA0000')
st.equals(locals['__bytes6'].value, '0x990000000000')
st.equals(locals['__bytes7'].value, '0x99356700000000')
st.equals(locals['__bytes8'].value, '0x99ABD41700000000')
st.equals(locals['__bytes9'].value, '0x99156744AF00000000')
st.equals(locals['__bytes13'].value, '0x99123423425300000000000000')
st.equals(locals['__bytes16'].value, '0x99AFAD23432400000000000000000000')
st.equals(locals['__bytes24'].value, '0x99AFAD234324000000000000000000000000000000000000')
st.equals(locals['__bytes32'].value, '0x9999ABD41799ABD4170000000000000000000000000000000000000000000000')
st.equals(Object.keys(locals).length, 16)
} catch (e) {
st.fail(e.message)
}
})
helper.decodeLocals(st, 7, traceManager, callTree, function (locals) {
try {
// st.equals(Object.keys(locals).length, 0)
st.equals(0, 0)
} catch (e) {
st.fail(e.message)
}
resolve({})
})
})
traceManager.resolveTrace(tx).then(() => {
debuggerEvent.trigger('newTraceLoaded', [traceManager.trace])
}).catch((error) => {
st.fail(error)
})
})
traceManager.resolveTrace(tx).then(() => {
debuggerEvent.trigger('newTraceLoaded', [traceManager.trace])
}).catch((error) => {
st.fail(error)
})
})
})
})
}
......@@ -8,52 +8,55 @@ import * as helper from './helper'
import { TraceManager } from '../../../src/trace/traceManager'
import { CodeManager } from '../../../src/code/codeManager'
module.exports = function (st, vm, privateKey, contractBytecode, compilationResult, cb) {
sendTx(vm, { nonce: 0, privateKey: privateKey }, null, 0, contractBytecode, function (error, txHash) {
if (error) {
return st.fail(error)
}
vm.web3.eth.getTransaction(txHash, function (error, tx) {
module.exports = function (st, vm, privateKey, contractBytecode, compilationResult) {
return new Promise((resolve) => {
sendTx(vm, { nonce: 0, privateKey: privateKey }, null, 0, contractBytecode, function (error, data) {
if (error) {
return st.fail(error)
}
tx.to = contractCreationToken('0')
var traceManager = new TraceManager({ web3: vm.web3 })
var codeManager = new CodeManager(traceManager)
codeManager.clear()
var solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager), getCode: codeManager.getCode.bind(codeManager) })
solidityProxy.reset(compilationResult)
var debuggerEvent = new EventManager()
var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true })
callTree.event.register('callTreeBuildFailed', (error) => {
st.fail(error)
})
callTree.event.register('callTreeReady', (scopes, scopeStarts) => {
helper.decodeLocals(st, 49, traceManager, callTree, function (locals) {
try {
st.equals(locals['dynbytes'].value, '0x64796e616d69636279746573')
st.equals(locals['smallstring'].value, 'test_test_test')
st.equals(Object.keys(locals).length, 2)
} catch (e) {
st.fail(e.message)
}
const txHash = data.hash
vm.web3.eth.getTransaction(txHash, function (error, tx) {
if (error) {
return st.fail(error)
}
tx.to = contractCreationToken('0')
var traceManager = new TraceManager({ web3: vm.web3 })
var codeManager = new CodeManager(traceManager)
codeManager.clear()
var solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager), getCode: codeManager.getCode.bind(codeManager) })
solidityProxy.reset(compilationResult)
var debuggerEvent = new EventManager()
var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true })
callTree.event.register('callTreeBuildFailed', (error) => {
st.fail(error)
})
helper.decodeLocals(st, 7, traceManager, callTree, function (locals) {
try {
// st.equals(Object.keys(locals).length, 0)
st.equals(0, 0)
} catch (e) {
st.fail(e.message)
}
cb()
callTree.event.register('callTreeReady', (scopes, scopeStarts) => {
helper.decodeLocals(st, 49, traceManager, callTree, function (locals) {
try {
st.equals(locals['dynbytes'].value, '0x64796e616d69636279746573')
st.equals(locals['smallstring'].value, 'test_test_test')
st.equals(Object.keys(locals).length, 2)
} catch (e) {
st.fail(e.message)
}
})
helper.decodeLocals(st, 7, traceManager, callTree, function (locals) {
try {
// st.equals(Object.keys(locals).length, 0)
st.equals(0, 0)
} catch (e) {
st.fail(e.message)
}
resolve({})
})
})
traceManager.resolveTrace(tx).then(() => {
debuggerEvent.trigger('newTraceLoaded', [traceManager.trace])
}).catch((error) => {
st.fail(error)
})
})
traceManager.resolveTrace(tx).then(() => {
debuggerEvent.trigger('newTraceLoaded', [traceManager.trace])
}).catch((error) => {
st.fail(error)
})
})
})
})
}
......@@ -12,11 +12,12 @@ module.exports = async function testMappingStorage (st, cb) {
var vm = await initVM(st, privateKey)
var output = compile(compilerInput(mappingStorage.contract))
output = JSON.parse(output)
sendTx(vm, {nonce: 0, privateKey: privateKey}, null, 0, output.contracts['test.sol']['SimpleMappingState'].evm.bytecode.object, function (error, txHash) {
sendTx(vm, {nonce: 0, privateKey: privateKey}, null, 0, output.contracts['test.sol']['SimpleMappingState'].evm.bytecode.object, function (error, data) {
if (error) {
console.log(error)
st.end(error)
} else {
const txHash = data.hash
vm.web3.eth.getTransaction(txHash, (error, tx) => {
if (error) {
console.log(error)
......@@ -31,12 +32,12 @@ module.exports = async function testMappingStorage (st, cb) {
function testMapping (st, vm, privateKey, contractAddress, output, cb) {
sendTx(vm, {nonce: 1, privateKey: privateKey}, contractAddress, 0, '2fd0a83a00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001074686973206973206120737472696e6700000000000000000000000000000000',
function (error, txHash) {
function (error, data) {
if (error) {
console.log(error)
st.end(error)
} else {
console.log(txHash)
const txHash = data.hash
vm.web3.eth.getTransaction(txHash, (error, tx) => {
if (error) {
console.log(error)
......
......@@ -6,36 +6,42 @@ import { vm as remixlibVM } from '@remix-project/remix-lib'
import VM from '@ethereumjs/vm'
import Common from '@ethereumjs/common'
export function sendTx (vm, from, to, value, data, cb) {
var tx = new Tx({
nonce: new BN(from.nonce++),
gasPrice: new BN(1),
gasLimit: new BN(3000000, 10),
to: to,
value: new BN(value, 10),
data: Buffer.from(data, 'hex')
})
tx = tx.sign(from.privateKey)
var block = Block.fromBlockData({
header: {
timestamp: new Date().getTime() / 1000 | 0,
number: 0
}
}) // still using default common
try {
vm.runTx({block: block, tx: tx, skipBalance: true, skipNonce: true}).then(function (result) {
setTimeout(() => {
cb(null, bufferToHex(tx.hash()))
}, 500)
}).catch((error) => {
console.error(error)
cb(error)
export function sendTx (vm, from, to, value, data, cb?) {
cb = cb || (() => {})
return new Promise ((resolve, reject) => {
var tx = new Tx({
nonce: new BN(from.nonce++),
gasPrice: new BN(1),
gasLimit: new BN(3000000, 10),
to: to,
value: new BN(value, 10),
data: Buffer.from(data, 'hex')
})
} catch (e) {
console.error(e)
}
tx = tx.sign(from.privateKey)
var block = Block.fromBlockData({
header: {
timestamp: new Date().getTime() / 1000 | 0,
number: 0
}
}) // still using default common
try {
vm.runTx({block: block, tx: tx, skipBalance: true, skipNonce: true}).then(function (result) {
setTimeout(() => {
const hash = bufferToHex(tx.hash())
cb(null, { hash, result })
resolve({ hash, result })
}, 500)
}).catch((error) => {
console.error(error)
cb(error)
reject(error)
})
} catch (e) {
console.error(e)
}
})
}
async function createVm (hardfork) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment