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