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