1+ 'use strict'
2+ import { NestedScope } from '@remix-project/remix-debug'
3+ import * as helper from './helper'
4+ const { ethers } = require ( 'ethers' )
5+
6+ module . exports = async function ( st , privateKey , contractBytecode , compilationResult , contractCode ) {
7+ st . plan ( 26 )
8+ const enableCtorTest = false
9+ // Test scenarios with expected parameter values
10+ const testCases = [
11+ {
12+ name : 'directCall' ,
13+ signature : 'directCall(uint256,string)' ,
14+ params : [ 123 , 'DirectTest' ] ,
15+ description : 'Direct function call parameters'
16+ } ,
17+ {
18+ name : 'internalCallTest' ,
19+ signature : 'internalCallTest(uint256,string)' ,
20+ params : [ 456 , 'InternalTest' ] ,
21+ description : 'Internal call parameters'
22+ } ,
23+ {
24+ name : 'thisCallTest' ,
25+ signature : 'thisCallTest(uint256,string)' ,
26+ params : [ 789 , 'ThisTest' ] ,
27+ description : 'This.function() call parameters'
28+ } ,
29+ {
30+ name : 'callTest' ,
31+ signature : 'callTest(uint256,string)' ,
32+ params : [ 111 , 'CallTest' ] ,
33+ description : 'CALL operation parameters'
34+ } ,
35+ {
36+ name : 'staticCallTest' ,
37+ signature : 'staticCallTest(uint256,string)' ,
38+ params : [ 222 , 'StaticTest' ] ,
39+ description : 'STATICCALL operation parameters'
40+ } ,
41+ {
42+ name : 'delegateCallTest' ,
43+ signature : 'delegateCallTest(uint256,string)' ,
44+ params : [ 333 , 'DelegateTest' ] ,
45+ description : 'DELEGATECALL operation parameters'
46+ } ,
47+ {
48+ name : 'createTest' ,
49+ signature : 'createTest(uint256,string)' ,
50+ params : [ 444 , 'CreateTest' ] ,
51+ description : 'CREATE operation parameters'
52+ } ,
53+ {
54+ name : 'create2Test' ,
55+ signature : 'create2Test(uint256,string,bytes32)' ,
56+ params : [ 555 , 'Create2Test' , '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' ] ,
57+ description : 'CREATE2 operation parameters'
58+ }
59+ ]
60+
61+ // Helper function to encode parameters
62+ function encodeParams ( signature : string , params : any [ ] ) : string {
63+ // Use ethers interface to encode function call
64+ const iface = new ethers . Interface ( [ `function ${ signature } ` ] )
65+ const functionName = signature . split ( '(' ) [ 0 ]
66+ return iface . encodeFunctionData ( functionName , params )
67+ }
68+
69+ // Helper function to find scope by function name and get its firstStep
70+ function findFunctionScope ( nestedScopes : NestedScope [ ] , functionName : string ) : { scope : NestedScope , firstStep : number } | null {
71+ let foundDuplicate = false // TODO this needs to be fixed, there are always 2 nodes for a single function call
72+ function traverse ( scope : NestedScope , parentFirstStep ?: number ) : { scope : NestedScope , firstStep : number } | null {
73+ // Check if this scope matches our function
74+ if ( scope . functionDefinition ?. name === functionName || scope . functionDefinition ?. kind === functionName ) {
75+ const firstStep = scope . firstStep || parentFirstStep || 0
76+ return { scope, firstStep }
77+ }
78+
79+ // Look for nested function calls within this scope
80+ if ( scope . children ) {
81+ for ( const child of scope . children ) {
82+ const result = traverse ( child , scope . firstStep || parentFirstStep )
83+ if ( result && foundDuplicate ) return result
84+ if ( result ) foundDuplicate = true
85+ }
86+ }
87+
88+ return null
89+ }
90+
91+ for ( const rootScope of nestedScopes ) {
92+ const result = traverse ( rootScope )
93+ if ( result ) return result
94+ }
95+
96+ return null
97+ }
98+
99+ try {
100+ if ( enableCtorTest ) {
101+ // First deploy the contract (constructor with inheritance test)
102+ console . log ( 'Deploying contract with constructor parameters...' )
103+ const constructorParams = [ 42 , 'ConstructorTest' ]
104+ const constructorData = ethers . AbiCoder . defaultAbiCoder ( ) . encode ( [ 'uint256' , 'string' ] , constructorParams )
105+ const deployBytecode = contractBytecode + constructorData . slice ( 2 ) // Remove 0x prefix
106+
107+ const { traceManager : deployTraceManager , callTree : deployCallTree , waitForCallTree : waitForDeployCallTree } =
108+ await helper . setupDebugger ( privateKey , deployBytecode , compilationResult , contractCode )
109+
110+ const { scopes : deployScopes , scopeStarts : deployScopeStarts } = await waitForDeployCallTree ( )
111+ const deployNestedScopes : NestedScope [ ] = deployCallTree . getScopesAsNestedJSON ( )
112+
113+ console . log ( deployNestedScopes )
114+
115+ // Test inherited constructor parameters
116+ const baseConstructorScope = findFunctionScope ( deployNestedScopes , 'constructor' )
117+ if ( baseConstructorScope ) {
118+ console . log ( `Testing constructor parameters at step ${ baseConstructorScope . firstStep } ` )
119+
120+ const symbolicStack = deployCallTree . getSymbolicStackAtStep ( baseConstructorScope . firstStep )
121+ console . log ( 'Constructor symbolic stack:' , symbolicStack )
122+
123+ await helper . decodeLocals ( st , baseConstructorScope . firstStep + 1 , deployTraceManager , deployCallTree , ( locals ) => {
124+ console . log ( 'Constructor locals:' , Object . keys ( locals ) )
125+
126+ // Look for constructor parameters
127+ if ( locals [ '_constructorValue' ] ) {
128+ st . equals ( locals [ '_constructorValue' ] . value , '42' , 'Constructor uint parameter should be decoded correctly' )
129+ }
130+ if ( locals [ '_constructorMessage' ] ) {
131+ st . equals ( locals [ '_constructorMessage' ] . value , 'ConstructorTest' , 'Constructor string parameter should be decoded correctly' )
132+ }
133+
134+ // Also check inherited constructor parameters
135+ if ( locals [ '_baseValue' ] ) {
136+ st . equals ( locals [ '_baseValue' ] . value , '52' , 'Base constructor uint parameter should be decoded correctly (42 + 10)' )
137+ }
138+ if ( locals [ '_baseMessage' ] ) {
139+ st . equals ( locals [ '_baseMessage' ] . value , 'Base: ConstructorTest' , 'Base constructor string parameter should be decoded correctly' )
140+ }
141+
142+ st . ok ( Object . keys ( locals ) . length > 0 , 'Constructor should have decoded local variables' )
143+ } )
144+ } else {
145+ st . fail ( 'Could not find constructor scope for parameter testing' )
146+ }
147+ }
148+
149+ // Now test each function call scenario
150+ for ( const testCase of testCases ) {
151+ console . log ( `\nTesting ${ testCase . name } : ${ testCase . description } ` )
152+
153+ const txData = encodeParams ( testCase . signature , testCase . params )
154+ console . log ( `Transaction data for ${ testCase . name } : ${ txData } ` )
155+
156+ const constructorParams = [ 42 , 'ConstructorTest' ]
157+ const constructorData = ethers . AbiCoder . defaultAbiCoder ( ) . encode ( [ 'uint256' , 'string' ] , constructorParams )
158+ const deployBytecode = contractBytecode + constructorData . slice ( 2 ) // Remove 0x prefix
159+ const { traceManager, callTree, waitForCallTree } = await helper . setupDebugger (
160+ privateKey ,
161+ deployBytecode ,
162+ compilationResult ,
163+ contractCode ,
164+ txData
165+ )
166+
167+ const { scopes, scopeStarts } = await waitForCallTree ( )
168+ const nestedScopes : NestedScope [ ] = callTree . getScopesAsNestedJSON ( )
169+
170+ // Find the target function scope
171+ const functionScope = findFunctionScope ( nestedScopes , testCase . name )
172+
173+ if ( functionScope ) {
174+ console . log ( `Found ${ testCase . name } scope with firstStep: ${ functionScope . firstStep } ` )
175+
176+ // Get symbolic stack at the first step of this function
177+ const symbolicStack = callTree . getSymbolicStackAtStep ( functionScope . firstStep )
178+ // console.log(`Symbolic stack for ${testCase.name}:`, symbolicStack)
179+
180+ // Decode locals at this step
181+ await helper . decodeLocals ( st , functionScope . firstStep + 3 , traceManager , callTree , ( locals ) => {
182+ console . log ( `${ testCase . name } locals:` , locals )
183+
184+ // Verify parameter decoding based on function signature
185+ if ( testCase . signature . includes ( 'uint256' ) ) {
186+ const uintParamNames = [ '_directValue' , '_testValue' , '_thisValue' , '_callValue' , '_staticValue' , '_delegateValue' , '_createValue' , '_create2Value' ]
187+ const expectedUintParam = uintParamNames . find ( name => locals [ name ] )
188+
189+ if ( expectedUintParam ) {
190+ st . equals ( locals [ expectedUintParam ] . value , testCase . params [ 0 ] . toString ( ) ,
191+ `${ testCase . name } : uint parameter should be decoded correctly` )
192+ } else {
193+ // Try alternative parameter names
194+ const allUintKeys = Object . keys ( locals ) . filter ( key =>
195+ key . includes ( 'Value' ) || key . includes ( 'value' ) || locals [ key ] . type ?. includes ( 'uint' ) )
196+ if ( allUintKeys . length > 0 ) {
197+ st . ok ( true , `${ testCase . name } : Found potential uint parameter: ${ allUintKeys [ 0 ] } ` )
198+ } else {
199+ st . fail ( `${ testCase . name } : Could not find uint parameter in locals` )
200+ }
201+ }
202+ }
203+
204+ if ( testCase . signature . includes ( 'string' ) ) {
205+ const stringParamNames = [ '_directMessage' , '_testMessage' , '_thisMessage' , '_callMessage' , '_staticMessage' , '_delegateMessage' , '_createMessage' , '_create2Message' ]
206+ const expectedStringParam = stringParamNames . find ( name => locals [ name ] )
207+
208+ if ( expectedStringParam ) {
209+ st . equals ( locals [ expectedStringParam ] . value , testCase . params [ 1 ] ,
210+ `${ testCase . name } : string parameter should be decoded correctly` )
211+ } else {
212+ // Try alternative parameter names
213+ const allStringKeys = Object . keys ( locals ) . filter ( key =>
214+ key . includes ( 'Message' ) || key . includes ( 'message' ) || locals [ key ] . type ?. includes ( 'string' ) )
215+ if ( allStringKeys . length > 0 ) {
216+ st . ok ( true , `${ testCase . name } : Found potential string parameter: ${ allStringKeys [ 0 ] } ` )
217+ } else {
218+ st . fail ( `${ testCase . name } : Could not find string parameter in locals` )
219+ }
220+ }
221+ }
222+
223+ st . ok ( Object . keys ( locals ) . length > 0 , `${ testCase . name } : Should have decoded local variables` )
224+ } )
225+
226+ // Additional tests for internal function calls
227+ if ( testCase . name === 'internalCallTest' ) {
228+ // Also check the internal function that gets called
229+ const internalScope = findFunctionScope ( nestedScopes , '_internalFunction' )
230+ if ( internalScope ) {
231+ console . log ( `Found _internalFunction scope with firstStep: ${ internalScope . firstStep } ` )
232+ await helper . decodeLocals ( st , internalScope . firstStep + 2 , traceManager , callTree , ( locals ) => {
233+ console . log ( '_internalFunction locals:' , Object . keys ( locals ) )
234+
235+ if ( locals [ '_internalValue' ] ) {
236+ st . equals ( locals [ '_internalValue' ] . value , '506' , 'Internal function uint parameter should be decoded correctly (456 + 50)' )
237+ }
238+ if ( locals [ '_internalMessage' ] ) {
239+ st . ok ( locals [ '_internalMessage' ] . value . includes ( 'Internal:' ) , 'Internal function string parameter should be decoded correctly' )
240+ }
241+ } )
242+ }
243+ }
244+
245+ // For external calls via this.function(), check the target function too
246+ if ( testCase . name === 'thisCallTest' ) {
247+ const externalScope = findFunctionScope ( nestedScopes , 'externalCallViaThis' )
248+ if ( externalScope ) {
249+ console . log ( `Found externalCallViaThis scope with firstStep: ${ externalScope . firstStep } ` )
250+ await helper . decodeLocals ( st , externalScope . firstStep + 2 , traceManager , callTree , ( locals ) => {
251+ console . log ( 'externalCallViaThis locals:' , Object . keys ( locals ) )
252+
253+ if ( locals [ '_externalValue' ] ) {
254+ st . equals ( locals [ '_externalValue' ] . value , '814' , 'External function uint parameter should be decoded correctly (789 + 25)' )
255+ }
256+ if ( locals [ '_externalMessage' ] ) {
257+ st . ok ( locals [ '_externalMessage' ] . value . includes ( 'This:' ) , 'External function string parameter should be decoded correctly' )
258+ }
259+ } )
260+ }
261+ }
262+
263+ } else {
264+ st . fail ( `Could not find scope for function ${ testCase . name } ` )
265+ }
266+ }
267+
268+ } catch ( error ) {
269+ console . error ( 'Test error:' , error )
270+ st . fail ( error . message || error )
271+ }
272+ }
0 commit comments