|
2 | 2 | //! |
3 | 3 | //! Parses raw extrinsic bytes into structured data. |
4 | 4 | //! |
5 | | -//! Supports two modes of pallet/method resolution: |
6 | | -//! - **Dynamic (metadata-based)**: Uses runtime metadata to resolve pallet and call names |
7 | | -//! from their indices. This is the preferred approach as it handles runtime upgrades |
8 | | -//! and chain-specific index differences automatically. |
9 | | -//! - **Hardcoded fallback**: Uses a static mapping of known pallet/method indices. |
10 | | -//! Used when no metadata is provided in the parsing context. |
| 5 | +//! Uses runtime metadata to resolve pallet and call names from their indices. |
| 6 | +//! Metadata is required (enforced at the TypeScript level in `fromHex`/`fromBytes`). |
11 | 7 |
|
12 | 8 | use crate::address::encode_ss58; |
13 | 9 | use crate::error::WasmDotError; |
@@ -148,44 +144,6 @@ fn resolve_call_from_metadata( |
148 | 144 | Some((pallet_name, call_name)) |
149 | 145 | } |
150 | 146 |
|
151 | | -/// Resolve pallet and call names using the hardcoded static mapping. |
152 | | -/// |
153 | | -/// This is the fallback when no metadata is available. It covers known |
154 | | -/// pallet indices for Polkadot, Kusama, and Westend. |
155 | | -fn resolve_call_hardcoded(pallet_index: u8, method_index: u8) -> (&'static str, &'static str) { |
156 | | - match (pallet_index, method_index) { |
157 | | - // Balances pallet |
158 | | - // Polkadot: 5, Kusama: 4, Westend: 10 |
159 | | - (4, 0) | (5, 0) | (10, 0) => ("balances", "transfer"), |
160 | | - (4, 3) | (5, 3) | (10, 3) => ("balances", "transferKeepAlive"), |
161 | | - (4, 4) | (5, 4) | (10, 4) => ("balances", "transferAll"), |
162 | | - |
163 | | - // Staking pallet |
164 | | - // Polkadot: 7, Kusama: 6, Westend: 8 |
165 | | - (6, 0) | (7, 0) | (8, 0) => ("staking", "bond"), |
166 | | - (6, 1) | (7, 1) | (8, 1) => ("staking", "bondExtra"), |
167 | | - (6, 2) | (7, 2) | (8, 2) => ("staking", "unbond"), |
168 | | - (6, 3) | (7, 3) | (8, 3) => ("staking", "withdrawUnbonded"), |
169 | | - (6, 6) | (7, 6) | (8, 6) => ("staking", "chill"), |
170 | | - (6, 18) | (7, 18) | (8, 18) => ("staking", "payoutStakers"), |
171 | | - |
172 | | - // Proxy pallet |
173 | | - // Polkadot: 29, Kusama: 29, Westend: 30 |
174 | | - (29, 0) | (30, 0) => ("proxy", "proxy"), |
175 | | - (29, 1) | (30, 1) => ("proxy", "addProxy"), |
176 | | - (29, 2) | (30, 2) => ("proxy", "removeProxy"), |
177 | | - (29, 4) | (30, 4) => ("proxy", "createPure"), |
178 | | - |
179 | | - // Utility pallet |
180 | | - // Polkadot: 26, Kusama: 24, Westend: 16 |
181 | | - (16, 0) | (24, 0) | (26, 0) => ("utility", "batch"), |
182 | | - (16, 2) | (24, 2) | (26, 2) => ("utility", "batchAll"), |
183 | | - |
184 | | - // Unknown |
185 | | - _ => ("unknown", "unknown"), |
186 | | - } |
187 | | -} |
188 | | - |
189 | 147 | /// Convert snake_case to camelCase. |
190 | 148 | /// |
191 | 149 | /// Examples: |
@@ -213,8 +171,8 @@ fn snake_to_camel(s: &str) -> String { |
213 | 171 |
|
214 | 172 | /// Parse call data into method info. |
215 | 173 | /// |
216 | | -/// When metadata is provided, uses dynamic resolution via `pallet_by_index()` and |
217 | | -/// `call_variant_by_index()`. Falls back to hardcoded mapping otherwise. |
| 174 | +/// Uses metadata for dynamic resolution via `pallet_by_index()` and |
| 175 | +/// `call_variant_by_index()`. Metadata is required. |
218 | 176 | fn parse_call_data( |
219 | 177 | call_data: &[u8], |
220 | 178 | address_prefix: u16, |
@@ -247,16 +205,19 @@ fn parse_call_data_with_size( |
247 | 205 | let method_index = call_data[1]; |
248 | 206 | let args_data = &call_data[2..]; |
249 | 207 |
|
250 | | - // Resolve pallet and method names: prefer metadata, fall back to hardcoded |
251 | | - let (pallet, name) = if let Some(md) = metadata { |
252 | | - resolve_call_from_metadata(md, pallet_index, method_index).unwrap_or_else(|| { |
253 | | - let (p, n) = resolve_call_hardcoded(pallet_index, method_index); |
254 | | - (p.to_string(), n.to_string()) |
255 | | - }) |
256 | | - } else { |
257 | | - let (p, n) = resolve_call_hardcoded(pallet_index, method_index); |
258 | | - (p.to_string(), n.to_string()) |
259 | | - }; |
| 208 | + // Resolve pallet and method names from metadata (required) |
| 209 | + let md = metadata.ok_or_else(|| { |
| 210 | + WasmDotError::InvalidTransaction( |
| 211 | + "Metadata required to resolve pallet/method names".to_string(), |
| 212 | + ) |
| 213 | + })?; |
| 214 | + let (pallet, name) = |
| 215 | + resolve_call_from_metadata(md, pallet_index, method_index).ok_or_else(|| { |
| 216 | + WasmDotError::InvalidTransaction(format!( |
| 217 | + "Unknown pallet index {} method index {} in metadata", |
| 218 | + pallet_index, method_index |
| 219 | + )) |
| 220 | + })?; |
260 | 221 |
|
261 | 222 | // Parse args based on method, getting bytes consumed |
262 | 223 | let (args, args_consumed) = |
@@ -465,7 +426,7 @@ fn parse_proxy_args( |
465 | 426 | )); |
466 | 427 | } |
467 | 428 |
|
468 | | - let proxy_type = resolve_proxy_type(args[cursor], metadata); |
| 429 | + let proxy_type = resolve_proxy_type(args[cursor], metadata)?; |
469 | 430 | cursor += 1; |
470 | 431 |
|
471 | 432 | let delay = u32::from_le_bytes([ |
@@ -496,7 +457,7 @@ fn parse_create_pure_args( |
496 | 457 | "truncated createPure args".to_string(), |
497 | 458 | )); |
498 | 459 | } |
499 | | - let proxy_type = resolve_proxy_type(args[0], metadata); |
| 460 | + let proxy_type = resolve_proxy_type(args[0], metadata)?; |
500 | 461 | let delay = u32::from_le_bytes([args[1], args[2], args[3], args[4]]); |
501 | 462 | let index = u16::from_le_bytes([args[5], args[6]]); |
502 | 463 |
|
@@ -563,7 +524,7 @@ fn parse_proxy_proxy_args( |
563 | 524 | "truncated proxy type".to_string(), |
564 | 525 | )); |
565 | 526 | } |
566 | | - let pt = resolve_proxy_type(args[cursor], metadata); |
| 527 | + let pt = resolve_proxy_type(args[cursor], metadata)?; |
567 | 528 | cursor += 1; |
568 | 529 | Some(pt) |
569 | 530 | } else { |
@@ -616,28 +577,20 @@ fn parse_multi_address(args: &[u8], address_prefix: u16) -> Result<(String, usiz |
616 | 577 | } |
617 | 578 | } |
618 | 579 |
|
619 | | -/// Resolve proxy type name from metadata, falling back to hardcoded Polkadot mainnet mapping. |
| 580 | +/// Resolve proxy type name from metadata. Metadata is required. |
620 | 581 | fn resolve_proxy_type( |
621 | 582 | proxy_type_byte: u8, |
622 | 583 | metadata: Option<&subxt_core::metadata::Metadata>, |
623 | | -) -> String { |
624 | | - if let Some(md) = metadata { |
625 | | - if let Some(name) = resolve_proxy_type_from_metadata(md, proxy_type_byte) { |
626 | | - return name; |
627 | | - } |
628 | | - } |
629 | | - // Fallback: Polkadot mainnet proxy type indices |
630 | | - match proxy_type_byte { |
631 | | - 0 => "Any".to_string(), |
632 | | - 1 => "NonTransfer".to_string(), |
633 | | - 2 => "Governance".to_string(), |
634 | | - 3 => "Staking".to_string(), |
635 | | - 4 => "IdentityJudgement".to_string(), |
636 | | - 5 => "CancelProxy".to_string(), |
637 | | - 6 => "Auction".to_string(), |
638 | | - 7 => "NominationPools".to_string(), |
639 | | - _ => format!("Unknown({})", proxy_type_byte), |
640 | | - } |
| 584 | +) -> Result<String, WasmDotError> { |
| 585 | + let md = metadata.ok_or_else(|| { |
| 586 | + WasmDotError::InvalidTransaction("Metadata required to resolve proxy type".to_string()) |
| 587 | + })?; |
| 588 | + resolve_proxy_type_from_metadata(md, proxy_type_byte).ok_or_else(|| { |
| 589 | + WasmDotError::InvalidTransaction(format!( |
| 590 | + "Unknown proxy type index {} in metadata", |
| 591 | + proxy_type_byte |
| 592 | + )) |
| 593 | + }) |
641 | 594 | } |
642 | 595 |
|
643 | 596 | /// Look up the ProxyType enum variant name from chain metadata. |
@@ -764,47 +717,10 @@ mod tests { |
764 | 717 | } |
765 | 718 |
|
766 | 719 | #[test] |
767 | | - fn test_resolve_hardcoded_polkadot_balances() { |
768 | | - assert_eq!( |
769 | | - resolve_call_hardcoded(5, 3), |
770 | | - ("balances", "transferKeepAlive") |
771 | | - ); |
772 | | - } |
773 | | - |
774 | | - #[test] |
775 | | - fn test_resolve_hardcoded_kusama_balances() { |
776 | | - assert_eq!( |
777 | | - resolve_call_hardcoded(4, 3), |
778 | | - ("balances", "transferKeepAlive") |
779 | | - ); |
780 | | - } |
781 | | - |
782 | | - #[test] |
783 | | - fn test_resolve_hardcoded_westend_balances() { |
784 | | - assert_eq!( |
785 | | - resolve_call_hardcoded(10, 3), |
786 | | - ("balances", "transferKeepAlive") |
787 | | - ); |
788 | | - } |
789 | | - |
790 | | - #[test] |
791 | | - fn test_resolve_hardcoded_unknown() { |
792 | | - assert_eq!(resolve_call_hardcoded(255, 255), ("unknown", "unknown")); |
793 | | - } |
794 | | - |
795 | | - #[test] |
796 | | - fn test_parse_call_data_without_metadata() { |
797 | | - // Polkadot balances::transferKeepAlive (pallet=5, method=3) with a dummy address + amount |
798 | | - let mut call_data = vec![5u8, 3u8]; // pallet=5, method=3 |
799 | | - call_data.push(0x00); // MultiAddress::Id variant |
800 | | - call_data.extend_from_slice(&[0u8; 32]); // dummy pubkey |
801 | | - call_data.push(0x04); // compact amount = 1 |
802 | | - |
803 | | - let result = parse_call_data(&call_data, 42, None).unwrap(); |
804 | | - assert_eq!(result.pallet, "balances"); |
805 | | - assert_eq!(result.name, "transferKeepAlive"); |
806 | | - assert_eq!(result.pallet_index, 5); |
807 | | - assert_eq!(result.method_index, 3); |
| 720 | + fn test_parse_call_data_without_metadata_returns_error() { |
| 721 | + let call_data = vec![5u8, 3u8, 0x00]; |
| 722 | + let result = parse_call_data(&call_data, 42, None); |
| 723 | + assert!(result.is_err()); |
808 | 724 | } |
809 | 725 |
|
810 | 726 | #[test] |
|
0 commit comments