1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
fn lower_require(
args: &[Expression],
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) {
if args.is_empty() {
ctx.record_error_with_suggestion(
"require() expects at least one argument",
"usage: require(condition) or require(condition, \"error message\")",
);
return;
}
let fail_label = ctx.next_label();
let ok_label = ctx.next_label();
// IR JumpIf branches when the condition is false.
if lower_expression(&args[0], ctx, instructions) {
instructions.push(Instruction::JumpIf { target: fail_label });
instructions.push(Instruction::Jump { target: ok_label });
}
instructions.push(Instruction::Label(fail_label));
if args.len() > 1 {
// Solidity 0.8.x supports three forms:
// require(cond) -> THROW null
// require(cond, "message") -> THROW keccak256("Error(string)")[..4] || abi.encode("message")
// require(cond, CustomError(args)) -> THROW keccak256("CustomError(types)")[..4] || abi.encode(args)
//
// For the custom error form, the second argument is a FunctionCall whose callee is
// a Variable naming the error type. We emit the EVM-canonical custom-error envelope
// so `catch <Name>(...)` / `catch (bytes memory)` selector guards can match and decode
// the payload verbatim (Task #131 — aligns `require` with `revert` payload shape so
// `catch Error(string memory r)` absorbs `require(cond, "r")` uniformly across
// cross-contract and self-call paths).
if let Expression::FunctionCall(_, callee, error_args) = &args[1] {
if let Expression::Variable(error_ident) = callee.as_ref() {
// Resolve the selector from the DECLARED `error` signature
// when available (same policy as `lower_revert_statement`).
let arg_types = revert_error_arg_types(&error_ident.name, error_args, ctx);
let selector = revert_error_selector(&error_ident.name, &arg_types);
let direct_static_path = error_args
.iter()
.all(|arg| is_direct_static_revert_arg(arg, ctx));
if direct_static_path {
let pre_len = instructions.len();
instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(
selector.to_vec(),
)));
let mut success = true;
for error_arg in error_args {
if !lower_direct_static_revert_arg_slot(error_arg, ctx, instructions) {
success = false;
break;
}
}
if success {
if !error_args.is_empty() {
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::BytesConcat,
arg_count: error_args.len() + 1,
});
}
instructions.push(Instruction::Throw);
instructions.push(Instruction::Label(ok_label));
return;
}
instructions.truncate(pre_len);
}
let pre_len = instructions.len();
instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(
selector.to_vec(),
)));
if error_args.is_empty() {
instructions.push(Instruction::Throw);
instructions.push(Instruction::Label(ok_label));
return;
}
if let Some(ok) =
lower_abi_encode_args_direct_from_slice(error_args, ctx, instructions)
{
if ok {
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::BytesConcat,
arg_count: 2,
});
instructions.push(Instruction::Throw);
instructions.push(Instruction::Label(ok_label));
return;
}
instructions.truncate(pre_len);
} else {
instructions.truncate(pre_len);
}
let pre_len = instructions.len();
let mut pushed = 0usize;
let mut success = true;
for error_arg in error_args {
if lower_expression(error_arg, ctx, instructions) {
pushed += 1;
} else {
success = false;
break;
}
}
if success && pushed == error_args.len() {
// Rewind the argument pushes so we can emit selector, then args, in
// the correct order for AbiEncode + BytesConcat (mirrors
// `lower_revert_statement` shape).
let mut arg_instrs = instructions.split_off(pre_len);
instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(
selector.to_vec(),
)));
instructions.append(&mut arg_instrs);
if pushed == 0 {
// `require(cond, CustomError())` — payload is just the 4-byte selector.
} else {
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::AbiEncode,
arg_count: pushed,
});
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::BytesConcat,
arg_count: 2,
});
}
instructions.push(Instruction::Throw);
instructions.push(Instruction::Label(ok_label));
return;
}
// Fallback on lowering failure: legacy error-name string so we still surface
// *something* on the error path.
instructions.truncate(pre_len);
let msg = if error_args.is_empty() {
error_ident.name.clone()
} else {
format!("{}({} args)", error_ident.name, error_args.len())
};
instructions.push(Instruction::PushLiteral(LiteralValue::String(
msg.as_bytes().to_vec(),
)));
instructions.push(Instruction::Throw);
instructions.push(Instruction::Label(ok_label));
return;
}
}
// Task #131 — `require(cond, "msg")` with a string literal emits the
// EVM-canonical `Error(string)` envelope:
// keccak256("Error(string)")[..4] || abi.encode(msg)
// matching the shape `revert("msg")` already produces (see
// `lower_revert_statement`). Without this alignment, a `catch Error(string)`
// clause's 4-byte selector guard misses the bare `"msg"` payload and
// falls through to a rethrow — breaking `try this.f()` for any callee
// whose revert path terminates in `require(_, "msg")` (batch60 JJ3).
if let Expression::StringLiteral(parts) = &args[1] {
instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(
error_string_literal_envelope(&string_literal_bytes(parts)),
)));
instructions.push(Instruction::Throw);
instructions.push(Instruction::Label(ok_label));
return;
}
// Task #131 follow-up — NON-LITERAL string messages (a string
// variable, `string.concat(...)`, a propagated constant, ...) must
// get the same `Error(string)` envelope as the literal case above
// and as `revert(msg)`. Previously they fell through to the bare
// `Throw` below, so `catch Error(string)` selector guards missed
// them and EVM tooling could not decode the revert reason. The
// helper is shared with `lower_revert_statement` to keep both
// payload shapes identical.
if revert_message_arg_is_string(&args[1], ctx)
&& emit_error_string_envelope_throw(&args[1], ctx, instructions)
{
instructions.push(Instruction::Label(ok_label));
return;
}
// Preserve diagnostics/type checking for the revert message expression and surface it
// in the VM fault state when possible (NeoVM THROW).
if lower_expression(&args[1], ctx, instructions) {
instructions.push(Instruction::Throw);
instructions.push(Instruction::Label(ok_label));
return;
}
}
// NeoVM THROW requires an exception value on the stack. `null` yields an empty message.
instructions.push(Instruction::PushLiteral(LiteralValue::Null));
instructions.push(Instruction::Throw);
instructions.push(Instruction::Label(ok_label));
}
fn lower_assert(args: &[Expression], ctx: &mut LoweringContext, instructions: &mut Vec<Instruction>) {
if args.len() != 1 {
ctx.record_error_with_suggestion(
"assert() expects exactly one argument",
"usage: assert(condition)",
);
return;
}
let fail_label = ctx.next_label();
let ok_label = ctx.next_label();
// IR JumpIf branches when the condition is false.
if lower_expression(&args[0], ctx, instructions) {
instructions.push(Instruction::JumpIf { target: fail_label });
instructions.push(Instruction::Jump { target: ok_label });
}
instructions.push(Instruction::Label(fail_label));
// Task #27 (compiler slice) / Task #107 — Solidity `assert(false)`
// compiles to an EVM Panic with code 0x01 (assertion failed). Route
// through the shared `emit_panic` helper which emits the canonical
// keccak256("Panic(uint256)")[0..4] || abi.encode(0x01)
// payload so `ExecutionResult.return_data` matches what Ethereum
// tooling expects and `try { ... } catch Panic(uint code)` clauses can
// decode the code verbatim (per try_catch.rs).
emit_panic(0x01, instructions);
instructions.push(Instruction::Label(ok_label));
}
fn lower_logical_or(
left: &Expression,
right: &Expression,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> bool {
let false_label = ctx.next_label();
let end_label = ctx.next_label();
if !lower_expression(left, ctx, instructions) {
return false;
}
instructions.push(Instruction::JumpIf {
target: false_label,
});
instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(true)));
instructions.push(Instruction::Jump { target: end_label });
instructions.push(Instruction::Label(false_label));
if !lower_expression(right, ctx, instructions) {
return false;
}
instructions.push(Instruction::Label(end_label));
true
}
fn lower_logical_and(
left: &Expression,
right: &Expression,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> bool {
let false_label = ctx.next_label();
let end_label = ctx.next_label();
if !lower_expression(left, ctx, instructions) {
return false;
}
instructions.push(Instruction::JumpIf {
target: false_label,
});
if !lower_expression(right, ctx, instructions) {
return false;
}
instructions.push(Instruction::Jump { target: end_label });
instructions.push(Instruction::Label(false_label));
instructions.push(Instruction::PushLiteral(LiteralValue::Boolean(false)));
instructions.push(Instruction::Label(end_label));
true
}