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
fn lower_expression_statement(
expr: &Expression,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> bool {
if let Expression::Assign(_, lhs, rhs) = expr {
lower_assignment(lhs, rhs, ctx, instructions);
} else if let Expression::FunctionCall(_, func, args) = expr {
if let Expression::Variable(identifier) = func.as_ref() {
if identifier.name == "require" {
lower_require(args, ctx, instructions);
return false;
}
if identifier.name == "assert" {
lower_assert(args, ctx, instructions);
return false;
}
}
if lower_expression(expr, ctx, instructions) {
instructions.push(Instruction::Drop(ValueType::Any));
}
} else if lower_expression(expr, ctx, instructions) {
instructions.push(Instruction::Drop(ValueType::Any));
}
false
}
fn lower_variable_definition_statement(
decl: &solang_parser::pt::VariableDeclaration,
init: Option<&Expression>,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> bool {
if let Some(ident) = &decl.name {
if ctx.is_local_in_current_scope(&ident.name) {
ctx.record_error_with_suggestion(
format!("local variable '{}' redeclared", ident.name),
"use a different variable name or assign to the existing variable instead of redeclaring",
);
} else {
let is_storage_reference = matches!(decl.storage, Some(PtStorageLocation::Storage(_)));
let mut inferred_type = if is_storage_reference {
None
} else {
infer_type_from_expression(&decl.ty, ctx)
};
// Best-effort: infer user-defined struct types for locals so that member
// access (`tmp.field`) can be lowered correctly for memory copies.
if !is_storage_reference && inferred_type.is_none() {
if let Expression::Variable(type_ident) = &decl.ty {
inferred_type = ctx
.defined_struct_types
.iter()
.chain(ctx.state_types.iter())
.chain(ctx.param_types.iter())
.chain(ctx.return_types.iter())
.chain(ctx.local_types.values())
.find_map(|ty| find_named_struct_type(ty, &type_ident.name));
}
}
let slot = ctx.allocate_local(ident.name.clone(), inferred_type.clone());
// An internal function-pointer local (`function (…) internal … f`)
// must dispatch through CALLA like an fp PARAMETER does — otherwise
// a later `f(args)` hits the silent compatibility fallback that
// drops the arguments and yields 0. Register the binding (arg count
// + has-return parsed from the declared type) so
// `try_lower_variable_call` emits a `CallIndirect` instead. Mirrors
// the parameter path in `ir/build/function.rs`.
if let Expression::Type(_, PtType::Function { params, returns, .. }) = &decl.ty {
let arg_count = params.len();
let has_return = returns
.as_ref()
.map(|(rets, _)| !rets.is_empty())
.unwrap_or(false);
ctx.register_function_pointer_binding(&ident.name, arg_count, has_return);
}
if let Some(initializer) = init {
if is_storage_reference {
if let Some(reference) = resolve_storage_reference(initializer, ctx) {
ctx.set_storage_alias(ident.name.clone(), reference);
} else if lower_expression(initializer, ctx, instructions) {
instructions.push(Instruction::Drop(ValueType::Any));
}
} else {
match parse_low_level_call_data(initializer, ctx) {
Ok(Some((method_name, encode_args))) => {
// Support `bytes data = abi.encodeWithSignature/encodeWithSelector(...)`
// for subsequent `address.call(data)` lowering.
let mut lowered = true;
for arg in &encode_args {
if !lower_expression(arg, ctx, instructions) {
lowered = false;
}
}
if lowered {
if encode_args.is_empty() {
instructions.push(Instruction::PushLiteral(
LiteralValue::Integer(BigInt::zero()),
));
instructions.push(Instruction::NewArray {
element_type: ValueType::Any,
});
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::NativeCall {
contract: NativeContract::StdLib,
method: "serialize".to_string(),
},
arg_count: 1,
});
} else {
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::AbiEncode,
arg_count: encode_args.len(),
});
}
instructions.push(Instruction::StoreLocal(slot));
ctx.set_call_data_local(slot, method_name);
}
}
Ok(None) => {
// Wave-#28 fix: `T[] memory got = this.method();`
// (and the interface-cast / address-typed shapes
// covered by `is_this_external_tuple_call`) must
// run the EVM-canonical dynamic-array decode on
// the returned ByteString before storing into
// the local. Without this, `got.length` reads
// the wire byte count (e.g. 224 for a five-elem
// `uint256[]`) instead of the decoded element
// count (5). See
// `try_lower_this_external_dynamic_assign` in
// `ir/statements/assignments/lower_assignment.rs`
// for the full rationale.
let decoded = if let Some(dst_type) = inferred_type.as_ref() {
try_lower_this_external_dynamic_assign(
slot,
initializer,
dst_type,
ctx,
instructions,
)
} else {
false
};
if !decoded && lower_expression(initializer, ctx, instructions) {
instructions.push(Instruction::StoreLocal(slot));
ctx.clear_call_data_local(slot);
}
}
Err(message) => {
ctx.record_error(message);
ctx.clear_call_data_local(slot);
}
}
}
} else if !is_storage_reference {
// Task #49 fix: `T[N] memory a;` (no initializer) must allocate a real
// StackItem::Array of length N with zero-initialized elements. Without
// this, push_default_for_value_type(Array) emits NEWARRAY 0 which fails
// at runtime (SETITEM "unsupported target Integer(0)", SIZE "unsupported
// type"). Mirrors the `new T[N]` path in lower_new_array_allocation.
if let Expression::ArraySubscript(_, array_type_expr, Some(length_expr)) =
&decl.ty
{
lower_new_array_allocation(
array_type_expr.as_ref(),
length_expr.as_ref(),
ctx,
instructions,
);
} else if let Some(value_type) = inferred_type.as_ref() {
push_default_for_value_type(value_type, ctx, instructions);
} else {
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::from(0u8),
)));
}
instructions.push(Instruction::StoreLocal(slot));
ctx.clear_call_data_local(slot);
}
}
} else {
ctx.record_error_with_suggestion(
"variable declaration missing identifier",
"every variable declaration must have a name: e.g. uint256 myVar = 0",
);
}
false
}
fn lower_emit_statement(
call: &Expression,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> bool {
lower_emit(call, ctx, instructions);
false
}
fn lower_assembly_statement(
block: &solang_parser::pt::YulBlock,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> bool {
// Task #99 — narrow yul support: lower mstore/mload/return + let/:= and a
// handful of arithmetic opcodes into NeoVM IR. Specialized handlers
// (e.g., extsload/exttload from the param-name sniffer) still take
// precedence when they recognise the enclosing function shape.
if lower_special_assembly(ctx, instructions) {
return false;
}
// Attempt to lower the yul block. If any statement is unsupported, we
// fall back to the legacy no-op compatibility warning so contracts that
// use more exotic yul (for/switch/sload/sstore/...) continue to compile.
if lower_yul_block(block, ctx, instructions) {
return false;
}
// Compatibility mode: preserve compilation for contracts that use
// inline assembly by treating unrecognized assembly blocks as no-ops.
ctx.record_warning_with_suggestion(
"inline assembly block compiled as no-op: NeoVM does not support EVM \
assembly instructions. Any logic inside this assembly block will be silently \
skipped at runtime.",
"replace inline assembly with equivalent Solidity code, or use Neo-specific \
builtins for low-level operations",
);
false
}