use std::collections::HashMap;
use walrus::ir::Value;
use crate::ir::{Expr, MethodCall, Statement};
use crate::wasm_analysis::{StackValue, TrackedHostCall};
use super::super::RecognitionContext;
use super::super::val_decoding::{strip_val_boilerplate, try_decode_symbol_small, resolve_arg, as_ref};
use super::storage::extract_storage_tier;
pub(super) fn recognize_event(
call: &TrackedHostCall,
ctx: &RecognitionContext,
crn: &HashMap<usize, String>,
) -> Option<Statement> {
let pn = &ctx.param_names;
if let Some(StackValue::CallResult(topics_id)) = call.args.first() {
let event_name = if let Some(elements) = ctx.vec_contents.get(topics_id) {
elements.first().and_then(|first| {
let first_stripped = strip_val_boilerplate(first);
match &first_stripped {
StackValue::Const(Value::I64(v)) => try_decode_symbol_small(*v),
StackValue::CallResult(cid) => ctx.memory_strings.get(cid).cloned(),
_ => None,
}
})
} else {
let mut found = None;
for check_id in (topics_id.saturating_sub(5)..*topics_id).rev() {
if let Some(s) = ctx.memory_strings.get(&check_id) {
found = Some(s.clone());
break;
}
}
found
};
if let Some(ename) = event_name {
for entry in ctx.all_entries {
if let stellar_xdr::curr::ScSpecEntry::EventV0(ev) = entry {
if ev.name.to_utf8_string_lossy() == ename {
let data_expr = resolve_arg(call.args.get(1)?, pn, crn);
let fields: Vec<(String, Expr)> = ev.params.iter()
.filter(|p| matches!(
p.location,
stellar_xdr::curr::ScSpecEventParamLocationV0::Data,
))
.map(|p| (p.name.to_utf8_string_lossy(), data_expr.clone()))
.collect();
let event_struct = if fields.len() == 1 {
Expr::StructLiteral { name: ename.clone(), fields }
} else {
Expr::StructLiteral {
name: ename.clone(),
fields: vec![("data".into(), data_expr)],
}
};
return Some(Statement::Expr(Expr::MethodChain {
receiver: Box::new(event_struct),
calls: vec![MethodCall {
name: "publish".into(),
args: vec![Expr::Var("&env".into())],
}],
}));
}
}
}
}
}
let topics = resolve_arg(call.args.first()?, pn, crn);
let data = resolve_arg(call.args.get(1)?, pn, crn);
Some(Statement::Expr(Expr::MethodChain {
receiver: Box::new(Expr::Var("env".into())),
calls: vec![
MethodCall { name: "events".into(), args: vec![] },
MethodCall { name: "publish".into(), args: vec![topics, data] },
],
}))
}
pub(super) fn recognize_cross_contract_call(
call: &TrackedHostCall,
param_names: &[String],
crn: &HashMap<usize, String>,
ctx: &RecognitionContext,
) -> Option<Statement> {
let addr = as_ref(resolve_arg(call.args.first()?, param_names, crn));
let func = resolve_arg(call.args.get(1)?, param_names, crn);
let args_expr = resolve_arg(call.args.get(2)?, param_names, crn);
let token_method = extract_token_method_name(&func);
if let Some(method_name) = token_method {
let vec_args = extract_vec_call_args(call.args.get(2)?, ctx, param_names, crn);
let client = Expr::MethodChain {
receiver: Box::new(Expr::HostCall {
module: "token::Client".into(),
name: "new".into(),
args: vec![Expr::Var("&env".into()), addr],
}),
calls: vec![MethodCall {
name: method_name.into(),
args: vec_args.iter().map(|a| as_ref(a.clone())).collect(),
}],
};
return Some(Statement::Let {
name: "result".into(),
mutable: false,
value: client,
});
}
if let Some(method_name) = extract_symbol_name(&func) {
let vec_args = extract_vec_call_args(call.args.get(2)?, ctx, param_names, crn);
let client_call = Expr::MethodChain {
receiver: Box::new(Expr::HostCall {
module: "contract_client".into(),
name: "new".into(),
args: vec![Expr::Var("&env".into()), addr.clone()],
}),
calls: vec![MethodCall {
name: method_name.clone(),
args: vec_args.iter().map(|a| as_ref(a.clone())).collect(),
}],
};
return Some(Statement::Let {
name: "result".into(),
mutable: false,
value: client_call,
});
}
Some(Statement::Let {
name: "result".into(),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(Expr::Var("env".into())),
calls: vec![MethodCall {
name: "invoke_contract".into(),
args: vec![addr, func, args_expr],
}],
},
})
}
fn extract_token_method_name(func_expr: &Expr) -> Option<&'static str> {
match func_expr {
Expr::MacroCall { name, args } if name == "symbol_short" => {
if let Some(Expr::Literal(crate::ir::Literal::Str(s))) = args.first() {
match s.as_str() {
"transfer" => Some("transfer"),
"burn" => Some("burn"),
"approve" => Some("approve"),
"balance" => Some("balance"),
"decimals" => Some("decimals"),
"name" => Some("name"),
"symbol" => Some("symbol"),
_ => None,
}
} else {
None
}
}
_ => None,
}
}
fn extract_symbol_name(expr: &Expr) -> Option<String> {
match expr {
Expr::MacroCall { name, args } if name == "symbol_short" => {
if let Some(Expr::Literal(crate::ir::Literal::Str(s))) = args.first() {
Some(s.clone())
} else {
None
}
}
Expr::Literal(crate::ir::Literal::Str(s)) => Some(s.clone()),
_ => None,
}
}
fn extract_vec_call_args(
vec_sv: &StackValue,
ctx: &RecognitionContext,
param_names: &[String],
crn: &HashMap<usize, String>,
) -> Vec<Expr> {
if let StackValue::CallResult(vec_id) = strip_val_boilerplate(vec_sv) {
if let Some(elements) = ctx.vec_contents.get(&vec_id) {
return elements.iter().map(|el| {
let stripped = strip_val_boilerplate(el);
resolve_arg(&stripped, param_names, crn)
}).collect();
}
}
vec![]
}
pub(super) fn recognize_extend_ttl(
call: &TrackedHostCall,
param_names: &[String],
crn: &HashMap<usize, String>,
) -> Option<Statement> {
let key_expr = as_ref(resolve_arg(call.args.first()?, param_names, crn));
let tier = extract_storage_tier(call.args.get(1)?)?;
let threshold = resolve_arg(call.args.get(2)?, param_names, crn);
let extend_to = resolve_arg(call.args.get(3)?, param_names, crn);
Some(Statement::Expr(Expr::MethodChain {
receiver: Box::new(Expr::Var("env".into())),
calls: vec![
MethodCall { name: "storage".into(), args: vec![] },
MethodCall { name: tier.into(), args: vec![] },
MethodCall { name: "extend_ttl".into(), args: vec![key_expr, threshold, extend_to] },
],
}))
}
pub(super) fn recognize_extend_instance_ttl(
call: &TrackedHostCall,
param_names: &[String],
crn: &HashMap<usize, String>,
) -> Option<Statement> {
let threshold = resolve_arg(call.args.first()?, param_names, crn);
let extend_to = resolve_arg(call.args.get(1)?, param_names, crn);
Some(Statement::Expr(Expr::MethodChain {
receiver: Box::new(Expr::Var("env".into())),
calls: vec![
MethodCall { name: "storage".into(), args: vec![] },
MethodCall { name: "instance".into(), args: vec![] },
MethodCall { name: "extend_ttl".into(), args: vec![threshold, extend_to] },
],
}))
}
pub(super) fn recognize_fail(
call: &TrackedHostCall,
param_names: &[String],
crn: &HashMap<usize, String>,
) -> Option<Statement> {
let error = resolve_arg(call.args.first()?, param_names, crn);
Some(Statement::Expr(Expr::MethodChain {
receiver: Box::new(Expr::Var("env".into())),
calls: vec![MethodCall {
name: "panic_with_error".into(),
args: vec![error],
}],
}))
}