From b8beaec5566741b1bf0c41594552c3dc48297d43 Mon Sep 17 00:00:00 2001 From: satoshiotomakan <127754187+satoshiotomakan@users.noreply.github.com> Date: Wed, 22 Nov 2023 16:54:19 +0100 Subject: [PATCH] [Rust]: Codegen tools (#3566) --- codegen-v2/Cargo.lock | 82 ++++- codegen-v2/Cargo.toml | 3 + codegen-v2/src/codegen/mod.rs | 1 + .../src/codegen/rust/blockchain_type.rs | 133 ++++++++ codegen-v2/src/codegen/rust/coin_crate.rs | 88 ++++++ codegen-v2/src/codegen/rust/coin_id.rs | 46 +++ .../codegen/rust/coin_integration_tests.rs | 133 ++++++++ codegen-v2/src/codegen/rust/mod.rs | 96 ++++++ codegen-v2/src/codegen/rust/new_blockchain.rs | 30 ++ codegen-v2/src/codegen/rust/toml_editor.rs | 112 +++++++ codegen-v2/src/lib.rs | 17 ++ codegen-v2/src/main.rs | 13 + codegen-v2/src/utils.rs | 123 ++++++++ rust/Cargo.lock | 34 +++ rust/tw_any_coin/Cargo.toml | 2 +- rust/tw_any_coin/src/ffi/tw_any_address.rs | 8 + rust/tw_any_coin/src/ffi/tw_any_signer.rs | 3 + rust/tw_any_coin/src/ffi/tw_message_signer.rs | 5 + .../src/ffi/tw_transaction_compiler.rs | 3 + .../src/test_utils/address_utils.rs | 105 +++++++ rust/tw_any_coin/src/test_utils/mod.rs | 11 +- .../tests/chains/aptos/aptos_address.rs | 73 +++++ rust/tw_any_coin/tests/chains/aptos/mod.rs | 1 + .../tests/chains/bitcoin/bitcoin_address.rs | 49 +++ rust/tw_any_coin/tests/chains/bitcoin/mod.rs | 7 + .../tests/chains/cosmos/cosmos_address.rs | 83 +++++ .../tests/chains/cosmos/cosmos_sign.rs | 65 ++++ rust/tw_any_coin/tests/chains/cosmos/mod.rs | 8 + .../tests/chains/ethereum/ethereum_address.rs | 48 +++ .../ethereum/ethereum_compile.rs} | 16 +- .../ethereum/ethereum_message_sign.rs} | 18 +- .../tests/chains/ethereum/ethereum_sign.rs | 56 ++++ rust/tw_any_coin/tests/chains/ethereum/mod.rs | 10 + .../internet_computer_address.rs | 64 ++++ .../tests/chains/internet_computer/mod.rs | 7 + rust/tw_any_coin/tests/chains/mod.rs | 4 + .../tests/chains/native_evmos/mod.rs | 1 + .../native_evmos/native_evmos_address.rs | 71 +++++ .../tests/chains/native_injective/mod.rs | 5 +- .../native_injective_address.rs | 71 +++++ .../tw_any_coin/tests/chains/thorchain/mod.rs | 3 +- .../chains/thorchain/thorchain_address.rs | 75 +++++ .../chains/thorchain/thorchain_compile.rs | 6 +- .../tests/chains/thorchain/thorchain_sign.rs | 11 +- .../tests/coin_address_derivation_tests.rs | 167 ++++++++++ .../tests/tw_any_address_ffi_tests.rs | 287 ------------------ .../tests/tw_any_signer_ffi_tests.rs | 101 ------ rust/tw_coin_registry/Cargo.toml | 7 + rust/tw_coin_registry/build.rs | 105 +++++++ rust/tw_coin_registry/src/blockchain_type.rs | 39 +-- rust/tw_coin_registry/src/dispatcher.rs | 15 +- rust/tw_coin_registry/src/lib.rs | 5 +- rust/tw_coin_registry/src/registry.rs | 7 + rust/wallet_core_rs/src/ffi/ethereum/abi.rs | 15 +- rust/wallet_core_rs/src/ffi/ethereum/rlp.rs | 6 +- rust/wallet_core_rs/tests/ethereum_abi.rs | 13 +- rust/wallet_core_rs/tests/ethereum_rlp.rs | 12 +- 57 files changed, 2086 insertions(+), 493 deletions(-) create mode 100644 codegen-v2/src/codegen/rust/blockchain_type.rs create mode 100644 codegen-v2/src/codegen/rust/coin_crate.rs create mode 100644 codegen-v2/src/codegen/rust/coin_id.rs create mode 100644 codegen-v2/src/codegen/rust/coin_integration_tests.rs create mode 100644 codegen-v2/src/codegen/rust/mod.rs create mode 100644 codegen-v2/src/codegen/rust/new_blockchain.rs create mode 100644 codegen-v2/src/codegen/rust/toml_editor.rs create mode 100644 codegen-v2/src/utils.rs create mode 100644 rust/tw_any_coin/src/test_utils/address_utils.rs create mode 100644 rust/tw_any_coin/tests/chains/aptos/aptos_address.rs create mode 100644 rust/tw_any_coin/tests/chains/bitcoin/bitcoin_address.rs create mode 100644 rust/tw_any_coin/tests/chains/bitcoin/mod.rs create mode 100644 rust/tw_any_coin/tests/chains/cosmos/cosmos_address.rs create mode 100644 rust/tw_any_coin/tests/chains/cosmos/cosmos_sign.rs create mode 100644 rust/tw_any_coin/tests/chains/cosmos/mod.rs create mode 100644 rust/tw_any_coin/tests/chains/ethereum/ethereum_address.rs rename rust/tw_any_coin/tests/{tw_transaction_compiler_ffi_tests.rs => chains/ethereum/ethereum_compile.rs} (92%) rename rust/tw_any_coin/tests/{tw_message_signer_ffi_tests.rs => chains/ethereum/ethereum_message_sign.rs} (89%) create mode 100644 rust/tw_any_coin/tests/chains/ethereum/ethereum_sign.rs create mode 100644 rust/tw_any_coin/tests/chains/ethereum/mod.rs create mode 100644 rust/tw_any_coin/tests/chains/internet_computer/internet_computer_address.rs create mode 100644 rust/tw_any_coin/tests/chains/internet_computer/mod.rs create mode 100644 rust/tw_any_coin/tests/chains/native_evmos/native_evmos_address.rs create mode 100644 rust/tw_any_coin/tests/chains/native_injective/native_injective_address.rs create mode 100644 rust/tw_any_coin/tests/chains/thorchain/thorchain_address.rs create mode 100644 rust/tw_any_coin/tests/coin_address_derivation_tests.rs delete mode 100644 rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs create mode 100644 rust/tw_coin_registry/build.rs diff --git a/codegen-v2/Cargo.lock b/codegen-v2/Cargo.lock index 32a0596b5ae..254400df25c 100644 --- a/codegen-v2/Cargo.lock +++ b/codegen-v2/Cargo.lock @@ -27,11 +27,23 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "codegen-v2" version = "0.1.0" dependencies = [ + "convert_case", "handlebars", "heck", + "pathdiff", "serde", "serde_json", "serde_yaml", + "toml_edit", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", ] [[package]] @@ -63,6 +75,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "generic-array" version = "0.14.7" @@ -93,6 +111,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" + [[package]] name = "heck" version = "0.4.1" @@ -106,7 +130,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown 0.14.2", ] [[package]] @@ -130,12 +164,24 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + [[package]] name = "once_cell" version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "pest" version = "2.5.7" @@ -241,7 +287,7 @@ version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c" dependencies = [ - "indexmap", + "indexmap 1.9.3", "itoa", "ryu", "serde", @@ -290,6 +336,23 @@ dependencies = [ "syn", ] +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap 2.1.0", + "toml_datetime", + "winnow", +] + [[package]] name = "typenum" version = "1.16.0" @@ -308,6 +371,12 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unsafe-libyaml" version = "0.2.8" @@ -319,3 +388,12 @@ name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winnow" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +dependencies = [ + "memchr", +] diff --git a/codegen-v2/Cargo.toml b/codegen-v2/Cargo.toml index 7598a3ad756..e024e4024ff 100644 --- a/codegen-v2/Cargo.toml +++ b/codegen-v2/Cargo.toml @@ -12,8 +12,11 @@ name = "parser" path = "src/main.rs" [dependencies] +convert_case = "0.6.0" +pathdiff = "0.2.1" serde = { version = "1.0.159", features = ["derive"] } serde_json = "1.0.95" serde_yaml = "0.9.21" +toml_edit = "0.21.0" handlebars = "4.3.6" heck = "0.4.1" diff --git a/codegen-v2/src/codegen/mod.rs b/codegen-v2/src/codegen/mod.rs index 52487305973..b0d31c99e15 100644 --- a/codegen-v2/src/codegen/mod.rs +++ b/codegen-v2/src/codegen/mod.rs @@ -4,4 +4,5 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +pub mod rust; pub mod swift; diff --git a/codegen-v2/src/codegen/rust/blockchain_type.rs b/codegen-v2/src/codegen/rust/blockchain_type.rs new file mode 100644 index 00000000000..debce4802d6 --- /dev/null +++ b/codegen-v2/src/codegen/rust/blockchain_type.rs @@ -0,0 +1,133 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::rust::toml_editor::Dependencies; +use crate::codegen::rust::{rust_source_directory, CoinItem}; +use crate::utils::FileContent; +use crate::Result; +use std::path::{Path, PathBuf}; + +const BLOCKCHAIN_TYPE_START: &str = "start_of_blockchain_type"; +const BLOCKCHAIN_TYPE_END: &str = "end_of_blockchain_type"; +const BLOCKCHAIN_ENTRIES_START: &str = "start_of_blockchain_entries"; +const BLOCKCHAIN_ENTRIES_END: &str = "end_of_blockchain_entries"; +const BLOCKCHAIN_DISPATCHER_START: &str = "start_of_blockchain_dispatcher"; +const BLOCKCHAIN_DISPATCHER_END: &str = "end_of_blockchain_dispatcher"; + +pub fn coin_registry_directory() -> PathBuf { + rust_source_directory().join("tw_coin_registry") +} + +pub fn blockchain_type_path() -> PathBuf { + coin_registry_directory() + .join("src") + .join("blockchain_type.rs") +} + +pub fn dispatcher_path() -> PathBuf { + coin_registry_directory().join("src").join("dispatcher.rs") +} + +pub struct CoinRegistry { + coin: CoinItem, +} + +impl CoinRegistry { + pub fn new(coin: CoinItem) -> CoinRegistry { + CoinRegistry { coin } + } + + pub fn add(self, path_to_new_blockchain_crate: &Path) -> Result<()> { + self.add_blockchain_crate_to_manifest_file(path_to_new_blockchain_crate)?; + self.add_blockchain_variant()?; + self.add_use_of_blockchain_entry()?; + self.add_blockchain_entry()?; + self.add_blockchain_dispatcher() + } + + fn add_blockchain_crate_to_manifest_file( + &self, + path_to_new_blockchain_crate: &Path, + ) -> Result<()> { + let path_to_cargo_manifest = coin_registry_directory().join("Cargo.toml"); + Dependencies::new(path_to_cargo_manifest).insert_dependency( + &self.coin.id.to_tw_crate_name(), + path_to_new_blockchain_crate, + ) + } + + fn add_blockchain_variant(&self) -> Result<()> { + let blockchain_type_rs_path = blockchain_type_path(); + let blockchain_type = self.coin.blockchain_type(); + + let mut blockchain_type_rs = FileContent::read(blockchain_type_rs_path)?; + + { + let mut enum_region = blockchain_type_rs + .find_region_with_comments(BLOCKCHAIN_TYPE_START, BLOCKCHAIN_TYPE_END)?; + enum_region.push_line(format!(" {blockchain_type},")); + enum_region.sort(); + } + + blockchain_type_rs.write() + } + + fn add_use_of_blockchain_entry(&self) -> Result<()> { + let dispatcher_rs_path = dispatcher_path(); + let blockchain_entry = self.coin.blockchain_entry(); + let tw_crate_name = self.coin.id.to_tw_crate_name(); + + let mut dispatcher_rs = FileContent::read(dispatcher_rs_path)?; + + { + let import_pattern = "use "; + let mut last_entry = dispatcher_rs.rfind_line(|line| line.contains(import_pattern))?; + last_entry.push_line_after(format!("use {tw_crate_name}::entry::{blockchain_entry};")); + } + + dispatcher_rs.write() + } + + fn add_blockchain_entry(&self) -> Result<()> { + let dispatcher_rs_path = dispatcher_path(); + let blockchain_entry = self.coin.blockchain_entry(); + let blockchain_entry_const = self.coin.blockchain_entry_upper_snake(); + + let mut dispatcher_rs = FileContent::read(dispatcher_rs_path)?; + + { + let mut entries_region = dispatcher_rs + .find_region_with_comments(BLOCKCHAIN_ENTRIES_START, BLOCKCHAIN_ENTRIES_END)?; + entries_region.push_line(format!( + "const {blockchain_entry_const}: {blockchain_entry} = {blockchain_entry};" + )); + entries_region.sort(); + } + + dispatcher_rs.write() + } + + fn add_blockchain_dispatcher(&self) -> Result<()> { + let dispatcher_rs_path = dispatcher_path(); + let blockchain_type = self.coin.blockchain_type(); + let blockchain_entry_const = self.coin.blockchain_entry_upper_snake(); + + let mut dispatcher_rs = FileContent::read(dispatcher_rs_path)?; + + { + let mut dispatcher_region = dispatcher_rs.find_region_with_comments( + BLOCKCHAIN_DISPATCHER_START, + BLOCKCHAIN_DISPATCHER_END, + )?; + dispatcher_region.push_line(format!( + " BlockchainType::{blockchain_type} => Ok(&{blockchain_entry_const})," + )); + dispatcher_region.sort(); + } + + dispatcher_rs.write() + } +} diff --git a/codegen-v2/src/codegen/rust/coin_crate.rs b/codegen-v2/src/codegen/rust/coin_crate.rs new file mode 100644 index 00000000000..19a957d9978 --- /dev/null +++ b/codegen-v2/src/codegen/rust/coin_crate.rs @@ -0,0 +1,88 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::rust::coin_id::CoinId; +use crate::codegen::rust::{chains_directory, rs_header, CoinItem}; +use crate::{Error, Result}; +use std::path::PathBuf; +use std::{fs, io}; + +pub fn coin_source_directory(id: &CoinId) -> PathBuf { + chains_directory().join(id.to_tw_crate_name()) +} + +pub struct CoinCrate { + coin: CoinItem, +} + +impl CoinCrate { + pub fn new(coin: CoinItem) -> CoinCrate { + CoinCrate { coin } + } + + /// Creates a Cargo crate with `entry.rs` file. + /// Returns the path to the create crate. + pub fn create(self) -> Result { + let header = rs_header(); + + let blockchain_path = coin_source_directory(&self.coin.id); + let blockchain_toml_path = blockchain_path.join("Cargo.toml"); + + let blockchain_src_path = blockchain_path.join("src"); + let blockchain_lib_rs_path = blockchain_src_path.join("lib.rs"); + let blockchain_entry_path = blockchain_src_path.join("entry.rs"); + + let tw_crate_name = self.coin.id.to_tw_crate_name(); + let blockchain_name = self.coin.blockchain_type(); + + if blockchain_path.exists() { + return Err(Error::IoError(io::Error::new( + io::ErrorKind::AlreadyExists, + "blockchain already exists", + ))); + } + + fs::create_dir(&blockchain_path)?; + fs::create_dir(&blockchain_src_path)?; + + let blockchain_toml = format!( + r#"[package] +name = "{tw_crate_name}" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = {{ path = "../../tw_coin_entry" }} +tw_proto = {{ path = "../../tw_proto" }} +"# + ); + fs::write(blockchain_toml_path, blockchain_toml)?; + + let blockchain_lib_rs = format!( + r#"{header} + +pub mod entry; +"# + ); + fs::write(blockchain_lib_rs_path, blockchain_lib_rs)?; + + let blockchain_entry = format!( + r#"{header} + +use tw_coin_entry::coin_entry::CoinEntry; + +pub struct {blockchain_name}Entry; + +impl CoinEntry for {blockchain_name}Entry {{ + // TODO declare associated types and implement methods from the 'CoinEntry' trait. +}} +"# + ); + fs::write(blockchain_entry_path, blockchain_entry)?; + + Ok(blockchain_path) + } +} diff --git a/codegen-v2/src/codegen/rust/coin_id.rs b/codegen-v2/src/codegen/rust/coin_id.rs new file mode 100644 index 00000000000..6f0eaed7c36 --- /dev/null +++ b/codegen-v2/src/codegen/rust/coin_id.rs @@ -0,0 +1,46 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::{Error, Result}; +use serde::de::Error as SerdeError; +use serde::{Deserialize, Deserializer}; + +#[derive(Clone, Eq, PartialEq)] +pub struct CoinId(String); + +impl CoinId { + /// Returns `Ok` if only the given `id` is a valid Rust identifier. + pub fn new(id: String) -> Result { + let first_letter = id + .chars() + .next() + .ok_or(Error::RegistryError("Invalid 'id'".to_string()))?; + let valid_chars = id.chars().all(|ch| ch.is_ascii_alphanumeric() || ch == '_'); + + if first_letter.is_numeric() || !valid_chars { + return Err(Error::RegistryError("Invalid 'id'".to_string())); + } + Ok(CoinId(id)) + } + + pub fn to_tw_crate_name(&self) -> String { + format!("tw_{}", self.0) + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl<'de> Deserialize<'de> for CoinId { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let id = String::deserialize(deserializer)?; + CoinId::new(id).map_err(|e| SerdeError::custom(format!("{e:?}"))) + } +} diff --git a/codegen-v2/src/codegen/rust/coin_integration_tests.rs b/codegen-v2/src/codegen/rust/coin_integration_tests.rs new file mode 100644 index 00000000000..3fde3ff1795 --- /dev/null +++ b/codegen-v2/src/codegen/rust/coin_integration_tests.rs @@ -0,0 +1,133 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::rust::coin_id::CoinId; +use crate::codegen::rust::{rs_header, tw_any_coin_directory, CoinItem}; +use crate::utils::FileContent; +use crate::{Error, Result}; +use std::fs; +use std::path::PathBuf; + +pub fn chains_integration_tests_directory() -> PathBuf { + tw_any_coin_directory().join("tests").join("chains") +} + +pub fn coin_integration_tests_directory(id: &CoinId) -> PathBuf { + chains_integration_tests_directory().join(id.as_str()) +} + +pub struct CoinIntegrationTests { + coin: CoinItem, +} + +impl CoinIntegrationTests { + pub fn new(coin: CoinItem) -> CoinIntegrationTests { + CoinIntegrationTests { coin } + } + + pub fn create(self) -> Result { + let blockchain_tests_path = self.coin_tests_directory(); + if blockchain_tests_path.exists() { + return Ok(blockchain_tests_path); + } + + fs::create_dir(&blockchain_tests_path)?; + + self.list_blockchain_in_chains_mod()?; + self.create_address_tests()?; + self.create_sign_tests()?; + self.create_chain_tests_mod_rs()?; + + Ok(blockchain_tests_path) + } + + fn coin_tests_directory(&self) -> PathBuf { + coin_integration_tests_directory(&self.coin.id) + } + + fn create_address_tests(&self) -> Result<()> { + let header = rs_header(); + let chain_id = self.coin.id.as_str(); + let address_tests_path = self + .coin_tests_directory() + .join(format!("{chain_id}_address.rs")); + + let address_tests_rs = format!( + r#"{header} + +#[test] +fn test_{chain_id}_address_normalization() {{ + todo!() +}} + +#[test] +fn test_{chain_id}_address_is_valid() {{ + todo!() +}} + +#[test] +fn test_{chain_id}_address_invalid() {{ + todo!() +}} + +#[test] +fn test_{chain_id}_address_get_data() {{ + todo!() +}} +"# + ); + fs::write(address_tests_path, address_tests_rs).map_err(Error::from) + } + + fn create_sign_tests(&self) -> Result<()> { + let header = rs_header(); + let chain_id = self.coin.id.as_str(); + let sign_tests_path = self + .coin_tests_directory() + .join(format!("{chain_id}_sign.rs")); + + let sign_tests_rs = format!( + r#"{header} + +#[test] +fn test_{chain_id}_sign() {{ + todo!() +}} +"# + ); + fs::write(sign_tests_path, sign_tests_rs).map_err(Error::from) + } + + fn create_chain_tests_mod_rs(&self) -> Result<()> { + let header = rs_header(); + let chain_id = self.coin.id.as_str(); + let blockchain_tests_mod_path = self.coin_tests_directory().join("mod.rs"); + + let blockchain_mod_rs = format!( + r#"{header} + +mod {chain_id}_address; +mod {chain_id}_sign; +"# + ); + fs::write(blockchain_tests_mod_path, blockchain_mod_rs).map_err(Error::from) + } + + fn list_blockchain_in_chains_mod(&self) -> Result<()> { + let chains_mod_path = chains_integration_tests_directory().join("mod.rs"); + let chain_id = self.coin.id.as_str(); + + let mut chains_mod_rs = FileContent::read(chains_mod_path)?; + + { + let mod_pattern = "mod "; + let mut last_mod = chains_mod_rs.rfind_line(|line| line.starts_with(mod_pattern))?; + last_mod.push_line_after(format!("mod {chain_id};")); + } + + chains_mod_rs.write() + } +} diff --git a/codegen-v2/src/codegen/rust/mod.rs b/codegen-v2/src/codegen/rust/mod.rs new file mode 100644 index 00000000000..b0d1a55910b --- /dev/null +++ b/codegen-v2/src/codegen/rust/mod.rs @@ -0,0 +1,96 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::rust::coin_id::CoinId; +use crate::{current_year, Error, Result}; +use convert_case::{Case, Casing}; +use std::path::PathBuf; +use std::{env, fs}; + +pub mod blockchain_type; +pub mod coin_crate; +pub mod coin_id; +pub mod coin_integration_tests; +pub mod new_blockchain; +pub mod toml_editor; + +pub fn rust_source_directory() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("rust") +} + +pub fn chains_directory() -> PathBuf { + rust_source_directory().join("chains") +} + +pub fn tw_any_coin_directory() -> PathBuf { + rust_source_directory().join("tw_any_coin") +} + +pub fn workspace_toml_path() -> PathBuf { + rust_source_directory().join("Cargo.toml") +} + +pub fn registry_json_path() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("registry.json") +} + +pub fn rs_header() -> String { + let current_year = current_year(); + format!( + r#"// Copyright © 2017-{current_year} Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree."# + ) +} + +#[derive(Clone, Deserialize)] +pub struct CoinItem { + pub id: CoinId, + pub name: String, + pub blockchain: String, +} + +impl CoinItem { + /// Transforms a coin name to a Rust name. + /// https://github.com/trustwallet/wallet-core/blob/3769f31b7d0c75126b2f426bb065364429aaa379/codegen/lib/coin_skeleton_gen.rb#L15-L22 + pub fn coin_type(&self) -> String { + self.name.replace([' ', '.', '-'], "") + } + + /// Returns the blockchain type in `UpperCamel` case. + pub fn blockchain_type(&self) -> String { + self.blockchain.to_case(Case::UpperCamel) + } + + /// Returns the blockchain type in `UPPER_SNAKE` case. + pub fn blockchain_entry_upper_snake(&self) -> String { + self.blockchain.to_case(Case::UpperSnake) + } + + /// Returns a Rust blockchain entry of the blockchain. + pub fn blockchain_entry(&self) -> String { + format!("{}Entry", self.blockchain_type()) + } +} + +pub(crate) fn read_coin_from_registry(coin: &CoinId) -> Result { + let registry_path = registry_json_path(); + + let registry_bytes = fs::read(registry_path)?; + let coins: Vec = + serde_json::from_slice(®istry_bytes).map_err(|e| Error::RegistryError(e.to_string()))?; + + coins + .into_iter() + .find(|item| item.id == *coin) + .ok_or(Error::InvalidCommand) +} diff --git a/codegen-v2/src/codegen/rust/new_blockchain.rs b/codegen-v2/src/codegen/rust/new_blockchain.rs new file mode 100644 index 00000000000..252bbcc92da --- /dev/null +++ b/codegen-v2/src/codegen/rust/new_blockchain.rs @@ -0,0 +1,30 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::codegen::rust::blockchain_type::CoinRegistry; +use crate::codegen::rust::coin_crate::CoinCrate; +use crate::codegen::rust::coin_id::CoinId; +use crate::codegen::rust::coin_integration_tests::CoinIntegrationTests; +use crate::codegen::rust::toml_editor::Workspace; +use crate::codegen::rust::{read_coin_from_registry, workspace_toml_path}; +use crate::Result; + +pub fn new_blockchain(coin: &str) -> Result<()> { + let coin_id = CoinId::new(coin.to_string())?; + let coin_item = read_coin_from_registry(&coin_id)?; + + // Create blockchain's crate. + let blockchain_crate_path = CoinCrate::new(coin_item.clone()).create()?; + + // Insert the created crate to the workspace. + Workspace::new(workspace_toml_path()).insert_crate(&blockchain_crate_path)?; + // Create integration tests. + CoinIntegrationTests::new(coin_item.clone()).create()?; + // Add the new blockchain to the `tw_coin_registry`. + CoinRegistry::new(coin_item).add(&blockchain_crate_path)?; + + Ok(()) +} diff --git a/codegen-v2/src/codegen/rust/toml_editor.rs b/codegen-v2/src/codegen/rust/toml_editor.rs new file mode 100644 index 00000000000..da33d5fe270 --- /dev/null +++ b/codegen-v2/src/codegen/rust/toml_editor.rs @@ -0,0 +1,112 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::{Error, Result}; +use std::fs; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use toml_edit::{Document, InlineTable, Item, Value}; + +const NEW_LINE_TAB_DECORATOR: &str = "\n "; +const NO_DECORATOR: &str = ""; + +pub struct Workspace { + path_to_toml: PathBuf, +} + +impl Workspace { + pub fn new(path_to_toml: PathBuf) -> Workspace { + Workspace { path_to_toml } + } + + pub fn insert_crate(self, path_to_crate: &Path) -> Result<()> { + let manifest = fs::read_to_string(&self.path_to_toml)?; + let mut manifest = Document::from_str(&manifest)?; + + let members = manifest["workspace"]["members"] + .as_array_mut() + .ok_or(Error::TomlFormat( + "Invalid 'workspace' TOML format".to_string(), + ))?; + + // Try to get a path to the crate relative to the `Cargo.toml`. + let relative_path_to_crate = relative_path_to_crate(&self.path_to_toml, path_to_crate)?; + + // Push the new member, sort and save the manifest. + + let relative_path_to_crate_decorated = Value::from(relative_path_to_crate.to_string()) + .decorated(NEW_LINE_TAB_DECORATOR, NO_DECORATOR); + + members.push_formatted(relative_path_to_crate_decorated); + members.sort_by(|x, y| x.as_str().cmp(&y.as_str())); + + fs::write(self.path_to_toml, manifest.to_string())?; + Ok(()) + } +} + +pub struct Dependencies { + path_to_toml: PathBuf, +} + +impl Dependencies { + pub fn new(path_to_toml: PathBuf) -> Dependencies { + Dependencies { path_to_toml } + } + + pub fn insert_dependency(self, dep_name: &str, path_to_dep_crate: &Path) -> Result<()> { + let manifest = fs::read_to_string(&self.path_to_toml)?; + let mut manifest = Document::from_str(&manifest)?; + + let dependencies = manifest["dependencies"] + .as_table_like_mut() + .ok_or(Error::TomlFormat("Invalid 'Cargo.toml' format".to_string()))?; + + // Try to get a path to the crate relative to the `Cargo.toml`. + let relative_path_to_crate = relative_path_to_crate(&self.path_to_toml, path_to_dep_crate)?; + + // Create the new dependency member (aka a TOML inline table with `path` key-value). + let mut new_member = InlineTable::new(); + new_member.insert("path", relative_path_to_crate.into()); + + // Push the new member, sort and save the manifest. + dependencies.insert(dep_name, Item::Value(Value::InlineTable(new_member).into())); + dependencies.sort_values(); + + fs::write(self.path_to_toml, manifest.to_string())?; + + Ok(()) + } +} + +/// Returns a path to the dependency accordingly to the Cargo manifest file. +/// The result string can be put to `Cargo.toml` as: +/// ```toml +/// tw_foo = { path = "" } +/// ``` +fn relative_path_to_crate( + path_to_cargo_manifest: &Path, + path_to_dependency: &Path, +) -> Result { + let absolute_path_to_crate_directory = path_to_cargo_manifest + .parent() + .ok_or_else(|| Error::io_error_other("Cannot get a parent directory".to_string()))? + .canonicalize()?; + let absolute_path_to_dependency = path_to_dependency.canonicalize()?; + + let relative_path_to_dependency = pathdiff::diff_paths( + absolute_path_to_dependency, + absolute_path_to_crate_directory, + ) + .ok_or_else(|| { + Error::io_error_other("Cannot get a relative path to the dependency".to_string()) + })? + .to_str() + .ok_or_else(|| Error::io_error_other("Invalid path to the crate".to_string()))? + .to_string(); + + Ok(relative_path_to_dependency) +} diff --git a/codegen-v2/src/lib.rs b/codegen-v2/src/lib.rs index ca6556bb44d..788f82cda8a 100644 --- a/codegen-v2/src/lib.rs +++ b/codegen-v2/src/lib.rs @@ -9,12 +9,15 @@ extern crate serde; use handlebars::{RenderError, TemplateError}; use serde_yaml::Error as YamlError; +use std::io; use std::io::Error as IoError; +use toml_edit::TomlError; pub mod codegen; pub mod manifest; #[cfg(test)] mod tests; +pub mod utils; pub type Result = std::result::Result; @@ -25,9 +28,17 @@ pub enum Error { RenderError(RenderError), TemplateError(TemplateError), BadFormat(String), + RegistryError(String), + TomlFormat(String), InvalidCommand, } +impl Error { + pub fn io_error_other(err: String) -> Error { + Error::IoError(IoError::new(io::ErrorKind::Other, err)) + } +} + impl From for Error { fn from(err: IoError) -> Self { Error::IoError(err) @@ -52,6 +63,12 @@ impl From for Error { } } +impl From for Error { + fn from(err: TomlError) -> Self { + Error::TomlFormat(err.to_string()) + } +} + fn current_year() -> u64 { use std::time::{SystemTime, UNIX_EPOCH}; diff --git a/codegen-v2/src/main.rs b/codegen-v2/src/main.rs index 1be255b2f20..39e5da606cf 100644 --- a/codegen-v2/src/main.rs +++ b/codegen-v2/src/main.rs @@ -4,6 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +use libparser::codegen::rust::new_blockchain::new_blockchain; use libparser::codegen::swift::RenderIntput; use libparser::manifest::parse_dir; use libparser::{Error, Result}; @@ -18,6 +19,18 @@ fn main() -> Result<()> { match args[1].as_str() { "swift" => generate_swift_bindings(), + "rust" => generate_rust(&args[2..]), + _ => Err(Error::InvalidCommand), + } +} + +fn generate_rust(args: &[String]) -> Result<()> { + if args.len() < 2 { + return Err(Error::InvalidCommand); + } + + match args[0].as_str() { + "new-blockchain" => new_blockchain(&args[1]), _ => Err(Error::InvalidCommand), } } diff --git a/codegen-v2/src/utils.rs b/codegen-v2/src/utils.rs new file mode 100644 index 00000000000..bb4b01d2902 --- /dev/null +++ b/codegen-v2/src/utils.rs @@ -0,0 +1,123 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::{Error, Result}; +use std::fs; +use std::path::{Path, PathBuf}; + +pub fn read_lines>(path: P) -> Result> { + let lines = fs::read_to_string(path)? + .split('\n') + .map(|line| line.to_string()) + .collect(); + Ok(lines) +} + +pub fn write_lines>(path: P, lines: Vec) -> Result<()> { + let content = lines.join("\n"); + fs::write(path, content).map_err(Error::from) +} + +pub struct FileContent { + path: PathBuf, + lines: Vec, +} + +impl FileContent { + pub fn read(path: PathBuf) -> Result { + read_lines(&path).map(|lines| FileContent { path, lines }) + } + + pub fn find_region_with_comments( + &mut self, + start_comment: &str, + end_comment: &str, + ) -> Result> { + // Find the position of the `start_comment`. + let start_comment_at = self + .lines + .iter() + .position(|line| line.contains(start_comment)) + .ok_or_else(|| { + Error::io_error_other(format!("Cannot find the `{start_comment}` line")) + })?; + let end_comment_at = self + .lines + .iter() + .skip(start_comment_at) + .position(|line| line.contains(end_comment)) + .ok_or_else(|| { + Error::io_error_other(format!("Cannot find the `{end_comment}` line")) + })? + + start_comment_at; + + let region_starts_at = start_comment_at + 1; + let region_ends_at = end_comment_at - 1; + + if region_starts_at > region_ends_at { + return Err(Error::io_error_other(format!( + "There must be the content between {start_comment} and {end_comment}" + ))); + } + + Ok(FileRegion { + lines: &mut self.lines, + region_starts_at, + region_ends_at, + }) + } + + pub fn rfind_line(&mut self, f: F) -> Result> + where + F: Fn(&str) -> bool, + { + let line_idx = self + .lines + .iter() + .rposition(|line| f(&line)) + .ok_or_else(|| { + Error::io_error_other(format!( + "{:?} file does not contain a required pattern", + self.path + )) + })?; + Ok(LinePointer { + lines: &mut self.lines, + line_idx, + }) + } + + pub fn write(self) -> Result<()> { + write_lines(self.path, self.lines) + } +} + +pub struct FileRegion<'a> { + lines: &'a mut Vec, + region_starts_at: usize, + region_ends_at: usize, +} + +impl<'a> FileRegion<'a> { + pub fn push_line(&mut self, line: String) { + self.lines.insert(self.region_ends_at + 1, line); + } + + pub fn sort(&mut self) { + self.lines[self.region_starts_at..self.region_ends_at].sort() + } +} + +pub struct LinePointer<'a> { + lines: &'a mut Vec, + line_idx: usize, +} + +impl<'a> LinePointer<'a> { + pub fn push_line_after(&mut self, line: String) { + self.lines.insert(self.line_idx + 1, line); + } +} diff --git a/rust/Cargo.lock b/rust/Cargo.lock index d58c01c250c..24a18ba843f 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -653,6 +653,12 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -1266,6 +1272,12 @@ dependencies = [ "semver", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.13" @@ -1489,6 +1501,25 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.37", +] + [[package]] name = "subtle" version = "2.4.1" @@ -1674,9 +1705,12 @@ dependencies = [ name = "tw_coin_registry" version = "0.1.0" dependencies = [ + "itertools", "lazy_static", "serde", "serde_json", + "strum", + "strum_macros", "tw_aptos", "tw_bitcoin", "tw_coin_entry", diff --git a/rust/tw_any_coin/Cargo.toml b/rust/tw_any_coin/Cargo.toml index bb8e9314f3d..e2aeea6d021 100644 --- a/rust/tw_any_coin/Cargo.toml +++ b/rust/tw_any_coin/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] tw_coin_entry = { path = "../tw_coin_entry" } tw_coin_registry = { path = "../tw_coin_registry" } +tw_encoding = { path = "../tw_encoding" } tw_hash = { path = "../tw_hash" } tw_keypair = { path = "../tw_keypair" } tw_memory = { path = "../tw_memory" } @@ -19,7 +20,6 @@ serde = { version = "1.0.163", features = ["derive"] } serde_json = { version = "1.0.96" } tw_any_coin = { path = "./", features = ["test-utils"] } tw_cosmos_sdk = { path = "../tw_cosmos_sdk", features = ["test-utils"] } -tw_encoding = { path = "../tw_encoding" } tw_keypair = { path = "../tw_keypair", features = ["test-utils"] } tw_memory = { path = "../tw_memory", features = ["test-utils"] } tw_misc = { path = "../tw_misc", features = ["test-utils"] } diff --git a/rust/tw_any_coin/src/ffi/tw_any_address.rs b/rust/tw_any_coin/src/ffi/tw_any_address.rs index de69826f9f8..ba8d39163c5 100644 --- a/rust/tw_any_coin/src/ffi/tw_any_address.rs +++ b/rust/tw_any_coin/src/ffi/tw_any_address.rs @@ -9,6 +9,7 @@ use crate::any_address::AnyAddress; use tw_coin_entry::derivation::Derivation; use tw_coin_entry::prefix::AddressPrefix; +use tw_coin_registry::coin_type::CoinType; use tw_keypair::ffi::pubkey::TWPublicKey; use tw_memory::ffi::tw_data::TWData; use tw_memory::ffi::tw_string::TWString; @@ -29,6 +30,7 @@ impl RawPtrTrait for TWAnyAddress {} pub unsafe extern "C" fn tw_any_address_is_valid(string: *const TWString, coin: u32) -> bool { let string = try_or_false!(TWString::from_ptr_as_ref(string)); let string = try_or_false!(string.as_str()); + let coin = try_or_false!(CoinType::try_from(coin)); AnyAddress::is_valid(coin, string, None) } @@ -51,6 +53,8 @@ pub unsafe extern "C" fn tw_any_address_is_valid_bech32( let hrp = try_or_false!(TWString::from_ptr_as_ref(hrp)); let hrp = try_or_false!(hrp.as_str()); + let coin = try_or_false!(CoinType::try_from(coin)); + let prefix = AddressPrefix::Hrp(hrp.to_string()); AnyAddress::is_valid(coin, string, Some(prefix)) } @@ -67,6 +71,7 @@ pub unsafe extern "C" fn tw_any_address_create_with_string( ) -> *mut TWAnyAddress { let string = try_or_else!(TWString::from_ptr_as_ref(string), std::ptr::null_mut); let string = try_or_else!(string.as_str(), std::ptr::null_mut); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); AnyAddress::with_string(coin, string, None) .map(|any_address| TWAnyAddress(any_address).into_ptr()) @@ -87,6 +92,7 @@ pub unsafe extern "C" fn tw_any_address_create_with_public_key_derivation( ) -> *mut TWAnyAddress { let public_key = try_or_else!(TWPublicKey::from_ptr_as_ref(public_key), std::ptr::null_mut); let derivation = try_or_else!(Derivation::from_raw(derivation), std::ptr::null_mut); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); AnyAddress::with_public_key(coin, public_key.as_ref().clone(), derivation, None) .map(|any_address| TWAnyAddress(any_address).into_ptr()) @@ -106,6 +112,7 @@ pub unsafe extern "C" fn tw_any_address_create_bech32_with_public_key( hrp: *const TWString, ) -> *mut TWAnyAddress { let public_key = try_or_else!(TWPublicKey::from_ptr_as_ref(public_key), std::ptr::null_mut); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); let hrp = try_or_else!(TWString::from_ptr_as_ref(hrp), std::ptr::null_mut); let hrp = try_or_else!(hrp.as_str(), std::ptr::null_mut); @@ -173,6 +180,7 @@ pub unsafe extern "C" fn tw_any_address_create_with_string_unchecked( ) -> *mut TWAnyAddress { let string = try_or_else!(TWString::from_ptr_as_ref(string), std::ptr::null_mut); let string = try_or_else!(string.as_str(), std::ptr::null_mut); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); AnyAddress::with_string_unchecked(coin, string) .map(|any_address| TWAnyAddress(any_address).into_ptr()) diff --git a/rust/tw_any_coin/src/ffi/tw_any_signer.rs b/rust/tw_any_coin/src/ffi/tw_any_signer.rs index 76e3965d5e9..c0bde8e921c 100644 --- a/rust/tw_any_coin/src/ffi/tw_any_signer.rs +++ b/rust/tw_any_coin/src/ffi/tw_any_signer.rs @@ -7,6 +7,7 @@ #![allow(clippy::missing_safety_doc)] use crate::any_signer::AnySigner; +use tw_coin_registry::coin_type::CoinType; use tw_memory::ffi::tw_data::TWData; use tw_memory::ffi::RawPtrTrait; use tw_misc::try_or_else; @@ -19,6 +20,7 @@ use tw_misc::try_or_else; #[no_mangle] pub unsafe extern "C" fn tw_any_signer_sign(input: *const TWData, coin: u32) -> *mut TWData { let input = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); AnySigner::sign(input.as_slice(), coin) .map(|output| TWData::from(output).into_ptr()) @@ -33,6 +35,7 @@ pub unsafe extern "C" fn tw_any_signer_sign(input: *const TWData, coin: u32) -> #[no_mangle] pub unsafe extern "C" fn tw_any_signer_plan(input: *const TWData, coin: u32) -> *mut TWData { let input = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); AnySigner::plan(input.as_slice(), coin) .map(|output| TWData::from(output).into_ptr()) diff --git a/rust/tw_any_coin/src/ffi/tw_message_signer.rs b/rust/tw_any_coin/src/ffi/tw_message_signer.rs index 32144ecc204..e1188890bf1 100644 --- a/rust/tw_any_coin/src/ffi/tw_message_signer.rs +++ b/rust/tw_any_coin/src/ffi/tw_message_signer.rs @@ -7,6 +7,7 @@ #![allow(clippy::missing_safety_doc)] use crate::message_signer::MessageSigner; +use tw_coin_registry::coin_type::CoinType; use tw_memory::ffi::tw_data::TWData; use tw_memory::ffi::RawPtrTrait; use tw_misc::{try_or_else, try_or_false}; @@ -19,6 +20,7 @@ use tw_misc::{try_or_else, try_or_false}; #[no_mangle] pub unsafe extern "C" fn tw_message_signer_sign(input: *const TWData, coin: u32) -> *mut TWData { let input = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); MessageSigner::sign_message(input.as_slice(), coin) .map(|output| TWData::from(output).into_ptr()) @@ -33,6 +35,7 @@ pub unsafe extern "C" fn tw_message_signer_sign(input: *const TWData, coin: u32) #[no_mangle] pub unsafe extern "C" fn tw_message_signer_verify(input: *const TWData, coin: u32) -> bool { let input = try_or_false!(TWData::from_ptr_as_ref(input)); + let coin = try_or_false!(CoinType::try_from(coin)); MessageSigner::verify_message(input.as_slice(), coin).unwrap_or_default() } @@ -47,6 +50,8 @@ pub unsafe extern "C" fn tw_message_signer_pre_image_hashes( coin: u32, ) -> *mut TWData { let input = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); + MessageSigner::message_preimage_hashes(input.as_slice(), coin) .map(|output| TWData::from(output).into_ptr()) .unwrap_or_else(|_| std::ptr::null_mut()) diff --git a/rust/tw_any_coin/src/ffi/tw_transaction_compiler.rs b/rust/tw_any_coin/src/ffi/tw_transaction_compiler.rs index a6f76e19681..bd227f4426f 100644 --- a/rust/tw_any_coin/src/ffi/tw_transaction_compiler.rs +++ b/rust/tw_any_coin/src/ffi/tw_transaction_compiler.rs @@ -7,6 +7,7 @@ #![allow(clippy::missing_safety_doc)] use crate::transaction_compiler::TransactionCompiler; +use tw_coin_registry::coin_type::CoinType; use tw_memory::ffi::tw_data::TWData; use tw_memory::ffi::tw_data_vector::TWDataVector; use tw_memory::ffi::RawPtrTrait; @@ -25,6 +26,7 @@ pub unsafe extern "C" fn tw_transaction_compiler_pre_image_hashes( input: *const TWData, ) -> *mut TWData { let input = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); TransactionCompiler::preimage_hashes(coin, input.as_slice()) .map(|output| TWData::from(output).into_ptr()) @@ -57,6 +59,7 @@ pub unsafe extern "C" fn tw_transaction_compiler_compile( TWDataVector::from_ptr_as_ref(public_keys), std::ptr::null_mut ); + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); TransactionCompiler::compile( coin, diff --git a/rust/tw_any_coin/src/test_utils/address_utils.rs b/rust/tw_any_coin/src/test_utils/address_utils.rs new file mode 100644 index 00000000000..8efd7de120e --- /dev/null +++ b/rust/tw_any_coin/src/test_utils/address_utils.rs @@ -0,0 +1,105 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::ffi::tw_any_address::{ + tw_any_address_create_bech32_with_public_key, tw_any_address_create_with_string, + tw_any_address_data, tw_any_address_delete, tw_any_address_description, + tw_any_address_is_valid, tw_any_address_is_valid_bech32, TWAnyAddress, +}; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_keypair::ffi::privkey::tw_private_key_get_public_key_by_type; +use tw_keypair::test_utils::tw_private_key_helper::TWPrivateKeyHelper; +use tw_keypair::test_utils::tw_public_key_helper::TWPublicKeyHelper; +use tw_keypair::tw::PublicKeyType; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_memory::test_utils::tw_string_helper::TWStringHelper; +use tw_memory::test_utils::tw_wrapper::{TWWrapper, WithDestructor}; + +pub type TWAnyAddressHelper = TWWrapper; + +impl WithDestructor for TWAnyAddress { + fn destructor() -> unsafe extern "C" fn(*mut Self) { + tw_any_address_delete + } +} + +pub fn test_address_normalization(coin: CoinType, denormalized: &str, normalized: &str) { + let expected = normalized; + let denormalized = TWStringHelper::create(denormalized); + + let any_address = TWAnyAddressHelper::wrap(unsafe { + tw_any_address_create_with_string(denormalized.ptr(), coin as u32) + }); + + let normalized = TWStringHelper::wrap(unsafe { tw_any_address_description(any_address.ptr()) }); + + assert_eq!(normalized.to_string(), Some(expected.to_string())); +} + +pub fn test_address_valid(coin: CoinType, address: &str) { + let address = TWStringHelper::create(address); + assert!(unsafe { tw_any_address_is_valid(address.ptr(), coin as u32) }); +} + +pub fn test_address_invalid(coin: CoinType, address: &str) { + let address = TWStringHelper::create(address); + assert!(!unsafe { tw_any_address_is_valid(address.ptr(), coin as u32) }); +} + +pub fn test_address_get_data(coin: CoinType, address: &str, data_hex: &str) { + let address_str = TWStringHelper::create(address); + let any_address = TWAnyAddressHelper::wrap(unsafe { + tw_any_address_create_with_string(address_str.ptr(), coin as u32) + }); + + let actual_data = TWDataHelper::wrap(unsafe { tw_any_address_data(any_address.ptr()) }) + .to_vec() + .unwrap_or_else(|| panic!("!tw_any_address_data")); + assert_eq!( + actual_data.to_hex(), + // Decode and encode again to allow to use `0x` and non-prefixed data hexes. + data_hex.decode_hex().unwrap().to_hex() + ); +} + +pub struct AddressCreateBech32WithPublicKey<'a> { + pub coin: CoinType, + pub private_key: &'a str, + pub public_key_type: PublicKeyType, + pub hrp: &'a str, + pub expected: &'a str, +} + +pub fn test_address_create_bech32_with_public_key(input: AddressCreateBech32WithPublicKey<'_>) { + let private_key = TWPrivateKeyHelper::with_hex(input.private_key); + let public_key = TWPublicKeyHelper::wrap(unsafe { + tw_private_key_get_public_key_by_type(private_key.ptr(), input.public_key_type as u32) + }); + let hrp = TWStringHelper::create(input.hrp); + + let any_address = TWAnyAddressHelper::wrap(unsafe { + tw_any_address_create_bech32_with_public_key(public_key.ptr(), input.coin as u32, hrp.ptr()) + }); + + let actual = TWStringHelper::wrap(unsafe { tw_any_address_description(any_address.ptr()) }); + assert_eq!(actual.to_string(), Some(input.expected.to_string())); +} + +pub struct AddressBech32IsValid<'a> { + pub coin: CoinType, + pub address: &'a str, + pub hrp: &'a str, +} + +pub fn test_address_bech32_is_valid(input: AddressBech32IsValid<'_>) { + let address_str = TWStringHelper::create(input.address); + let hrp = TWStringHelper::create(input.hrp); + // Should be valid even though Osmosis chain has `osmo` default hrp. + let result = + unsafe { tw_any_address_is_valid_bech32(address_str.ptr(), input.coin as u32, hrp.ptr()) }; + assert!(result); +} diff --git a/rust/tw_any_coin/src/test_utils/mod.rs b/rust/tw_any_coin/src/test_utils/mod.rs index 671dbb1a436..7bdd5ac4e99 100644 --- a/rust/tw_any_coin/src/test_utils/mod.rs +++ b/rust/tw_any_coin/src/test_utils/mod.rs @@ -4,13 +4,4 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -use crate::ffi::tw_any_address::{tw_any_address_delete, TWAnyAddress}; -use tw_memory::test_utils::tw_wrapper::{TWWrapper, WithDestructor}; - -pub type TWAnyAddressHelper = TWWrapper; - -impl WithDestructor for TWAnyAddress { - fn destructor() -> unsafe extern "C" fn(*mut Self) { - tw_any_address_delete - } -} +pub mod address_utils; diff --git a/rust/tw_any_coin/tests/chains/aptos/aptos_address.rs b/rust/tw_any_coin/tests/chains/aptos/aptos_address.rs new file mode 100644 index 00000000000..788cdc5aeec --- /dev/null +++ b/rust/tw_any_coin/tests/chains/aptos/aptos_address.rs @@ -0,0 +1,73 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::test_utils::address_utils::{ + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, +}; +use tw_coin_registry::coin_type::CoinType; + +#[test] +fn test_aptos_address_normalization() { + test_address_normalization( + CoinType::Aptos, + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + ); +} + +#[test] +fn test_aptos_address_is_valid() { + test_address_valid(CoinType::Aptos, "0x1"); + test_address_valid( + CoinType::Aptos, + "0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", + ); + test_address_valid( + CoinType::Aptos, + "eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", + ); + test_address_valid( + CoinType::Aptos, + "19aadeca9388e009d136245b9a67423f3eee242b03142849eb4f81a4a409e59c", + ); + test_address_valid( + CoinType::Aptos, + "0x777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb", + ); + test_address_valid( + CoinType::Aptos, + "0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175", + ); +} + +#[test] +fn test_aptos_address_invalid() { + // Empty + test_address_invalid(CoinType::Aptos, ""); + // Invalid Hex + test_address_invalid( + CoinType::Aptos, + "Seff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", + ); + // Too long + test_address_invalid( + CoinType::Aptos, + "eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175bb", + ); + test_address_invalid( + CoinType::Aptos, + "0xSeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", + ); +} + +#[test] +fn test_aptos_address_get_data() { + test_address_get_data( + CoinType::Aptos, + "0x1", + "0000000000000000000000000000000000000000000000000000000000000001", + ); +} diff --git a/rust/tw_any_coin/tests/chains/aptos/mod.rs b/rust/tw_any_coin/tests/chains/aptos/mod.rs index 0e046226232..ecea2c75bfd 100644 --- a/rust/tw_any_coin/tests/chains/aptos/mod.rs +++ b/rust/tw_any_coin/tests/chains/aptos/mod.rs @@ -4,6 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +mod aptos_address; mod aptos_compile; mod aptos_sign; mod test_cases; diff --git a/rust/tw_any_coin/tests/chains/bitcoin/bitcoin_address.rs b/rust/tw_any_coin/tests/chains/bitcoin/bitcoin_address.rs new file mode 100644 index 00000000000..0397c6c7768 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/bitcoin/bitcoin_address.rs @@ -0,0 +1,49 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::test_utils::address_utils::{ + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, +}; +use tw_coin_registry::coin_type::CoinType; + +#[test] +fn test_bitcoin_address_normalization() { + test_address_normalization( + CoinType::Bitcoin, + "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", + "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", + ); +} + +#[test] +fn test_bitcoin_address_is_valid() { + test_address_valid(CoinType::Bitcoin, "1MrZNGN7mfWZiZNQttrzHjfw72jnJC2JNx"); + test_address_valid( + CoinType::Bitcoin, + "bc1qunq74p3h8425hr6wllevlvqqr6sezfxj262rff", + ); + test_address_valid( + CoinType::Bitcoin, + "bc1pwse34zfpvt344rvlt7tw0ngjtfh9xasc4q03avf0lk74jzjpzjuqaz7ks5", + ); +} + +#[test] +fn test_bitcoin_address_invalid() { + test_address_invalid( + CoinType::Bitcoin, + "0xb16db98b365b1f89191996942612b14f1da4bd5f", + ); +} + +#[test] +fn test_bitcoin_address_get_data() { + test_address_get_data( + CoinType::Bitcoin, + "1MrZNGN7mfWZiZNQttrzHjfw72jnJC2JNx", + "314d725a4e474e376d66575a695a4e517474727a486a667737326a6e4a43324a4e78", + ); +} diff --git a/rust/tw_any_coin/tests/chains/bitcoin/mod.rs b/rust/tw_any_coin/tests/chains/bitcoin/mod.rs new file mode 100644 index 00000000000..00682e69022 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/bitcoin/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod bitcoin_address; diff --git a/rust/tw_any_coin/tests/chains/cosmos/cosmos_address.rs b/rust/tw_any_coin/tests/chains/cosmos/cosmos_address.rs new file mode 100644 index 00000000000..964414dff44 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/cosmos/cosmos_address.rs @@ -0,0 +1,83 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::test_utils::address_utils::{ + test_address_bech32_is_valid, test_address_create_bech32_with_public_key, + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, + AddressBech32IsValid, AddressCreateBech32WithPublicKey, +}; +use tw_coin_registry::coin_type::CoinType; +use tw_keypair::tw::PublicKeyType; + +#[test] +fn test_cosmos_address_normalization() { + test_address_normalization( + CoinType::Cosmos, + "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08", + "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08", + ); +} + +#[test] +fn test_cosmos_address_is_valid() { + test_address_valid( + CoinType::Cosmos, + "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", + ); + test_address_valid( + CoinType::Cosmos, + "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08", + ); + test_address_valid( + CoinType::Cosmos, + "cosmosvalconspub1zcjduepqjnnwe2jsywv0kfc97pz04zkm7tc9k2437cde2my3y5js9t7cw9mstfg3sa", + ); +} + +#[test] +fn test_cosmos_address_invalid() { + test_address_invalid( + CoinType::Cosmos, + "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax6", + ); + test_address_invalid( + CoinType::Cosmos, + "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe", + ); + test_address_invalid( + CoinType::Cosmos, + "bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", + ); +} + +#[test] +fn test_cosmos_address_get_data() { + test_address_get_data( + CoinType::Cosmos, + "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08", + "818c5dc04ccbd1dcb454fb7c06da564b0e22955d", + ); +} + +#[test] +fn test_any_address_is_valid_bech32() { + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::Cosmos, + address: "juno1mry47pkga5tdswtluy0m8teslpalkdq0gnn4mf", + hrp: "juno", + }); +} + +#[test] +fn test_any_address_create_bech32_with_public_key() { + test_address_create_bech32_with_public_key(AddressCreateBech32WithPublicKey { + coin: CoinType::Cosmos, + private_key: "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + public_key_type: PublicKeyType::Secp256k1, + hrp: "juno", + expected: "juno1ten42eesehw0ktddcp0fws7d3ycsqez3fksy86", + }); +} diff --git a/rust/tw_any_coin/tests/chains/cosmos/cosmos_sign.rs b/rust/tw_any_coin/tests/chains/cosmos/cosmos_sign.rs new file mode 100644 index 00000000000..b9140fb9731 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/cosmos/cosmos_sign.rs @@ -0,0 +1,65 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::ffi::tw_any_signer::tw_any_signer_sign; +use tw_coin_entry::error::SigningErrorType; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::DecodeHex; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_proto::{deserialize, serialize}; + +#[test] +fn test_any_signer_sign_cosmos() { + use tw_proto::Cosmos::Proto; + use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; + + let private_key = "8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af" + .decode_hex() + .unwrap(); + + let send_msg = Proto::mod_Message::Send { + from_address: "cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx".into(), + to_address: "cosmos18s0hdnsllgcclweu9aymw4ngktr2k0rkygdzdp".into(), + amounts: vec![Proto::Amount { + denom: "uatom".into(), + amount: "400000".into(), + }], + ..Proto::mod_Message::Send::default() + }; + let input = Proto::SigningInput { + signing_mode: Proto::SigningMode::Protobuf, + account_number: 546179, + chain_id: "cosmoshub-4".into(), + sequence: 0, + fee: Some(Proto::Fee { + gas: 200000, + amounts: vec![Proto::Amount { + denom: "uatom".into(), + amount: "1000".into(), + }], + }), + private_key: private_key.into(), + messages: vec![Proto::Message { + message_oneof: MessageEnum::send_coins_message(send_msg), + }], + ..Proto::SigningInput::default() + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = TWDataHelper::wrap(unsafe { + tw_any_signer_sign(input_data.ptr(), CoinType::Cosmos as u32) + }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Proto::SigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseBItY29zbW9zMThzMGhkbnNsbGdjY2x3ZXU5YXltdzRuZ2t0cjJrMHJreWdkemRwGg8KBXVhdG9tEgY0MDAwMDASZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJEgQKAggBEhMKDQoFdWF0b20SBDEwMDAQwJoMGkCvvVE6d29P30cO9/lnXyGunWMPxNY12NuqDcCnFkNM0H4CUQdl1Gc9+ogIJbro5nyzZzlv9rl2/GsZox/JXoCX"}"#; + assert_eq!(output.serialized, expected); +} diff --git a/rust/tw_any_coin/tests/chains/cosmos/mod.rs b/rust/tw_any_coin/tests/chains/cosmos/mod.rs new file mode 100644 index 00000000000..47b109ad2c9 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/cosmos/mod.rs @@ -0,0 +1,8 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod cosmos_address; +mod cosmos_sign; diff --git a/rust/tw_any_coin/tests/chains/ethereum/ethereum_address.rs b/rust/tw_any_coin/tests/chains/ethereum/ethereum_address.rs new file mode 100644 index 00000000000..f0a03ec2cde --- /dev/null +++ b/rust/tw_any_coin/tests/chains/ethereum/ethereum_address.rs @@ -0,0 +1,48 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::test_utils::address_utils::{ + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, +}; +use tw_coin_registry::blockchain_type::BlockchainType; +use tw_coin_registry::registry::coin_items_by_blockchain; + +#[test] +fn test_ethereum_address_normalization() { + for coin in coin_items_by_blockchain(BlockchainType::Ethereum) { + test_address_normalization( + coin.coin_id, + "0xb16db98b365b1f89191996942612b14f1da4bd5f", + "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f", + ); + } +} + +#[test] +fn test_ethereum_address_is_valid() { + for coin in coin_items_by_blockchain(BlockchainType::Ethereum) { + test_address_valid(coin.coin_id, "0xb16db98b365b1f89191996942612b14f1da4bd5f"); + test_address_valid(coin.coin_id, "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f"); + } +} + +#[test] +fn test_ethereum_address_invalid() { + for coin in coin_items_by_blockchain(BlockchainType::Ethereum) { + test_address_invalid(coin.coin_id, "b16Db98B365B1f89191996942612B14F1Da4Bd5f"); + } +} + +#[test] +fn test_ethereum_address_get_data() { + for coin in coin_items_by_blockchain(BlockchainType::Ethereum) { + test_address_get_data( + coin.coin_id, + "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f", + "b16Db98B365B1f89191996942612B14F1Da4Bd5f", + ); + } +} diff --git a/rust/tw_any_coin/tests/tw_transaction_compiler_ffi_tests.rs b/rust/tw_any_coin/tests/chains/ethereum/ethereum_compile.rs similarity index 92% rename from rust/tw_any_coin/tests/tw_transaction_compiler_ffi_tests.rs rename to rust/tw_any_coin/tests/chains/ethereum/ethereum_compile.rs index ea875557d4c..f31a380d456 100644 --- a/rust/tw_any_coin/tests/tw_transaction_compiler_ffi_tests.rs +++ b/rust/tw_any_coin/tests/chains/ethereum/ethereum_compile.rs @@ -10,6 +10,7 @@ use tw_any_coin::ffi::tw_transaction_compiler::{ tw_transaction_compiler_compile, tw_transaction_compiler_pre_image_hashes, }; use tw_coin_entry::error::SigningErrorType; +use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::{DecodeHex, ToHex}; use tw_memory::test_utils::tw_data_helper::TWDataHelper; use tw_memory::test_utils::tw_data_vector_helper::TWDataVectorHelper; @@ -18,8 +19,6 @@ use tw_proto::Ethereum::Proto; use tw_proto::TxCompiler::Proto as CompilerProto; use tw_proto::{deserialize, serialize}; -const ETHEREUM_COIN_TYPE: u32 = 60; - #[test] fn test_transaction_compiler_eth() { let transfer = Proto::mod_Transaction::Transfer { @@ -41,7 +40,7 @@ fn test_transaction_compiler_eth() { // Step 2: Obtain preimage hash let input_data = TWDataHelper::create(serialize(&input).unwrap()); let preimage_data = TWDataHelper::wrap(unsafe { - tw_transaction_compiler_pre_image_hashes(ETHEREUM_COIN_TYPE, input_data.ptr()) + tw_transaction_compiler_pre_image_hashes(CoinType::Ethereum as u32, input_data.ptr()) }) .to_vec() .expect("!tw_transaction_compiler_pre_image_hashes returned nullptr"); @@ -68,7 +67,7 @@ fn test_transaction_compiler_eth() { let input_data = TWDataHelper::create(serialize(&input).unwrap()); let output_data = TWDataHelper::wrap(unsafe { tw_transaction_compiler_compile( - ETHEREUM_COIN_TYPE, + CoinType::Ethereum as u32, input_data.ptr(), signatures.ptr(), public_keys.ptr(), @@ -100,11 +99,12 @@ fn test_transaction_compiler_plan_not_supported() { ..Proto::SigningInput::default() }; let input_data = TWDataHelper::create(serialize(&input).unwrap()); - let plan = - TWDataHelper::wrap(unsafe { tw_any_signer_plan(input_data.ptr(), ETHEREUM_COIN_TYPE) }); + let plan = TWDataHelper::wrap(unsafe { + tw_any_signer_plan(input_data.ptr(), CoinType::Ethereum as u32) + }); assert!( plan.is_null(), - "Transaction plan is expected to be not supported by the {} coin", - ETHEREUM_COIN_TYPE + "Transaction plan is expected to be not supported by the {:?} coin", + CoinType::Ethereum ); } diff --git a/rust/tw_any_coin/tests/tw_message_signer_ffi_tests.rs b/rust/tw_any_coin/tests/chains/ethereum/ethereum_message_sign.rs similarity index 89% rename from rust/tw_any_coin/tests/tw_message_signer_ffi_tests.rs rename to rust/tw_any_coin/tests/chains/ethereum/ethereum_message_sign.rs index 0a58ddfbec4..1b348169f49 100644 --- a/rust/tw_any_coin/tests/tw_message_signer_ffi_tests.rs +++ b/rust/tw_any_coin/tests/chains/ethereum/ethereum_message_sign.rs @@ -8,12 +8,11 @@ use tw_any_coin::ffi::tw_message_signer::{ tw_message_signer_pre_image_hashes, tw_message_signer_sign, tw_message_signer_verify, }; use tw_coin_entry::error::SigningErrorType; +use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::{DecodeHex, ToHex}; use tw_memory::test_utils::tw_data_helper::TWDataHelper; use tw_proto::{deserialize, serialize, Ethereum, TxCompiler}; -const ETHEREUM_COIN_TYPE: u32 = 60; - #[test] fn test_tw_message_signer_sign() { let input = Ethereum::Proto::MessageSigningInput { @@ -27,10 +26,11 @@ fn test_tw_message_signer_sign() { }; let input_data = TWDataHelper::create(serialize(&input).unwrap()); - let output = - TWDataHelper::wrap(unsafe { tw_message_signer_sign(input_data.ptr(), ETHEREUM_COIN_TYPE) }) - .to_vec() - .expect("!tw_message_signer_sign returned nullptr"); + let output = TWDataHelper::wrap(unsafe { + tw_message_signer_sign(input_data.ptr(), CoinType::Ethereum as u32) + }) + .to_vec() + .expect("!tw_message_signer_sign returned nullptr"); let output: Ethereum::Proto::MessageSigningOutput = deserialize(&output).unwrap(); assert_eq!(output.error, SigningErrorType::OK); @@ -47,7 +47,7 @@ fn test_tw_message_signer_verify() { }; let input_data = TWDataHelper::create(serialize(&input).unwrap()); - let verified = unsafe { tw_message_signer_verify(input_data.ptr(), ETHEREUM_COIN_TYPE) }; + let verified = unsafe { tw_message_signer_verify(input_data.ptr(), CoinType::Ethereum as u32) }; assert!(verified); } @@ -60,7 +60,7 @@ fn test_tw_message_signer_verify_invalid() { }; let input_data = TWDataHelper::create(serialize(&input).unwrap()); - let verified = unsafe { tw_message_signer_verify(input_data.ptr(), ETHEREUM_COIN_TYPE) }; + let verified = unsafe { tw_message_signer_verify(input_data.ptr(), CoinType::Ethereum as u32) }; assert!(!verified); } @@ -78,7 +78,7 @@ fn test_tw_message_signer_pre_image_hashes() { let input_data = TWDataHelper::create(serialize(&input).unwrap()); let output = TWDataHelper::wrap(unsafe { - tw_message_signer_pre_image_hashes(input_data.ptr(), ETHEREUM_COIN_TYPE) + tw_message_signer_pre_image_hashes(input_data.ptr(), CoinType::Ethereum as u32) }) .to_vec() .expect("!tw_message_signer_sign returned nullptr"); diff --git a/rust/tw_any_coin/tests/chains/ethereum/ethereum_sign.rs b/rust/tw_any_coin/tests/chains/ethereum/ethereum_sign.rs new file mode 100644 index 00000000000..47a78da82e7 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/ethereum/ethereum_sign.rs @@ -0,0 +1,56 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_any_coin::ffi::tw_any_signer::tw_any_signer_sign; +use tw_coin_entry::error::SigningErrorType; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_number::U256; +use tw_proto::{deserialize, serialize}; + +#[test] +fn test_any_signer_sign_eth() { + use tw_proto::Ethereum::Proto; + + let private = "0x4646464646464646464646464646464646464646464646464646464646464646" + .decode_hex() + .unwrap(); + + let transfer = Proto::mod_Transaction::Transfer { + amount: U256::encode_be_compact(1_000_000_000_000_000_000), + data: Cow::default(), + }; + + let input = Proto::SigningInput { + chain_id: U256::encode_be_compact(1), + nonce: U256::encode_be_compact(9), + gas_price: U256::encode_be_compact(20_000_000_000), + gas_limit: U256::encode_be_compact(21_000), + to_address: "0x3535353535353535353535353535353535353535".into(), + transaction: Some(Proto::Transaction { + transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), + }), + private_key: private.into(), + ..Proto::SigningInput::default() + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = TWDataHelper::wrap(unsafe { + tw_any_signer_sign(input_data.ptr(), CoinType::Ethereum as u32) + }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Proto::SigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected = "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83"; + assert_eq!(output.encoded.to_hex(), expected); +} diff --git a/rust/tw_any_coin/tests/chains/ethereum/mod.rs b/rust/tw_any_coin/tests/chains/ethereum/mod.rs new file mode 100644 index 00000000000..153b12547b5 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/ethereum/mod.rs @@ -0,0 +1,10 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod ethereum_address; +mod ethereum_compile; +mod ethereum_message_sign; +mod ethereum_sign; diff --git a/rust/tw_any_coin/tests/chains/internet_computer/internet_computer_address.rs b/rust/tw_any_coin/tests/chains/internet_computer/internet_computer_address.rs new file mode 100644 index 00000000000..ef44d0e022f --- /dev/null +++ b/rust/tw_any_coin/tests/chains/internet_computer/internet_computer_address.rs @@ -0,0 +1,64 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::test_utils::address_utils::{ + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, +}; +use tw_coin_registry::coin_type::CoinType; + +#[test] +fn test_thorchain_address_normalization() { + test_address_normalization( + CoinType::InternetComputer, + "290CC7C359F44C8516FC169C5ED4F0F3AE2E24BF5DE0D4C51F5E7545B5474FAA", + "290cc7c359f44c8516fc169c5ed4f0f3ae2e24bf5de0d4c51f5e7545b5474faa", + ); +} + +#[test] +fn test_thorchain_address_is_valid() { + test_address_valid( + CoinType::InternetComputer, + "fb257577279ecac604d4780214af95aa6adc3a814f6f3d6d7ac844d1deca500a", + ); + test_address_valid( + CoinType::InternetComputer, + "e90c48d54847f4758f1d6b589a1db2500757a49a6722d4f775e050107b4b752d", + ); + test_address_valid( + CoinType::InternetComputer, + "a7c5baf393aed527ef6fb3869fbf84dd4e562edf9b04bd8f9bfbd6b8e6a22776", + ); + test_address_valid( + CoinType::InternetComputer, + "4cb2ca5cfcfa1d952f8cd7f0ec46c96e1023ab057b83a2c7ce236b9e71ccca0b", + ); +} + +#[test] +fn test_thorchain_address_invalid() { + test_address_invalid( + CoinType::InternetComputer, + "3357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278b", + ); + test_address_invalid( + CoinType::InternetComputer, + "3357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278bce", + ); + test_address_invalid( + CoinType::InternetComputer, + "553357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278", + ); +} + +#[test] +fn test_thorchain_address_get_data() { + test_address_get_data( + CoinType::InternetComputer, + "4cb2ca5cfcfa1d952f8cd7f0ec46c96e1023ab057b83a2c7ce236b9e71ccca0b", + "4cb2ca5cfcfa1d952f8cd7f0ec46c96e1023ab057b83a2c7ce236b9e71ccca0b", + ); +} diff --git a/rust/tw_any_coin/tests/chains/internet_computer/mod.rs b/rust/tw_any_coin/tests/chains/internet_computer/mod.rs new file mode 100644 index 00000000000..e305a45c6be --- /dev/null +++ b/rust/tw_any_coin/tests/chains/internet_computer/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod internet_computer_address; diff --git a/rust/tw_any_coin/tests/chains/mod.rs b/rust/tw_any_coin/tests/chains/mod.rs index 165ba051b71..ddd04b53632 100644 --- a/rust/tw_any_coin/tests/chains/mod.rs +++ b/rust/tw_any_coin/tests/chains/mod.rs @@ -5,6 +5,10 @@ // file LICENSE at the root of the source code distribution tree. mod aptos; +mod bitcoin; +mod cosmos; +mod ethereum; +mod internet_computer; mod native_evmos; mod native_injective; mod thorchain; diff --git a/rust/tw_any_coin/tests/chains/native_evmos/mod.rs b/rust/tw_any_coin/tests/chains/native_evmos/mod.rs index 4d62bbecabe..2a92924fc00 100644 --- a/rust/tw_any_coin/tests/chains/native_evmos/mod.rs +++ b/rust/tw_any_coin/tests/chains/native_evmos/mod.rs @@ -4,4 +4,5 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +mod native_evmos_address; mod native_evmos_sign; diff --git a/rust/tw_any_coin/tests/chains/native_evmos/native_evmos_address.rs b/rust/tw_any_coin/tests/chains/native_evmos/native_evmos_address.rs new file mode 100644 index 00000000000..d779aabc230 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/native_evmos/native_evmos_address.rs @@ -0,0 +1,71 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::test_utils::address_utils::{ + test_address_bech32_is_valid, test_address_create_bech32_with_public_key, + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, + AddressBech32IsValid, AddressCreateBech32WithPublicKey, +}; +use tw_coin_registry::coin_type::CoinType; +use tw_keypair::tw::PublicKeyType; + +#[test] +fn test_native_evmos_address_normalization() { + test_address_normalization( + CoinType::NativeEvmos, + "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34", + "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34", + ); +} + +#[test] +fn test_native_evmos_address_is_valid() { + test_address_valid( + CoinType::NativeEvmos, + "evmos14py36sx57ud82t9yrks9z6hdsrpn5x6k0r05np", + ); + test_address_valid( + CoinType::NativeEvmos, + "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34", + ); +} + +#[test] +fn test_native_evmos_address_invalid() { + test_address_invalid( + CoinType::NativeEvmos, + "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw", + ); +} + +#[test] +fn test_native_evmos_address_get_data() { + test_address_get_data( + CoinType::NativeEvmos, + "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34", + "f1829676db577682e944fc3493d451b67ff3e29f", + ); +} + +#[test] +fn test_any_address_is_valid_bech32() { + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::Cosmos, + address: "evmos14py36sx57ud82t9yrks9z6hdsrpn5x6k0r05np", + hrp: "evmos", + }); +} + +#[test] +fn test_any_address_create_bech32_with_public_key() { + test_address_create_bech32_with_public_key(AddressCreateBech32WithPublicKey { + coin: CoinType::NativeEvmos, + private_key: "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed", + public_key_type: PublicKeyType::Secp256k1Extended, + hrp: "evmos", + expected: "evmos14py36sx57ud82t9yrks9z6hdsrpn5x6k0r05np", + }); +} diff --git a/rust/tw_any_coin/tests/chains/native_injective/mod.rs b/rust/tw_any_coin/tests/chains/native_injective/mod.rs index 2781305c015..a421b328188 100644 --- a/rust/tw_any_coin/tests/chains/native_injective/mod.rs +++ b/rust/tw_any_coin/tests/chains/native_injective/mod.rs @@ -4,5 +4,6 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -pub mod native_injective_compile; -pub mod native_injective_sign; +mod native_injective_address; +mod native_injective_compile; +mod native_injective_sign; diff --git a/rust/tw_any_coin/tests/chains/native_injective/native_injective_address.rs b/rust/tw_any_coin/tests/chains/native_injective/native_injective_address.rs new file mode 100644 index 00000000000..1bd9c4a8f8e --- /dev/null +++ b/rust/tw_any_coin/tests/chains/native_injective/native_injective_address.rs @@ -0,0 +1,71 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::test_utils::address_utils::{ + test_address_bech32_is_valid, test_address_create_bech32_with_public_key, + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, + AddressBech32IsValid, AddressCreateBech32WithPublicKey, +}; +use tw_coin_registry::coin_type::CoinType; +use tw_keypair::tw::PublicKeyType; + +#[test] +fn test_native_injective_address_normalization() { + test_address_normalization( + CoinType::NativeInjective, + "inj14py36sx57ud82t9yrks9z6hdsrpn5x6k8tf7m3", + "inj14py36sx57ud82t9yrks9z6hdsrpn5x6k8tf7m3", + ); +} + +#[test] +fn test_native_injective_address_is_valid() { + test_address_valid( + CoinType::NativeInjective, + "inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a", + ); + test_address_valid( + CoinType::NativeInjective, + "inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd", + ); +} + +#[test] +fn test_native_injective_address_invalid() { + test_address_invalid( + CoinType::NativeInjective, + "ini13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a", + ); +} + +#[test] +fn test_native_injective_address_get_data() { + test_address_get_data( + CoinType::NativeInjective, + "inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd", + "36c36d9875ec1efced515e14246f8422903ade2e", + ); +} + +#[test] +fn test_any_address_is_valid_bech32() { + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::Cosmos, + address: "evmos14py36sx57ud82t9yrks9z6hdsrpn5x6k0r05np", + hrp: "evmos", + }); +} + +#[test] +fn test_any_address_create_bech32_with_public_key() { + test_address_create_bech32_with_public_key(AddressCreateBech32WithPublicKey { + coin: CoinType::NativeInjective, + private_key: "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed", + public_key_type: PublicKeyType::Secp256k1Extended, + hrp: "inj", + expected: "inj14py36sx57ud82t9yrks9z6hdsrpn5x6k8tf7m3", + }); +} diff --git a/rust/tw_any_coin/tests/chains/thorchain/mod.rs b/rust/tw_any_coin/tests/chains/thorchain/mod.rs index 3f38961fcbb..20d258d5105 100644 --- a/rust/tw_any_coin/tests/chains/thorchain/mod.rs +++ b/rust/tw_any_coin/tests/chains/thorchain/mod.rs @@ -5,7 +5,6 @@ // file LICENSE at the root of the source code distribution tree. mod test_cases; +mod thorchain_address; mod thorchain_compile; mod thorchain_sign; - -const THORCHAIN_COIN_TYPE: u32 = 931; diff --git a/rust/tw_any_coin/tests/chains/thorchain/thorchain_address.rs b/rust/tw_any_coin/tests/chains/thorchain/thorchain_address.rs new file mode 100644 index 00000000000..03ea3d0c188 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/thorchain/thorchain_address.rs @@ -0,0 +1,75 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::test_utils::address_utils::{ + test_address_bech32_is_valid, test_address_create_bech32_with_public_key, + test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid, + AddressBech32IsValid, AddressCreateBech32WithPublicKey, +}; +use tw_coin_registry::coin_type::CoinType; +use tw_keypair::tw::PublicKeyType; + +#[test] +fn test_thorchain_address_normalization() { + test_address_normalization( + CoinType::THORChain, + "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", + "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", + ); +} + +#[test] +fn test_thorchain_address_is_valid() { + test_address_valid( + CoinType::THORChain, + "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", + ); + test_address_valid( + CoinType::THORChain, + "thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65", + ); +} + +#[test] +fn test_thorchain_address_invalid() { + test_address_invalid( + CoinType::THORChain, + "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", + ); + test_address_invalid( + CoinType::THORChain, + "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0e", + ); +} + +#[test] +fn test_thorchain_address_get_data() { + test_address_get_data( + CoinType::THORChain, + "thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65", + "c1e4df75a50e1d5b45d6e03f6157bc6169bfb46a", + ); +} + +#[test] +fn test_any_address_is_valid_bech32() { + test_address_bech32_is_valid(AddressBech32IsValid { + coin: CoinType::Cosmos, + address: "thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65", + hrp: "thor", + }); +} + +#[test] +fn test_any_address_create_bech32_with_public_key() { + test_address_create_bech32_with_public_key(AddressCreateBech32WithPublicKey { + coin: CoinType::Cosmos, + private_key: "8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed", + public_key_type: PublicKeyType::Secp256k1, + hrp: "thor", + expected: "thor1p05ufmhfpkjzqmc2u8humvgcqatq0esjqzrcut", + }); +} diff --git a/rust/tw_any_coin/tests/chains/thorchain/thorchain_compile.rs b/rust/tw_any_coin/tests/chains/thorchain/thorchain_compile.rs index ed5a2eb354c..ca2fda25eab 100644 --- a/rust/tw_any_coin/tests/chains/thorchain/thorchain_compile.rs +++ b/rust/tw_any_coin/tests/chains/thorchain/thorchain_compile.rs @@ -8,11 +8,11 @@ use crate::chains::thorchain::test_cases::send_fd0445af::{ signing_input, JSON_SIGNING_SIGNATURE, JSON_SIGNING_SIGNATURE_JSON, JSON_TX, JSON_TX_PREIMAGE, PRIVATE_KEY, }; -use crate::chains::thorchain::THORCHAIN_COIN_TYPE; use tw_any_coin::ffi::tw_transaction_compiler::{ tw_transaction_compiler_compile, tw_transaction_compiler_pre_image_hashes, }; use tw_coin_entry::error::SigningErrorType; +use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::ToHex; use tw_hash::H256; use tw_keypair::ecdsa::secp256k1; @@ -38,7 +38,7 @@ fn test_any_signer_compile_thorchain() { // Step 2: Obtain preimage hash let input_data = TWDataHelper::create(serialize(&input).unwrap()); let preimage_data = TWDataHelper::wrap(unsafe { - tw_transaction_compiler_pre_image_hashes(THORCHAIN_COIN_TYPE, input_data.ptr()) + tw_transaction_compiler_pre_image_hashes(CoinType::THORChain as u32, input_data.ptr()) }) .to_vec() .expect("!tw_transaction_compiler_pre_image_hashes returned nullptr"); @@ -75,7 +75,7 @@ fn test_any_signer_compile_thorchain() { let input_data = TWDataHelper::create(serialize(&input).unwrap()); let output_data = TWDataHelper::wrap(unsafe { tw_transaction_compiler_compile( - THORCHAIN_COIN_TYPE, + CoinType::THORChain as u32, input_data.ptr(), signatures.ptr(), public_keys.ptr(), diff --git a/rust/tw_any_coin/tests/chains/thorchain/thorchain_sign.rs b/rust/tw_any_coin/tests/chains/thorchain/thorchain_sign.rs index 585166daa76..c45c196b026 100644 --- a/rust/tw_any_coin/tests/chains/thorchain/thorchain_sign.rs +++ b/rust/tw_any_coin/tests/chains/thorchain/thorchain_sign.rs @@ -7,9 +7,9 @@ use crate::chains::thorchain::test_cases::send_fd0445af::{ signing_input, JSON_SIGNING_SIGNATURE, JSON_SIGNING_SIGNATURE_JSON, JSON_TX, PRIVATE_KEY, }; -use crate::chains::thorchain::THORCHAIN_COIN_TYPE; use tw_any_coin::ffi::tw_any_signer::tw_any_signer_sign; use tw_coin_entry::error::SigningErrorType; +use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::{DecodeHex, ToHex}; use tw_memory::test_utils::tw_data_helper::TWDataHelper; use tw_proto::Cosmos::Proto; @@ -25,10 +25,11 @@ fn test_any_signer_sign_thorchain() { let input_data = TWDataHelper::create(serialize(&input).unwrap()); - let output = - TWDataHelper::wrap(unsafe { tw_any_signer_sign(input_data.ptr(), THORCHAIN_COIN_TYPE) }) - .to_vec() - .expect("!tw_any_signer_sign returned nullptr"); + let output = TWDataHelper::wrap(unsafe { + tw_any_signer_sign(input_data.ptr(), CoinType::THORChain as u32) + }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); let output: Proto::SigningOutput = deserialize(&output).unwrap(); assert_eq!(output.error, SigningErrorType::OK); diff --git a/rust/tw_any_coin/tests/coin_address_derivation_tests.rs b/rust/tw_any_coin/tests/coin_address_derivation_tests.rs new file mode 100644 index 00000000000..7ce749d3883 --- /dev/null +++ b/rust/tw_any_coin/tests/coin_address_derivation_tests.rs @@ -0,0 +1,167 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_any_coin::ffi::tw_any_address::{ + tw_any_address_create_with_public_key_derivation, tw_any_address_description, +}; +use tw_any_coin::test_utils::address_utils::TWAnyAddressHelper; +use tw_coin_entry::derivation::Derivation; +use tw_coin_registry::coin_type::CoinType; +use tw_coin_registry::registry::get_coin_item; +use tw_keypair::ffi::privkey::tw_private_key_get_public_key_by_type; +use tw_keypair::test_utils::tw_private_key_helper::TWPrivateKeyHelper; +use tw_keypair::test_utils::tw_public_key_helper::TWPublicKeyHelper; +use tw_memory::test_utils::tw_string_helper::TWStringHelper; + +#[test] +fn test_coin_address_derivation() { + let private_key = TWPrivateKeyHelper::with_hex( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ); + + for coin in CoinType::iter() { + let coin_item = get_coin_item(coin).unwrap(); + + // Skip unsupported blockchains. + if !coin_item.blockchain.is_supported() { + continue; + } + + let public_key = TWPublicKeyHelper::wrap(unsafe { + tw_private_key_get_public_key_by_type( + private_key.ptr(), + coin_item.public_key_type as u32, + ) + }); + + let expected_address = match coin { + CoinType::Ethereum + | CoinType::AcalaEVM + | CoinType::Arbitrum + | CoinType::ArbitrumNova + | CoinType::Aurora + | CoinType::AvalancheCChain + | CoinType::Boba + | CoinType::Callisto + | CoinType::Celo + | CoinType::ConfluxeSpace + | CoinType::CronosChain + | CoinType::ECOChain + | CoinType::EthereumClassic + | CoinType::Evmos + | CoinType::Fantom + | CoinType::GoChain + | CoinType::KavaEvm + | CoinType::Klaytn + | CoinType::KuCoinCommunityChain + | CoinType::Meter + | CoinType::Metis + | CoinType::Moonbeam + | CoinType::Moonriver + | CoinType::Optimism + | CoinType::Zksync + | CoinType::PolygonzkEVM + | CoinType::OKXChain + | CoinType::POANetwork + | CoinType::Polygon + | CoinType::SmartBitcoinCash + | CoinType::SmartChain + | CoinType::SmartChainLegacy + | CoinType::Theta + | CoinType::ThetaFuel + | CoinType::ThunderCore + | CoinType::TomoChain + | CoinType::VeChain + | CoinType::Wanchain + | CoinType::xDai + | CoinType::IoTeXEVM + | CoinType::Scroll + | CoinType::OpBNB + | CoinType::Neon + | CoinType::Base + | CoinType::Linea + | CoinType::Greenfield + | CoinType::Mantle + | CoinType::ZenEON => "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309", + // end_of_ethereum_coin_address_derivation - DO NOT REMOVE + CoinType::Bitcoin + // TODO all Bitcoin-based blockchains should have different addresses. + // It should be fixed when Bitcoin is finalized. + | CoinType::Litecoin + | CoinType::Dogecoin + | CoinType::Dash + | CoinType::Viacoin + | CoinType::DigiByte + | CoinType::Monacoin + | CoinType::Syscoin + | CoinType::Pivx + | CoinType::Firo + | CoinType::BitcoinCash + | CoinType::BitcoinGold + | CoinType::Ravencoin + | CoinType::Qtum + | CoinType::eCash + | CoinType::Stratis + => "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", + CoinType::Aptos => "0x9006fa46f038224e8004bdda97f2e7a60c2c3d135bce7cb15541e5c0aae907a4", + CoinType::Cosmos => "cosmos1ten42eesehw0ktddcp0fws7d3ycsqez3lynlqx", + CoinType::Stargaze => "stars1ten42eesehw0ktddcp0fws7d3ycsqez3tcyzth", + CoinType::Juno => "juno1ten42eesehw0ktddcp0fws7d3ycsqez3fksy86", + CoinType::Stride => "stride1ten42eesehw0ktddcp0fws7d3ycsqez3u0nr52", + CoinType::Axelar => "axelar1ten42eesehw0ktddcp0fws7d3ycsqez3m29ht8", + CoinType::Crescent => "cre1ten42eesehw0ktddcp0fws7d3ycsqez3mvq64t", + CoinType::Kujira => "kujira1ten42eesehw0ktddcp0fws7d3ycsqez3wv38dv", + CoinType::Comdex => "comdex1ten42eesehw0ktddcp0fws7d3ycsqez3ct3ae3", + CoinType::Neutron => "neutron1ten42eesehw0ktddcp0fws7d3ycsqez3mm6a6p", + CoinType::Sommelier => "somm1ten42eesehw0ktddcp0fws7d3ycsqez3ncun3v", + CoinType::FetchAI => "fetch1ten42eesehw0ktddcp0fws7d3ycsqez3ve6mz3", + CoinType::Mars => "mars1ten42eesehw0ktddcp0fws7d3ycsqez3ze2x4a", + CoinType::Umee => "umee1ten42eesehw0ktddcp0fws7d3ycsqez3djwqy5", + CoinType::Noble => "noble1ten42eesehw0ktddcp0fws7d3ycsqez3h8xhcg", + CoinType::Sei => "sei1ten42eesehw0ktddcp0fws7d3ycsqez3jgzfx8", + CoinType::Tia => "celestia1ten42eesehw0ktddcp0fws7d3ycsqez3wwz06t", + CoinType::Coreum => "core1ten42eesehw0ktddcp0fws7d3ycsqez3v2ty8a", + CoinType::Quasar => "quasar1ten42eesehw0ktddcp0fws7d3ycsqez338fzdr", + CoinType::Persistence => "persistence1ten42eesehw0ktddcp0fws7d3ycsqez33g4vwz", + CoinType::Akash => "akash1ten42eesehw0ktddcp0fws7d3ycsqez3jl7ceu", + CoinType::Terra => "terra1ten42eesehw0ktddcp0fws7d3ycsqez3eqflzx", + CoinType::TerraV2 => "terra1ten42eesehw0ktddcp0fws7d3ycsqez3eqflzx", + CoinType::Kava => "kava1ten42eesehw0ktddcp0fws7d3ycsqez3r38zkp", + CoinType::Bluzelle => "bluzelle1ten42eesehw0ktddcp0fws7d3ycsqez32usaxh", + CoinType::BandChain => "band1ten42eesehw0ktddcp0fws7d3ycsqez3xtnacw", + CoinType::Rootstock => "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309", + CoinType::THORChain => "thor1ten42eesehw0ktddcp0fws7d3ycsqez3er2y4e", + CoinType::CryptoOrg => "cro1ten42eesehw0ktddcp0fws7d3ycsqez38lmxuh", + CoinType::Ronin => "ronin:Ac1ec44E4f0ca7D172B7803f6836De87Fb72b309", + CoinType::Secret => "secret1ten42eesehw0ktddcp0fws7d3ycsqez3ap8ka6", + CoinType::Osmosis => "osmo1ten42eesehw0ktddcp0fws7d3ycsqez3hlq0k5", + CoinType::NativeEvmos => "evmos14s0vgnj0pjnazu4hsqlksdk7slah9vcfvt8ssm", + CoinType::Agoric => "agoric1ten42eesehw0ktddcp0fws7d3ycsqez3de3qss", + CoinType::NativeInjective => "inj14s0vgnj0pjnazu4hsqlksdk7slah9vcfyrp6ct", + CoinType::NativeCanto => "canto14s0vgnj0pjnazu4hsqlksdk7slah9vcfuuhw7m", + CoinType::InternetComputer => "290cc7c359f44c8516fc169c5ed4f0f3ae2e24bf5de0d4c51f5e7545b5474faa", + // end_of_coin_address_derivation - DO NOT REMOVE + _ => panic!("{:?} must be covered", coin), + }; + + let any_address = TWAnyAddressHelper::wrap(unsafe { + tw_any_address_create_with_public_key_derivation( + public_key.ptr(), + coin as u32, + Derivation::Default as u32, + ) + }); + + let description = + TWStringHelper::wrap(unsafe { tw_any_address_description(any_address.ptr()) }); + assert_eq!( + description.to_string(), + Some(expected_address.to_string()), + "Invalid {:?} address", + coin + ); + } +} diff --git a/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs b/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs deleted file mode 100644 index 0f4a6e9d928..00000000000 --- a/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs +++ /dev/null @@ -1,287 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -use tw_any_coin::ffi::tw_any_address::{ - tw_any_address_create_bech32_with_public_key, tw_any_address_create_with_public_key_derivation, - tw_any_address_create_with_string, tw_any_address_data, tw_any_address_description, - tw_any_address_is_valid, tw_any_address_is_valid_bech32, -}; -use tw_any_coin::test_utils::TWAnyAddressHelper; -use tw_coin_entry::derivation::Derivation; -use tw_coin_registry::blockchain_type::BlockchainType; -use tw_coin_registry::registry::supported_coin_items; -use tw_encoding::hex::DecodeHex; -use tw_keypair::ffi::privkey::tw_private_key_get_public_key_by_type; -use tw_keypair::test_utils::tw_private_key_helper::TWPrivateKeyHelper; -use tw_keypair::test_utils::tw_public_key_helper::TWPublicKeyHelper; -use tw_keypair::tw::PublicKeyType; -use tw_memory::test_utils::tw_data_helper::TWDataHelper; -use tw_memory::test_utils::tw_string_helper::TWStringHelper; - -const ETHEREUM_COIN_TYPE: u32 = 60; -const OSMOSIS_COIN_TYPE: u32 = 10000118; - -#[test] -fn test_any_address_derive() { - let private_key = TWPrivateKeyHelper::with_hex( - "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", - ); - - for coin in supported_coin_items() { - let public_key = TWPublicKeyHelper::wrap(unsafe { - tw_private_key_get_public_key_by_type(private_key.ptr(), coin.public_key_type as u32) - }); - - // TODO match `CoinType` when it's generated. - let expected_address = match coin.blockchain { - BlockchainType::Aptos => { - "0x9006fa46f038224e8004bdda97f2e7a60c2c3d135bce7cb15541e5c0aae907a4" - }, - // By default, Bitcoin will return a P2PKH address. - BlockchainType::Bitcoin => "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", - BlockchainType::Cosmos if coin.id == "cosmos" => { - "cosmos1ten42eesehw0ktddcp0fws7d3ycsqez3lynlqx" - }, - // Skip other Cosmos chains as they have different addresses. - // TODO fix this when `CoinType` is generated by a codegen tool. - BlockchainType::Cosmos => continue, - BlockchainType::Ethereum => "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309", - BlockchainType::InternetComputer => { - "290cc7c359f44c8516fc169c5ed4f0f3ae2e24bf5de0d4c51f5e7545b5474faa" - }, - BlockchainType::NativeEvmos => "evmos14s0vgnj0pjnazu4hsqlksdk7slah9vcfvt8ssm", - BlockchainType::NativeInjective => "inj14s0vgnj0pjnazu4hsqlksdk7slah9vcfyrp6ct", - BlockchainType::Ronin => "ronin:Ac1ec44E4f0ca7D172B7803f6836De87Fb72b309", - BlockchainType::Thorchain => "thor1ten42eesehw0ktddcp0fws7d3ycsqez3er2y4e", - BlockchainType::Unsupported => unreachable!(), - }; - - let any_address = TWAnyAddressHelper::wrap(unsafe { - tw_any_address_create_with_public_key_derivation( - public_key.ptr(), - coin.coin_id, - Derivation::Default as u32, - ) - }); - - let description = - TWStringHelper::wrap(unsafe { tw_any_address_description(any_address.ptr()) }); - assert_eq!(description.to_string(), Some(expected_address.to_string())); - } -} - -#[test] -fn test_any_address_normalize_eth() { - for coin in supported_coin_items() { - // TODO match `CoinType` when it's generated. - let (denormalized, expected_normalized) = match coin.blockchain { - BlockchainType::Aptos => ( - "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", - "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", - ), - BlockchainType::Bitcoin => ( - "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", - "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", - ), - BlockchainType::Cosmos if coin.id == "cosmos" => ( - "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08", - "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08", - ), - // Skip other Cosmos chains until `CoinType` is not generated by a codegen tool. - BlockchainType::Cosmos => continue, - BlockchainType::Ethereum => ( - "0xb16db98b365b1f89191996942612b14f1da4bd5f", - "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f", - ), - BlockchainType::InternetComputer => ( - "290CC7C359F44C8516FC169C5ED4F0F3AE2E24BF5DE0D4C51F5E7545B5474FAA", - "290cc7c359f44c8516fc169c5ed4f0f3ae2e24bf5de0d4c51f5e7545b5474faa", - ), - BlockchainType::NativeEvmos => ( - "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34", - "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34", - ), - BlockchainType::NativeInjective => ( - "inj14py36sx57ud82t9yrks9z6hdsrpn5x6k8tf7m3", - "inj14py36sx57ud82t9yrks9z6hdsrpn5x6k8tf7m3", - ), - BlockchainType::Ronin => ( - "0xb16db98b365b1f89191996942612b14f1da4bd5f", - "ronin:b16Db98B365B1f89191996942612B14F1Da4Bd5f", - ), - BlockchainType::Thorchain => ( - "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", - "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", - ), - BlockchainType::Unsupported => unreachable!(), - }; - - let denormalized = TWStringHelper::create(denormalized); - - let any_address = TWAnyAddressHelper::wrap(unsafe { - tw_any_address_create_with_string(denormalized.ptr(), coin.coin_id) - }); - - let normalized = - TWStringHelper::wrap(unsafe { tw_any_address_description(any_address.ptr()) }); - - assert_eq!( - normalized.to_string(), - Some(expected_normalized.to_string()) - ); - } -} - -#[test] -fn test_any_address_is_valid_coin() { - for coin in supported_coin_items() { - let valid = match coin.blockchain { - BlockchainType::Aptos => vec![ - "0x1", - "0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", - "eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", - "19aadeca9388e009d136245b9a67423f3eee242b03142849eb4f81a4a409e59c", - "0x777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb", - "0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175", - ], - BlockchainType::Bitcoin => vec![ - "1MrZNGN7mfWZiZNQttrzHjfw72jnJC2JNx", - "bc1qunq74p3h8425hr6wllevlvqqr6sezfxj262rff", - "bc1pwse34zfpvt344rvlt7tw0ngjtfh9xasc4q03avf0lk74jzjpzjuqaz7ks5", - ], - BlockchainType::Cosmos if coin.id == "cosmos" => vec![ - "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", - "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax69k08", - "cosmosvalconspub1zcjduepqjnnwe2jsywv0kfc97pz04zkm7tc9k2437cde2my3y5js9t7cw9mstfg3sa", - ], - // Skip other Cosmos chains until `CoinType` is not generated by a codegen tool. - BlockchainType::Cosmos => continue, - BlockchainType::Ethereum => vec![ - "0xb16db98b365b1f89191996942612b14f1da4bd5f", - "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f", - ], - BlockchainType::InternetComputer => vec![ - "fb257577279ecac604d4780214af95aa6adc3a814f6f3d6d7ac844d1deca500a", - "e90c48d54847f4758f1d6b589a1db2500757a49a6722d4f775e050107b4b752d", - "a7c5baf393aed527ef6fb3869fbf84dd4e562edf9b04bd8f9bfbd6b8e6a22776", - "4cb2ca5cfcfa1d952f8cd7f0ec46c96e1023ab057b83a2c7ce236b9e71ccca0b", - ], - BlockchainType::NativeEvmos => vec![ - "evmos14py36sx57ud82t9yrks9z6hdsrpn5x6k0r05np", - "evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw34" - ], - BlockchainType::NativeInjective => vec![ - "inj13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a", - "inj1xmpkmxr4as00em23tc2zgmuyy2gr4h3wgcl6vd" - ], - BlockchainType::Ronin => vec![ - "0xb16db98b365b1f89191996942612b14f1da4bd5f", - "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f", - "ronin:b16db98b365b1f89191996942612b14f1da4bd5f", - "ronin:b16Db98B365B1f89191996942612B14F1Da4Bd5f", - ], - BlockchainType::Thorchain => vec![ - "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0exn2r", - "thor1c8jd7ad9pcw4k3wkuqlkz4auv95mldr2kyhc65", - ], - _ => unreachable!(), - }; - - for valid_addr in valid { - let valid = TWStringHelper::create(valid_addr); - assert!(unsafe { tw_any_address_is_valid(valid.ptr(), coin.coin_id) }); - } - } -} - -#[test] -fn test_any_address_is_valid_coin_invalid() { - for coin in supported_coin_items() { - let invalid = match coin.blockchain { - BlockchainType::Aptos => { - vec![ - "", // Empty - "Seff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", // Invalid Hex - "eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175bb", // Too long - "0xSeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", - ] - }, - BlockchainType::Bitcoin => { - vec!["0xb16db98b365b1f89191996942612b14f1da4bd5f"] - }, - BlockchainType::Cosmos => vec![ - "cosmosvaloper1sxx9mszve0gaedz5ld7qdkjkfv8z992ax6", - "one1a50tun737ulcvwy0yvve0pvu5skq0kjargvhwe", - "bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2", - ], - BlockchainType::Ethereum | BlockchainType::Ronin => { - vec!["b16Db98B365B1f89191996942612B14F1Da4Bd5f"] - }, - BlockchainType::InternetComputer => vec![ - "3357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278b", - "3357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278bce", - "553357cba483f268d044d4bbd4639f82c16028a76eebdf62c51bc11fc918d278", - ], - BlockchainType::NativeEvmos => vec!["evmos17xpfvakm2amg962yls6f84z3kell8c5ljcjw"], - BlockchainType::NativeInjective => vec!["ini13u6g7vqgw074mgmf2ze2cadzvkz9snlwcrtq8a"], - BlockchainType::Thorchain => vec![ - "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02", - "thor1z53wwe7md6cewz9sqwqzn0aavpaun0gw0e", - ], - BlockchainType::Unsupported => unreachable!(), - }; - - for invalid_addr in invalid { - let valid = TWStringHelper::create(invalid_addr); - assert!(!unsafe { tw_any_address_is_valid(valid.ptr(), coin.coin_id) }); - } - } -} - -#[test] -fn test_any_address_get_data_eth() { - let addr = "0xb16Db98B365B1f89191996942612B14F1Da4Bd5f"; - - let address_str = TWStringHelper::create(addr); - let any_address = TWAnyAddressHelper::wrap(unsafe { - tw_any_address_create_with_string(address_str.ptr(), ETHEREUM_COIN_TYPE) - }); - let data = TWDataHelper::wrap(unsafe { tw_any_address_data(any_address.ptr()) }); - assert_eq!(data.to_vec(), Some(addr.decode_hex().unwrap())); -} - -#[test] -fn test_any_address_is_valid_bech32() { - let addr = "juno1mry47pkga5tdswtluy0m8teslpalkdq0gnn4mf"; - - let address_str = TWStringHelper::create(addr); - let hrp = TWStringHelper::create("juno"); - // Should be valid even though Osmosis chain has `osmo` default hrp. - let result = - unsafe { tw_any_address_is_valid_bech32(address_str.ptr(), OSMOSIS_COIN_TYPE, hrp.ptr()) }; - assert!(result); -} - -#[test] -fn test_any_address_create_bech32_with_public_key() { - let private_key = TWPrivateKeyHelper::with_hex( - "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", - ); - let public_key = TWPublicKeyHelper::wrap(unsafe { - tw_private_key_get_public_key_by_type(private_key.ptr(), PublicKeyType::Secp256k1 as u32) - }); - let hrp = TWStringHelper::create("juno"); - - // Should be valid even though Osmosis chain has `osmo` default hrp. - let any_address = TWAnyAddressHelper::wrap(unsafe { - tw_any_address_create_bech32_with_public_key(public_key.ptr(), OSMOSIS_COIN_TYPE, hrp.ptr()) - }); - - let description = - TWStringHelper::wrap(unsafe { tw_any_address_description(any_address.ptr()) }); - let expected = "juno1ten42eesehw0ktddcp0fws7d3ycsqez3fksy86"; - assert_eq!(description.to_string(), Some(expected.to_string())); -} diff --git a/rust/tw_any_coin/tests/tw_any_signer_ffi_tests.rs b/rust/tw_any_coin/tests/tw_any_signer_ffi_tests.rs index 7677f6c838b..74cd4e04d4b 100644 --- a/rust/tw_any_coin/tests/tw_any_signer_ffi_tests.rs +++ b/rust/tw_any_coin/tests/tw_any_signer_ffi_tests.rs @@ -4,109 +4,8 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -use std::borrow::Cow; use tw_any_coin::ffi::tw_any_signer::tw_any_signer_sign; -use tw_coin_entry::error::SigningErrorType; -use tw_encoding::hex::{DecodeHex, ToHex}; use tw_memory::test_utils::tw_data_helper::TWDataHelper; -use tw_number::U256; -use tw_proto::{deserialize, serialize}; - -const COSMOS_COIN_TYPE: u32 = 118; -const ETHEREUM_COIN_TYPE: u32 = 60; - -#[test] -fn test_any_signer_sign_eth() { - use tw_proto::Ethereum::Proto; - - let private = "0x4646464646464646464646464646464646464646464646464646464646464646" - .decode_hex() - .unwrap(); - - let transfer = Proto::mod_Transaction::Transfer { - amount: U256::encode_be_compact(1_000_000_000_000_000_000), - data: Cow::default(), - }; - - let input = Proto::SigningInput { - chain_id: U256::encode_be_compact(1), - nonce: U256::encode_be_compact(9), - gas_price: U256::encode_be_compact(20_000_000_000), - gas_limit: U256::encode_be_compact(21_000), - to_address: "0x3535353535353535353535353535353535353535".into(), - transaction: Some(Proto::Transaction { - transaction_oneof: Proto::mod_Transaction::OneOftransaction_oneof::transfer(transfer), - }), - private_key: private.into(), - ..Proto::SigningInput::default() - }; - - let input_data = TWDataHelper::create(serialize(&input).unwrap()); - - let output = - TWDataHelper::wrap(unsafe { tw_any_signer_sign(input_data.ptr(), ETHEREUM_COIN_TYPE) }) - .to_vec() - .expect("!tw_any_signer_sign returned nullptr"); - - let output: Proto::SigningOutput = deserialize(&output).unwrap(); - assert_eq!(output.error, SigningErrorType::OK); - assert!(output.error_message.is_empty()); - - let expected = "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83"; - assert_eq!(output.encoded.to_hex(), expected); -} - -#[test] -fn test_any_signer_sign_cosmos() { - use tw_proto::Cosmos::Proto; - use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum; - - let private_key = "8bbec3772ddb4df68f3186440380c301af116d1422001c1877d6f5e4dba8c8af" - .decode_hex() - .unwrap(); - - let send_msg = Proto::mod_Message::Send { - from_address: "cosmos1mky69cn8ektwy0845vec9upsdphktxt03gkwlx".into(), - to_address: "cosmos18s0hdnsllgcclweu9aymw4ngktr2k0rkygdzdp".into(), - amounts: vec![Proto::Amount { - denom: "uatom".into(), - amount: "400000".into(), - }], - ..Proto::mod_Message::Send::default() - }; - let input = Proto::SigningInput { - signing_mode: Proto::SigningMode::Protobuf, - account_number: 546179, - chain_id: "cosmoshub-4".into(), - sequence: 0, - fee: Some(Proto::Fee { - gas: 200000, - amounts: vec![Proto::Amount { - denom: "uatom".into(), - amount: "1000".into(), - }], - }), - private_key: private_key.into(), - messages: vec![Proto::Message { - message_oneof: MessageEnum::send_coins_message(send_msg), - }], - ..Proto::SigningInput::default() - }; - - let input_data = TWDataHelper::create(serialize(&input).unwrap()); - - let output = - TWDataHelper::wrap(unsafe { tw_any_signer_sign(input_data.ptr(), COSMOS_COIN_TYPE) }) - .to_vec() - .expect("!tw_any_signer_sign returned nullptr"); - - let output: Proto::SigningOutput = deserialize(&output).unwrap(); - assert_eq!(output.error, SigningErrorType::OK); - assert!(output.error_message.is_empty()); - - let expected = r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CpIBCo8BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm8KLWNvc21vczFta3k2OWNuOGVrdHd5MDg0NXZlYzl1cHNkcGhrdHh0MDNna3dseBItY29zbW9zMThzMGhkbnNsbGdjY2x3ZXU5YXltdzRuZ2t0cjJrMHJreWdkemRwGg8KBXVhdG9tEgY0MDAwMDASZQpOCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAuzvXOQ3owLGf5VGjeSzHzbpEfRn1+alK0HB4T4dVjZJEgQKAggBEhMKDQoFdWF0b20SBDEwMDAQwJoMGkCvvVE6d29P30cO9/lnXyGunWMPxNY12NuqDcCnFkNM0H4CUQdl1Gc9+ogIJbro5nyzZzlv9rl2/GsZox/JXoCX"}"#; - assert_eq!(output.serialized, expected); -} #[test] fn test_any_signer_sign_unknown_coin() { diff --git a/rust/tw_coin_registry/Cargo.toml b/rust/tw_coin_registry/Cargo.toml index e8a4155f09b..a63ba08b267 100644 --- a/rust/tw_coin_registry/Cargo.toml +++ b/rust/tw_coin_registry/Cargo.toml @@ -7,6 +7,8 @@ edition = "2021" lazy_static = "1.4.0" serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.96" +strum = "0.25" +strum_macros = "0.25" tw_aptos = { path = "../tw_aptos" } tw_bitcoin = { path = "../tw_bitcoin" } tw_coin_entry = { path = "../tw_coin_entry" } @@ -22,3 +24,8 @@ tw_native_evmos = { path = "../chains/tw_native_evmos" } tw_native_injective = { path = "../chains/tw_native_injective" } tw_ronin = { path = "../tw_ronin" } tw_thorchain = { path = "../chains/tw_thorchain" } + +[build-dependencies] +itertools = "0.10.5" +serde = { version = "1.0.163", features = ["derive"] } +serde_json = "1.0.96" diff --git a/rust/tw_coin_registry/build.rs b/rust/tw_coin_registry/build.rs new file mode 100644 index 00000000000..4c79c1d01f4 --- /dev/null +++ b/rust/tw_coin_registry/build.rs @@ -0,0 +1,105 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use itertools::Itertools; +use serde::Deserialize; +use std::io::Write; +use std::path::PathBuf; +use std::{env, fs}; + +/// We're only interested in `id` and `name` of the coin to generate `CoinType` enum. +#[derive(Deserialize)] +struct CoinItem { + #[serde(rename = "coinId")] + coin_id: u64, + name: String, +} + +/// Transforms a coin name to a Rust name. +/// https://github.com/trustwallet/wallet-core/blob/3769f31b7d0c75126b2f426bb065364429aaa379/codegen/lib/coin_skeleton_gen.rb#L15-L22 +fn format_name(name: &str) -> String { + name.replace([' ', '.', '-'], "") +} + +fn generate_coin_type(coins: &[CoinItem]) -> String { + const RAW_TYPE: &str = "u32"; + const ENUM_NAME: &str = "CoinType"; + + let coin_types_variants = coins + .iter() + .map(|coin| format!("\t{} = {},\n", format_name(&coin.name), coin.coin_id)) + .join(""); + + format!( + r#"#[allow(non_camel_case_types)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[derive(strum_macros::EnumIter, strum_macros::FromRepr)] +#[repr({RAW_TYPE})] +pub enum CoinType {{ +{coin_types_variants} +}} + +impl {ENUM_NAME} {{ + pub fn iter() -> impl IntoIterator {{ + use strum::IntoEnumIterator; + + ::iter() + }} +}} + +impl TryFrom<{RAW_TYPE}> for {ENUM_NAME} {{ + type Error = (); + + fn try_from(num: {RAW_TYPE}) -> Result<{ENUM_NAME}, ()> {{ + {ENUM_NAME}::from_repr(num).ok_or(()) + }} +}} + +impl<'de> serde::Deserialize<'de> for {ENUM_NAME} {{ + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + {{ + let num_value: {RAW_TYPE} = {RAW_TYPE}::deserialize(deserializer)?; + {ENUM_NAME}::try_from(num_value).map_err(|_| serde::de::Error::custom("Invalid `CoinType` value")) + }} +}} +"# + ) +} + +fn generate_and_write_coin_type(coins: &[CoinItem]) { + let coin_type_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("coin_type.rs"); + + let coin_type_content = generate_coin_type(coins); + + let mut file = fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(coin_type_path) + .expect("Error creating/opening coin_type.rs"); + file.write_all(coin_type_content.as_bytes()) + .expect("Error writing coin_type.rs"); +} + +fn main() { + let registry_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("..") + .join("registry.json"); + let registry_path_str = registry_path.to_str().unwrap(); + + // Re-run this build.rs if the `proto` directory has been changed (i.e. a new file is added). + println!("cargo:rerun-if-changed={registry_path_str}"); + + let registry_bytes = fs::read(registry_path).expect("Error reading registry.json"); + + let coins: Vec = + serde_json::from_slice(®istry_bytes).expect("registry.json expected to be valid"); + + generate_and_write_coin_type(&coins); +} diff --git a/rust/tw_coin_registry/src/blockchain_type.rs b/rust/tw_coin_registry/src/blockchain_type.rs index c344d6b16b5..357697cd83c 100644 --- a/rust/tw_coin_registry/src/blockchain_type.rs +++ b/rust/tw_coin_registry/src/blockchain_type.rs @@ -4,15 +4,13 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -use crate::error::RegistryError; -use serde::de::Error; -use serde::{Deserialize, Deserializer}; -use std::str::FromStr; +use serde::Deserialize; /// Blockchain implementation type. /// Extend this enum when adding new blockchains. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Deserialize, PartialEq)] pub enum BlockchainType { + // start_of_blockchain_type - USED TO GENERATE CODE Aptos, Bitcoin, Cosmos, @@ -22,34 +20,13 @@ pub enum BlockchainType { NativeInjective, Ronin, Thorchain, + // end_of_blockchain_type - USED TO GENERATE CODE + #[serde(other)] Unsupported, } -impl<'de> Deserialize<'de> for BlockchainType { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - BlockchainType::from_str(&s).map_err(|e| Error::custom(format!("{e:?}"))) - } -} - -impl FromStr for BlockchainType { - type Err = RegistryError; - - fn from_str(s: &str) -> Result { - match s { - "Aptos" => Ok(BlockchainType::Aptos), - "Bitcoin" => Ok(BlockchainType::Bitcoin), - "Cosmos" => Ok(BlockchainType::Cosmos), - "Ethereum" => Ok(BlockchainType::Ethereum), - "InternetComputer" => Ok(BlockchainType::InternetComputer), - "NativeEvmos" => Ok(BlockchainType::NativeEvmos), - "NativeInjective" => Ok(BlockchainType::NativeInjective), - "Ronin" => Ok(BlockchainType::Ronin), - "Thorchain" => Ok(BlockchainType::Thorchain), - _ => Ok(BlockchainType::Unsupported), - } +impl BlockchainType { + pub fn is_supported(&self) -> bool { + !matches!(self, BlockchainType::Unsupported) } } diff --git a/rust/tw_coin_registry/src/dispatcher.rs b/rust/tw_coin_registry/src/dispatcher.rs index 0f3c36e2b70..02850a513d0 100644 --- a/rust/tw_coin_registry/src/dispatcher.rs +++ b/rust/tw_coin_registry/src/dispatcher.rs @@ -24,6 +24,7 @@ use tw_thorchain::entry::ThorchainEntry; pub type CoinEntryExtStaticRef = &'static dyn CoinEntryExt; pub type EvmEntryExtStaticRef = &'static dyn EvmEntryExt; +// start_of_blockchain_entries - USED TO GENERATE CODE const APTOS: AptosEntry = AptosEntry; const BITCOIN: BitcoinEntry = BitcoinEntry; const COSMOS: CosmosEntry = CosmosEntry; @@ -33,9 +34,11 @@ const NATIVE_EVMOS: NativeEvmosEntry = NativeEvmosEntry; const NATIVE_INJECTIVE: NativeInjectiveEntry = NativeInjectiveEntry; const RONIN: RoninEntry = RoninEntry; const THORCHAIN: ThorchainEntry = ThorchainEntry; +// end_of_blockchain_entries - USED TO GENERATE CODE pub fn blockchain_dispatcher(blockchain: BlockchainType) -> RegistryResult { match blockchain { + // start_of_blockchain_dispatcher - USED TO GENERATE CODE BlockchainType::Aptos => Ok(&APTOS), BlockchainType::Bitcoin => Ok(&BITCOIN), BlockchainType::Cosmos => Ok(&COSMOS), @@ -44,8 +47,9 @@ pub fn blockchain_dispatcher(blockchain: BlockchainType) -> RegistryResult Ok(&NATIVE_EVMOS), BlockchainType::NativeInjective => Ok(&NATIVE_INJECTIVE), BlockchainType::Ronin => Ok(&RONIN), - BlockchainType::Thorchain => Ok(&THORCHAIN), BlockchainType::Unsupported => Err(RegistryError::Unsupported), + BlockchainType::Thorchain => Ok(&THORCHAIN), + // end_of_blockchain_dispatcher - USED TO GENERATE CODE } } @@ -61,15 +65,8 @@ pub fn coin_dispatcher( pub fn evm_dispatcher(coin: CoinType) -> RegistryResult { let item = get_coin_item(coin)?; match item.blockchain { - BlockchainType::Aptos => Err(RegistryError::Unsupported), - BlockchainType::Bitcoin => Err(RegistryError::Unsupported), - BlockchainType::Cosmos => Err(RegistryError::Unsupported), BlockchainType::Ethereum => Ok(ÐEREUM), - BlockchainType::InternetComputer => Err(RegistryError::Unsupported), - BlockchainType::NativeEvmos => Err(RegistryError::Unsupported), - BlockchainType::NativeInjective => Err(RegistryError::Unsupported), BlockchainType::Ronin => Ok(&RONIN), - BlockchainType::Thorchain => Err(RegistryError::Unsupported), - BlockchainType::Unsupported => Err(RegistryError::Unsupported), + _ => Err(RegistryError::Unsupported), } } diff --git a/rust/tw_coin_registry/src/lib.rs b/rust/tw_coin_registry/src/lib.rs index cbad9faaa44..d8e50a8f1c6 100644 --- a/rust/tw_coin_registry/src/lib.rs +++ b/rust/tw_coin_registry/src/lib.rs @@ -6,7 +6,10 @@ pub mod blockchain_type; pub mod coin_context; -pub mod coin_type; pub mod dispatcher; pub mod error; pub mod registry; + +pub mod coin_type { + include!(concat!(env!("OUT_DIR"), "/coin_type.rs")); +} diff --git a/rust/tw_coin_registry/src/registry.rs b/rust/tw_coin_registry/src/registry.rs index e438210c278..58b6fef8cc7 100644 --- a/rust/tw_coin_registry/src/registry.rs +++ b/rust/tw_coin_registry/src/registry.rs @@ -51,6 +51,13 @@ pub fn supported_coin_items() -> impl Iterator { registry_iter().filter(|item| !matches!(item.blockchain, BlockchainType::Unsupported)) } +#[inline] +pub fn coin_items_by_blockchain( + blockchain: BlockchainType, +) -> impl Iterator { + registry_iter().filter(move |item| item.blockchain == blockchain) +} + fn parse_registry_json() -> RegistryMap { let items: Vec = serde_json::from_str(REGISTRY_JSON).expect("registry.json expected to be valid"); diff --git a/rust/wallet_core_rs/src/ffi/ethereum/abi.rs b/rust/wallet_core_rs/src/ffi/ethereum/abi.rs index 76b9906d0bc..84f64877781 100644 --- a/rust/wallet_core_rs/src/ffi/ethereum/abi.rs +++ b/rust/wallet_core_rs/src/ffi/ethereum/abi.rs @@ -20,9 +20,10 @@ use tw_misc::try_or_else; /// \return serialized `EthereumAbi::Proto::ContractCallDecodingOutput`. #[no_mangle] pub unsafe extern "C" fn tw_ethereum_abi_decode_contract_call( - coin: CoinType, + coin: u32, input: *const TWData, ) -> *mut TWData { + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); let input_data = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); let evm_dispatcher = try_or_else!(evm_dispatcher(coin), std::ptr::null_mut); @@ -39,9 +40,10 @@ pub unsafe extern "C" fn tw_ethereum_abi_decode_contract_call( /// \return The serialized data of a `TW.EthereumAbi.Proto.ParamsDecodingOutput` proto object. #[no_mangle] pub unsafe extern "C" fn tw_ethereum_abi_decode_params( - coin: CoinType, + coin: u32, input: *const TWData, ) -> *mut TWData { + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); let input_data = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); let evm_dispatcher = try_or_else!(evm_dispatcher(coin), std::ptr::null_mut); @@ -58,9 +60,10 @@ pub unsafe extern "C" fn tw_ethereum_abi_decode_params( /// \return function type signature as a Non-null string. #[no_mangle] pub unsafe extern "C" fn tw_ethereum_abi_function_get_signature( - coin: CoinType, + coin: u32, input: *const TWData, ) -> *mut TWString { + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); let input_data = try_or_else!(TWData::from_ptr_as_ref(input), || TWString::new() .into_ptr()); let evm_dispatcher = try_or_else!(evm_dispatcher(coin), || TWString::new().into_ptr()); @@ -78,9 +81,10 @@ pub unsafe extern "C" fn tw_ethereum_abi_function_get_signature( /// \return The serialized data of a `TW.EthereumAbi.Proto.FunctionEncodingOutput` proto object. #[no_mangle] pub unsafe extern "C" fn tw_ethereum_abi_encode_function( - coin: CoinType, + coin: u32, input: *const TWData, ) -> *mut TWData { + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); let input_data = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); let evm_dispatcher = try_or_else!(evm_dispatcher(coin), std::ptr::null_mut); @@ -97,9 +101,10 @@ pub unsafe extern "C" fn tw_ethereum_abi_encode_function( /// \return The serialized data of a `TW.EthereumAbi.Proto.ValueDecodingOutput` proto object. #[no_mangle] pub unsafe extern "C" fn tw_ethereum_abi_decode_value( - coin: CoinType, + coin: u32, input: *const TWData, ) -> *mut TWData { + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); let input_data = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); let evm_dispatcher = try_or_else!(evm_dispatcher(coin), std::ptr::null_mut); diff --git a/rust/wallet_core_rs/src/ffi/ethereum/rlp.rs b/rust/wallet_core_rs/src/ffi/ethereum/rlp.rs index 308228bd09b..f13470891c6 100644 --- a/rust/wallet_core_rs/src/ffi/ethereum/rlp.rs +++ b/rust/wallet_core_rs/src/ffi/ethereum/rlp.rs @@ -18,10 +18,8 @@ use tw_misc::try_or_else; /// \param input Non-null serialized `EthereumRlp::Proto::EncodingInput`. /// \return serialized `EthereumRlp::Proto::EncodingOutput`. #[no_mangle] -pub unsafe extern "C" fn tw_ethereum_rlp_encode( - coin: CoinType, - input: *const TWData, -) -> *mut TWData { +pub unsafe extern "C" fn tw_ethereum_rlp_encode(coin: u32, input: *const TWData) -> *mut TWData { + let coin = try_or_else!(CoinType::try_from(coin), std::ptr::null_mut); let input_data = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); let evm_dispatcher = try_or_else!(evm_dispatcher(coin), std::ptr::null_mut); evm_dispatcher diff --git a/rust/wallet_core_rs/tests/ethereum_abi.rs b/rust/wallet_core_rs/tests/ethereum_abi.rs index 85b98a043dc..50f1dd09d7e 100644 --- a/rust/wallet_core_rs/tests/ethereum_abi.rs +++ b/rust/wallet_core_rs/tests/ethereum_abi.rs @@ -17,12 +17,11 @@ use wallet_core_rs::ffi::ethereum::abi::{ tw_ethereum_abi_function_get_signature, }; +use tw_coin_registry::coin_type::CoinType; use Proto::mod_ParamType::OneOfparam as ParamTypeEnum; use Proto::mod_Token::OneOftoken as TokenEnum; use Proto::AbiError as AbiErrorKind; -const ETHEREUM_COIN_TYPE: u32 = 60; - fn param(name: &str, kind: ParamTypeEnum<'static>) -> Proto::Param<'static> { Proto::Param { name: name.to_string().into(), @@ -58,7 +57,7 @@ fn test_ethereum_abi_decode_contract_call() { let input_data = TWDataHelper::create(serialize(&input).unwrap()); let output_data = TWDataHelper::wrap(unsafe { - tw_ethereum_abi_decode_contract_call(ETHEREUM_COIN_TYPE, input_data.ptr()) + tw_ethereum_abi_decode_contract_call(CoinType::Ethereum as u32, input_data.ptr()) }) .to_vec() .expect("!tw_ethereum_abi_decode_contract_call returned nullptr"); @@ -98,7 +97,7 @@ fn test_ethereum_abi_decode_params() { let input_data = TWDataHelper::create(serialize(&input).unwrap()); let output_data = TWDataHelper::wrap(unsafe { - tw_ethereum_abi_decode_params(ETHEREUM_COIN_TYPE, input_data.ptr()) + tw_ethereum_abi_decode_params(CoinType::Ethereum as u32, input_data.ptr()) }) .to_vec() .expect("!tw_ethereum_abi_decode_params returned nullptr"); @@ -135,7 +134,7 @@ fn test_ethereum_abi_function_get_signature() { let input_data = TWDataHelper::create(serialize(&input).unwrap()); let actual = TWStringHelper::wrap(unsafe { - tw_ethereum_abi_function_get_signature(ETHEREUM_COIN_TYPE, input_data.ptr()) + tw_ethereum_abi_function_get_signature(CoinType::Ethereum as u32, input_data.ptr()) }) .to_string() .expect("!tw_ethereum_abi_function_get_signature returned nullptr"); @@ -156,7 +155,7 @@ fn test_ethereum_abi_encode_function() { let input_data = TWDataHelper::create(serialize(&input).unwrap()); let output_data = TWDataHelper::wrap(unsafe { - tw_ethereum_abi_encode_function(ETHEREUM_COIN_TYPE, input_data.ptr()) + tw_ethereum_abi_encode_function(CoinType::Ethereum as u32, input_data.ptr()) }) .to_vec() .expect("!tw_ethereum_abi_encode_function returned nullptr"); @@ -182,7 +181,7 @@ fn test_ethereum_abi_decode_value() { let input_data = TWDataHelper::create(serialize(&input).unwrap()); let output_data = TWDataHelper::wrap(unsafe { - tw_ethereum_abi_decode_value(ETHEREUM_COIN_TYPE, input_data.ptr()) + tw_ethereum_abi_decode_value(CoinType::Ethereum as u32, input_data.ptr()) }) .to_vec() .expect("!tw_ethereum_abi_decode_value returned nullptr"); diff --git a/rust/wallet_core_rs/tests/ethereum_rlp.rs b/rust/wallet_core_rs/tests/ethereum_rlp.rs index 29811ac0def..2326c482042 100644 --- a/rust/wallet_core_rs/tests/ethereum_rlp.rs +++ b/rust/wallet_core_rs/tests/ethereum_rlp.rs @@ -5,6 +5,7 @@ // file LICENSE at the root of the source code distribution tree. use tw_coin_entry::error::SigningErrorType; +use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::ToHex; use tw_memory::test_utils::tw_data_helper::TWDataHelper; use tw_proto::EthereumRlp::Proto as RlpProto; @@ -12,8 +13,6 @@ use tw_proto::{deserialize, serialize}; use wallet_core_rs::ffi::ethereum::rlp::tw_ethereum_rlp_encode; use RlpProto::mod_RlpItem::OneOfitem as Item; -const ETHEREUM_COIN_TYPE: u32 = 60; - #[test] fn test_ethereum_rlp() { let item = RlpProto::RlpItem { @@ -22,10 +21,11 @@ fn test_ethereum_rlp() { let input = RlpProto::EncodingInput { item: Some(item) }; let input_data = TWDataHelper::create(serialize(&input).unwrap()); - let output_data = - TWDataHelper::wrap(unsafe { tw_ethereum_rlp_encode(ETHEREUM_COIN_TYPE, input_data.ptr()) }) - .to_vec() - .expect("!tw_ethereum_rlp_encode returned nullptr"); + let output_data = TWDataHelper::wrap(unsafe { + tw_ethereum_rlp_encode(CoinType::Ethereum as u32, input_data.ptr()) + }) + .to_vec() + .expect("!tw_ethereum_rlp_encode returned nullptr"); let output: RlpProto::EncodingOutput = deserialize(&output_data).expect("!tw_ethereum_rlp_encode returned an invalid output");