use std::collections::VecDeque;
use crate::codegen::cfg::{ControlFlowGraph, Instr};
use crate::codegen::events::EventEmitter;
use crate::codegen::expression::expression;
use crate::codegen::vartable::Vartable;
use crate::codegen::{Builtin, Expression, Options};
use crate::sema::ast::{self, Function, Namespace, RetrieveType, StringLocation, Type};
use ink::env::hash::{Blake2x256, CryptoHash};
use parity_scale_codec::Encode;
use solang_parser::pt;
pub(super) struct SubstrateEventEmitter<'a> {
pub(super) args: &'a [ast::Expression],
pub(super) ns: &'a Namespace,
pub(super) event_no: usize,
}
fn topic_hash(encoded: &[u8]) -> Vec<u8> {
let mut buf = [0; 32];
if encoded.len() <= 32 {
buf[..encoded.len()].copy_from_slice(encoded);
} else {
<Blake2x256 as CryptoHash>::hash(encoded, &mut buf);
};
buf.into()
}
impl EventEmitter for SubstrateEventEmitter<'_> {
fn emit(
&self,
contract_no: usize,
func: &Function,
cfg: &mut ControlFlowGraph,
vartab: &mut Vartable,
opt: &Options,
) {
let mut data = Vec::new();
let mut data_tys = Vec::new();
let mut topics = Vec::new();
let mut topic_tys = Vec::new();
let loc = pt::Loc::Builtin;
let event = &self.ns.events[self.event_no];
let contract_name = &self.ns.contracts[event.contract.unwrap_or(contract_no)].name;
let hash_len = Box::new(Expression::NumberLiteral(loc, Type::Uint(32), 32.into()));
if !event.anonymous {
let encoded = format!("\0{}::{}", contract_name, &event.name);
topics.push(Expression::AllocDynamicBytes(
loc,
Type::Slice(Type::Uint(8).into()),
hash_len.clone(),
Some(topic_hash(encoded.as_bytes())),
));
topic_tys.push(Type::DynamicBytes);
};
let mut topic_prefixes: VecDeque<Vec<u8>> = event
.fields
.iter()
.filter(|field| field.indexed)
.map(|field| {
format!(
"{}::{}::{}",
contract_name,
&event.name,
&field.name_as_str()
)
.into_bytes()
.encode()
})
.collect();
for (ast_exp, field) in self.args.iter().zip(event.fields.iter()) {
let value_exp = expression(ast_exp, cfg, contract_no, Some(func), self.ns, vartab, opt);
let value_var = vartab.temp_anonymous(&value_exp.ty());
let value = Expression::Variable(loc, value_exp.ty(), value_var);
cfg.add(
vartab,
Instr::Set {
loc,
res: value_var,
expr: value_exp,
},
);
data_tys.push(value.ty());
data.push(value.clone());
if !field.indexed {
continue;
}
let encoded = Expression::AbiEncode {
loc,
tys: vec![value.ty()],
packed: vec![],
args: vec![value],
};
let concatenated = Expression::StringConcat(
loc,
Type::DynamicBytes,
StringLocation::CompileTime(topic_prefixes.pop_front().unwrap()),
StringLocation::RunTime(encoded.into()),
);
vartab.new_dirty_tracker();
let var_buffer = vartab.temp_anonymous(&Type::DynamicBytes);
cfg.add(
vartab,
Instr::Set {
loc,
res: var_buffer,
expr: concatenated,
},
);
let buffer = Expression::Variable(loc, Type::DynamicBytes, var_buffer);
let compare = Expression::UnsignedMore(
loc,
Expression::Builtin(
loc,
vec![Type::Uint(32)],
Builtin::ArrayLength,
vec![buffer.clone()],
)
.into(),
hash_len.clone(),
);
let hash_topic_block = cfg.new_basic_block("hash_topic".into());
let done_block = cfg.new_basic_block("done".into());
cfg.add(
vartab,
Instr::BranchCond {
cond: compare,
true_block: hash_topic_block,
false_block: done_block,
},
);
cfg.set_basic_block(hash_topic_block);
cfg.add(
vartab,
Instr::WriteBuffer {
buf: buffer.clone(),
offset: Expression::NumberLiteral(loc, Type::Uint(32), 0.into()),
value: Expression::Builtin(
loc,
vec![Type::Bytes(32)],
Builtin::Blake2_256,
vec![buffer.clone()],
),
},
);
vartab.set_dirty(var_buffer);
cfg.add(vartab, Instr::Branch { block: done_block });
cfg.set_basic_block(done_block);
cfg.set_phis(done_block, vartab.pop_dirty_tracker());
topic_tys.push(Type::DynamicBytes);
topics.push(buffer);
}
cfg.add(
vartab,
Instr::EmitEvent {
event_no: self.event_no,
data,
data_tys,
topics,
topic_tys,
},
);
}
}