traverse_graph/builtin.rs
1//! Defines known Solidity built-in functions and properties.
2
3use once_cell::sync::Lazy;
4use std::collections::HashMap;
5
6/// Represents a Solidity built-in function or property.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct BuiltinFunction {
9 pub name: &'static str,
10 /// The type of the object this built-in applies to (e.g., "address", "bytes", "array").
11 /// Use "any_array" for array types like uint[], bytes[], etc.
12 pub object_type: &'static str,
13 /// A string representation of the return type (e.g., "uint256", "bool", "(bool,bytes)", "void").
14 pub return_type: &'static str,
15 /// Indicates if calling this built-in modifies the state of the object it's called on.
16 pub mutates_state: bool,
17 // TODO: Add argument types if needed for more complex analysis later.
18 // pub arguments: Vec<&'static str>,
19}
20
21static BUILTIN_FUNCTIONS: Lazy<HashMap<&'static str, Vec<BuiltinFunction>>> = Lazy::new(|| {
22 let mut m = HashMap::new();
23
24 let mut add = |builtin: BuiltinFunction| {
25 m.entry(builtin.name)
26 .or_insert_with(Vec::new)
27 .push(builtin);
28 };
29
30 // --- Array Built-ins (Dynamic Storage Arrays T[]) ---
31 add(BuiltinFunction {
32 name: "push",
33 object_type: "any_array",
34 // >=0.6.0 returns member reference, <0.6.0 returns void.
35 // >=0.8.0 returns nothing (void) again for storage arrays.
36 // Let's assume void for simplicity, as the return value is often unused.
37 return_type: "void",
38 mutates_state: true,
39 });
40 add(BuiltinFunction {
41 name: "pop",
42 object_type: "any_array",
43 return_type: "void", // Returns void
44 mutates_state: true,
45 });
46 // --- Array/Bytes/String Members ---
47 add(BuiltinFunction {
48 name: "length",
49 object_type: "any_array", // T[], bytes, string
50 return_type: "uint256",
51 mutates_state: false,
52 });
53 add(BuiltinFunction {
54 name: "length",
55 object_type: "bytes", // T[], bytes, string
56 return_type: "uint256",
57 mutates_state: false,
58 });
59 add(BuiltinFunction {
60 name: "length",
61 object_type: "string", // T[], bytes, string
62 return_type: "uint256",
63 mutates_state: false,
64 });
65
66 // --- Address Members ---
67 add(BuiltinFunction {
68 name: "balance",
69 object_type: "address",
70 return_type: "uint256",
71 mutates_state: false, // Read-only view of balance
72 });
73 add(BuiltinFunction {
74 name: "code",
75 object_type: "address",
76 return_type: "bytes", // bytes memory
77 mutates_state: false,
78 });
79 add(BuiltinFunction {
80 name: "codehash",
81 object_type: "address",
82 return_type: "bytes32",
83 mutates_state: false,
84 });
85 add(BuiltinFunction {
86 name: "transfer", // address payable only before 0.8.0
87 object_type: "address",
88 return_type: "void",
89 mutates_state: true, // Modifies balances, reverts on failure
90 });
91 add(BuiltinFunction {
92 name: "send", // address payable only before 0.8.0
93 object_type: "address",
94 return_type: "bool", // Returns success status
95 mutates_state: true, // Modifies balances
96 });
97 add(BuiltinFunction {
98 name: "call", // address payable before 0.8.0
99 object_type: "address",
100 return_type: "(bool,bytes)", // (success, return_data)
101 mutates_state: true, // Can modify state of called contract
102 });
103 add(BuiltinFunction {
104 name: "delegatecall", // address payable before 0.8.0
105 object_type: "address",
106 return_type: "(bool,bytes)", // (success, return_data)
107 mutates_state: true, // Modifies state of *this* contract
108 });
109 add(BuiltinFunction {
110 name: "staticcall", // address payable before 0.8.0
111 object_type: "address",
112 return_type: "(bool,bytes)", // (success, return_data)
113 mutates_state: false, // Cannot modify state
114 });
115
116 // --- Function Type Members ---
117 add(BuiltinFunction {
118 name: "selector",
119 object_type: "function", // External function type
120 return_type: "bytes4",
121 mutates_state: false,
122 });
123 add(BuiltinFunction {
124 name: "address",
125 object_type: "function", // External function type
126 return_type: "address",
127 mutates_state: false,
128 });
129
130 // --- Bytes Members (Specific) ---
131 // Note: `bytes` also has `.length` handled above by "any_array" logic.
132 // Other operations like slicing `bytes[i]` or concatenation are operators, not members.
133
134 // --- String Members (Specific) ---
135 // Note: `string` also has `.length` handled above by "any_array" logic.
136 add(BuiltinFunction {
137 name: "concat",
138 object_type: "string", // Global abi function now, but was member-like? Check docs.
139 // Let's assume global `abi.string.concat` for now.
140 // Keeping this commented out as it's likely not a direct member.
141 return_type: "string", // memory
142 mutates_state: false,
143 });
144
145 // --- Global Functions/Variables ---
146 // These are generally NOT handled by this map, as they aren't accessed via member syntax (`.`).
147 // They are typically handled by specific query captures or NodeType checks.
148 // Examples:
149 // - block.* (timestamp, number, difficulty, gaslimit, basefee, chainid, coinbase) -> NodeType::Evm?
150 // - msg.* (sender, value, data, sig) -> NodeType::Evm?
151 // - tx.* (origin, gasprice) -> NodeType::Evm?
152 // - abi.* (encode, encodePacked, encodeWithSignature, encodeWithSelector, decode) -> NodeType::Abi?
153 // - bytes.concat(...) -> Global function?
154 // - string.concat(...) -> Global function?
155 // - type(T).creationCode / type(T).runtimeCode -> Special `type()` expression
156 // - type(T).name / type(T).interfaceId -> Special `type()` expression
157 // - type(T).min / type(T).max -> Special `type()` expression
158 // - Mathematical: addmod, mulmod -> Global functions
159 // - Cryptographic: keccak256, sha256, ripemd160, ecrecover -> Global functions
160 // - Contract Related: this, selfdestruct -> Special keywords/nodes
161 // - Error Handling: require, assert, revert -> Special nodes (RequireCondition, etc.)
162
163 m
164});
165
166/// Checks if a function name corresponds to any known built-in.
167pub fn is_builtin(name: &str) -> bool {
168 BUILTIN_FUNCTIONS.contains_key(name)
169}
170
171/// Looks up a built-in function by its name and the type of the object it's called on.
172///
173/// Handles generic types like "any_array".
174pub fn get_builtin(name: &str, object_type: &str) -> Option<&'static BuiltinFunction> {
175 BUILTIN_FUNCTIONS.get(name).and_then(|candidates| {
176 candidates.iter().find(|builtin| {
177 // Direct match or generic array match
178 builtin.object_type == object_type
179 || (builtin.object_type == "any_array" && object_type.ends_with("[]"))
180 || (builtin.object_type == "any_array" && object_type == "bytes") // bytes has .length like arrays
181 || (builtin.object_type == "any_array" && object_type == "string") // string has .length like arrays
182 || (builtin.object_type == "bytes" && object_type.ends_with("[]")) // length applies to arrays too
183 || (builtin.object_type == "string" && object_type.ends_with("[]")) // length applies to arrays too
184 })
185 })
186}
187
188/// Checks if a specific built-in function mutates state.
189pub fn is_mutating_builtin(name: &str, object_type: &str) -> bool {
190 get_builtin(name, object_type).map_or(false, |b| b.mutates_state)
191}
192
193/// Gets the return type string for a specific built-in function.
194pub fn get_builtin_return_type(name: &str, object_type: &str) -> Option<&'static str> {
195 get_builtin(name, object_type).map(|b| b.return_type)
196}
197