fn try_lower_address_balance(
inner: &Expression,
member: &Identifier,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> Option<bool> {
if member.name != "balance" {
return None;
}
if matches!(
infer_type_from_expression(inner, ctx),
Some(ValueType::Address)
) {
if !lower_expression(inner, ctx, instructions) {
return Some(false);
}
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::NativeCall {
contract: NativeContract::Gas,
method: "balanceOf".to_string(),
},
arg_count: 1,
});
return Some(true);
}
None
}
fn try_lower_length_property(
inner: &Expression,
member: &Identifier,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> Option<bool> {
if member.name != "length" {
return None;
}
if let Expression::MemberAccess(_, code_inner, code_member) = inner {
if code_member.name == "code"
&& matches!(
infer_type_from_expression(code_inner.as_ref(), ctx),
Some(ValueType::Address)
)
{
if !lower_expression(code_inner.as_ref(), ctx, instructions) {
return Some(false);
}
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::NativeCall {
contract: NativeContract::ContractManagement,
method: "isContract".to_string(),
},
arg_count: 1,
});
let not_contract_label = ctx.next_label();
let end_label = ctx.next_label();
instructions.push(Instruction::JumpIf {
target: not_contract_label,
});
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::one(),
)));
instructions.push(Instruction::Jump { target: end_label });
instructions.push(Instruction::Label(not_contract_label));
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
instructions.push(Instruction::Label(end_label));
return Some(true);
}
}
if let Expression::Variable(base) = inner {
if let Some(state_index) = ctx.state_index_map.get(&base.name) {
if matches!(ctx.state_type(*state_index), Some(ValueType::Array(_))) {
instructions.push(Instruction::LoadState(*state_index));
return Some(true);
}
}
}
if let Expression::FunctionCall(_, func, args) = inner {
if args.is_empty() {
if let Expression::Variable(fn_ident) = func.as_ref() {
if let Some(state_var_name) = ctx.storage_pointer_returning_fn(&fn_ident.name) {
if let Some(state_index) = ctx.state_index_map.get(state_var_name) {
if matches!(ctx.state_type(*state_index), Some(ValueType::Array(_))) {
instructions.push(Instruction::LoadState(*state_index));
return Some(true);
}
}
}
}
}
}
if let Some(reference) = resolve_storage_reference(inner, ctx) {
if matches!(reference.value_type, ValueType::Array(_)) {
if !emit_storage_load(&reference, ctx, instructions) {
return Some(false);
}
return Some(true);
}
}
if lower_expression(inner, ctx, instructions) {
instructions.push(Instruction::GetSize);
return Some(true);
}
Some(false)
}
fn try_lower_current_key(
inner: &Expression,
member: &Identifier,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> Option<bool> {
if member.name != "currentKey" {
return None;
}
if !lower_expression(inner, ctx, instructions) {
return Some(false);
}
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::Syscall("System.Iterator.Value".to_string()),
arg_count: 1,
});
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::zero(),
)));
instructions.push(Instruction::ArrayGet);
Some(true)
}
fn try_lower_current_value(
inner: &Expression,
member: &Identifier,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> Option<bool> {
if member.name != "currentValue" {
return None;
}
if !lower_expression(inner, ctx, instructions) {
return Some(false);
}
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::Syscall("System.Iterator.Value".to_string()),
arg_count: 1,
});
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(
BigInt::one(),
)));
instructions.push(Instruction::ArrayGet);
Some(true)
}
fn try_lower_code_property(
inner: &Expression,
member: &Identifier,
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) -> Option<bool> {
if member.name == "codehash" {
if matches!(
infer_type_from_expression(inner, ctx),
Some(ValueType::Address)
) {
ctx.record_warning_with_suggestion(
"address.codehash auto-mapped to the contract script hash on Neo N3. For contract addresses this returns the address itself; for non-contract addresses it returns bytes32(0).",
"Prefer explicit ContractManagement.isContract checks when you need Neo-native contract detection semantics.",
);
if !lower_expression(inner, ctx, instructions) {
return Some(false);
}
instructions.push(Instruction::Dup);
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::NativeCall {
contract: NativeContract::ContractManagement,
method: "isContract".to_string(),
},
arg_count: 1,
});
let not_contract_label = ctx.next_label();
let end_label = ctx.next_label();
instructions.push(Instruction::JumpIf {
target: not_contract_label,
});
instructions.push(Instruction::Jump { target: end_label });
instructions.push(Instruction::Label(not_contract_label));
instructions.push(Instruction::Drop(ValueType::Any));
instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(vec![
0u8;
32
])));
instructions.push(Instruction::Label(end_label));
return Some(true);
}
return None;
}
if member.name != "code" {
return None;
}
if lower_expression(inner, ctx, instructions) {
ctx.record_warning_with_suggestion(
"address.code auto-mapped to the Neo contract script bytes via ContractManagement.getContract(). This differs from EVM runtime bytecode semantics and returns empty bytes for non-contract addresses.",
"Use ContractManagement.isContract(address) or address.code.length when you only need existence checks.",
);
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::GetContractScript,
arg_count: 1,
});
return Some(true);
}
Some(false)
}