diff --git a/Cargo.lock b/Cargo.lock index bc1c4c44a3..3389bd4513 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15848,7 +15848,6 @@ dependencies = [ "hex", "polkadot-parachain", "polkadot-primitives", - "regex", "sp-core", "sp-runtime", "substrate-build-script-utils", diff --git a/Cargo.toml b/Cargo.toml index f803ea3fae..abf1e25a9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,7 +76,6 @@ tokio = { version = "1.24.2", features = ["macros", "sync"] } url = "2.2.2" jsonrpsee = { version = "0.16.2", features = ["server"] } hex-literal = "0.4.1" -regex = "1.6.0" rlp = "0.5" tracing = "0.1.34" similar-asserts = { version = "1.1.0" } diff --git a/bin/xcm-tools/Cargo.toml b/bin/xcm-tools/Cargo.toml index 1c67590003..3b90bc2df1 100644 --- a/bin/xcm-tools/Cargo.toml +++ b/bin/xcm-tools/Cargo.toml @@ -25,7 +25,6 @@ xcm-builder = { workspace = true, features = ["std"] } xcm-executor = { workspace = true, features = ["std"] } hex = { workspace = true } -regex = { workspace = true } [build-dependencies] substrate-build-script-utils = { workspace = true } diff --git a/bin/xcm-tools/src/cli.rs b/bin/xcm-tools/src/cli.rs index fd922fa51f..979c81e603 100644 --- a/bin/xcm-tools/src/cli.rs +++ b/bin/xcm-tools/src/cli.rs @@ -16,8 +16,6 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . -use regex::Regex; - /// Astar XCM tools. #[derive(Debug, clap::Parser)] #[clap(subcommand_required = true)] @@ -30,21 +28,19 @@ pub struct Cli { /// Possible subcommands of the main binary. #[derive(Debug, clap::Subcommand)] pub enum Subcommand { - /// Prints relay-chain AccountId + /// Prints relay-chain SS58 account Id RelayChainAccount, - /// Prints parachain AccountId. - ParachainAccount(ParachainAccountCmd), + /// Prints parachains sovereign SS58 account Id. + SovereignAccount(SovereignAccountCmd), /// Prints AssetId for desired parachain asset. AssetId(AssetIdCmd), - /// Prints AccountId32 for the derived multilocation. - /// In case parachain-id is provided, multilocation is in format { parents: 1, X2(Parachain, AccountId32) }. - /// In case parachain-id is omitted, multilocation is in format { parents: 1, X1(AccountId32) }. - AccountId32(AccountId32Cmd), + /// Prints derived remote SS58 account for the derived multilocation. + RemoteAccount(RemoteAccountCmd), } /// Helper that prints AccountId of parachain. #[derive(Debug, clap::Parser)] -pub struct ParachainAccountCmd { +pub struct SovereignAccountCmd { /// Print address for sibling parachain [child by default]. #[clap(short)] pub sibling: bool, @@ -63,33 +59,79 @@ pub struct AssetIdCmd { /// Helper that prints the derived AccountId32 value for the multilocation. #[derive(Debug, clap::Parser)] -pub struct AccountId32Cmd { +pub struct RemoteAccountCmd { /// Parachain id in case sender is from a sibling parachain. #[clap(short, long, default_value = None)] pub parachain_id: Option, - /// AccountId32 (SS58 scheme, public key) of the sender account. - #[clap(short, long, value_parser = account_id_32_parser)] - pub account_id_32: [u8; 32], + /// Public key (SS58 or H160) in hex format. Must be either 32 or 20 bytes long. + #[clap(short, long)] + pub account_key: AccountWrapper, +} + +#[derive(Debug, Clone)] +pub struct AccountWrapper { + account: [u8; 32], + is_32: bool, } -/// Used to parse AccountId32 as [u8; 32] from the received string. -fn account_id_32_parser(account_str: &str) -> Result<[u8; 32], String> { - let re = Regex::new(r"^0x([0-9a-fA-F]{64})$").map_err(|e| e.to_string())?; - if !re.is_match(account_str) { - return Err( - "Invalid AccountId32 received. Expected format is '0x1234...4321' (64 hex digits)." - .into(), - ); +impl AccountWrapper { + /// Get AccountId32 public key (SS58) or error if it is not 32 bytes long. + pub fn get_account_id_32(&self) -> Result<[u8; 32], &str> { + if self.is_32 { + Ok(self.account) + } else { + Err("Account is not 32 bytes long") + } } - let hex_acc = re - .captures(account_str) - .expect("Regex match confirmed above.") - .get(1) - .expect("Group 1 confirmed in match above.") - .as_str(); - let decoded_hex = hex::decode(hex_acc).expect("Regex ensures correctness; infallible."); + /// Get AccountKey20 public key (H160) or error if it is not 20 bytes long. + pub fn get_account_key_20(&self) -> Result<[u8; 20], &str> { + if !self.is_32 { + let mut account = [0u8; 20]; + account.copy_from_slice(&self.account[0..20]); + Ok(account) + } else { + Err("Account is not 20 bytes long") + } + } - TryInto::<[u8; 32]>::try_into(decoded_hex) - .map_err(|_| "Failed to create [u8; 32] array from received account Id string.".into()) + /// `true` if AccountId32, `false` if AccountKey20. + pub fn is_32_bytes(&self) -> bool { + self.is_32 + } +} + +impl std::str::FromStr for AccountWrapper { + type Err = String; + + fn from_str(account_pub_key: &str) -> Result { + if let Some(rest) = account_pub_key.strip_prefix("0x") { + if let Some(pos) = rest.chars().position(|c| !c.is_ascii_hexdigit()) { + Err(format!( + "Expected account public key in hex format, found illegal hex character at position: {}", + 2 + pos, + )) + } else { + if rest.len() == 40 { + let mut account = [0u8; 32]; + account[0..20].copy_from_slice(&hex::decode(rest).unwrap()); + Ok(AccountWrapper { + account, + is_32: false, + }) + } else if rest.len() == 64 { + let mut account = [0u8; 32]; + account.copy_from_slice(&hex::decode(rest).unwrap()); + Ok(AccountWrapper { + account, + is_32: true, + }) + } else { + Err("Account key should be 20 or 32 bytes long".into()) + } + } + } else { + Err("Account key should start with '0x'".into()) + } + } } diff --git a/bin/xcm-tools/src/command.rs b/bin/xcm-tools/src/command.rs index c32127f792..feff5593bd 100644 --- a/bin/xcm-tools/src/command.rs +++ b/bin/xcm-tools/src/command.rs @@ -45,7 +45,7 @@ pub fn run() -> Result<(), Error> { ParentIsPreset::::convert_ref(&MultiLocation::parent()).unwrap(); println!("{}", relay_account); } - Some(Subcommand::ParachainAccount(cmd)) => { + Some(Subcommand::SovereignAccount(cmd)) => { let parachain_account = if cmd.sibling { let location = MultiLocation { parents: 1, @@ -66,7 +66,7 @@ pub fn run() -> Result<(), Error> { println!("pallet_assets: {}", cmd.asset_id); println!("EVM XC20: 0x{}", HexDisplay::from(&data)); } - Some(Subcommand::AccountId32(cmd)) => { + Some(Subcommand::RemoteAccount(cmd)) => { let mut sender_multilocation = MultiLocation::parent(); if let Some(parachain_id) = cmd.parachain_id { @@ -75,13 +75,23 @@ pub fn run() -> Result<(), Error> { .expect("infallible, short sequence"); } - sender_multilocation - .append_with(X1(AccountId32 { - id: cmd.account_id_32, - // network is not relevant for account derivation - network: None, - })) - .expect("infallible, short sequence"); + if cmd.account_key.is_32_bytes() { + sender_multilocation + .append_with(X1(AccountId32 { + id: cmd.account_key.get_account_id_32().unwrap(), + // network is not relevant for account derivation + network: None, + })) + .expect("infallible, short sequence"); + } else { + sender_multilocation + .append_with(X1(AccountKey20 { + key: cmd.account_key.get_account_key_20().unwrap(), + // network is not relevant for account derivation + network: None, + })) + .expect("infallible, short sequence"); + } let derived_acc = HashedDescription::>::convert(