@@ -2,19 +2,23 @@ use std::collections::HashSet;
22
33use ethers:: types:: U256 ;
44
5- use heimdall_common:: ether:: evm:: core:: { types:: convert_bitmask, vm:: State } ;
5+ use heimdall_common:: ether:: evm:: core:: {
6+ types:: { byte_size_to_type, convert_bitmask} ,
7+ vm:: State ,
8+ } ;
69use tracing:: { debug, trace} ;
710
811use crate :: {
9- core:: analyze:: AnalyzerState ,
12+ core:: analyze:: { AnalyzerState , AnalyzerType } ,
1013 interfaces:: { AnalyzedFunction , CalldataFrame , TypeHeuristic } ,
14+ utils:: constants:: { AND_BITMASK_REGEX , AND_BITMASK_REGEX_2 } ,
1115 Error ,
1216} ;
1317
1418pub fn argument_heuristic (
1519 function : & mut AnalyzedFunction ,
1620 state : & State ,
17- _ : & mut AnalyzerState ,
21+ analyzer_state : & mut AnalyzerState ,
1822) -> Result < ( ) , Error > {
1923 match state. last_instruction . opcode {
2024 // CALLDATALOAD
@@ -76,6 +80,84 @@ pub fn argument_heuristic(
7680 }
7781 }
7882
83+ // RETURN
84+ 0xf3 => {
85+ // Safely convert U256 to usize
86+ let size: usize = state. last_instruction . inputs [ 1 ] . try_into ( ) . unwrap_or ( 0 ) ;
87+
88+ let return_memory_operations = function. get_memory_range (
89+ state. last_instruction . inputs [ 0 ] ,
90+ state. last_instruction . inputs [ 1 ] ,
91+ ) ;
92+ let return_memory_operations_solidified = return_memory_operations
93+ . iter ( )
94+ . map ( |x| x. operations . solidify ( ) )
95+ . collect :: < Vec < String > > ( )
96+ . join ( ", " ) ;
97+
98+ // add the return statement to the function logic
99+ if analyzer_state. analyzer_type == AnalyzerType :: Solidity {
100+ if return_memory_operations. len ( ) <= 1 {
101+ function. logic . push ( format ! ( "return {return_memory_operations_solidified};" ) ) ;
102+ } else {
103+ function. logic . push ( format ! (
104+ "return abi.encodePacked({return_memory_operations_solidified});"
105+ ) ) ;
106+ }
107+ } else if analyzer_state. analyzer_type == AnalyzerType :: Yul {
108+ function. logic . push ( format ! (
109+ "return({}, {})" ,
110+ state. last_instruction. input_operations[ 0 ] . yulify( ) ,
111+ state. last_instruction. input_operations[ 1 ] . yulify( )
112+ ) ) ;
113+ }
114+
115+ // if we've already determined a return type, we don't want to do it again.
116+ // we use bytes32 as a default return type
117+ if function. returns != Some ( String :: from ( "bytes32" ) ) {
118+ return Ok ( ( ) ) ;
119+ }
120+
121+ // if the any input op is ISZERO(x), this is a boolean return
122+ if return_memory_operations. iter ( ) . any ( |x| x. operations . opcode . name == "ISZERO" ) {
123+ function. returns = Some ( String :: from ( "bool" ) ) ;
124+ }
125+ // if the size of returndata is > 32, it must be a bytes memory return.
126+ // it could be a struct, but we cant really determine that from the bytecode
127+ else if size > 32 {
128+ function. returns = Some ( String :: from ( "bytes memory" ) ) ;
129+ } else {
130+ // attempt to find a return type within the return memory operations
131+ let byte_size = match AND_BITMASK_REGEX
132+ . find ( & return_memory_operations_solidified)
133+ . ok ( )
134+ . flatten ( )
135+ {
136+ Some ( bitmask) => {
137+ let cast = bitmask. as_str ( ) ;
138+
139+ cast. matches ( "ff" ) . count ( )
140+ }
141+ None => match AND_BITMASK_REGEX_2
142+ . find ( & return_memory_operations_solidified)
143+ . ok ( )
144+ . flatten ( )
145+ {
146+ Some ( bitmask) => {
147+ let cast = bitmask. as_str ( ) ;
148+
149+ cast. matches ( "ff" ) . count ( )
150+ }
151+ None => 32 ,
152+ } ,
153+ } ;
154+
155+ // convert the cast size to a string
156+ let ( _, cast_types) = byte_size_to_type ( byte_size) ;
157+ function. returns = Some ( cast_types[ 0 ] . to_string ( ) ) ;
158+ }
159+ }
160+
79161 // integer type heuristics
80162 0x02 | 0x04 | 0x05 | 0x06 | 0x07 | 0x08 | 0x09 | 0x0b | 0x10 | 0x11 | 0x12 | 0x13 => {
81163 // check if this instruction is operating on a known argument.
0 commit comments