lib/evm/selectors/index.js
import debugModule from "debug";
const debug = debugModule("debugger:evm:selectors");
import { createSelectorTree, createLeaf } from "reselect-tree";
import trace from "lib/trace/selectors";
const WORD_SIZE = 0x20;
/**
* create EVM-level selectors for a given trace step selector
* may specify additional selectors to include
*/
function createStepSelectors(step, state = null) {
let base = {
/**
* .trace
*
* trace step info related to operation
*/
trace: createLeaf(
[step], ({gasCost, op, pc}) => ({gasCost, op, pc})
),
/**
* .programCounter
*/
programCounter: createLeaf(
["./trace"], (step) => step.pc
),
/**
* .isJump
*/
isJump: createLeaf(
["./trace"], (step) => (
step.op != "JUMPDEST" && step.op.indexOf("JUMP") == 0
)
),
/**
* .isCall
*
* whether the opcode will switch to another calling context
*/
isCall: createLeaf(
["./trace"], (step) => step.op == "CALL" || step.op == "DELEGATECALL"
),
/**
* .isCreate
*/
isCreate: createLeaf(
["./trace"], (step) => step.op == "CREATE"
),
/**
* .isHalting
*
* whether the instruction halts or returns from a calling context
*/
isHalting: createLeaf(
["./trace"], (step) => step.op == "STOP" || step.op == "RETURN"
)
};
if (state) {
const isRelative = (path) => (
typeof path == "string" && (
path.startsWith("./") || path.startsWith("../")
)
);
if (isRelative(state)) {
state = `../${state}`;
}
Object.assign(base, {
/**
* .callAddress
*
* address transferred to by call operation
*/
callAddress: createLeaf(
["./isCall", "./trace", state],
(matches, step, {stack}) => {
if (!matches) return null;
let address = stack[stack.length - 2]
address = "0x" + address.substring(24);
return address;
}
),
/**
* .createBinary
*
* binary code to execute via create operation
*/
createBinary: createLeaf(
["./isCreate", "./trace", state],
(matches, step, {stack, memory}) => {
if (!matches) return null;
// Get the code that's going to be created from memory.
// Note we multiply by 2 because these offsets are in bytes.
const offset = parseInt(stack[stack.length - 2], 16) * 2;
const length = parseInt(stack[stack.length - 3], 16) * 2;
return "0x" + memory.join("").substring(offset, offset + length);
}
)
});
}
return base;
}
const evm = createSelectorTree({
/**
* evm.state
*/
state: (state) => state.evm,
/**
* evm.info
*/
info: {
/**
* evm.info.contexts
*/
contexts: createLeaf(['/state'], (state) => state.info.contexts.byContext),
/**
* evm.info.instances
*/
instances: createLeaf(['/state'], (state) => state.info.instances.byAddress),
/**
* evm.info.binaries
*/
binaries: {
_: createLeaf(['/state'], (state) => state.info.contexts.byBinary),
/**
* evm.info.binaries.search
*
* returns function (binary) => context
*/
search: createLeaf(['./_'], (binaries) => {
// HACK ignore link references for search
// link references come in two forms: with underscores or all zeroes
// the underscore format is used by Truffle to reference links by name
// zeroes are used by solc directly, as libraries inject their own
// address at CREATE-time
const toRegExp = (binary) =>
new RegExp(`^${binary.replace(/__.{38}|0{40}/g, ".{40}")}`)
let matchers = Object.entries(binaries)
.map( ([binary, {context}]) => ({
context,
regex: toRegExp(binary)
}))
return (binary) => matchers
.filter( ({ context, regex }) => binary.match(regex) )
.map( ({ context }) => ({ context }) )
[0] || null;
})
}
},
/**
* evm.current
*/
current: {
/**
* evm.current.callstack
*/
callstack: (state) => state.evm.proc.callstack,
/**
* evm.current.call
*/
call: createLeaf(
["./callstack"],
(stack) => stack.length ? stack[stack.length - 1] : {}
),
/**
* evm.current.context
*/
context: createLeaf(
["./call", "/info/instances", "/info/binaries/search", "/info/contexts"],
({address, binary}, instances, search, contexts) => {
let record;
if (address) {
record = instances[address];
binary = record.binary
} else {
record = search(binary);
}
let context = contexts[(record || {}).context];
return {
...context,
binary
}
}
),
/**
* evm.current.state
*
* evm state info: as of last operation, before op defined in step
*/
state: Object.assign({}, ...(
[
"depth",
"error",
"gas",
"memory",
"stack",
"storage"
].map( (param) => ({
[param]: createLeaf([trace.step], (step) => step[param])
}))
)),
/**
* evm.current.step
*/
step: createStepSelectors(trace.step, "./state")
},
/**
* evm.next
*/
next: {
/**
* evm.next.state
*
* evm state as a result of next step operation
*/
state: Object.assign({}, ...(
[
"depth",
"error",
"gas",
"memory",
"stack",
"storage"
].map( (param) => ({
[param]: createLeaf([trace.next], (step) => step[param])
}))
)),
step: createStepSelectors(trace.next, "./state")
}
});
export default evm;