This repository has been archived by the owner on Feb 26, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
index.js
334 lines (323 loc) · 12 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
import debugModule from "debug";
const debug = debugModule("debugger:txlog:sagas");
import { put, takeEvery, select } from "redux-saga/effects";
import { prefixName } from "lib/helpers";
import * as Codec from "@truffle/codec";
import * as actions from "../actions";
import { TICK } from "lib/trace/actions";
import * as trace from "lib/trace/sagas";
import * as data from "lib/data/sagas";
import txlog from "../selectors";
function* tickSaga() {
yield* updateTransactionLogSaga();
yield* trace.signalTickSagaCompletion();
}
function* updateTransactionLogSaga() {
const pointer = yield select(txlog.current.pointer); //log pointer, not AST pointer
const step = yield select(txlog.current.step);
if (yield select(txlog.current.isHalting)) {
//note that we process this case first so that it overrides the others!
const newPointer = yield select(txlog.current.externalReturnPointer);
const status = yield select(txlog.current.returnStatus);
if (status) {
if (yield select(txlog.current.isSelfDestruct)) {
const beneficiary = yield select(txlog.current.beneficiary);
//note: this selector returns null for a value-destroying selfdestruct
debug("sd: %o %o", pointer, newPointer);
yield put(actions.selfdestruct(pointer, newPointer, step, beneficiary));
} else {
const decodings = yield* data.decodeReturnValue();
const rawData = yield select(txlog.current.returnData);
debug("external return: %o %o", pointer, newPointer);
yield put(
actions.externalReturn(pointer, newPointer, step, decodings, rawData)
);
}
} else {
const error = (yield* data.decodeReturnValue())[0];
debug("revert: %o %o", pointer, newPointer);
yield put(actions.revert(pointer, newPointer, step, error));
}
} else if (yield select(txlog.current.isJump)) {
const jumpDirection = yield select(txlog.current.jumpDirection);
if (jumpDirection === "i") {
const internal = yield select(txlog.next.inInternalSourceOrYul); //don't log jumps into internal sources or Yul
if (!internal) {
//we don't do any decoding/fn identification here because that's handled by
//the function identification case
if (!(yield select(txlog.current.waitingForInternalCallToAbsorb))) {
const newPointer = yield select(txlog.current.nextActionPointer);
debug("internal call: %o %o", pointer, newPointer);
yield put(actions.internalCall(pointer, newPointer, step));
} else {
debug("absorbed call: %o", pointer);
yield put(actions.absorbedCall(pointer));
}
}
} else if (jumpDirection === "o") {
const internal = yield select(txlog.current.inInternalSourceOrYul); //don't log jumps out of internal sources or Yul
const astMatchesTxLog = yield select(
txlog.current.currentFunctionIsAsExpected
); //don't log returns from the wrong function...?
//(I've added this second check due to a strange case Amal found, hopefully this doesn't screw anything up)
if (!internal && astMatchesTxLog) {
//in this case, we have to do decoding & fn identification
const newPointer = yield select(txlog.current.internalReturnPointer);
const outputAllocations = yield select(
txlog.current.outputParameterAllocations
);
if (outputAllocations) {
const compilationId = yield select(txlog.current.compilationId);
//can't do a yield* inside a map, have to do this loop manually
let variables = [];
for (let { name, definition, pointer } of outputAllocations) {
name = name ? name : undefined; //replace "" with undefined
const decodedValue = yield* data.decode(
definition,
pointer,
compilationId
);
variables.push({ name, value: decodedValue });
}
debug("internal return: %o %o", pointer, newPointer);
yield put(
actions.internalReturn(pointer, newPointer, step, variables)
);
} else {
debug("internal return: %o %o", pointer, newPointer);
yield put(
actions.internalReturn(pointer, newPointer, step, undefined)
); //I guess?
}
}
}
} else if (yield select(txlog.current.isCall)) {
const newPointer = yield select(txlog.current.nextActionPointer);
const address = yield select(txlog.current.callAddress);
const value = yield select(txlog.current.callValue);
//distinguishing DELEGATECALL vs CALLCODE seems unnecessary here
const isDelegate = yield select(txlog.current.isDelegateCallBroad);
//we need to determine what kind of call this is.
//we'll sort them into: function, constructor, message, library
//(library is a placeholder to be replaced later)
const context = yield select(txlog.current.callContext);
const calldata = yield select(txlog.current.callData);
const instant = yield select(txlog.current.isInstantCallOrCreate);
const kind = callKind(context, calldata, instant);
const absorb = yield select(txlog.current.absorbNextInternalCall);
const decoding = yield* data.decodeCall();
if (instant) {
const status = yield select(txlog.current.returnStatus);
debug("instacall: %o %o", pointer, newPointer);
yield put(
actions.instantExternalCall(
pointer,
newPointer, //note: doesn't actually change the current pointer
step,
address,
context,
value,
isDelegate,
kind,
decoding,
calldata,
absorb,
status
)
);
} else {
debug("external call: %o %o", pointer, newPointer);
yield put(
actions.externalCall(
pointer,
newPointer,
step,
address,
context,
value,
isDelegate,
kind,
decoding,
calldata,
absorb
)
);
}
} else if (yield select(txlog.current.isCreate)) {
const newPointer = yield select(txlog.current.nextActionPointer);
const address = yield select(txlog.current.createdAddress);
const context = yield select(txlog.current.callContext);
const value = yield select(txlog.current.createValue);
const salt = yield select(txlog.current.salt); //is null for an ordinary create
const instant = yield select(txlog.current.isInstantCallOrCreate);
const binary = yield select(txlog.current.createBinary);
const decoding = yield* data.decodeCall();
if (instant) {
const status = yield select(txlog.current.returnStatus);
debug("instacreate: %o %o", pointer, newPointer);
yield put(
actions.instantCreate(
pointer,
newPointer, //note: doesn't actually change the current pointer
step,
address,
context,
value,
salt,
decoding,
binary,
status
)
);
} else {
debug("create: %o %o", pointer, newPointer);
yield put(
actions.create(
pointer,
newPointer,
step,
address,
context,
value,
salt,
decoding,
binary
)
);
}
} else if (yield select(txlog.current.isLog)) {
const decoding = (yield* data.decodeLog())[0]; //just assume first decoding is correct
//(note: because we know the event ID, there should typically only be one decoding)
const rawInfo = yield select(txlog.current.rawEventInfo);
const newPointer = yield select(txlog.current.nextActionPointer);
yield put(actions.logEvent(pointer, newPointer, step, decoding, rawInfo));
} else if (yield select(txlog.current.isStore)) {
//note: in the future this is going to get much more complicated so as to
//include decoded info and combining things...
const newPointer = yield select(txlog.current.nextActionPointer);
const rawSlot = yield select(txlog.current.rawStorageSlot);
const rawValue = yield select(txlog.current.rawStorageValue);
yield put(actions.store(pointer, newPointer, step, rawSlot, rawValue));
} else if (yield select(txlog.current.onFunctionDefinition)) {
if (yield select(txlog.current.waitingForFunctionDefinition)) {
debug("identifying");
const inputAllocations = yield select(
txlog.current.inputParameterAllocations
);
debug("inputAllocations: %O", inputAllocations);
if (inputAllocations) {
const functionNode = yield select(txlog.current.astNode);
const contractNode = yield select(txlog.current.contract);
const compilationId = yield select(txlog.current.compilationId);
//can't do a yield* inside a map, have to do this loop manually
let variables = [];
for (let { name, definition, pointer } of inputAllocations) {
const decodedValue = yield* data.decode(
definition,
pointer,
compilationId
);
variables.push({ name, value: decodedValue });
}
debug("identify: %o", pointer);
yield put(
actions.identifyFunctionCall(
pointer,
functionNode,
contractNode,
variables
)
);
}
}
}
}
function callKind(context, calldata, instant) {
if (context) {
if (context.contractKind === "library") {
return instant ? "message" : "library";
//for an instant return, just get it out of the way and set it to
//message rather than leaving it open (it'll get resolved in favor
//of message by our criteria)
} else {
const abi = context.abi;
const selector = calldata
.slice(0, 2 + 2 * Codec.Evm.Utils.SELECTOR_SIZE)
.padEnd("00", 2 + 2 * Codec.Evm.Utils.SELECTOR_SIZE);
debug("selector: %s", selector);
if (abi && selector in abi) {
return "function";
}
}
}
return "message";
}
export function* reset() {
const initialCall = yield select(txlog.transaction.initialCall);
yield put(actions.reset());
if (initialCall) {
yield put(initialCall);
}
}
export function* unload() {
yield put(actions.unloadTransaction());
}
export function* begin() {
const pointer = yield select(txlog.current.pointer);
const newPointer = yield select(txlog.current.nextActionPointer);
const origin = yield select(txlog.transaction.origin);
debug("origin: %o", pointer);
yield put(actions.recordOrigin(pointer, origin));
const {
address,
binary,
storageAddress,
value,
data: calldata
} = yield select(txlog.current.call);
const context = yield select(txlog.current.context);
//note: there was an instant check here (based on checking if there are no
//trace steps) but I took it out, because even though having no trace steps
//is essentially an insta-call, the debugger doesn't treat it that way (it
//will see the return later), so we shouldn't here either
const decoding = yield* data.decodeCall(true); //pass flag to decode *current* call
if (address) {
const kind = callKind(context, calldata, false); //no insta-calls here!
const absorb = yield select(txlog.transaction.absorbFirstInternalCall);
debug("initial call: %o %o", pointer, newPointer);
yield put(
actions.externalCall(
pointer,
newPointer,
-1, //initial call considered to happen at "step -1"
address,
context,
value,
false, //initial call is never delegate
kind,
decoding,
calldata,
absorb
)
);
} else {
debug("initial create: %o %o", pointer, newPointer);
yield put(
actions.create(
pointer,
newPointer,
-1, //initial call considered to happen at "step -1"
storageAddress,
context,
value,
null, //initial create never has salt
decoding,
binary
)
);
}
}
export function* saga() {
yield takeEvery(TICK, tickSaga);
}
export default prefixName("txlog", saga);