mirror of
https://gitlab.com/pezkuwichain/pezkuwi-sdk.git
synced 2026-10-20 18:56:23 +00:00
fix(usdt-bridge): replace fragile Debug-string event matching with real byte decoding
listen_deposits/listen_withdrawals matched the bridge address by checking whether a Debug-formatted string of the decoded dynamic Value contained a 10-char slice of the address's SS58 text - flagged earlier this session as unverified and provably broken (that same Debug string was also fed into convert_ss58_prefix, which only parses real SS58 text and could never have succeeded on it). Added account_id_from_dynamic(), which decodes an AccountId32's actual 32 bytes out of the event field (peeling the single-item composite wrapper subxt's dynamic API adds, same nesting seen in Tiki storage this session) and account_bytes_to_ss58() to re-encode them properly. Both listeners now compare raw bytes against the target address instead of matching strings, and derive the sender's real SS58 address for logging and convert_ss58_prefix() instead of a Debug string. Verified against a live event, not just read: sent a real 1 wUSDT transfer from Treasury_1 to the multisig and confirmed the decoder correctly extracted and matched the multisig's AccountId32 bytes from the resulting Assets.Transferred event.
This commit is contained in:
1 parent
45e4b26068
commit
042e1df314
1 file changed
+57
-18
@@ -289,6 +289,38 @@ fn convert_ss58_prefix(addr: &str, target_prefix: u16) -> Result<String> {
|
|||||||
Ok(account.to_ss58check_with_version(sp_core::crypto::Ss58AddressFormat::custom(target_prefix)))
|
Ok(account.to_ss58check_with_version(sp_core::crypto::Ss58AddressFormat::custom(target_prefix)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decodes an AccountId32 out of a dynamic event field, replacing the previous
|
||||||
|
/// Debug-string-contains-substring matching (never verified against a live node, and provably
|
||||||
|
/// wrong: it fed a `{:?}`-formatted value into convert_ss58_prefix, which can only parse real
|
||||||
|
/// SS58 text - that call could never have succeeded). AccountId32 fields decode as a composite
|
||||||
|
/// of 32 byte-sized primitives, sometimes wrapped in one extra single-item composite layer (the
|
||||||
|
/// same nesting observed in Tiki storage this session) - this peels exactly that.
|
||||||
|
fn account_id_from_dynamic(value: &subxt::ext::scale_value::Value<u32>) -> Option<[u8; 32]> {
|
||||||
|
use subxt::ext::scale_value::{Composite, ValueDef};
|
||||||
|
let mut current = value;
|
||||||
|
loop {
|
||||||
|
match ¤t.value {
|
||||||
|
ValueDef::Composite(Composite::Unnamed(items)) => {
|
||||||
|
if items.len() == 32 && items.iter().all(|v| v.as_u128().is_some()) {
|
||||||
|
let bytes: Vec<u8> =
|
||||||
|
items.iter().map(|v| v.as_u128().unwrap() as u8).collect();
|
||||||
|
return bytes.try_into().ok();
|
||||||
|
} else if items.len() == 1 {
|
||||||
|
current = &items[0];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn account_bytes_to_ss58(bytes: [u8; 32], prefix: u16) -> String {
|
||||||
|
sp_core::crypto::AccountId32::from(bytes)
|
||||||
|
.to_ss58check_with_version(sp_core::crypto::Ss58AddressFormat::custom(prefix))
|
||||||
|
}
|
||||||
|
|
||||||
fn load_wallet_addresses(seed_path: &PathBuf) -> Result<(String, String)> {
|
fn load_wallet_addresses(seed_path: &PathBuf) -> Result<(String, String)> {
|
||||||
let content = std::fs::read_to_string(seed_path).context("Failed to read seed file")?;
|
let content = std::fs::read_to_string(seed_path).context("Failed to read seed file")?;
|
||||||
let wallet: WalletSeed = serde_json::from_str(&content).context("Failed to parse seed")?;
|
let wallet: WalletSeed = serde_json::from_str(&content).context("Failed to parse seed")?;
|
||||||
@@ -914,20 +946,20 @@ async fn listen_deposits(config: &BridgeConfig) -> Result<()> {
|
|||||||
.and_then(|v| v.as_u128())
|
.and_then(|v| v.as_u128())
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
// Check if transfer is to bridge address
|
// Check if transfer is to bridge address - real byte comparison, not string
|
||||||
//
|
// matching (see account_id_from_dynamic's doc comment for why the previous
|
||||||
// CAVEAT (must be resolved before any mainnet fund flow): matching via a
|
// Debug-string approach was not just fragile but provably broken).
|
||||||
// Debug-formatted string of the decoded dynamic Value against a 10-char
|
let target_bytes = sp_core::crypto::AccountId32::from_ss58check(polkadot_addr)
|
||||||
// slice of the bridge's SS58 text is fragile and unverified against a live
|
.map(|a| Into::<[u8; 32]>::into(a))
|
||||||
// node in this environment - a Debug rendering of a raw AccountId32/byte
|
.ok();
|
||||||
// composite is not guaranteed to contain the address's SS58 text at all.
|
let to_bytes = to_field.and_then(account_id_from_dynamic);
|
||||||
// This should be replaced with subxt's typed/codegen API
|
|
||||||
// (#[subxt::subxt(runtime_metadata_path = ...)]) decoding straight into
|
if to_bytes.is_some() && to_bytes == target_bytes {
|
||||||
// AccountId32, tested against testnet, before this handles real value.
|
{
|
||||||
if let Some(to_value) = to_field {
|
let from_str = from_field
|
||||||
let to_str = format!("{:?}", to_value);
|
.and_then(account_id_from_dynamic)
|
||||||
if to_str.contains(&polkadot_addr[..10]) {
|
.map(|b| account_bytes_to_ss58(b, 0))
|
||||||
let from_str = from_field.map(|f| format!("{:?}", f)).unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let tx_hash = format!("block-{}-{}", block_number, from_str);
|
let tx_hash = format!("block-{}-{}", block_number, from_str);
|
||||||
|
|
||||||
info!("📥 DEPOSIT DETECTED!");
|
info!("📥 DEPOSIT DETECTED!");
|
||||||
@@ -1044,10 +1076,17 @@ async fn listen_withdrawals(config: &BridgeConfig) -> Result<()> {
|
|||||||
.and_then(|v| v.as_u128())
|
.and_then(|v| v.as_u128())
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
if let Some(to_value) = to_field {
|
let target_bytes = sp_core::crypto::AccountId32::from_ss58check(pezkuwi_bridge_addr)
|
||||||
let to_str = format!("{:?}", to_value);
|
.map(|a| Into::<[u8; 32]>::into(a))
|
||||||
if to_str.contains(&pezkuwi_bridge_addr[..10]) {
|
.ok();
|
||||||
let from_str = from_field.map(|f| format!("{:?}", f)).unwrap_or_default();
|
let to_bytes = to_field.and_then(account_id_from_dynamic);
|
||||||
|
|
||||||
|
if to_bytes.is_some() && to_bytes == target_bytes {
|
||||||
|
{
|
||||||
|
let from_str = from_field
|
||||||
|
.and_then(account_id_from_dynamic)
|
||||||
|
.map(|b| account_bytes_to_ss58(b, 42))
|
||||||
|
.unwrap_or_default();
|
||||||
let tx_hash = format!("block-{}-{}", block_number, from_str);
|
let tx_hash = format!("block-{}-{}", block_number, from_str);
|
||||||
|
|
||||||
info!("📤 WITHDRAWAL DETECTED!");
|
info!("📤 WITHDRAWAL DETECTED!");
|
||||||
|
|||||||
Reference in new issue
Block a user