mod storage;
mod auth;
mod context;
mod crypto;
mod collections;
mod types;
use std::collections::HashMap;
use crate::ir::{Expr, Literal, MethodCall, Statement};
use crate::wasm_analysis::TrackedHostCall;
use super::RecognitionContext;
use super::val_decoding::{
extract_u32_val, resolve_arg,
};
pub fn recognize_call(
call: &TrackedHostCall,
ctx: &RecognitionContext,
crn: &HashMap<usize, String>,
) -> Option<Statement> {
let pn = &ctx.param_names;
let name = call.host_func.name;
match name {
"get_contract_data" => {
storage::recognize_storage_get(call, pn, crn, ctx.all_entries)
}
"put_contract_data" => {
storage::recognize_storage_set(call, pn, crn, ctx.all_entries)
}
"has_contract_data" => {
storage::recognize_storage_has(call, pn, crn, ctx.all_entries)
}
"del_contract_data" => {
storage::recognize_storage_del(call, pn, crn, ctx.all_entries)
}
"require_auth" => {
auth::recognize_require_auth(call, pn, crn)
}
"require_auth_for_args" => {
auth::recognize_require_auth_for_args(call, pn, crn, ctx)
}
"get_current_contract_address" => Some(Statement::Let {
name: "contract_addr".into(),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(Expr::Var("env".into())),
calls: vec![MethodCall {
name: "current_contract_address".into(),
args: vec![],
}],
},
}),
"verify_sig_ed25519" => {
crypto::recognize_ed25519_verify(call, pn, crn)
}
"contract_event" => context::recognize_event(call, ctx, crn),
"map_new" => Some(Statement::Let {
name: "map".into(),
mutable: false,
value: Expr::HostCall {
module: "Map".into(),
name: "new".into(),
args: vec![Expr::Var("&env".into())],
},
}),
"vec_new" => Some(Statement::Let {
name: "vec".into(),
mutable: false,
value: Expr::HostCall {
module: "Vec".into(),
name: "new".into(),
args: vec![Expr::Var("&env".into())],
},
}),
"vec_get" => {
let vec_arg = resolve_arg(call.args.first()?, pn, crn);
let idx_arg = resolve_arg(call.args.get(1)?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "item".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(vec_arg),
calls: vec![MethodCall { name: "get".into(), args: vec![idx_arg] }],
},
})
}
"vec_len" => {
let vec_arg = resolve_arg(call.args.first()?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "len".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(vec_arg),
calls: vec![MethodCall { name: "len".into(), args: vec![] }],
},
})
}
"vec_push_back" => {
let vec_arg = resolve_arg(call.args.first()?, pn, crn);
let val_arg = resolve_arg(call.args.get(1)?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "vec".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(vec_arg),
calls: vec![MethodCall { name: "push_back".into(), args: vec![val_arg] }],
},
})
}
"vec_push_front" => {
let vec_arg = resolve_arg(call.args.first()?, pn, crn);
let val_arg = resolve_arg(call.args.get(1)?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "vec".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(vec_arg),
calls: vec![MethodCall { name: "push_front".into(), args: vec![val_arg] }],
},
})
}
"vec_pop_back" => {
let vec_arg = resolve_arg(call.args.first()?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "vec".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(vec_arg),
calls: vec![MethodCall { name: "pop_back".into(), args: vec![] }],
},
})
}
"vec_pop_front" => {
let vec_arg = resolve_arg(call.args.first()?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "vec".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(vec_arg),
calls: vec![MethodCall { name: "pop_front".into(), args: vec![] }],
},
})
}
"vec_put" => {
let vec_arg = resolve_arg(call.args.first()?, pn, crn);
let idx_arg = resolve_arg(call.args.get(1)?, pn, crn);
let val_arg = resolve_arg(call.args.get(2)?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "vec".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(vec_arg),
calls: vec![MethodCall { name: "set".into(), args: vec![idx_arg, val_arg] }],
},
})
}
"vec_append" => {
let vec_a = resolve_arg(call.args.first()?, pn, crn);
let vec_b = resolve_arg(call.args.get(1)?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "vec".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(vec_a),
calls: vec![MethodCall { name: "append".into(), args: vec![Expr::Ref(Box::new(vec_b))] }],
},
})
}
"vec_slice" => {
let vec_arg = resolve_arg(call.args.first()?, pn, crn);
let start = resolve_arg(call.args.get(1)?, pn, crn);
let end = resolve_arg(call.args.get(2)?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "vec".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(vec_arg),
calls: vec![MethodCall { name: "slice".into(), args: vec![start, end] }],
},
})
}
"vec_unpack_to_linear_memory" => None,
"map_get" => {
let map_arg = resolve_arg(call.args.first()?, pn, crn);
let key_arg = resolve_arg(call.args.get(1)?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "item".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(map_arg),
calls: vec![MethodCall { name: "get".into(), args: vec![key_arg] }],
},
})
}
"map_has" => {
let map_arg = resolve_arg(call.args.first()?, pn, crn);
let key_arg = resolve_arg(call.args.get(1)?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "has_key".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(map_arg),
calls: vec![MethodCall { name: "contains_key".into(), args: vec![key_arg] }],
},
})
}
"map_put" => {
let map_arg = resolve_arg(call.args.first()?, pn, crn);
let key_arg = resolve_arg(call.args.get(1)?, pn, crn);
let val_arg = resolve_arg(call.args.get(2)?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "map".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(map_arg),
calls: vec![MethodCall { name: "set".into(), args: vec![key_arg, val_arg] }],
},
})
}
"map_del" => {
let map_arg = resolve_arg(call.args.first()?, pn, crn);
let key_arg = resolve_arg(call.args.get(1)?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "map".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(map_arg),
calls: vec![MethodCall { name: "remove".into(), args: vec![key_arg] }],
},
})
}
"map_len" => {
let map_arg = resolve_arg(call.args.first()?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "len".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(map_arg),
calls: vec![MethodCall { name: "len".into(), args: vec![] }],
},
})
}
"map_keys" => {
let map_arg = resolve_arg(call.args.first()?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "keys".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(map_arg),
calls: vec![MethodCall { name: "keys".into(), args: vec![] }],
},
})
}
"map_values" => {
let map_arg = resolve_arg(call.args.first()?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "values".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(map_arg),
calls: vec![MethodCall { name: "values".into(), args: vec![] }],
},
})
}
"vec_new_from_linear_memory" => {
collections::recognize_vec_new_from_linear_memory(
call, pn, crn,
ctx.vec_contents, ctx.all_entries,
&ctx.memory_strings,
)
}
"map_new_from_linear_memory" => {
collections::recognize_map_new_from_linear_memory(
call, pn, crn,
ctx.map_contents, ctx.all_entries,
ctx.analyzed,
)
}
"map_unpack_to_linear_memory" => {
collections::recognize_map_unpack(
call, pn, crn,
ctx.all_entries, ctx.analyzed,
ctx.unpack_field_ids,
)
}
"symbol_new_from_linear_memory" => {
let value = if let Some(decoded) = ctx.memory_strings.get(&call.call_site_id) {
Expr::HostCall {
module: "Symbol".into(),
name: "new".into(),
args: vec![
Expr::Var("&env".into()),
Expr::Literal(Literal::Str(decoded.clone())),
],
}
} else {
Expr::Raw("/* symbol from memory */".into())
};
Some(Statement::Let {
name: "sym".into(),
mutable: false,
value,
})
}
"string_new_from_linear_memory" => {
let value = if let Some(decoded) = ctx.memory_strings.get(&call.call_site_id) {
Expr::HostCall {
module: "String".into(),
name: "from_str".into(),
args: vec![
Expr::Var("&env".into()),
Expr::Literal(Literal::Str(decoded.clone())),
],
}
} else {
Expr::Raw("/* string from memory */".into())
};
Some(Statement::Let {
name: "str_val".into(),
mutable: false,
value,
})
}
"symbol_index_in_linear_memory" => {
collections::recognize_symbol_index(call, pn, crn, ctx.analyzed)
}
"symbol_len" | "symbol_copy_to_linear_memory"
| "string_len"
| "string_copy_to_linear_memory" => None,
"bytes_new" => Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "bytes_val".into()),
mutable: false,
value: Expr::HostCall {
module: "Bytes".into(),
name: "new".into(),
args: vec![Expr::Var("&env".into())],
},
}),
"bytes_new_from_linear_memory" => {
let ptr = call.args.get(0).and_then(extract_u32_val);
let len = call.args.get(1).and_then(extract_u32_val);
let byte_data = ptr.and_then(|p| len.and_then(|l| {
ctx.analyzed.read_linear_memory(p, l)
}));
let data_expr = match byte_data {
Some(bytes) if bytes.iter().all(|b| b.is_ascii_graphic() || *b == b' ') => {
let s = String::from_utf8_lossy(&bytes);
Expr::Literal(crate::ir::Literal::Str(format!("b\"{}\"", s)))
}
Some(bytes) if bytes.len() <= 64 && !bytes.is_empty() => {
let hex: Vec<String> = bytes.iter().map(|b| format!("0x{:02x}", b)).collect();
Expr::Raw(format!("&[{}]", hex.join(", ")))
}
Some(_) => {
return Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "bytes_val".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(Expr::Var("Bytes".into())),
calls: vec![crate::ir::MethodCall {
name: "new".into(),
args: vec![Expr::Var("&env".into())],
}],
},
});
}
_ => {
return Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "bytes_val".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(Expr::Var("Bytes".into())),
calls: vec![crate::ir::MethodCall {
name: "new".into(),
args: vec![Expr::Var("&env".into())],
}],
},
});
}
};
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "bytes_val".into()),
mutable: false,
value: Expr::HostCall {
module: "Bytes".into(),
name: "from_slice".into(),
args: vec![Expr::Var("&env".into()), data_expr],
},
})
}
"bytes_len" => {
let bytes_arg = resolve_arg(call.args.first()?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "len".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(bytes_arg),
calls: vec![MethodCall { name: "len".into(), args: vec![] }],
},
})
}
"bytes_get" => {
let bytes_arg = resolve_arg(call.args.first()?, pn, crn);
let idx_arg = resolve_arg(call.args.get(1)?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "byte_val".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(bytes_arg),
calls: vec![MethodCall { name: "get".into(), args: vec![idx_arg] }],
},
})
}
"bytes_put" => {
let bytes_arg = resolve_arg(call.args.first()?, pn, crn);
let idx_arg = resolve_arg(call.args.get(1)?, pn, crn);
let val_arg = resolve_arg(call.args.get(2)?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "bytes_val".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(bytes_arg),
calls: vec![MethodCall { name: "set".into(), args: vec![idx_arg, val_arg] }],
},
})
}
"bytes_push" => {
let bytes_arg = resolve_arg(call.args.first()?, pn, crn);
let val_arg = resolve_arg(call.args.get(1)?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "bytes_val".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(bytes_arg),
calls: vec![MethodCall { name: "push".into(), args: vec![val_arg] }],
},
})
}
"bytes_pop" => {
let bytes_arg = resolve_arg(call.args.first()?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "bytes_val".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(bytes_arg),
calls: vec![MethodCall { name: "pop".into(), args: vec![] }],
},
})
}
"bytes_append" => {
let bytes_a = resolve_arg(call.args.first()?, pn, crn);
let bytes_b = resolve_arg(call.args.get(1)?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "bytes_val".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(bytes_a),
calls: vec![MethodCall { name: "append".into(), args: vec![Expr::Ref(Box::new(bytes_b))] }],
},
})
}
"bytes_copy_to_linear_memory"
| "bytes_copy_from_linear_memory" => None,
"get_ledger_sequence" => Some(Statement::Let {
name: "sequence".into(),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(Expr::Var("env".into())),
calls: vec![
MethodCall { name: "ledger".into(), args: vec![] },
MethodCall { name: "sequence".into(), args: vec![] },
],
},
}),
"get_ledger_timestamp" => Some(Statement::Let {
name: "timestamp".into(),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(Expr::Var("env".into())),
calls: vec![
MethodCall { name: "ledger".into(), args: vec![] },
MethodCall { name: "timestamp".into(), args: vec![] },
],
},
}),
"call" => context::recognize_cross_contract_call(call, pn, crn, ctx),
"log_from_linear_memory" => None,
"obj_from_u64" => {
let val = resolve_arg(call.args.first()?, pn, crn);
Some(Statement::Let { name: "u64_val".into(), mutable: false, value: val })
}
"obj_from_i64" => {
let val = resolve_arg(call.args.first()?, pn, crn);
Some(Statement::Let { name: "i64_val".into(), mutable: false, value: val })
}
"obj_to_u64" => {
let val = resolve_arg(call.args.first()?, pn, crn);
Some(Statement::Let { name: "u64_val".into(), mutable: false, value: val })
}
"obj_to_i64" => {
let val = resolve_arg(call.args.first()?, pn, crn);
Some(Statement::Let { name: "i64_val".into(), mutable: false, value: val })
}
"obj_from_u128_pieces" => {
let hi = resolve_arg(call.args.first()?, pn, crn);
let lo = resolve_arg(call.args.get(1)?, pn, crn);
if let Some(source) = collections::detect_128_roundtrip(&hi, &lo) {
return Some(Statement::Let {
name: "u128_val".into(),
mutable: false,
value: source,
});
}
Some(Statement::Let {
name: "u128_val".into(),
mutable: false,
value: Expr::BinOp {
left: Box::new(Expr::BinOp {
left: Box::new(hi),
op: crate::ir::BinOp::Shl,
right: Box::new(Expr::Literal(Literal::I64(64))),
}),
op: crate::ir::BinOp::BitOr,
right: Box::new(lo),
},
})
}
"obj_from_i128_pieces" => {
let hi = resolve_arg(call.args.first()?, pn, crn);
let lo = resolve_arg(call.args.get(1)?, pn, crn);
if let Some(source) = collections::detect_128_roundtrip(&hi, &lo) {
return Some(Statement::Let {
name: "i128_val".into(),
mutable: false,
value: source,
});
}
Some(Statement::Let {
name: "i128_val".into(),
mutable: false,
value: Expr::BinOp {
left: Box::new(Expr::BinOp {
left: Box::new(hi),
op: crate::ir::BinOp::Shl,
right: Box::new(Expr::Literal(Literal::I64(64))),
}),
op: crate::ir::BinOp::BitOr,
right: Box::new(lo),
},
})
}
"obj_to_u128_lo64" | "obj_to_i128_lo64" => {
let src = resolve_arg(call.args.first()?, pn, crn);
let suffix = match &src {
Expr::Var(name) => format!("{}_lo", name),
_ => "lo64".into(),
};
Some(Statement::Let { name: suffix, mutable: false, value: src })
}
"obj_to_u128_hi64" | "obj_to_i128_hi64" => {
let src = resolve_arg(call.args.first()?, pn, crn);
let suffix = match &src {
Expr::Var(name) => format!("{}_hi", name),
_ => "hi64".into(),
};
Some(Statement::Let { name: suffix, mutable: false, value: src })
}
"obj_from_u256_pieces" | "obj_from_i256_pieces"
| "obj_to_u256_hi_hi" | "obj_to_u256_hi_lo"
| "obj_to_u256_lo_hi" | "obj_to_u256_lo_lo" => None,
"i256_add" | "u256_add" => {
collections::recognize_u256_binop(call, pn, crn, crate::ir::BinOp::Add)
}
"i256_sub" | "u256_sub" => {
collections::recognize_u256_binop(call, pn, crn, crate::ir::BinOp::Sub)
}
"i256_mul" | "u256_mul" => {
collections::recognize_u256_binop(call, pn, crn, crate::ir::BinOp::Mul)
}
"i256_div" | "u256_div" => {
collections::recognize_u256_binop(call, pn, crn, crate::ir::BinOp::Div)
}
"i256_rem_euclid" | "u256_rem_euclid" => {
collections::recognize_u256_binop(call, pn, crn, crate::ir::BinOp::Rem)
}
"i256_shl" | "u256_shl" => {
collections::recognize_u256_binop(call, pn, crn, crate::ir::BinOp::Shl)
}
"i256_shr" | "u256_shr" => {
collections::recognize_u256_binop(call, pn, crn, crate::ir::BinOp::Shr)
}
"i256_pow" | "u256_pow" => {
collections::recognize_u256_pow(call, pn, crn)
}
"extend_contract_data_ttl" => context::recognize_extend_ttl(call, pn, crn),
"extend_current_contract_instance_and_code_ttl" => {
context::recognize_extend_instance_ttl(call, pn, crn)
}
"fail_with_error" => context::recognize_fail(call, pn, crn),
"obj_cmp" => {
let a = resolve_arg(call.args.first()?, pn, crn);
let b = resolve_arg(call.args.get(1)?, pn, crn);
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "cmp_result".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(a),
calls: vec![MethodCall { name: "cmp".into(), args: vec![Expr::Ref(Box::new(b))] }],
},
})
}
"bls12_381_check_g1_is_in_subgroup"
| "bls12_381_g1_add" | "bls12_381_g1_mul"
| "bls12_381_g1_msm" | "bls12_381_g1_neg"
| "bls12_381_check_g2_is_in_subgroup"
| "bls12_381_g2_add" | "bls12_381_g2_mul"
| "bls12_381_g2_msm" | "bls12_381_g2_neg"
| "bls12_381_map_fp_to_g1" | "bls12_381_map_fp2_to_g2"
| "bls12_381_hash_to_g1" | "bls12_381_hash_to_g2"
| "bls12_381_multi_pairing_check" => {
let func_name = name.strip_prefix("bls12_381_").unwrap_or(name);
let method_args: Vec<Expr> = call.args.iter()
.map(|a| resolve_arg(a, pn, crn))
.collect();
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| func_name.replace("_to_", "_").into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(Expr::Var("env".into())),
calls: vec![
MethodCall { name: "crypto".into(), args: vec![] },
MethodCall { name: func_name.into(), args: method_args },
],
},
})
}
"bn254_check_g1_is_in_subgroup"
| "bn254_g1_add" | "bn254_g1_mul"
| "bn254_g1_msm" | "bn254_g1_neg"
| "bn254_check_g2_is_in_subgroup"
| "bn254_g2_add" | "bn254_g2_mul"
| "bn254_g2_msm" | "bn254_g2_neg"
| "bn254_map_fp_to_g1" | "bn254_map_fp2_to_g2"
| "bn254_hash_to_g1" | "bn254_hash_to_g2"
| "bn254_multi_pairing_check" => {
let func_name = name.strip_prefix("bn254_").unwrap_or(name);
let method_args: Vec<Expr> = call.args.iter()
.map(|a| resolve_arg(a, pn, crn))
.collect();
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| func_name.replace("_to_", "_").into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(Expr::Var("env".into())),
calls: vec![
MethodCall { name: "crypto".into(), args: vec![] },
MethodCall { name: func_name.into(), args: method_args },
],
},
})
}
"multi_pairing_check" => {
let args: Vec<Expr> = call.args.iter()
.map(|a| resolve_arg(a, pn, crn))
.collect();
Some(Statement::Let {
name: crn.get(&call.call_site_id).cloned().unwrap_or_else(|| "multi_pairing_check".into()),
mutable: false,
value: Expr::MethodChain {
receiver: Box::new(Expr::Var("env".into())),
calls: vec![
MethodCall { name: "crypto".into(), args: vec![] },
MethodCall { name: "multi_pairing_check".into(), args },
],
},
})
}
_ => None,
}
}