neo_solidity/ir/statements/
logical.rs

1fn lower_require(
2    args: &[Expression],
3    ctx: &mut LoweringContext,
4    instructions: &mut Vec<Instruction>,
5) {
6    if args.is_empty() {
7        ctx.record_error_with_suggestion(
8            "require() expects at least one argument",
9            "usage: require(condition) or require(condition, \"error message\")",
10        );
11        return;
12    }
13
14    let fail_label = ctx.next_label();
15    let ok_label = ctx.next_label();
16
17    // IR JumpIf branches when the condition is false.
18    if lower_expression(&args[0], ctx, instructions) {
19        instructions.push(Instruction::JumpIf { target: fail_label });
20        instructions.push(Instruction::Jump { target: ok_label });
21    }
22
23    instructions.push(Instruction::Label(fail_label));
24    if args.len() > 1 {
25        // Solidity 0.8.x supports three forms:
26        //   require(cond)              -> THROW null
27        //   require(cond, "message")   -> THROW "message"
28        //   require(cond, CustomError(args)) -> THROW "CustomError"
29        //
30        // For the custom error form, the second argument is a FunctionCall whose callee is
31        // a Variable naming the error type. We extract the name and serialize the error
32        // arguments into the thrown string for better diagnostics. NeoVM has no ABI-encoded
33        // error data, so we format as "ErrorName(arg_count)" to preserve maximum context.
34        if let Expression::FunctionCall(_, callee, error_args) = &args[1] {
35            if let Expression::Variable(error_ident) = callee.as_ref() {
36                // Build a diagnostic string: "ErrorName(N args)" where N is the arg count.
37                // We still evaluate args for side effects, then drop them.
38                for error_arg in error_args {
39                    if lower_expression(error_arg, ctx, instructions) {
40                        instructions.push(Instruction::Drop(ValueType::Any));
41                    }
42                }
43                let msg = if error_args.is_empty() {
44                    error_ident.name.clone()
45                } else {
46                    format!("{}({} args)", error_ident.name, error_args.len())
47                };
48                instructions.push(Instruction::PushLiteral(LiteralValue::String(
49                    msg.as_bytes().to_vec(),
50                )));
51                instructions.push(Instruction::Throw);
52                instructions.push(Instruction::Label(ok_label));
53                return;
54            }
55        }
56
57        // Preserve diagnostics/type checking for the revert message expression and surface it
58        // in the VM fault state when possible (NeoVM THROW).
59        if lower_expression(&args[1], ctx, instructions) {
60            instructions.push(Instruction::Throw);
61            instructions.push(Instruction::Label(ok_label));
62            return;
63        }
64    }
65
66    // NeoVM THROW requires an exception value on the stack. `null` yields an empty message.
67    instructions.push(Instruction::PushLiteral(LiteralValue::Null));
68    instructions.push(Instruction::Throw);
69    instructions.push(Instruction::Label(ok_label));
70}
71
72fn lower_assert(args: &[Expression], ctx: &mut LoweringContext, instructions: &mut Vec<Instruction>) {
73    if args.len() != 1 {
74        ctx.record_error_with_suggestion(
75            "assert() expects exactly one argument",
76            "usage: assert(condition)",
77        );
78        return;
79    }
80
81    let fail_label = ctx.next_label();
82    let ok_label = ctx.next_label();
83
84    // IR JumpIf branches when the condition is false.
85    if lower_expression(&args[0], ctx, instructions) {
86        instructions.push(Instruction::JumpIf { target: fail_label });
87        instructions.push(Instruction::Jump { target: ok_label });
88    }
89
90    instructions.push(Instruction::Label(fail_label));
91    // Solidity assert() panics. NeoVM THROW is catchable, so we use it with a panic-like marker.
92    instructions.push(Instruction::PushLiteral(LiteralValue::String(
93        b"Panic: 0x01".to_vec(),
94    )));
95    instructions.push(Instruction::Throw);
96
97    instructions.push(Instruction::Label(ok_label));
98}
99
100fn lower_logical_or(
101    left: &Expression,
102    right: &Expression,
103    ctx: &mut LoweringContext,
104    instructions: &mut Vec<Instruction>,
105) -> bool {
106    let false_label = ctx.next_label();
107    let end_label = ctx.next_label();
108
109    if !lower_expression(left, ctx, instructions) {
110        return false;
111    }
112
113    instructions.push(Instruction::JumpIf {
114        target: false_label,
115    });
116    instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(true)));
117    instructions.push(Instruction::Jump { target: end_label });
118    instructions.push(Instruction::Label(false_label));
119
120    if !lower_expression(right, ctx, instructions) {
121        return false;
122    }
123
124    instructions.push(Instruction::Label(end_label));
125    true
126}
127
128fn lower_logical_and(
129    left: &Expression,
130    right: &Expression,
131    ctx: &mut LoweringContext,
132    instructions: &mut Vec<Instruction>,
133) -> bool {
134    let false_label = ctx.next_label();
135    let end_label = ctx.next_label();
136
137    if !lower_expression(left, ctx, instructions) {
138        return false;
139    }
140
141    instructions.push(Instruction::JumpIf {
142        target: false_label,
143    });
144
145    if !lower_expression(right, ctx, instructions) {
146        return false;
147    }
148
149    instructions.push(Instruction::Jump { target: end_label });
150    instructions.push(Instruction::Label(false_label));
151    instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(false)));
152    instructions.push(Instruction::Label(end_label));
153    true
154}