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