neo_solidity/ir/expressions/calls/builtins/
resolved.rs

1fn try_lower_resolved_builtin_call(
2    func: &Expression,
3    args: &[Expression],
4    ctx: &mut LoweringContext,
5    instructions: &mut Vec<Instruction>,
6) -> Option<bool> {
7    let builtin = resolve_builtin_call(func)?;
8
9    if matches!(&builtin, BuiltinCall::RuntimeNotify) {
10        validate_runtime_notify_call(args, ctx);
11    }
12
13    if matches!(&builtin, BuiltinCall::TypeOf) {
14        ctx.record_error(
15            "`type(...)` is only supported via members like `type(uint256).max`, `type(int256).min`, or `type(IInterface).interfaceId`",
16        );
17        return Some(false);
18    }
19
20    let (min_args, max_args) = match &builtin {
21        BuiltinCall::RuntimeNotify => (2, Some(2)),
22        BuiltinCall::RuntimeCheckWitness => (1, Some(1)),
23        // Solidity allows abi.encode()/encodePacked() with zero arguments.
24        BuiltinCall::AbiEncode | BuiltinCall::AbiEncodePacked | BuiltinCall::AbiEncodeCall => (0, None),
25        BuiltinCall::AbiEncodeWithSignature => (1, None),
26        BuiltinCall::AbiDecode => (2, None),
27        BuiltinCall::Keccak256 => (1, Some(1)),
28        BuiltinCall::Ecrecover => (4, Some(4)),
29        BuiltinCall::StorageFind => (1, None),
30        BuiltinCall::StoragePut => (2, Some(2)),
31        BuiltinCall::StorageGet | BuiltinCall::StorageDelete => (1, Some(1)),
32        BuiltinCall::ContractCall => (3, Some(3)),
33        BuiltinCall::ContractCallWithFlags => (4, Some(4)),
34        BuiltinCall::NotifySerialized => (1, Some(1)),
35        BuiltinCall::VerifySignature => (3, Some(3)),
36        BuiltinCall::DeployContract => (2, Some(3)),
37        BuiltinCall::GetContract | BuiltinCall::GetContractScript => (1, Some(1)),
38        BuiltinCall::GetNeoAccountState => (1, Some(1)),
39        BuiltinCall::NativeCall { .. } => (0, None),
40        BuiltinCall::Syscall(_) => (0, None),
41        BuiltinCall::TypeOf => (1, Some(1)),
42        BuiltinCall::BytesConcat => (0, None),
43    };
44
45    if args.len() < min_args || max_args.is_some_and(|max| args.len() > max) {
46        ctx.record_error(format!(
47            "builtin call requires between {} and {} argument(s), got {}",
48            min_args,
49            max_args
50                .map(|v| v.to_string())
51                .unwrap_or_else(|| "∞".to_string()),
52            args.len()
53        ));
54        return Some(false);
55    }
56
57    let mut success = true;
58    match &builtin {
59        BuiltinCall::AbiDecode => {
60            if let Some(first) = args.first() {
61                if !lower_expression(first, ctx, instructions) {
62                    success = false;
63                }
64            }
65        }
66        _ => {
67            for arg in args {
68                if !lower_expression(arg, ctx, instructions) {
69                    success = false;
70                }
71            }
72        }
73    }
74
75    if success {
76        match builtin {
77            BuiltinCall::RuntimeNotify
78            | BuiltinCall::RuntimeCheckWitness
79            | BuiltinCall::AbiEncode
80            | BuiltinCall::AbiEncodePacked
81            | BuiltinCall::AbiEncodeCall
82            | BuiltinCall::AbiDecode
83            | BuiltinCall::Keccak256
84            | BuiltinCall::Ecrecover
85            | BuiltinCall::StorageFind
86            | BuiltinCall::StoragePut
87            | BuiltinCall::StorageGet
88            | BuiltinCall::StorageDelete
89            | BuiltinCall::ContractCallWithFlags
90            | BuiltinCall::NotifySerialized
91            | BuiltinCall::VerifySignature
92            | BuiltinCall::DeployContract
93            | BuiltinCall::GetContract
94            | BuiltinCall::GetContractScript
95            | BuiltinCall::GetNeoAccountState
96            | BuiltinCall::NativeCall { .. }
97            | BuiltinCall::Syscall(_)
98            | BuiltinCall::BytesConcat => {
99                instructions.push(Instruction::CallBuiltin {
100                    builtin,
101                    arg_count: args.len(),
102                });
103            }
104            BuiltinCall::ContractCall => {
105                if ctx.is_safe {
106                    // In safe (view/pure) contexts, default contract calls to read-only.
107                    instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
108                        BigInt::from(0x05u8),
109                    )));
110                    instructions.push(Instruction::CallBuiltin {
111                        builtin: BuiltinCall::ContractCallWithFlags,
112                        arg_count: 4,
113                    });
114                } else {
115                    instructions.push(Instruction::CallBuiltin {
116                        builtin: BuiltinCall::ContractCall,
117                        arg_count: args.len(),
118                    });
119                }
120            }
121            BuiltinCall::AbiEncodeWithSignature => {
122                let mut selector = Vec::new();
123                if let Some(Expression::StringLiteral(parts)) = args.first() {
124                    let bytes = string_literal_bytes(parts);
125                    let mut hasher = Keccak256::new();
126                    hasher.update(&bytes);
127                    let digest = hasher.finalize();
128                    selector.extend_from_slice(&digest[..4]);
129                }
130                for _ in args {
131                    instructions.push(Instruction::Drop(ValueType::Any));
132                }
133                instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(selector)));
134            }
135            BuiltinCall::TypeOf => {}
136        }
137    }
138
139    Some(success)
140}