Skip to content

Commit 85eac11

Browse files
authored
Merge pull request #6803 from remix-project-org/debugger_part5
Debugger part5
2 parents 7f37217 + 0a89739 commit 85eac11

File tree

11 files changed

+521
-525
lines changed

11 files changed

+521
-525
lines changed

libs/remix-debug/src/solidity-decoder/internalCallTree.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,10 @@ export class InternalCallTree {
226226
debug: boolean
227227
/** get from cache */
228228
getCache: (key: string) => Promise<any>
229+
/** fn entry location */
230+
fnJumpDest: {
231+
[Key: string]: number
232+
}
229233

230234
/**
231235
* constructor
@@ -316,6 +320,7 @@ export class InternalCallTree {
316320
this.variables = {}
317321
this.symbolicStackManager.reset()
318322
this.mergedScope = {}
323+
this.fnJumpDest = {}
319324
}
320325

321326
/**
@@ -671,7 +676,6 @@ async function buildTree (tree: InternalCallTree, step, scopeId, isCreation, fun
671676
let previousValidSourceLocation = validSourceLocation || currentSourceLocation
672677
let compilationResult: CompilerAbstract
673678
let currentAddress = ''
674-
const fnJumpDest = {}
675679
while (step < tree.traceManager.trace.length) {
676680
let sourceLocation
677681
let validSourceLocation
@@ -763,7 +767,7 @@ async function buildTree (tree: InternalCallTree, step, scopeId, isCreation, fun
763767

764768
functionDefinition = await resolveFunctionDefinition(tree, sourceLocation, generatedSources, address)
765769

766-
if (!tree.scopes[scopeId].functionDefinition && stepDetail.op === 'JUMPDEST' && functionDefinition && tree.scopes[scopeId].firstStep === step - 1) {
770+
if (!tree.scopes[scopeId].functionDefinition && stepDetail.op === 'JUMPDEST' && functionDefinition && (tree.scopes[scopeId].firstStep === step - 1 || tree.scopes[scopeId].firstStep === step - 2)) {
767771
tree.scopes[scopeId].functionDefinition = functionDefinition
768772
tree.scopes[scopeId].lowLevelScope = false
769773
await registerFunctionParameters(tree, functionDefinition, step - 1, scopeId, contractObj, previousSourceLocation, address)
@@ -789,12 +793,12 @@ async function buildTree (tree: InternalCallTree, step, scopeId, isCreation, fun
789793
const internalfunctionCall = /*functionDefinition &&*/ (sourceLocation && sourceLocation.jump === 'i') /*&& functionDefinition.kind !== 'constructor'*/
790794
const isJumpOutOfFunction = /*functionDefinition &&*/ (sourceLocation && sourceLocation.jump === 'o') /*&& functionDefinition.kind !== 'constructor'*/
791795

792-
if (stepDetail.op === 'JUMP' && functionDefinition && internalfunctionCall && !fnJumpDest[functionDefinition.id]) {
796+
if (stepDetail.op === 'JUMP' && functionDefinition && internalfunctionCall && !tree.fnJumpDest[currentAddress + ' ' + functionDefinition.id]) {
793797
// record entry point for that function
794-
fnJumpDest[functionDefinition.id] = nextStepDetail && nextStepDetail.pc // JUMPDEST
798+
tree.fnJumpDest[currentAddress + ' ' + functionDefinition.id] = nextStepDetail && nextStepDetail.pc // JUMPDEST
795799
}
796800

797-
const currentStepIsFunctionEntryPoint = functionDefinition && nextStepDetail && nextStepDetail.pc === fnJumpDest[functionDefinition.id]
801+
const currentStepIsFunctionEntryPoint = functionDefinition && nextStepDetail && nextStepDetail.pc === tree.fnJumpDest[currentAddress + ' ' + functionDefinition.id]
798802
let lowLevelScope = internalfunctionCall // by default assume it's a low level scope
799803
if (isInternalTxInstrn) lowLevelScope = false
800804
if (currentStepIsFunctionEntryPoint) lowLevelScope = false
@@ -806,7 +810,7 @@ async function buildTree (tree: InternalCallTree, step, scopeId, isCreation, fun
806810
try {
807811
previousSourceLocation = null
808812
const newScopeId = scopeId === '' ? subScope.toString() : scopeId + '.' + subScope
809-
console.log('Entering new scope at step ', step, newScopeId, isInternalTxInstrn, internalfunctionCall, newSymbolicStack)
813+
console.log('Entering new scope at step ', step, newScopeId, isInternalTxInstrn, internalfunctionCall)
810814
tree.scopeStarts[step] = newScopeId
811815
const startExecutionLine = lineColumnPos && lineColumnPos.start ? lineColumnPos.start.line + 1 : undefined
812816
tree.scopes[newScopeId] = { firstStep: step, locals: {}, isCreation, gasCost: 0, startExecutionLine, functionDefinition: null, opcodeInfo: stepDetail, stackBeforeJumping: newSymbolicStack, lowLevelScope: true }
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
'use strict'
2+
import tape from 'tape'
3+
import { CompilerAbstract } from '@remix-project/remix-solidity'
4+
const compiler = require('solc')
5+
const fs = require('fs')
6+
const path = require('path')
7+
const compilerInput = require('../helpers/compilerHelper').compilerInput
8+
9+
const callTypesContract = {
10+
contract: `// SPDX-License-Identifier: MIT
11+
pragma solidity ^0.8.0;
12+
13+
// Contract A
14+
contract ContractA {
15+
ContractB public contractBAddress;
16+
ContractC public contractCAddress;
17+
18+
constructor() {
19+
contractBAddress = new ContractB();
20+
contractCAddress = new ContractC();
21+
}
22+
23+
function callContracts() public {
24+
// CALL to ContractB
25+
(bool successB, ) = address(contractBAddress).call{value: 0}(abi.encodeWithSignature("contractBFunction()"));
26+
require(successB, "Call to ContractB failed");
27+
28+
// STATICCALL to ContractC
29+
(bool successC, bytes memory resultC) = address(contractCAddress).staticcall(abi.encodeWithSignature("contractCFunction()"));
30+
require(successC, "Staticcall to ContractC failed");
31+
32+
// DELEGATECALL to ContractB
33+
(bool successD, ) = address(contractBAddress).delegatecall(abi.encodeWithSignature("contractBFunction()"));
34+
require(successD, "Delegatecall to ContractB failed");
35+
36+
// DELEGATECALL to ContractB
37+
(successD, ) = address(contractBAddress).delegatecall(abi.encodeWithSignature("contractBFunction()"));
38+
require(successD, "Delegatecall to ContractB failed");
39+
}
40+
}
41+
42+
// Contract B
43+
contract ContractB {
44+
function contractBFunction() public pure returns (string memory) {
45+
return "ContractB function called";
46+
}
47+
}
48+
49+
// Contract C
50+
contract ContractC {
51+
function contractCFunction() public pure returns (string memory) {
52+
return "ContractC function called";
53+
}
54+
}`
55+
}
56+
57+
const callTypesTest = require('./localsTests/callTypes')
58+
59+
tape('call types', function (t) {
60+
t.test('call types decoder', async function (st) {
61+
const privateKey = Buffer.from('503f38a9c967ed597e47fe25643985f032b072db8075426a92110f82df48dfcb', 'hex')
62+
await test(st, privateKey)
63+
})
64+
})
65+
66+
async function test (st, privateKey) {
67+
let output = compiler.compile(compilerInput(callTypesContract.contract))
68+
output = JSON.parse(output)
69+
const sources = {
70+
target: 'test.sol',
71+
sources: { 'test.sol': { content: callTypesContract.contract } }
72+
}
73+
const compilationResults = new CompilerAbstract('json', output, sources)
74+
console.log('callTypesTest')
75+
await callTypesTest(st, privateKey, output.contracts['test.sol']['ContractA'].evm.bytecode.object, compilationResults, callTypesContract.contract)
76+
77+
st.end()
78+
}

libs/remix-debug/test/decoder/contracts/intLocal.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ contract proxy {
1111
contract intLocal {
1212
constructor () public {
1313
proxy.testStruct memory p;
14+
p.one = 12;
15+
p.two = 14;
1416
uint8 ui8 = 130;
1517
uint16 ui16 = 456;
1618
uint32 ui32 = 4356;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
'use strict'
2+
import { NestedScope } from '@remix-project/remix-debug'
3+
import * as helper from './helper'
4+
5+
module.exports = async function (st, privateKey, contractBytecode, compilationResult, contractCode) {
6+
// Traverse the call tree to verify call types
7+
let callCount = 0
8+
let staticCallCount = 0
9+
let delegateCallCount = 0
10+
let contractBFunctionTotalCount = 0
11+
let contractCFunctionTotalCount = 0
12+
function traverseScopes(scope: NestedScope, parent?: NestedScope) {
13+
if (scope.functionDefinition && scope.functionDefinition.name && scope.functionDefinition.name.includes('contractBFunction')) contractBFunctionTotalCount++
14+
if (scope.functionDefinition && scope.functionDefinition.name && scope.functionDefinition.name.includes('contractCFunction')) contractCFunctionTotalCount++
15+
16+
if (parent && parent.opcodeInfo.op === 'CALL' && scope.functionDefinition && scope.functionDefinition.name && scope.functionDefinition.name.includes('contractBFunction')) {
17+
callCount++
18+
} else if (parent && parent.opcodeInfo.op === 'STATICCALL' && scope.functionDefinition && scope.functionDefinition.name && scope.functionDefinition.name.includes('contractCFunction')) {
19+
staticCallCount++
20+
} else if (parent && parent.opcodeInfo.op === 'DELEGATECALL' && scope.functionDefinition && scope.functionDefinition.name && scope.functionDefinition.name.includes('contractBFunction')) {
21+
delegateCallCount++
22+
}
23+
24+
if (scope.children) {
25+
scope.children.forEach(child => traverseScopes(child, scope))
26+
}
27+
}
28+
try {
29+
// Deploy the contract first (constructor deployment)
30+
const { traceManager: deployTraceManager, callTree: deployCallTree, waitForCallTree: waitForDeployCallTree } = await helper.setupDebugger(privateKey, contractBytecode, compilationResult, contractCode)
31+
32+
await waitForDeployCallTree()
33+
34+
// Now call the callContracts function
35+
const callContractsFunctionSig = '0x00dbe2a5' // callContracts()
36+
const { traceManager, callTree, waitForCallTree } = await helper.setupDebugger(
37+
privateKey,
38+
contractBytecode,
39+
compilationResult,
40+
contractCode,
41+
callContractsFunctionSig
42+
)
43+
44+
const { scopes, scopeStarts } = await waitForCallTree()
45+
46+
// Get the nested JSON representation of scopes
47+
const nestedScopes: NestedScope[] = callTree.getScopesAsNestedJSON()
48+
49+
traverseScopes(nestedScopes[0])
50+
51+
// Verify we found the expected call types
52+
st.equals(callCount, 1, 'Should find exactly one CALL to contractBFunction')
53+
st.equals(staticCallCount, 1, 'Should find exactly one STATICCALL to contractCFunction')
54+
st.equals(delegateCallCount, 2, 'Should find exactly two DELEGATECALL to contractBFunction')
55+
st.equals(contractBFunctionTotalCount, 3, 'Should find exactly two call to contractBFunction')
56+
st.equals(contractCFunctionTotalCount, 1, 'Should find exactly one call to contractCFunction')
57+
58+
} catch (error) {
59+
st.fail(error.message || error)
60+
}
61+
}
Lines changed: 20 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,28 @@
11
'use strict'
2-
3-
import deepequal from 'deep-equal'
4-
import * as sourceMappingDecoder from '../../../src/source/sourceMappingDecoder'
5-
import * as vmCall from '../../vmCall'
6-
import { TraceManager } from '../../../src/trace/traceManager'
7-
import { CodeManager } from '../../../src/code/codeManager'
8-
import { SolidityProxy } from '../../../src/solidity-decoder/solidityProxy'
9-
import { InternalCallTree } from '../../../src/solidity-decoder/internalCallTree'
10-
import { EventManager } from '../../../src/eventManager'
112
import * as helper from './helper'
123

134
module.exports = async function (st, privateKey, contractBytecode, compilationResult, contractCode) {
14-
let txHash
15-
let web3
165
try {
17-
web3 = await (vmCall as any).getWeb3()
18-
const hash = await (vmCall as any).sendTx(web3, { nonce: 0, privateKey: privateKey }, undefined, 0, contractBytecode)
19-
const receipt = await web3.getTransactionReceipt(hash)
20-
const to = receipt.contractAddress
21-
console.log('to', to)
22-
// call to level11
23-
txHash = await (vmCall as any).sendTx(web3, { nonce: 1, privateKey: privateKey }, to, 0, 'a372a595000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001520000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015400000000000000000000000000000000000000000000000000000000000000')
24-
} catch (e) {
25-
return st.fail(e)
26-
}
27-
return new Promise((resolve) => {
28-
web3.getTransaction(txHash)
29-
.then(tx => {
30-
const traceManager = new TraceManager({ web3 })
31-
const codeManager = new CodeManager(traceManager)
32-
codeManager.clear()
33-
const solidityProxy = new SolidityProxy({
34-
getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager),
35-
getCode: codeManager.getCode.bind(codeManager),
36-
compilationResult: () => compilationResult
37-
})
38-
const debuggerEvent = new EventManager()
39-
const offsetToLineColumnConverter = {
40-
offsetToLineColumn: (rawLocation) => {
41-
return new Promise((resolve) => {
42-
const lineBreaks = sourceMappingDecoder.getLinebreakPositions(contractCode)
43-
resolve(sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, lineBreaks))
44-
})
45-
}
6+
const txData = 'a372a595000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001520000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015400000000000000000000000000000000000000000000000000000000000000'
7+
8+
const { traceManager, callTree, waitForCallTree } = await helper.setupDebugger(privateKey, contractBytecode, compilationResult, contractCode, txData)
9+
10+
await waitForCallTree()
11+
12+
await helper.decodeLocals(st, 140, traceManager, callTree, function (locals) {
13+
try {
14+
const expected = {
15+
"p": { "value": "45", "type": "uint256" },
16+
"foo": { "length": "1", "value": [{ "value": "3", "type": "uint8" }], "type": "uint8[1]" },
17+
"boo": { "length": "1", "value": [{ "length": "2", "value": [{ "value": "R", "type": "string" }, { "value": "T", "type": "string" }], "type": "string[2]" }], "type": "string[2][1]" }
4618
}
47-
const callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true }, offsetToLineColumnConverter)
48-
callTree.event.register('callTreeBuildFailed', (error) => {
49-
st.fail(error)
50-
})
51-
callTree.event.register('callTreeNotReady', (reason) => {
52-
st.fail(reason)
53-
})
54-
callTree.event.register('callTreeReady', () => {
55-
helper.decodeLocals(st, 140, traceManager, callTree, function (locals) {
56-
try {
57-
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]" } }
58-
st.deepEqual(locals, expected)
59-
} catch (e) {
60-
st.fail(e.message)
61-
}
62-
resolve({})
63-
})
64-
})
19+
st.deepEqual(locals, expected)
20+
} catch (e) {
21+
st.fail(e.message)
22+
}
23+
})
6524

66-
traceManager.resolveTrace(tx).then(() => {
67-
debuggerEvent.trigger('newTraceLoaded', [traceManager.trace])
68-
}).catch((error) => {
69-
st.fail(error)
70-
})
71-
})
72-
.catch(error => st.fail(error))
73-
})
25+
} catch (error) {
26+
st.fail(error)
27+
}
7428
}

0 commit comments

Comments
 (0)