neo_solidity/ir/expressions/dispatch/
binary.rs

1fn fixed_len_bytes_be_from_hex_number(expr: &Expression, fixed_len: u16) -> Option<Vec<u8>> {
2    let Expression::HexNumberLiteral(_, value, unit) = expr else {
3        return None;
4    };
5
6    if unit.is_some() {
7        return None;
8    }
9
10    let raw = value.trim().trim_start_matches("0x");
11    let mut hex: String = raw
12        .chars()
13        .filter(|c| !c.is_whitespace() && *c != '_')
14        .collect();
15    if hex.is_empty() {
16        return None;
17    }
18
19    if hex.len() % 2 == 1 {
20        hex.insert(0, '0');
21    }
22
23    let bytes = hex_decode(&hex).ok()?;
24    let fixed_len = fixed_len as usize;
25    if bytes.len() > fixed_len {
26        return None;
27    }
28
29    let mut out = vec![0u8; fixed_len - bytes.len()];
30    out.extend_from_slice(&bytes);
31    Some(out)
32}
33
34fn lower_bytes_eq_hex_number_literal(
35    left: &Expression,
36    right: &Expression,
37    ctx: &mut LoweringContext,
38    instructions: &mut Vec<Instruction>,
39    operator: BinaryOperator,
40) -> Option<bool> {
41    if !matches!(operator, BinaryOperator::Eq | BinaryOperator::Ne) {
42        return None;
43    }
44
45    // Preserve Solidity left-to-right evaluation order while ensuring `bytesN` comparisons work
46    // with hex-number literals like `0x01ffc9a7` (commonly used for ERC165 interface IDs).
47    if let Some(ValueType::ByteArray {
48        fixed_len: Some(fixed_len),
49    }) = infer_type_from_expression(left, ctx)
50    {
51        let literal_expr = match right {
52            Expression::Parenthesis(_, inner) => inner.as_ref(),
53            other => other,
54        };
55
56        if let Some(bytes) = fixed_len_bytes_be_from_hex_number(literal_expr, fixed_len) {
57            if lower_expression(left, ctx, instructions) {
58                instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(bytes)));
59                instructions.push(Instruction::BinaryOp(operator));
60                return Some(true);
61            }
62            return Some(false);
63        }
64    }
65
66    if let Some(ValueType::ByteArray {
67        fixed_len: Some(fixed_len),
68    }) = infer_type_from_expression(right, ctx)
69    {
70        let literal_expr = match left {
71            Expression::Parenthesis(_, inner) => inner.as_ref(),
72            other => other,
73        };
74
75        if let Some(bytes) = fixed_len_bytes_be_from_hex_number(literal_expr, fixed_len) {
76            instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(bytes)));
77            if lower_expression(right, ctx, instructions) {
78                instructions.push(Instruction::BinaryOp(operator));
79                return Some(true);
80            }
81            return Some(false);
82        }
83    }
84
85    None
86}
87
88fn lower_binary_expr(
89    left: &Expression,
90    right: &Expression,
91    ctx: &mut LoweringContext,
92    instructions: &mut Vec<Instruction>,
93    operator: BinaryOperator,
94) -> bool {
95    if let Some(result) =
96        lower_bytes_eq_hex_number_literal(left, right, ctx, instructions, operator)
97    {
98        return result;
99    }
100
101    if !lower_expression(left, ctx, instructions) || !lower_expression(right, ctx, instructions) {
102        return false;
103    }
104
105    // Solidity 0.8+ panics on division/modulo by zero.
106    if matches!(operator, BinaryOperator::Div | BinaryOperator::Mod) {
107        let tmp_id = ctx.next_label();
108        let rhs_local = ctx.allocate_local(format!("__div_rhs_{tmp_id}"), None);
109
110        // Preserve left operand on the stack while we validate RHS.
111        instructions.push(Instruction::StoreLocal(rhs_local)); // pops RHS
112
113        // if rhs == 0 -> THROW("Panic: 0x12")
114        let ok_label = ctx.next_label();
115        instructions.push(Instruction::LoadLocal(rhs_local));
116        instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
117        instructions.push(Instruction::BinaryOp(BinaryOperator::Eq));
118        instructions.push(Instruction::JumpIf { target: ok_label });
119        instructions.push(Instruction::PushLiteral(LiteralValue::String(
120            b"Panic: 0x12".to_vec(),
121        )));
122        instructions.push(Instruction::Throw);
123        instructions.push(Instruction::Label(ok_label));
124
125        // Reload RHS and perform the operation.
126        instructions.push(Instruction::LoadLocal(rhs_local));
127    }
128
129    instructions.push(Instruction::BinaryOp(operator));
130    true
131}