fn lower_emit(expr: &Expression, ctx: &mut LoweringContext, instructions: &mut Vec<Instruction>) {
let Expression::FunctionCall(_, func, args) = expr else {
return;
};
let Expression::Variable(identifier) = func.as_ref() else {
return;
};
let Some(signature) = ctx.event_evm_signature(&identifier.name).cloned() else {
ctx.record_error_with_suggestion(
format!(
"emit references event `{}` which has no resolved declaration; the event would \
be missing from the contract manifest and the notification would fault on Neo \
nodes >= 3.6 (HF_Basilisk validates notifications against the manifest)",
identifier.name
),
"declare the event in the emitting contract (or a base contract it inherits)",
);
return;
};
if args.len() != signature.params.len() {
ctx.record_error_with_suggestion(
format!(
"emit of event `{}` passes {} argument(s) but the declaration has {} \
parameter(s); the notification would mismatch the manifest declaration and \
fault on Neo nodes >= 3.6",
identifier.name,
args.len(),
signature.params.len()
),
"match the emit's argument list to the event declaration",
);
return;
}
let canonical_types: Vec<String> = signature
.params
.iter()
.map(|param| param.canonical_type.clone())
.collect();
if native_transfer_standard(&identifier.name, signature.is_anonymous, &canonical_types)
.is_some()
{
lower_emit_native_transfer(&identifier.name, args, ctx, instructions);
return;
}
lower_emit_evm_shape(&identifier.name, &signature, args, ctx, instructions);
}
fn lower_emit_native_transfer(
event_name: &str,
args: &[Expression],
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) {
let original_len = instructions.len();
instructions.push(Instruction::PushLiteral(LiteralValue::String(
event_name.as_bytes().to_vec(),
)));
for (idx, arg) in args.iter().enumerate() {
if !lower_expression(arg, ctx, instructions) {
instructions.truncate(original_len);
ctx.record_error(format!(
"could not lower argument {idx} of `emit {event_name}(...)`; refusing to emit a \
partial notification"
));
return;
}
match idx {
0 | 1 => emit_zero_address_to_null(ctx, instructions),
2 => instructions.push(Instruction::Convert {
target: ConvertTarget::Integer,
}),
_ => {}
}
}
if let Some(index) = ctx.event_index_map.get(event_name) {
instructions.push(Instruction::EmitEvent {
event_index: *index,
arg_count: args.len(),
});
} else {
instructions.push(Instruction::EmitEventByName {
name: event_name.to_string(),
arg_count: args.len(),
});
}
}
fn emit_zero_address_to_null(ctx: &mut LoweringContext, instructions: &mut Vec<Instruction>) {
let end_label = ctx.next_label();
instructions.push(Instruction::Dup);
instructions.push(Instruction::Convert {
target: ConvertTarget::Integer,
});
instructions.push(Instruction::PushLiteral(LiteralValue::Integer(BigInt::zero())));
instructions.push(Instruction::BinaryOp(BinaryOperator::Eq));
instructions.push(Instruction::JumpIf { target: end_label });
instructions.push(Instruction::Drop(ValueType::Address));
instructions.push(Instruction::PushLiteral(LiteralValue::Null));
instructions.push(Instruction::Label(end_label));
}
fn lower_emit_evm_shape(
event_name: &str,
signature: &EventSignature,
args: &[Expression],
ctx: &mut LoweringContext,
instructions: &mut Vec<Instruction>,
) {
let original_len = instructions.len();
let mut indexed: Vec<&Expression> = Vec::new();
let mut indexed_params: Vec<&EventParamInfo> = Vec::new();
let mut non_indexed: Vec<&Expression> = Vec::new();
for (arg, info) in args.iter().zip(signature.params.iter()) {
if info.indexed {
indexed.push(arg);
indexed_params.push(info);
} else {
non_indexed.push(arg);
}
}
instructions.push(Instruction::PushLiteral(LiteralValue::String(
event_name.as_bytes().to_vec(),
)));
if !signature.is_anonymous {
instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(
signature.topic0.to_vec(),
)));
}
let mut success = true;
for (arg, info) in indexed.iter().zip(indexed_params.iter()) {
if info.is_dynamic {
let is_bytes_like =
info.canonical_type == "string" || info.canonical_type == "bytes";
if is_bytes_like {
if !lower_expression(arg, ctx, instructions) {
success = false;
break;
}
} else {
match lower_abi_encode_args_direct(&[*arg], ctx, instructions) {
Some(true) => {}
_ => {
success = false;
break;
}
}
}
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::Keccak256,
arg_count: 1,
});
continue;
}
let pre = instructions.len();
match lower_static_abi_slots_for_expr(arg, ctx, instructions, false) {
Some(0) => {
success = false;
break;
}
Some(1) => {
}
Some(n) => {
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::BytesConcat,
arg_count: n,
});
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::Keccak256,
arg_count: 1,
});
}
None => {
instructions.truncate(pre);
if !lower_expression(arg, ctx, instructions) {
success = false;
break;
}
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::AbiEncode,
arg_count: 1,
});
}
}
}
if success {
if non_indexed.is_empty() {
instructions.push(Instruction::PushLiteral(LiteralValue::ByteArray(Vec::new())));
} else {
let pre_data = instructions.len();
let refs: Vec<&Expression> = non_indexed.clone();
match lower_abi_encode_args_direct(&refs, ctx, instructions) {
Some(true) => {
}
Some(false) => {
success = false;
}
None => {
instructions.truncate(pre_data);
for arg in &non_indexed {
if !lower_expression(arg, ctx, instructions) {
success = false;
break;
}
}
if success {
instructions.push(Instruction::CallBuiltin {
builtin: BuiltinCall::AbiEncode,
arg_count: non_indexed.len(),
});
}
}
}
}
}
if !success {
instructions.truncate(original_len);
ctx.record_error(format!(
"could not lower the arguments of `emit {event_name}(...)`; refusing to emit a \
partial notification"
));
return;
}
let topic0_slot = if signature.is_anonymous { 0 } else { 1 };
let state_item_count = topic0_slot + indexed.len() + 1; if let Some(index) = ctx.event_index_map.get(event_name) {
instructions.push(Instruction::EmitEvent {
event_index: *index,
arg_count: state_item_count,
});
} else {
instructions.push(Instruction::EmitEventByName {
name: event_name.to_string(),
arg_count: state_item_count,
});
}
}