Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
Merge pull request #3520 from trufflesuite/viz
Browse files Browse the repository at this point in the history
Enhancement: The visualizer (v1: Function calls and such)
  • Loading branch information
haltman-at authored Nov 24, 2020
2 parents af6dddd + 4d1796b commit b2fa8dd
Show file tree
Hide file tree
Showing 17 changed files with 1,904 additions and 26 deletions.
6 changes: 4 additions & 2 deletions packages/codec/lib/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ export function* decodeCalldata(
let selector: string;
//first: is this a creation call?
if (isConstructor) {
allocation = allocations.constructorAllocations[contextHash].input;
allocation = (
allocations.constructorAllocations[contextHash] || { input: undefined }
).input;
} else {
//skipping any error-handling on this read, as a calldata read can't throw anyway
let rawSelector = yield* read(
Expand All @@ -92,7 +94,7 @@ export function* decodeCalldata(
);
selector = Conversion.toHexString(rawSelector);
allocation = (
allocations.functionAllocations[contextHash][selector] || {
(allocations.functionAllocations[contextHash] || {})[selector] || {
input: undefined
}
).input;
Expand Down
2 changes: 2 additions & 0 deletions packages/debugger/lib/controller/sagas/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {prefixName, isDeliberatelySkippedNodeType} from "lib/helpers";

import * as trace from "lib/trace/sagas";
import * as data from "lib/data/sagas";
import * as txlog from "lib/txlog/sagas";
import * as evm from "lib/evm/sagas";
import * as solidity from "lib/solidity/sagas";
import * as stacktrace from "lib/stacktrace/sagas";
Expand Down Expand Up @@ -310,4 +311,5 @@ export function* reset() {
yield* solidity.reset();
yield* trace.reset();
yield* stacktrace.reset();
yield* txlog.reset();
}
65 changes: 65 additions & 0 deletions packages/debugger/lib/data/sagas/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export function* decodeReturnValue() {
const contexts = yield select(data.views.contexts);
const status = yield select(data.current.returnStatus); //may be undefined
const returnAllocation = yield select(data.current.returnAllocation); //may be null
debug("returnAllocation: %O", returnAllocation);

const decoder = Codec.decodeReturndata(
{
Expand Down Expand Up @@ -165,6 +166,70 @@ export function* decodeReturnValue() {
return result.value;
}

//by default, decodes the call being made at the current step;
//if the flag is passed, instead decodes the call you're currently in
export function* decodeCall(decodeCurrent = false) {
const isCall = yield select(data.current.isCall);
const isCreate = yield select(data.current.isCreate);
if (!isCall && !isCreate && !decodeCurrent) {
return null;
}
const currentCallIsCreate = yield select(data.current.currentCallIsCreate);
const userDefinedTypes = yield select(data.views.userDefinedTypes);
let state = decodeCurrent
? yield select(data.current.state)
: yield select(data.next.state);
if (decodeCurrent && currentCallIsCreate) {
//if we want to decode the *current* call, but the current call
//is a creation, we had better pass in the code, not the calldata
state = {
...state,
calldata: state.code
};
}
const allocations = yield select(data.info.allocations);
debug("allocations: %O", allocations);
const contexts = yield select(data.views.contexts);
const context = decodeCurrent
? yield select(data.current.context)
: yield select(data.current.callContext);
const isConstructor = decodeCurrent
? yield select(data.current.currentCallIsCreate)
: isCreate;

const decoder = Codec.decodeCalldata(
{
state,
userDefinedTypes,
allocations,
contexts,
currentContext: context
},
isConstructor
);

debug("beginning decoding");
let result = decoder.next();
while (!result.done) {
debug("request received");
let request = result.value;
let response;
switch (request.type) {
//skip storage case, it won't happen here
case "code":
response = yield* requestCode(request.address);
break;
default:
debug("unrecognized request type!");
}
debug("sending response");
result = decoder.next(response);
}
//at this point, result.value holds the final value
debug("done decoding");
return result.value;
}

//NOTE: calling this *can* add a new instance, which will not
//go away on a reset! Yes, this is a little weird, but we
//decided this is OK for now
Expand Down
59 changes: 55 additions & 4 deletions packages/debugger/lib/data/selectors/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ function debuggerContextToDecoderContext(context) {
contractKind,
isConstructor,
abi: Codec.AbiData.Utils.computeSelectors(abi),
fallbackAbi: {
fallback: (abi || []).find(item => item.type === "fallback") || null,
receive: (abi || []).find(item => item.type === "receive") || null
},
payable,
compiler,
compilationId
Expand Down Expand Up @@ -885,15 +889,15 @@ const data = createSelectorTree({
}
),

/*
/**
* data.current.inModifier
*/
inModifier: createLeaf(
["./function"],
node => node && node.nodeType === "ModifierDefinition"
),

/*
/**
* data.current.inFunctionOrModifier
*/
inFunctionOrModifier: createLeaf(
Expand Down Expand Up @@ -1404,6 +1408,33 @@ const data = createSelectorTree({
return allocation.output;
}
}
),

/**
* data.current.isCall
*/
isCall: createLeaf([evm.current.step.isCall], identity),

/**
* data.current.isCreate
*/
isCreate: createLeaf([evm.current.step.isCreate], identity),

/**
* data.current.currentCallIsCreate
*/
currentCallIsCreate: createLeaf(
[evm.current.call],
call => call.binary !== undefined
),

/**
* data.current.callContext
* note that we convert to decoder context!
*/
callContext: createLeaf(
[evm.current.step.callContext],
debuggerContextToDecoderContext
)
},

Expand All @@ -1428,12 +1459,32 @@ const data = createSelectorTree({

/**
* data.next.state.returndata
* NOTE: this is only for use by returnValue(); this is *not*
* NOTE: this is only for use by decodeReturnValue(); this is *not*
* an accurate reflection of the current contents of returndata!
* we don't track that at the moment
*/
returndata: createLeaf([evm.current.step.returnValue], data =>
Codec.Conversion.toBytes(data)
),

/**
* data.next.state.calldata
* NOTE: this is only for use by decodeCall(); this is *not*
* necessarily the actual next contents of calldata!
*/
calldata: createLeaf(
[
evm.current.step.isCall,
evm.current.step.isCreate,
evm.current.step.callData,
evm.current.step.createBinary
],
(isCall, isCreate, data, binary) => {
if (!isCall && !isCreate) {
return null;
}
return Codec.Conversion.toBytes(isCall ? data : binary);
}
)
},

Expand Down Expand Up @@ -1475,7 +1526,7 @@ const data = createSelectorTree({
}
),

/*
/**
* data.next.modifierBeingInvoked
*/
modifierBeingInvoked: createLeaf(
Expand Down
6 changes: 4 additions & 2 deletions packages/debugger/lib/debugger.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Session from "./session";
import { createNestedSelector } from "reselect-tree";

import dataSelector from "./data/selectors";
import txlogSelector from "./txlog/selectors";
import astSelector from "./ast/selectors";
import traceSelector from "./trace/selectors";
import evmSelector from "./evm/selectors";
Expand All @@ -26,7 +27,7 @@ const Debugger = {
* @param {{contracts: Array<Artifact>, files: Array<String>, provider: Web3Provider, compilations: Array<Compilation>}} options -
* @return {Debugger} instance
*/
forTx: async function(txHash, options = {}) {
forTx: async function (txHash, options = {}) {
let { contracts, files, provider, compilations, lightMode } = options;
if (!compilations) {
compilations = Compilations.Utils.shimArtifacts(contracts, files);
Expand All @@ -44,7 +45,7 @@ const Debugger = {
* @param {{contracts: Array<Artifact>, files: Array<String>, provider: Web3Provider, compilations: Array<Compilation>}} options -
* @return {Debugger} instance
*/
forProject: async function(options = {}) {
forProject: async function (options = {}) {
let { contracts, files, provider, compilations, lightMode } = options;
if (!compilations) {
compilations = Compilations.Utils.shimArtifacts(contracts, files);
Expand Down Expand Up @@ -74,6 +75,7 @@ const Debugger = {
return createNestedSelector({
ast: astSelector,
data: dataSelector,
txlog: txlogSelector,
trace: traceSelector,
evm: evmSelector,
solidity: soliditySelector,
Expand Down
58 changes: 50 additions & 8 deletions packages/debugger/lib/evm/selectors/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
isShortCallMnemonic,
isDelegateCallMnemonicBroad,
isDelegateCallMnemonicStrict,
isStaticCallMnemonic
isStaticCallMnemonic,
isSelfDestructMnemonic
} from "lib/helpers";

const ZERO_WORD = "00".repeat(Codec.Evm.Utils.WORD_SIZE);
Expand Down Expand Up @@ -126,6 +127,13 @@ function createStepSelectors(step, state = null) {
*/
isCreate: createLeaf(["./trace"], step => isCreateMnemonic(step.op)),

/**
* .isSelfDestruct
*/
isSelfDestruct: createLeaf(["./trace"], step =>
isSelfDestructMnemonic(step.op)
),

/**
* .isCreate2
*/
Expand Down Expand Up @@ -319,6 +327,21 @@ function createStepSelectors(step, state = null) {
}
),

/**
* .salt
*/
salt: createLeaf(
["./isCreate2", state],

(isCreate2, { stack }) => {
if (!isCreate2) {
return null;
}

return "0x" + stack[stack.length - 4];
}
),

/**
* .callContext
*
Expand Down Expand Up @@ -540,7 +563,7 @@ const evm = createSelectorTree({
),

/**
* evm.current.step.isInstantCallOrReturn
* evm.current.step.isInstantCallOrCreate
*
* are we doing a call or create for which there are no trace steps?
* This can happen if:
Expand All @@ -564,15 +587,15 @@ const evm = createSelectorTree({
),

/**
* .isNormalHalting
* evm.current.step.isNormalHalting
*/
isNormalHalting: createLeaf(
["./isHalting", "./returnStatus"],
(isHalting, status) => isHalting && status
),

/**
* .isHalting
* evm.current.step.isHalting
*
* whether the instruction halts or returns from a calling context
* HACK: the check for stepsRemainining === 0 is a hack to cover
Expand All @@ -595,19 +618,20 @@ const evm = createSelectorTree({

/**
* evm.current.step.returnStatus
* checks the return status of the *current* halting instruction
* returns null if not halting
* checks the return status of the *current* halting instruction or insta-call
* returns null if not halting & not an insta-call
* (returns a boolean -- true for success, false for failure)
*/
returnStatus: createLeaf(
[
"./isHalting",
"./isInstantCallOrCreate",
"/next/state",
trace.stepsRemaining,
"/transaction/status"
],
(isHalting, { stack }, remaining, finalStatus) => {
if (!isHalting) {
(isHalting, isInstaCall, { stack }, remaining, finalStatus) => {
if (!isHalting && !isInstaCall) {
return null; //not clear this'll do much good since this may get
//read as false, but, oh well, may as well
}
Expand Down Expand Up @@ -670,6 +694,24 @@ const evm = createSelectorTree({
}
return stack[stack.length - 1];
}
),

/**
* evm.current.step.beneficiary
* NOTE: for a value-destroying selfdestruct, returns null
*/
beneficiary: createLeaf(
["./isSelfDestruct", "../state", "../call"],

(isSelfDestruct, { stack }, { storageAddress: currentAddress }) => {
if (!isSelfDestruct) {
return null;
}
const beneficiary = Codec.Evm.Utils.toAddress(
stack[stack.length - 1]
);
return beneficiary !== currentAddress ? beneficiary : null;
}
)
},

Expand Down
2 changes: 2 additions & 0 deletions packages/debugger/lib/session/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import configureStore from "lib/store";
import * as controller from "lib/controller/actions";
import * as actions from "./actions";
import data from "lib/data/selectors";
import txlog from "lib/txlog/selectors";
import stacktrace from "lib/stacktrace/selectors";
import session from "lib/session/selectors";
import * as dataSagas from "lib/data/sagas";
Expand Down Expand Up @@ -513,6 +514,7 @@ export default class Session {
return createNestedSelector({
ast,
data,
txlog,
trace,
evm,
solidity,
Expand Down
Loading

0 comments on commit b2fa8dd

Please sign in to comment.