@@ -2,19 +2,23 @@ use std::collections::HashSet;
2
2
3
3
use ethers:: types:: U256 ;
4
4
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
+ } ;
6
9
use tracing:: { debug, trace} ;
7
10
8
11
use crate :: {
9
- core:: analyze:: AnalyzerState ,
12
+ core:: analyze:: { AnalyzerState , AnalyzerType } ,
10
13
interfaces:: { AnalyzedFunction , CalldataFrame , TypeHeuristic } ,
14
+ utils:: constants:: { AND_BITMASK_REGEX , AND_BITMASK_REGEX_2 } ,
11
15
Error ,
12
16
} ;
13
17
14
18
pub fn argument_heuristic (
15
19
function : & mut AnalyzedFunction ,
16
20
state : & State ,
17
- _ : & mut AnalyzerState ,
21
+ analyzer_state : & mut AnalyzerState ,
18
22
) -> Result < ( ) , Error > {
19
23
match state. last_instruction . opcode {
20
24
// CALLDATALOAD
@@ -76,6 +80,84 @@ pub fn argument_heuristic(
76
80
}
77
81
}
78
82
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
+
79
161
// integer type heuristics
80
162
0x02 | 0x04 | 0x05 | 0x06 | 0x07 | 0x08 | 0x09 | 0x0b | 0x10 | 0x11 | 0x12 | 0x13 => {
81
163
// check if this instruction is operating on a known argument.
0 commit comments