From 6ef3eb511ef566145251f9551d4e2987ffe5c552 Mon Sep 17 00:00:00 2001 From: satoshiotomakan <127754187+satoshiotomakan@users.noreply.github.com> Date: Fri, 24 Nov 2023 14:22:29 +0100 Subject: [PATCH] [Rust]: Extend codegen-v2 capabilities (#3570) * [Rust]: Small refactoring * [Rust]: Add `TemplateGenerator` * Add new blockchain crate templates * [Rust]: Add new blockchain integration tests templates * [Rust]: Add new blockchain to test_coin_address_derivation * [C++]: Add cpp::new_blockchain * Add C++ Entry.h template * [C++]: Fix cpp::new_blockchain * [C++]: Add TWCoinType, TWBlockchain enum generating * [C++]: Add Blockchain entry to the `Coin.cpp` dispatcher * [Codegen]: Some improvements * [Codegen]: Revert coin_skeleton_gen.rb * [Codegen]: Generate Blockchain.proto file * [Codegen]: Generate TWCoinTypeTests.cppp file * [Codegen]: Generate TWAnySignerTests.cpp, TWAnyAddressTests.cpp files * [Codegen]: Fix Protobuf * [Codegen]: Refactor codegen-v1 to allow to generate mobile tests only * [Codegen]: Generate CoinAddressDerivationTests.cpp cases * [Codegen]: Add new-evmchain cmd * [Codegen]: Refactor C++ code generators * [Codegen]: Refactor Rust generators, fix sorting error * [Codegen]: Run codegen-v2 new-blockchain-rust iotex on Rust CI pipeline * [CI] Trigger CI --- .github/workflows/linux-ci-rust.yml | 15 ++ codegen-v2/Cargo.lock | 10 ++ codegen-v2/Cargo.toml | 1 + .../cpp/blockchain_dispatcher_generator.rs | 75 ++++++++++ codegen-v2/src/codegen/cpp/entry_generator.rs | 43 ++++++ codegen-v2/src/codegen/cpp/mod.rs | 45 ++++++ codegen-v2/src/codegen/cpp/new_blockchain.rs | 36 +++++ codegen-v2/src/codegen/cpp/new_evmchain.rs | 22 +++ codegen-v2/src/codegen/cpp/templates/Entry.h | 19 +++ .../cpp/templates/TWAnyAddressTests.cpp | 26 ++++ .../cpp/templates/TWAnySignerTests.cpp | 19 +++ .../codegen/cpp/templates/TWCoinTypeTests.cpp | 31 ++++ .../cpp/tw_any_address_tests_generator.rs | 39 +++++ .../cpp/tw_any_signer_tests_generator.rs | 39 +++++ codegen-v2/src/codegen/cpp/tw_blockchain.rs | 41 ++++++ ...coin_address_derivation_tests_generator.rs | 65 +++++++++ .../src/codegen/cpp/tw_coin_type_generator.rs | 37 +++++ .../cpp/tw_coin_type_tests_generator.rs | 39 +++++ codegen-v2/src/codegen/mod.rs | 3 + codegen-v2/src/codegen/proto/mod.rs | 18 +++ .../src/codegen/proto/new_blockchain.rs | 13 ++ .../src/codegen/proto/proto_generator.rs | 37 +++++ .../codegen/proto/templates/Blockchain.proto | 39 +++++ .../rust/blockchain_dispatcher_generator.rs | 80 +++++++++++ .../src/codegen/rust/blockchain_type.rs | 133 ------------------ .../codegen/rust/blockchain_type_generator.rs | 41 ++++++ .../coin_address_derivation_test_generator.rs | 56 ++++++++ codegen-v2/src/codegen/rust/coin_crate.rs | 84 +++++------ .../codegen/rust/coin_integration_tests.rs | 108 +++++++------- .../rust/coin_registry_manifest_generator.rs | 21 +++ codegen-v2/src/codegen/rust/mod.rs | 72 ++-------- codegen-v2/src/codegen/rust/new_blockchain.rs | 25 ++-- codegen-v2/src/codegen/rust/new_evmchain.rs | 14 ++ .../templates/blockchain_crate/Cargo.toml | 10 ++ .../templates/blockchain_crate/address.rs | 36 +++++ .../templates/blockchain_crate/compiler.rs | 52 +++++++ .../rust/templates/blockchain_crate/entry.rs | 91 ++++++++++++ .../rust/templates/blockchain_crate/lib.rs | 10 ++ .../rust/templates/blockchain_crate/signer.rs | 29 ++++ .../integration_tests/address_tests.rs | 30 ++++ .../integration_tests/compile_tests.rs | 10 ++ .../rust/templates/integration_tests/mod.rs | 9 ++ .../templates/integration_tests/sign_tests.rs | 10 ++ codegen-v2/src/codegen/rust/toml_editor.rs | 6 +- codegen-v2/src/codegen/template_generator.rs | 80 +++++++++++ codegen-v2/src/{codegen/rust => }/coin_id.rs | 0 codegen-v2/src/lib.rs | 2 + codegen-v2/src/main.rs | 46 ++++-- codegen-v2/src/registry.rs | 89 ++++++++++++ codegen-v2/src/utils.rs | 58 ++++++-- codegen/bin/newcoin | 2 +- codegen/bin/newcoin-mobile-tests | 22 +++ codegen/lib/coin_skeleton_gen.rb | 63 ++++++--- include/TrustWalletCore/TWBlockchain.h | 4 +- include/TrustWalletCore/TWCoinType.h | 1 + ...sts.rs => coin_address_derivation_test.rs} | 7 +- rust/tw_coin_entry/src/coin_entry.rs | 7 + rust/tw_coin_registry/src/dispatcher.rs | 2 +- tests/common/CoinAddressDerivationTests.cpp | 2 + 59 files changed, 1664 insertions(+), 360 deletions(-) create mode 100644 codegen-v2/src/codegen/cpp/blockchain_dispatcher_generator.rs create mode 100644 codegen-v2/src/codegen/cpp/entry_generator.rs create mode 100644 codegen-v2/src/codegen/cpp/mod.rs create mode 100644 codegen-v2/src/codegen/cpp/new_blockchain.rs create mode 100644 codegen-v2/src/codegen/cpp/new_evmchain.rs create mode 100644 codegen-v2/src/codegen/cpp/templates/Entry.h create mode 100644 codegen-v2/src/codegen/cpp/templates/TWAnyAddressTests.cpp create mode 100644 codegen-v2/src/codegen/cpp/templates/TWAnySignerTests.cpp create mode 100644 codegen-v2/src/codegen/cpp/templates/TWCoinTypeTests.cpp create mode 100644 codegen-v2/src/codegen/cpp/tw_any_address_tests_generator.rs create mode 100644 codegen-v2/src/codegen/cpp/tw_any_signer_tests_generator.rs create mode 100644 codegen-v2/src/codegen/cpp/tw_blockchain.rs create mode 100644 codegen-v2/src/codegen/cpp/tw_coin_address_derivation_tests_generator.rs create mode 100644 codegen-v2/src/codegen/cpp/tw_coin_type_generator.rs create mode 100644 codegen-v2/src/codegen/cpp/tw_coin_type_tests_generator.rs create mode 100644 codegen-v2/src/codegen/proto/mod.rs create mode 100644 codegen-v2/src/codegen/proto/new_blockchain.rs create mode 100644 codegen-v2/src/codegen/proto/proto_generator.rs create mode 100644 codegen-v2/src/codegen/proto/templates/Blockchain.proto create mode 100644 codegen-v2/src/codegen/rust/blockchain_dispatcher_generator.rs delete mode 100644 codegen-v2/src/codegen/rust/blockchain_type.rs create mode 100644 codegen-v2/src/codegen/rust/blockchain_type_generator.rs create mode 100644 codegen-v2/src/codegen/rust/coin_address_derivation_test_generator.rs create mode 100644 codegen-v2/src/codegen/rust/coin_registry_manifest_generator.rs create mode 100644 codegen-v2/src/codegen/rust/new_evmchain.rs create mode 100644 codegen-v2/src/codegen/rust/templates/blockchain_crate/Cargo.toml create mode 100644 codegen-v2/src/codegen/rust/templates/blockchain_crate/address.rs create mode 100644 codegen-v2/src/codegen/rust/templates/blockchain_crate/compiler.rs create mode 100644 codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs create mode 100644 codegen-v2/src/codegen/rust/templates/blockchain_crate/lib.rs create mode 100644 codegen-v2/src/codegen/rust/templates/blockchain_crate/signer.rs create mode 100644 codegen-v2/src/codegen/rust/templates/integration_tests/address_tests.rs create mode 100644 codegen-v2/src/codegen/rust/templates/integration_tests/compile_tests.rs create mode 100644 codegen-v2/src/codegen/rust/templates/integration_tests/mod.rs create mode 100644 codegen-v2/src/codegen/rust/templates/integration_tests/sign_tests.rs create mode 100644 codegen-v2/src/codegen/template_generator.rs rename codegen-v2/src/{codegen/rust => }/coin_id.rs (100%) create mode 100644 codegen-v2/src/registry.rs create mode 100755 codegen/bin/newcoin-mobile-tests rename rust/tw_any_coin/tests/{coin_address_derivation_tests.rs => coin_address_derivation_test.rs} (96%) diff --git a/.github/workflows/linux-ci-rust.yml b/.github/workflows/linux-ci-rust.yml index 9a27424cea2..e0dcee23cfd 100644 --- a/.github/workflows/linux-ci-rust.yml +++ b/.github/workflows/linux-ci-rust.yml @@ -74,3 +74,18 @@ jobs: - name: Gather and check Rust code coverage run: | tools/check-coverage rust/coverage.stats rust/coverage.info + + # Generate files for a blockchain in the end of the pipeline. + # Please note the blockchain should not be implemented in Rust at the moment of running this step, + # otherwise consider either generate files for another blockchain or remove this step at all. + - name: Test codegen-v2 new-blockchain-rust + run: | + cargo run -- new-blockchain-rust iotex + working-directory: codegen-v2 + + # Check if `new-blockchain-rust` command has generated files that do not break project compilation. + - name: Check Rust compiles + run: | + cargo check --tests + working-directory: rust + diff --git a/codegen-v2/Cargo.lock b/codegen-v2/Cargo.lock index 254400df25c..6caac47e7df 100644 --- a/codegen-v2/Cargo.lock +++ b/codegen-v2/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -27,6 +36,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "codegen-v2" version = "0.1.0" dependencies = [ + "aho-corasick", "convert_case", "handlebars", "heck", diff --git a/codegen-v2/Cargo.toml b/codegen-v2/Cargo.toml index e024e4024ff..52ad244f84a 100644 --- a/codegen-v2/Cargo.toml +++ b/codegen-v2/Cargo.toml @@ -12,6 +12,7 @@ name = "parser" path = "src/main.rs" [dependencies] +aho-corasick = "1.1.2" convert_case = "0.6.0" pathdiff = "0.2.1" serde = { version = "1.0.159", features = ["derive"] } diff --git a/codegen-v2/src/codegen/cpp/blockchain_dispatcher_generator.rs b/codegen-v2/src/codegen/cpp/blockchain_dispatcher_generator.rs new file mode 100644 index 00000000000..58b13ac452c --- /dev/null +++ b/codegen-v2/src/codegen/cpp/blockchain_dispatcher_generator.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 crate::codegen::cpp::cpp_source_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +const COIN_INCLUDES_END: &str = "end_of_coin_includes_marker_do_not_modify"; +const COIN_DISPATCHER_DECLARATIONS_END: &str = + "end_of_coin_dipatcher_declarations_marker_do_not_modify"; +const COIN_DISPATCHER_SWITCH_END: &str = "end_of_coin_dipatcher_switch_marker_do_not_modify"; + +fn dispatcher_coin_cpp_path() -> PathBuf { + cpp_source_directory().join("Coin.cpp") +} + +/// Represents `Coin.cpp`. +pub struct BlockchainDispatcherGenerator; + +impl BlockchainDispatcherGenerator { + pub fn generate_new_blockchain_type_dispatching(coin: &CoinItem) -> Result<()> { + let mut file_content = FileContent::read(dispatcher_coin_cpp_path())?; + + Self::generate_include_of_blockchain_entry(coin, &mut file_content)?; + Self::generate_blockchain_entry_constant(coin, &mut file_content)?; + Self::generate_blockchain_dispatcher_case(coin, &mut file_content)?; + + file_content.write() + } + + fn generate_include_of_blockchain_entry( + coin: &CoinItem, + file_content: &mut FileContent, + ) -> Result<()> { + let blockchain_type = coin.blockchain_type(); + + let mut line_marker = file_content.rfind_line(|line| line.contains(COIN_INCLUDES_END))?; + line_marker.push_line_before(format!(r#"#include "{blockchain_type}/Entry.h""#)); + + Ok(()) + } + + fn generate_blockchain_entry_constant( + coin: &CoinItem, + file_content: &mut FileContent, + ) -> Result<()> { + let blockchain_type = coin.blockchain_type(); + + let mut entries_region = + file_content.rfind_line(|line| line.contains(COIN_DISPATCHER_DECLARATIONS_END))?; + entries_region.push_line_before(format!("{blockchain_type}::Entry {blockchain_type}DP;")); + + Ok(()) + } + + fn generate_blockchain_dispatcher_case( + coin: &CoinItem, + file_content: &mut FileContent, + ) -> Result<()> { + let blockchain_type = coin.blockchain_type(); + + let mut entries_region = + file_content.rfind_line(|line| line.contains(COIN_DISPATCHER_SWITCH_END))?; + entries_region.push_line_before(format!( + " case TWBlockchain{blockchain_type}: entry = &{blockchain_type}DP; break;" + )); + + Ok(()) + } +} diff --git a/codegen-v2/src/codegen/cpp/entry_generator.rs b/codegen-v2/src/codegen/cpp/entry_generator.rs new file mode 100644 index 00000000000..033c94d6856 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/entry_generator.rs @@ -0,0 +1,43 @@ +// 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::cpp::cpp_source_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::registry::CoinItem; +use crate::{Error, Result}; +use std::path::PathBuf; +use std::{fs, io}; + +const ENTRY_HEADER_TEMPLATE: &str = include_str!("templates/Entry.h"); + +pub fn coin_source_directory(coin: &CoinItem) -> PathBuf { + cpp_source_directory().join(coin.blockchain_type()) +} + +pub struct EntryGenerator; + +impl EntryGenerator { + pub fn generate(coin: &CoinItem) -> Result { + let blockchain_dir = coin_source_directory(coin); + let entry_header_path = blockchain_dir.join("Entry.h"); + + if blockchain_dir.exists() { + return Err(Error::IoError(io::Error::new( + io::ErrorKind::AlreadyExists, + "blockchain already exists", + ))); + } + + fs::create_dir(&blockchain_dir)?; + + TemplateGenerator::new(ENTRY_HEADER_TEMPLATE) + .write_to(entry_header_path.clone()) + .with_default_patterns(coin) + .write()?; + + Ok(entry_header_path) + } +} diff --git a/codegen-v2/src/codegen/cpp/mod.rs b/codegen-v2/src/codegen/cpp/mod.rs new file mode 100644 index 00000000000..87443e4ad6d --- /dev/null +++ b/codegen-v2/src/codegen/cpp/mod.rs @@ -0,0 +1,45 @@ +// 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::registry::CoinItem; +use std::env; +use std::path::PathBuf; + +pub mod blockchain_dispatcher_generator; +pub mod entry_generator; +pub mod new_blockchain; +pub mod new_evmchain; +pub mod tw_any_address_tests_generator; +pub mod tw_any_signer_tests_generator; +pub mod tw_blockchain; +pub mod tw_coin_address_derivation_tests_generator; +pub mod tw_coin_type_generator; +pub mod tw_coin_type_tests_generator; + +pub fn cpp_source_directory() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("src") +} + +pub fn cpp_include_directory() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("include") + .join("TrustWalletCore") +} + +pub fn integration_tests_directory() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("tests") +} + +pub fn coin_integration_tests_directory(coin: &CoinItem) -> PathBuf { + integration_tests_directory() + .join("chains") + .join(coin.coin_type()) +} diff --git a/codegen-v2/src/codegen/cpp/new_blockchain.rs b/codegen-v2/src/codegen/cpp/new_blockchain.rs new file mode 100644 index 00000000000..75634f481de --- /dev/null +++ b/codegen-v2/src/codegen/cpp/new_blockchain.rs @@ -0,0 +1,36 @@ +// 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::cpp::blockchain_dispatcher_generator::BlockchainDispatcherGenerator; +use crate::codegen::cpp::entry_generator::EntryGenerator; +use crate::codegen::cpp::tw_any_address_tests_generator::TWAnyAddressTestsGenerator; +use crate::codegen::cpp::tw_any_signer_tests_generator::TWAnySignerTestsGenerator; +use crate::codegen::cpp::tw_blockchain::TWBlockchainGenerator; +use crate::codegen::cpp::tw_coin_address_derivation_tests_generator::CoinAddressDerivationTestsGenerator; +use crate::codegen::cpp::tw_coin_type_generator::TWCoinTypeGenerator; +use crate::codegen::cpp::tw_coin_type_tests_generator::TWCoinTypeTestsGenerator; +use crate::registry::CoinItem; +use crate::Result; + +pub fn new_blockchain(coin: &CoinItem) -> Result<()> { + // Generate C++ files. + EntryGenerator::generate(coin)?; + + // Add the new coin type to the `TWCoinType` enum. + TWCoinTypeGenerator::generate_coin_type_variant(coin)?; + // Add the new blockchain type to the `TWBlockchain` enum. + TWBlockchainGenerator::generate_blockchain_type_variant(coin)?; + // Add the blockchain entry to the dispatcher `Coin.cpp`. + BlockchainDispatcherGenerator::generate_new_blockchain_type_dispatching(coin)?; + + // Add integration tests. + TWCoinTypeTestsGenerator::generate(coin)?; + TWAnyAddressTestsGenerator::generate(coin)?; + TWAnySignerTestsGenerator::generate(coin)?; + CoinAddressDerivationTestsGenerator::generate_new_coin_type_case(coin)?; + + Ok(()) +} diff --git a/codegen-v2/src/codegen/cpp/new_evmchain.rs b/codegen-v2/src/codegen/cpp/new_evmchain.rs new file mode 100644 index 00000000000..36c29e127e2 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/new_evmchain.rs @@ -0,0 +1,22 @@ +// 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::cpp::tw_coin_address_derivation_tests_generator::CoinAddressDerivationTestsGenerator; +use crate::codegen::cpp::tw_coin_type_generator::TWCoinTypeGenerator; +use crate::codegen::cpp::tw_coin_type_tests_generator::TWCoinTypeTestsGenerator; +use crate::registry::CoinItem; +use crate::Result; + +pub fn new_evmchain(coin: &CoinItem) -> Result<()> { + // Add the new coin type to the `TWCoinType` enum. + TWCoinTypeGenerator::generate_coin_type_variant(coin)?; + + // Add integration tests. + TWCoinTypeTestsGenerator::generate(coin)?; + CoinAddressDerivationTestsGenerator::generate_new_evm_coin_type_case(coin)?; + + Ok(()) +} diff --git a/codegen-v2/src/codegen/cpp/templates/Entry.h b/codegen-v2/src/codegen/cpp/templates/Entry.h new file mode 100644 index 00000000000..a07c98e471d --- /dev/null +++ b/codegen-v2/src/codegen/cpp/templates/Entry.h @@ -0,0 +1,19 @@ +// Copyright © 2017-{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. + +#pragma once + +#include "rust/RustCoinEntry.h" + +namespace TW::{BLOCKCHAIN} { + +/// Entry point for {BLOCKCHAIN} coin. +/// Note: do not put the implementation here (no matter how simple), to avoid having coin-specific includes in this file +class Entry : public Rust::RustCoinEntry { +}; + +} // namespace TW::{BLOCKCHAIN} + diff --git a/codegen-v2/src/codegen/cpp/templates/TWAnyAddressTests.cpp b/codegen-v2/src/codegen/cpp/templates/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..35516cf2597 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/templates/TWAnyAddressTests.cpp @@ -0,0 +1,26 @@ +// Copyright © 2017-{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. + +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +// TODO: Finalize tests + +TEST(TW{COIN_TYPE}, Address) { + // TODO: Finalize test implementation + + auto string = STRING("__ADD_VALID_ADDRESS_HERE__"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinType{COIN_TYPE})); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "__CORRESPONDING_ADDRESS_DATA__"); +} diff --git a/codegen-v2/src/codegen/cpp/templates/TWAnySignerTests.cpp b/codegen-v2/src/codegen/cpp/templates/TWAnySignerTests.cpp new file mode 100644 index 00000000000..38f1493b916 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/templates/TWAnySignerTests.cpp @@ -0,0 +1,19 @@ +// Copyright © 2017-{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. + +#include +#include "HexCoding.h" + +#include "TestUtilities.h" +#include + +using namespace TW; + +// TODO: Finalize tests + +TEST(TWAnySigner{COIN_TYPE}, Sign) { + // TODO: Finalize test implementation +} diff --git a/codegen-v2/src/codegen/cpp/templates/TWCoinTypeTests.cpp b/codegen-v2/src/codegen/cpp/templates/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..83124519c83 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/templates/TWCoinTypeTests.cpp @@ -0,0 +1,31 @@ +// Copyright © 2017-{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. + +#include "TestUtilities.h" +#include +#include + +TEST(TW{COIN_TYPE}CoinType, TWCoinType) { + const auto coin = TWCoinType{COIN_TYPE}; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("{EXPLORER_SAMPLE_TX}")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("{EXPLORER_SAMPLE_ACCOUNT}")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "{COIN_ID}"); + assertStringsEqual(name, "{COIN_TYPE}"); + assertStringsEqual(symbol, "{SYMBOL}"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), {DECIMALS}); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchain{BLOCKCHAIN}); + ASSERT_EQ(TWCoinTypeP2pkhPrefix(coin), {P2PKH_PREFIX}); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), {P2SH_PREFIX}); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), {STATIC_PREFIX}); + assertStringsEqual(txUrl, "{EXPLORER_URL}{EXPLORER_TX_PATH}{EXPLORER_SAMPLE_TX}"); + assertStringsEqual(accUrl, "{EXPLORER_URL}{EXPLORER_ACCOUNT_PATH}{EXPLORER_SAMPLE_ACCOUNT}"); +} diff --git a/codegen-v2/src/codegen/cpp/tw_any_address_tests_generator.rs b/codegen-v2/src/codegen/cpp/tw_any_address_tests_generator.rs new file mode 100644 index 00000000000..f7d7384a0d8 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/tw_any_address_tests_generator.rs @@ -0,0 +1,39 @@ +// 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::cpp::coin_integration_tests_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::registry::CoinItem; +use crate::Result; +use std::fs; +use std::path::PathBuf; + +const TW_ANY_ADDRESS_TESTS_TEMPLATE: &str = include_str!("templates/TWAnyAddressTests.cpp"); + +pub fn tw_any_address_tests_path(coin: &CoinItem) -> PathBuf { + coin_integration_tests_directory(coin).join("TWAnyAddressTests.cpp") +} + +pub struct TWAnyAddressTestsGenerator; + +impl TWAnyAddressTestsGenerator { + pub fn generate(coin: &CoinItem) -> Result<()> { + let coin_tests_dir = coin_integration_tests_directory(coin); + let tw_any_address_tests_path = coin_tests_dir.join("TWAnyAddressTests.cpp"); + + fs::create_dir_all(coin_tests_dir)?; + if tw_any_address_tests_path.exists() { + return Ok(()); + } + + TemplateGenerator::new(TW_ANY_ADDRESS_TESTS_TEMPLATE) + .write_to(tw_any_address_tests_path) + .with_default_patterns(coin) + .write()?; + + Ok(()) + } +} diff --git a/codegen-v2/src/codegen/cpp/tw_any_signer_tests_generator.rs b/codegen-v2/src/codegen/cpp/tw_any_signer_tests_generator.rs new file mode 100644 index 00000000000..32391ffe618 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/tw_any_signer_tests_generator.rs @@ -0,0 +1,39 @@ +// 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::cpp::coin_integration_tests_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::registry::CoinItem; +use crate::Result; +use std::fs; +use std::path::PathBuf; + +const TW_ANY_SIGNER_TESTS_TEMPLATE: &str = include_str!("templates/TWAnySignerTests.cpp"); + +pub fn tw_any_signer_tests_path(coin: &CoinItem) -> PathBuf { + coin_integration_tests_directory(coin).join("TWAnySignerTests.cpp") +} + +pub struct TWAnySignerTestsGenerator; + +impl TWAnySignerTestsGenerator { + pub fn generate(coin: &CoinItem) -> Result<()> { + let coin_tests_dir = coin_integration_tests_directory(coin); + let tw_any_signer_tests_path = coin_tests_dir.join("TWAnySignerTests.cpp"); + + fs::create_dir_all(coin_tests_dir)?; + if tw_any_signer_tests_path.exists() { + return Ok(()); + } + + TemplateGenerator::new(TW_ANY_SIGNER_TESTS_TEMPLATE) + .write_to(tw_any_signer_tests_path) + .with_default_patterns(coin) + .write()?; + + Ok(()) + } +} diff --git a/codegen-v2/src/codegen/cpp/tw_blockchain.rs b/codegen-v2/src/codegen/cpp/tw_blockchain.rs new file mode 100644 index 00000000000..dee2986d328 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/tw_blockchain.rs @@ -0,0 +1,41 @@ +// 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::cpp::cpp_include_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +/// An offset because of removed blockchain enum types. +const BLOCKCHAIN_TYPE_OFFSET: usize = 2; + +pub fn tw_blockchain_path() -> PathBuf { + cpp_include_directory().join("TWBlockchain.h") +} + +/// Represents `TWBlockchain.h`. +pub struct TWBlockchainGenerator; + +impl TWBlockchainGenerator { + pub fn generate_blockchain_type_variant(coin: &CoinItem) -> Result<()> { + let coin_type = coin.blockchain_type(); + + let mut tw_blockchain_type_rs = FileContent::read(tw_blockchain_path())?; + + { + let mut enum_region = + tw_blockchain_type_rs.find_region_with_prefix(" TWBlockchain")?; + // Add an offset because of removed blockchain enum types. + let new_blockchain_id = enum_region.count_lines() + BLOCKCHAIN_TYPE_OFFSET; + enum_region.push_line(format!( + " TWBlockchain{coin_type} = {new_blockchain_id}," + )); + } + + tw_blockchain_type_rs.write() + } +} diff --git a/codegen-v2/src/codegen/cpp/tw_coin_address_derivation_tests_generator.rs b/codegen-v2/src/codegen/cpp/tw_coin_address_derivation_tests_generator.rs new file mode 100644 index 00000000000..772fea23676 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/tw_coin_address_derivation_tests_generator.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 crate::codegen::cpp::integration_tests_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +const COIN_ADDRESS_DERIVATION_TESTS_END: &str = + "end_of_coin_address_derivation_tests_marker_do_not_modify"; +const EVM_ADDRESS_DERIVATION_TESTS_END: &str = + "end_of_evm_address_derivation_tests_marker_do_not_modify"; + +pub fn coin_address_derivation_tests_path() -> PathBuf { + integration_tests_directory() + .join("common") + .join("CoinAddressDerivationTests.cpp") +} + +/// Represents `CoinAddressDerivationTests.cpp`. +pub struct CoinAddressDerivationTestsGenerator; + +impl CoinAddressDerivationTestsGenerator { + pub fn generate_new_coin_type_case(coin: &CoinItem) -> Result<()> { + let coin_type = coin.coin_type(); + + let mut coin_address_derivation_test_rs = + FileContent::read(coin_address_derivation_tests_path())?; + + { + let mut switch_case_region = coin_address_derivation_test_rs + .rfind_line(|line| line.contains(COIN_ADDRESS_DERIVATION_TESTS_END))?; + + #[rustfmt::skip] + let test_case = format!( +r#" case TWCoinType{coin_type}: + EXPECT_EQ(address, "__TODO__"); + break;"# +); + + switch_case_region.push_paragraph_before(test_case); + } + + coin_address_derivation_test_rs.write() + } + + pub fn generate_new_evm_coin_type_case(coin: &CoinItem) -> Result<()> { + let coin_type = coin.coin_type(); + + let mut evm_address_derivation_test_rs = + FileContent::read(coin_address_derivation_tests_path())?; + + { + let mut switch_case_region = evm_address_derivation_test_rs + .rfind_line(|line| line.contains(EVM_ADDRESS_DERIVATION_TESTS_END))?; + switch_case_region.push_line_before(format!(" case TWCoinType{coin_type}:")); + } + + evm_address_derivation_test_rs.write() + } +} diff --git a/codegen-v2/src/codegen/cpp/tw_coin_type_generator.rs b/codegen-v2/src/codegen/cpp/tw_coin_type_generator.rs new file mode 100644 index 00000000000..385f69076cd --- /dev/null +++ b/codegen-v2/src/codegen/cpp/tw_coin_type_generator.rs @@ -0,0 +1,37 @@ +// 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::cpp::cpp_include_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +const TW_COIN_TYPE_END: &str = "end_of_tw_coin_type_marker_do_not_modify"; + +pub fn tw_coin_type_path() -> PathBuf { + cpp_include_directory().join("TWCoinType.h") +} + +/// Represents `TWCoinType.h`. +pub struct TWCoinTypeGenerator; + +impl TWCoinTypeGenerator { + pub fn generate_coin_type_variant(coin: &CoinItem) -> Result<()> { + let coin_type = coin.coin_type(); + let coin_id_number = coin.coin_id_number; + + let mut tw_coin_type_rs = FileContent::read(tw_coin_type_path())?; + + { + let mut enum_region = + tw_coin_type_rs.rfind_line(|line| line.contains(TW_COIN_TYPE_END))?; + enum_region.push_line_before(format!(" TWCoinType{coin_type} = {coin_id_number},")); + } + + tw_coin_type_rs.write() + } +} diff --git a/codegen-v2/src/codegen/cpp/tw_coin_type_tests_generator.rs b/codegen-v2/src/codegen/cpp/tw_coin_type_tests_generator.rs new file mode 100644 index 00000000000..2790c302138 --- /dev/null +++ b/codegen-v2/src/codegen/cpp/tw_coin_type_tests_generator.rs @@ -0,0 +1,39 @@ +// 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::cpp::coin_integration_tests_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::registry::CoinItem; +use crate::Result; +use std::fs; +use std::path::PathBuf; + +const TW_COIN_TYPE_TESTS_TEMPLATE: &str = include_str!("templates/TWCoinTypeTests.cpp"); + +pub fn tw_coin_type_tests_path(coin: &CoinItem) -> PathBuf { + coin_integration_tests_directory(coin).join("TWCoinTypeTests.cpp") +} + +pub struct TWCoinTypeTestsGenerator; + +impl TWCoinTypeTestsGenerator { + pub fn generate(coin: &CoinItem) -> Result<()> { + let coin_tests_dir = coin_integration_tests_directory(coin); + let tw_coin_type_tests_path = coin_tests_dir.join("TWCoinTypeTests.cpp"); + + fs::create_dir(coin_tests_dir)?; + if tw_coin_type_tests_path.exists() { + return Ok(()); + } + + TemplateGenerator::new(TW_COIN_TYPE_TESTS_TEMPLATE) + .write_to(tw_coin_type_tests_path) + .with_default_patterns(coin) + .write()?; + + Ok(()) + } +} diff --git a/codegen-v2/src/codegen/mod.rs b/codegen-v2/src/codegen/mod.rs index b0d31c99e15..2f1b1a70839 100644 --- a/codegen-v2/src/codegen/mod.rs +++ b/codegen-v2/src/codegen/mod.rs @@ -4,5 +4,8 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +pub mod cpp; +pub mod proto; pub mod rust; pub mod swift; +pub mod template_generator; diff --git a/codegen-v2/src/codegen/proto/mod.rs b/codegen-v2/src/codegen/proto/mod.rs new file mode 100644 index 00000000000..e760e33c8ea --- /dev/null +++ b/codegen-v2/src/codegen/proto/mod.rs @@ -0,0 +1,18 @@ +// 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::env; +use std::path::PathBuf; + +pub mod new_blockchain; +pub mod proto_generator; + +pub fn proto_source_directory() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("src") + .join("proto") +} diff --git a/codegen-v2/src/codegen/proto/new_blockchain.rs b/codegen-v2/src/codegen/proto/new_blockchain.rs new file mode 100644 index 00000000000..805e342f124 --- /dev/null +++ b/codegen-v2/src/codegen/proto/new_blockchain.rs @@ -0,0 +1,13 @@ +// 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::proto::proto_generator::ProtoGenerator; +use crate::registry::CoinItem; +use crate::Result; + +pub fn new_blockchain(coin: &CoinItem) -> Result<()> { + ProtoGenerator::generate(coin) +} diff --git a/codegen-v2/src/codegen/proto/proto_generator.rs b/codegen-v2/src/codegen/proto/proto_generator.rs new file mode 100644 index 00000000000..3b08ef8b16c --- /dev/null +++ b/codegen-v2/src/codegen/proto/proto_generator.rs @@ -0,0 +1,37 @@ +// 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::proto::proto_source_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::registry::CoinItem; +use crate::Result; +use std::path::PathBuf; + +const PROTO_TEMPLATE: &str = include_str!("templates/Blockchain.proto"); + +pub fn blockchain_proto_path(coin: &CoinItem) -> PathBuf { + let blockchain_type = coin.blockchain_type(); + proto_source_directory().join(format!("{blockchain_type}.proto")) +} + +pub struct ProtoGenerator; + +impl ProtoGenerator { + pub fn generate(coin: &CoinItem) -> Result<()> { + let proto_path = blockchain_proto_path(coin); + + if proto_path.exists() { + return Ok(()); + } + + TemplateGenerator::new(PROTO_TEMPLATE) + .write_to(proto_path) + .with_default_patterns(coin) + .write()?; + + Ok(()) + } +} diff --git a/codegen-v2/src/codegen/proto/templates/Blockchain.proto b/codegen-v2/src/codegen/proto/templates/Blockchain.proto new file mode 100644 index 00000000000..c78772ce0a5 --- /dev/null +++ b/codegen-v2/src/codegen/proto/templates/Blockchain.proto @@ -0,0 +1,39 @@ +// Copyright © 2017-{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. + +syntax = "proto3"; + +package TW.{BLOCKCHAIN}.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +// TODO: typical balance transfer, add more fields needed to sign +message TransferMessage { + int64 amount = 1; + int64 fee = 2; + string to = 3; +} + +// TODO: Input data necessary to create a signed transaction. +message SigningInput { + bytes private_key = 1; + + oneof message_oneof { + TransferMessage transfer = 2; + } +} + +// Transaction signing output. +message SigningOutput { + // Signed and encoded transaction bytes. + bytes encoded = 1; + + // A possible error, `OK` if none. + Common.Proto.SigningError error = 2; + + string error_message = 3; +} diff --git a/codegen-v2/src/codegen/rust/blockchain_dispatcher_generator.rs b/codegen-v2/src/codegen/rust/blockchain_dispatcher_generator.rs new file mode 100644 index 00000000000..4c00268a7ca --- /dev/null +++ b/codegen-v2/src/codegen/rust/blockchain_dispatcher_generator.rs @@ -0,0 +1,80 @@ +// 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_registry_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +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 dispatcher_path() -> PathBuf { + coin_registry_directory().join("src").join("dispatcher.rs") +} + +pub struct BlockchainDispatcherGenerator; + +impl BlockchainDispatcherGenerator { + pub fn generate_new_blockchain_type_dispatching(coin: &CoinItem) -> Result<()> { + let dispatcher_rs_path = dispatcher_path(); + let mut dispatcher_rs = FileContent::read(dispatcher_rs_path)?; + + Self::generate_use_of_blockchain_entry(coin, &mut dispatcher_rs)?; + Self::generate_blockchain_entry_constant(coin, &mut dispatcher_rs)?; + Self::generate_blockchain_dispatch(coin, &mut dispatcher_rs)?; + + dispatcher_rs.write() + } + + fn generate_use_of_blockchain_entry( + coin: &CoinItem, + file_content: &mut FileContent, + ) -> Result<()> { + let import_pattern = "use "; + let blockchain_entry = coin.blockchain_entry(); + let tw_crate_name = coin.id.to_tw_crate_name(); + + let mut last_entry = file_content.rfind_line(|line| line.contains(import_pattern))?; + last_entry.push_line_after(format!("use {tw_crate_name}::entry::{blockchain_entry};")); + + Ok(()) + } + + fn generate_blockchain_entry_constant( + coin: &CoinItem, + file_content: &mut FileContent, + ) -> Result<()> { + let blockchain_entry = coin.blockchain_entry(); + let blockchain_entry_const = coin.blockchain_entry_upper_snake(); + + let mut entries_region = file_content + .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(); + + Ok(()) + } + + fn generate_blockchain_dispatch(coin: &CoinItem, file_content: &mut FileContent) -> Result<()> { + let blockchain_type = coin.blockchain_type(); + let blockchain_entry_const = coin.blockchain_entry_upper_snake(); + + let mut dispatcher_region = file_content + .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(); + + Ok(()) + } +} diff --git a/codegen-v2/src/codegen/rust/blockchain_type.rs b/codegen-v2/src/codegen/rust/blockchain_type.rs deleted file mode 100644 index debce4802d6..00000000000 --- a/codegen-v2/src/codegen/rust/blockchain_type.rs +++ /dev/null @@ -1,133 +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 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/blockchain_type_generator.rs b/codegen-v2/src/codegen/rust/blockchain_type_generator.rs new file mode 100644 index 00000000000..f82ce5144fa --- /dev/null +++ b/codegen-v2/src/codegen/rust/blockchain_type_generator.rs @@ -0,0 +1,41 @@ +// 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_registry_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +const BLOCKCHAIN_TYPE_START: &str = "start_of_blockchain_type"; +const BLOCKCHAIN_TYPE_END: &str = "end_of_blockchain_type"; + +pub fn blockchain_type_path() -> PathBuf { + coin_registry_directory() + .join("src") + .join("blockchain_type.rs") +} + +/// Represents `BlockchainType` enum generator. +pub struct BlockchainTypeGenerator; + +impl BlockchainTypeGenerator { + pub fn add_new_blockchain_type(coin: &CoinItem) -> Result<()> { + let blockchain_type_rs_path = blockchain_type_path(); + let blockchain_type = 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() + } +} diff --git a/codegen-v2/src/codegen/rust/coin_address_derivation_test_generator.rs b/codegen-v2/src/codegen/rust/coin_address_derivation_test_generator.rs new file mode 100644 index 00000000000..25b2b9933b4 --- /dev/null +++ b/codegen-v2/src/codegen/rust/coin_address_derivation_test_generator.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 crate::codegen::rust::tw_any_coin_directory; +use crate::registry::CoinItem; +use crate::utils::FileContent; +use crate::Result; +use std::path::PathBuf; + +const COIN_ADDRESS_DERIVATION_TEST_END: &str = + "end_of_coin_address_derivation_tests_marker_do_not_modify"; +const EVM_ADDRESS_DERIVATION_TEST_END: &str = + "end_of_evm_address_derivation_tests_marker_do_not_modify"; + +pub fn coin_address_derivation_test_path() -> PathBuf { + tw_any_coin_directory() + .join("tests") + .join("coin_address_derivation_test.rs") +} + +pub struct CoinAddressDerivationTestGenerator; + +impl CoinAddressDerivationTestGenerator { + pub fn generate_new_coin_type_case(coin: &CoinItem) -> Result<()> { + let coin_type = coin.coin_type(); + + let mut coin_address_derivation_test_rs = + FileContent::read(coin_address_derivation_test_path())?; + + { + let mut end_of_test = coin_address_derivation_test_rs + .rfind_line(|line| line.contains(COIN_ADDRESS_DERIVATION_TEST_END))?; + end_of_test.push_line_before(format!(" CoinType::{coin_type} => todo!(),")); + } + + coin_address_derivation_test_rs.write() + } + + pub fn generate_new_evm_coin_type_case(coin: &CoinItem) -> Result<()> { + let coin_type = coin.coin_type(); + + let mut coin_address_derivation_test_rs = + FileContent::read(coin_address_derivation_test_path())?; + + { + let mut end_of_test = coin_address_derivation_test_rs + .rfind_line(|line| line.contains(EVM_ADDRESS_DERIVATION_TEST_END))?; + end_of_test.push_line_before(format!(" | CoinType::{coin_type}")); + } + + coin_address_derivation_test_rs.write() + } +} diff --git a/codegen-v2/src/codegen/rust/coin_crate.rs b/codegen-v2/src/codegen/rust/coin_crate.rs index 19a957d9978..8b6ef18b3c1 100644 --- a/codegen-v2/src/codegen/rust/coin_crate.rs +++ b/codegen-v2/src/codegen/rust/coin_crate.rs @@ -4,12 +4,21 @@ // 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::codegen::rust::chains_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::coin_id::CoinId; +use crate::registry::CoinItem; use crate::{Error, Result}; use std::path::PathBuf; use std::{fs, io}; +const BLOCKCHAIN_ADDRESS_TEMPLATE: &str = include_str!("templates/blockchain_crate/address.rs"); +const BLOCKCHAIN_COMPILER_TEMPLATE: &str = include_str!("templates/blockchain_crate/compiler.rs"); +const BLOCKCHAIN_ENTRY_TEMPLATE: &str = include_str!("templates/blockchain_crate/entry.rs"); +const BLOCKCHAIN_MANIFEST_TEMPLATE: &str = include_str!("templates/blockchain_crate/Cargo.toml"); +const BLOCKCHAIN_LIB_TEMPLATE: &str = include_str!("templates/blockchain_crate/lib.rs"); +const BLOCKCHAIN_SIGNER_TEMPLATE: &str = include_str!("templates/blockchain_crate/signer.rs"); + pub fn coin_source_directory(id: &CoinId) -> PathBuf { chains_directory().join(id.to_tw_crate_name()) } @@ -26,17 +35,15 @@ impl CoinCrate { /// 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(); + let blockchain_compiler_path = blockchain_src_path.join("compiler.rs"); + let blockchain_address_rs_path = blockchain_src_path.join("address.rs"); + let blockchain_signer_rs_path = blockchain_src_path.join("signer.rs"); if blockchain_path.exists() { return Err(Error::IoError(io::Error::new( @@ -48,40 +55,35 @@ impl CoinCrate { 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)?; + TemplateGenerator::new(BLOCKCHAIN_MANIFEST_TEMPLATE) + .write_to(blockchain_toml_path) + .with_default_patterns(&self.coin) + .write()?; + + TemplateGenerator::new(BLOCKCHAIN_LIB_TEMPLATE) + .write_to(blockchain_lib_rs_path) + .with_default_patterns(&self.coin) + .write()?; + + TemplateGenerator::new(BLOCKCHAIN_ENTRY_TEMPLATE) + .write_to(blockchain_entry_path) + .with_default_patterns(&self.coin) + .write()?; + + TemplateGenerator::new(BLOCKCHAIN_COMPILER_TEMPLATE) + .write_to(blockchain_compiler_path) + .with_default_patterns(&self.coin) + .write()?; + + TemplateGenerator::new(BLOCKCHAIN_ADDRESS_TEMPLATE) + .write_to(blockchain_address_rs_path) + .with_default_patterns(&self.coin) + .write()?; + + TemplateGenerator::new(BLOCKCHAIN_SIGNER_TEMPLATE) + .write_to(blockchain_signer_rs_path) + .with_default_patterns(&self.coin) + .write()?; Ok(blockchain_path) } diff --git a/codegen-v2/src/codegen/rust/coin_integration_tests.rs b/codegen-v2/src/codegen/rust/coin_integration_tests.rs index 3fde3ff1795..18c5356f064 100644 --- a/codegen-v2/src/codegen/rust/coin_integration_tests.rs +++ b/codegen-v2/src/codegen/rust/coin_integration_tests.rs @@ -4,13 +4,20 @@ // 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::codegen::rust::tw_any_coin_directory; +use crate::codegen::template_generator::TemplateGenerator; +use crate::coin_id::CoinId; +use crate::registry::CoinItem; use crate::utils::FileContent; -use crate::{Error, Result}; +use crate::Result; use std::fs; use std::path::PathBuf; +const ADDRESS_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/address_tests.rs"); +const COMPILE_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/compile_tests.rs"); +const MOD_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/mod.rs"); +const SIGN_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/sign_tests.rs"); + pub fn chains_integration_tests_directory() -> PathBuf { tw_any_coin_directory().join("tests").join("chains") } @@ -19,6 +26,12 @@ pub fn coin_integration_tests_directory(id: &CoinId) -> PathBuf { chains_integration_tests_directory().join(id.as_str()) } +pub fn coin_address_derivation_test_path() -> PathBuf { + tw_any_coin_directory() + .join("tests") + .join("coin_address_derivation_test.rs") +} + pub struct CoinIntegrationTests { coin: CoinItem, } @@ -38,6 +51,7 @@ impl CoinIntegrationTests { self.list_blockchain_in_chains_mod()?; self.create_address_tests()?; + self.create_compile_tests()?; self.create_sign_tests()?; self.create_chain_tests_mod_rs()?; @@ -49,71 +63,48 @@ impl CoinIntegrationTests { } fn create_address_tests(&self) -> Result<()> { - let header = rs_header(); - let chain_id = self.coin.id.as_str(); + let coin_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) + .join(format!("{coin_id}_address.rs")); + + TemplateGenerator::new(ADDRESS_TESTS_TEMPLATE) + .write_to(address_tests_path) + .with_default_patterns(&self.coin) + .write() + } + + fn create_compile_tests(&self) -> Result<()> { + let coin_id = self.coin.id.as_str(); + let compile_tests_path = self + .coin_tests_directory() + .join(format!("{coin_id}_compile.rs")); + + TemplateGenerator::new(COMPILE_TESTS_TEMPLATE) + .write_to(compile_tests_path) + .with_default_patterns(&self.coin) + .write() } fn create_sign_tests(&self) -> Result<()> { - let header = rs_header(); - let chain_id = self.coin.id.as_str(); + let coin_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) + .join(format!("{coin_id}_sign.rs")); + + TemplateGenerator::new(SIGN_TESTS_TEMPLATE) + .write_to(sign_tests_path) + .with_default_patterns(&self.coin) + .write() } 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) + TemplateGenerator::new(MOD_TESTS_TEMPLATE) + .write_to(blockchain_tests_mod_path) + .with_default_patterns(&self.coin) + .write() } fn list_blockchain_in_chains_mod(&self) -> Result<()> { @@ -124,8 +115,9 @@ mod {chain_id}_sign; { 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};")); + let mut mod_region = chains_mod_rs.find_region_with_prefix(mod_pattern)?; + mod_region.push_line(format!("mod {chain_id};")); + mod_region.sort(); } chains_mod_rs.write() diff --git a/codegen-v2/src/codegen/rust/coin_registry_manifest_generator.rs b/codegen-v2/src/codegen/rust/coin_registry_manifest_generator.rs new file mode 100644 index 00000000000..6b0ff03b2f2 --- /dev/null +++ b/codegen-v2/src/codegen/rust/coin_registry_manifest_generator.rs @@ -0,0 +1,21 @@ +// 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_registry_directory; +use crate::codegen::rust::toml_editor::Dependencies; +use crate::registry::CoinItem; +use crate::Result; +use std::path::Path; + +pub struct CoinRegistryManifestGenerator; + +impl CoinRegistryManifestGenerator { + pub fn add_dependency(coin: &CoinItem, 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(&coin.id.to_tw_crate_name(), path_to_new_blockchain_crate) + } +} diff --git a/codegen-v2/src/codegen/rust/mod.rs b/codegen-v2/src/codegen/rust/mod.rs index b0d1a55910b..7edc77d5062 100644 --- a/codegen-v2/src/codegen/rust/mod.rs +++ b/codegen-v2/src/codegen/rust/mod.rs @@ -4,17 +4,17 @@ // 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::env; use std::path::PathBuf; -use std::{env, fs}; -pub mod blockchain_type; +pub mod blockchain_dispatcher_generator; +pub mod blockchain_type_generator; +pub mod coin_address_derivation_test_generator; pub mod coin_crate; -pub mod coin_id; pub mod coin_integration_tests; +pub mod coin_registry_manifest_generator; pub mod new_blockchain; +pub mod new_evmchain; pub mod toml_editor; pub fn rust_source_directory() -> PathBuf { @@ -35,62 +35,6 @@ 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) +pub fn coin_registry_directory() -> PathBuf { + rust_source_directory().join("tw_coin_registry") } diff --git a/codegen-v2/src/codegen/rust/new_blockchain.rs b/codegen-v2/src/codegen/rust/new_blockchain.rs index 252bbcc92da..2f3a4996dd2 100644 --- a/codegen-v2/src/codegen/rust/new_blockchain.rs +++ b/codegen-v2/src/codegen/rust/new_blockchain.rs @@ -4,27 +4,32 @@ // 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::blockchain_dispatcher_generator::BlockchainDispatcherGenerator; +use crate::codegen::rust::blockchain_type_generator::BlockchainTypeGenerator; +use crate::codegen::rust::coin_address_derivation_test_generator::CoinAddressDerivationTestGenerator; 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::coin_registry_manifest_generator::CoinRegistryManifestGenerator; use crate::codegen::rust::toml_editor::Workspace; -use crate::codegen::rust::{read_coin_from_registry, workspace_toml_path}; +use crate::codegen::rust::workspace_toml_path; +use crate::registry::CoinItem; 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)?; - +pub fn new_blockchain(coin: &CoinItem) -> Result<()> { // Create blockchain's crate. - let blockchain_crate_path = CoinCrate::new(coin_item.clone()).create()?; + let blockchain_crate_path = CoinCrate::new(coin.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()?; + CoinIntegrationTests::new(coin.clone()).create()?; + CoinAddressDerivationTestGenerator::generate_new_coin_type_case(coin)?; + // Add the new blockchain to the `tw_coin_registry`. - CoinRegistry::new(coin_item).add(&blockchain_crate_path)?; + BlockchainTypeGenerator::add_new_blockchain_type(coin)?; + CoinRegistryManifestGenerator::add_dependency(coin, &blockchain_crate_path)?; + BlockchainDispatcherGenerator::generate_new_blockchain_type_dispatching(coin)?; Ok(()) } diff --git a/codegen-v2/src/codegen/rust/new_evmchain.rs b/codegen-v2/src/codegen/rust/new_evmchain.rs new file mode 100644 index 00000000000..2222306a4de --- /dev/null +++ b/codegen-v2/src/codegen/rust/new_evmchain.rs @@ -0,0 +1,14 @@ +// 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_address_derivation_test_generator::CoinAddressDerivationTestGenerator; +use crate::registry::CoinItem; +use crate::Result; + +pub fn new_evmchain(coin: &CoinItem) -> Result<()> { + // Modify integration tests. + CoinAddressDerivationTestGenerator::generate_new_evm_coin_type_case(coin) +} diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/Cargo.toml b/codegen-v2/src/codegen/rust/templates/blockchain_crate/Cargo.toml new file mode 100644 index 00000000000..9d5a7a7d04a --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "{TW_CRATE_NAME}" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_proto = { path = "../../tw_proto" } diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/address.rs b/codegen-v2/src/codegen/rust/templates/blockchain_crate/address.rs new file mode 100644 index 00000000000..96d862a63cd --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/address.rs @@ -0,0 +1,36 @@ +// Copyright © 2017-{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. + +use std::fmt; +use std::str::FromStr; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::AddressError; +use tw_memory::Data; + +pub struct {BLOCKCHAIN}Address { + // TODO add necessary fields. +} + +impl CoinAddress for {BLOCKCHAIN}Address { + #[inline] + fn data(&self) -> Data { + todo!() + } +} + +impl FromStr for {BLOCKCHAIN}Address { + type Err = AddressError; + + fn from_str(_s: &str) -> Result { + todo!() + } +} + +impl fmt::Display for {BLOCKCHAIN}Address { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + todo!() + } +} diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/compiler.rs b/codegen-v2/src/codegen/rust/templates/blockchain_crate/compiler.rs new file mode 100644 index 00000000000..2e1831cf5b6 --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/compiler.rs @@ -0,0 +1,52 @@ +// Copyright © 2017-{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. + +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::error::SigningResult; +use tw_coin_entry::signing_output_error; +use tw_proto::{BLOCKCHAIN}::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct {BLOCKCHAIN}Compiler; + +impl {BLOCKCHAIN}Compiler { + #[inline] + pub fn preimage_hashes( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> CompilerProto::PreSigningOutput<'static> { + Self::preimage_hashes_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e)) + } + + fn preimage_hashes_impl( + _coin: &dyn CoinContext, + _input: Proto::SigningInput<'_>, + ) -> SigningResult> { + todo!() + } + + #[inline] + pub fn compile( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Proto::SigningOutput<'static> { + Self::compile_impl(coin, input, signatures, public_keys) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn compile_impl( + _coin: &dyn CoinContext, + _input: Proto::SigningInput<'_>, + _signatures: Vec, + _public_keys: Vec, + ) -> SigningResult> { + todo!() + } +} diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs b/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs new file mode 100644 index 00000000000..afbada1d43e --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs @@ -0,0 +1,91 @@ +// Copyright © 2017-{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. + +use crate::address::{BLOCKCHAIN}Address; +use crate::compiler::{BLOCKCHAIN}Compiler; +use crate::signer::{BLOCKCHAIN}Signer; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::AddressResult; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::prefix::NoPrefix; +use tw_keypair::tw::PublicKey; +use tw_proto::{BLOCKCHAIN}::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct {BLOCKCHAIN}Entry; + +impl CoinEntry for {BLOCKCHAIN}Entry { + type AddressPrefix = NoPrefix; + type Address = {BLOCKCHAIN}Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + + #[inline] + fn parse_address( + &self, + _coin: &dyn CoinContext, + _address: &str, + _prefix: Option, + ) -> AddressResult { + todo!() + } + + #[inline] + fn parse_address_unchecked( + &self, + _coin: &dyn CoinContext, + address: &str, + ) -> AddressResult { + {BLOCKCHAIN}Address::from_str(address) + } + + #[inline] + fn derive_address( + &self, + _coin: &dyn CoinContext, + _public_key: PublicKey, + _derivation: Derivation, + _prefix: Option, + ) -> AddressResult { + todo!() + } + + #[inline] + fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + {BLOCKCHAIN}Signer::sign(coin, input) + } + + #[inline] + fn preimage_hashes( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + {BLOCKCHAIN}Compiler::preimage_hashes(coin, input) + } + + #[inline] + fn compile( + &self, + coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + {BLOCKCHAIN}Compiler::compile(coin, input, signatures, public_keys) + } +} diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/lib.rs b/codegen-v2/src/codegen/rust/templates/blockchain_crate/lib.rs new file mode 100644 index 00000000000..c5ac4e098e5 --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/lib.rs @@ -0,0 +1,10 @@ +// Copyright © 2017-{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. + +pub mod address; +pub mod compiler; +pub mod entry; +pub mod signer; diff --git a/codegen-v2/src/codegen/rust/templates/blockchain_crate/signer.rs b/codegen-v2/src/codegen/rust/templates/blockchain_crate/signer.rs new file mode 100644 index 00000000000..17cb69f66e9 --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/blockchain_crate/signer.rs @@ -0,0 +1,29 @@ +// Copyright © 2017-{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. + +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::SigningResult; +use tw_coin_entry::signing_output_error; +use tw_proto::{BLOCKCHAIN}::Proto; + +pub struct {BLOCKCHAIN}Signer; + +impl {BLOCKCHAIN}Signer { + pub fn sign( + coin: &dyn CoinContext, + input: Proto::SigningInput<'_>, + ) -> Proto::SigningOutput<'static> { + Self::sign_impl(coin, input) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn sign_impl( + _coin: &dyn CoinContext, + _input: Proto::SigningInput<'_>, + ) -> SigningResult> { + todo!() + } +} diff --git a/codegen-v2/src/codegen/rust/templates/integration_tests/address_tests.rs b/codegen-v2/src/codegen/rust/templates/integration_tests/address_tests.rs new file mode 100644 index 00000000000..6a3a7e6a330 --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/integration_tests/address_tests.rs @@ -0,0 +1,30 @@ +// Copyright © 2017-{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. + +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_{COIN_ID}_address_normalization() { + test_address_normalization(CoinType::{COIN_TYPE}, "DENORMALIZED", "EXPECTED"); +} + +#[test] +fn test_{COIN_ID}_address_is_valid() { + test_address_valid(CoinType::{COIN_TYPE}, "VALID ADDRESS"); +} + +#[test] +fn test_{COIN_ID}_address_invalid() { + test_address_invalid(CoinType::{COIN_TYPE}, "INVALID ADDRESS"); +} + +#[test] +fn test_{COIN_ID}_address_get_data() { + test_address_get_data(CoinType::{COIN_TYPE}, "ADDRESS", "HEX(DATA)"); +} diff --git a/codegen-v2/src/codegen/rust/templates/integration_tests/compile_tests.rs b/codegen-v2/src/codegen/rust/templates/integration_tests/compile_tests.rs new file mode 100644 index 00000000000..d403c5e154b --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/integration_tests/compile_tests.rs @@ -0,0 +1,10 @@ +// Copyright © 2017-{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. + +#[test] +fn test_{COIN_ID}_compile() { + todo!() +} diff --git a/codegen-v2/src/codegen/rust/templates/integration_tests/mod.rs b/codegen-v2/src/codegen/rust/templates/integration_tests/mod.rs new file mode 100644 index 00000000000..96f7f590af1 --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/integration_tests/mod.rs @@ -0,0 +1,9 @@ +// Copyright © 2017-{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. + +mod {COIN_ID}_address; +mod {COIN_ID}_compile; +mod {COIN_ID}_sign; diff --git a/codegen-v2/src/codegen/rust/templates/integration_tests/sign_tests.rs b/codegen-v2/src/codegen/rust/templates/integration_tests/sign_tests.rs new file mode 100644 index 00000000000..9a24b45d74b --- /dev/null +++ b/codegen-v2/src/codegen/rust/templates/integration_tests/sign_tests.rs @@ -0,0 +1,10 @@ +// Copyright © 2017-{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. + +#[test] +fn test_{COIN_ID}_sign() { + todo!() +} diff --git a/codegen-v2/src/codegen/rust/toml_editor.rs b/codegen-v2/src/codegen/rust/toml_editor.rs index da33d5fe270..0ac35b3c53d 100644 --- a/codegen-v2/src/codegen/rust/toml_editor.rs +++ b/codegen-v2/src/codegen/rust/toml_editor.rs @@ -37,8 +37,8 @@ impl Workspace { // 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); + let relative_path_to_crate_decorated = + Value::from(relative_path_to_crate).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())); @@ -73,7 +73,7 @@ impl Dependencies { 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.insert(dep_name, Item::Value(Value::InlineTable(new_member))); dependencies.sort_values(); fs::write(self.path_to_toml, manifest.to_string())?; diff --git a/codegen-v2/src/codegen/template_generator.rs b/codegen-v2/src/codegen/template_generator.rs new file mode 100644 index 00000000000..39f1780f1f1 --- /dev/null +++ b/codegen-v2/src/codegen/template_generator.rs @@ -0,0 +1,80 @@ +// 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::registry::CoinItem; +use crate::{current_year, Error, Result}; +use std::fs; +use std::path::PathBuf; + +const PATTERNS_CAPACITY: usize = 20; + +pub struct TemplateGenerator { + template_content: &'static str, + write_to: Option, + to_replace: Vec, + replace_with: Vec, +} + +impl TemplateGenerator { + pub fn new(template_content: &'static str) -> TemplateGenerator { + TemplateGenerator { + template_content, + write_to: None, + to_replace: Vec::with_capacity(PATTERNS_CAPACITY), + replace_with: Vec::with_capacity(PATTERNS_CAPACITY), + } + } + + pub fn write_to(mut self, write_to: PathBuf) -> TemplateGenerator { + self.write_to = Some(write_to); + self + } + + /// Use default patterns. + pub fn with_default_patterns(self, coin: &CoinItem) -> TemplateGenerator { + self.add_pattern("{YEAR}", current_year()) + .add_pattern("{BLOCKCHAIN}", coin.blockchain_type()) + .add_pattern("{TW_CRATE_NAME}", coin.id.to_tw_crate_name()) + .add_pattern("{COIN_ID}", coin.id.as_str()) + .add_pattern("{COIN_TYPE}", coin.coin_type()) + .add_pattern("{SYMBOL}", &coin.symbol) + .add_pattern("{DECIMALS}", coin.decimals) + .add_pattern("{P2PKH_PREFIX}", coin.p2pkh_prefix) + .add_pattern("{P2SH_PREFIX}", coin.p2sh_prefix) + .add_pattern("{STATIC_PREFIX}", coin.static_prefix) + .add_pattern("{EXPLORER_URL}", &coin.explorer.url) + .add_pattern("{EXPLORER_TX_PATH}", &coin.explorer.tx_path) + .add_pattern("{EXPLORER_ACCOUNT_PATH}", &coin.explorer.account_path) + .add_pattern("{EXPLORER_SAMPLE_TX}", &coin.explorer.sample_tx) + .add_pattern("{EXPLORER_SAMPLE_ACCOUNT}", &coin.explorer.sample_account) + } + + pub fn add_pattern( + mut self, + to_replace: K, + replace_with: V, + ) -> TemplateGenerator { + self.to_replace.push(to_replace.to_string()); + self.replace_with.push(replace_with.to_string()); + self + } + + pub fn write(self) -> Result<()> { + let write_to_path = self.write_to.ok_or_else(|| { + Error::io_error_other("Incorrect use of 'TemplateGenerator'".to_string()) + })?; + let file_to_write = fs::File::create(write_to_path)?; + + aho_corasick::AhoCorasick::new(self.to_replace) + .map_err(|e| Error::io_error_other(format!("Invalid patterns: {e}")))? + .try_stream_replace_all( + self.template_content.as_bytes(), + file_to_write, + &self.replace_with, + ) + .map_err(Error::from) + } +} diff --git a/codegen-v2/src/codegen/rust/coin_id.rs b/codegen-v2/src/coin_id.rs similarity index 100% rename from codegen-v2/src/codegen/rust/coin_id.rs rename to codegen-v2/src/coin_id.rs diff --git a/codegen-v2/src/lib.rs b/codegen-v2/src/lib.rs index 788f82cda8a..5227f533701 100644 --- a/codegen-v2/src/lib.rs +++ b/codegen-v2/src/lib.rs @@ -14,7 +14,9 @@ use std::io::Error as IoError; use toml_edit::TomlError; pub mod codegen; +pub mod coin_id; pub mod manifest; +pub mod registry; #[cfg(test)] mod tests; pub mod utils; diff --git a/codegen-v2/src/main.rs b/codegen-v2/src/main.rs index 39e5da606cf..a45bb39d733 100644 --- a/codegen-v2/src/main.rs +++ b/codegen-v2/src/main.rs @@ -4,9 +4,11 @@ // 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::codegen::{cpp, proto, rust}; +use libparser::coin_id::CoinId; use libparser::manifest::parse_dir; +use libparser::registry::read_coin_from_registry; use libparser::{Error, Result}; use std::fs::read_to_string; @@ -18,21 +20,45 @@ fn main() -> Result<()> { } match args[1].as_str() { + "new-blockchain-rust" => new_blockchain_rust(&args[2..]), + "new-blockchain" => new_blockchain(&args[2..]), + "new-evmchain" => new_evmchain(&args[2..]), "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); - } +fn new_blockchain_rust(args: &[String]) -> Result<()> { + let coin_str = args.iter().next().ok_or_else(|| Error::InvalidCommand)?; + let coin_id = CoinId::new(coin_str.clone())?; + let coin_item = read_coin_from_registry(&coin_id)?; - match args[0].as_str() { - "new-blockchain" => new_blockchain(&args[1]), - _ => Err(Error::InvalidCommand), - } + rust::new_blockchain::new_blockchain(&coin_item)?; + + Ok(()) +} + +fn new_blockchain(args: &[String]) -> Result<()> { + let coin_str = args.iter().next().ok_or_else(|| Error::InvalidCommand)?; + let coin_id = CoinId::new(coin_str.clone())?; + let coin_item = read_coin_from_registry(&coin_id)?; + + proto::new_blockchain::new_blockchain(&coin_item)?; + rust::new_blockchain::new_blockchain(&coin_item)?; + cpp::new_blockchain::new_blockchain(&coin_item)?; + + Ok(()) +} + +fn new_evmchain(args: &[String]) -> Result<()> { + let coin_str = args.iter().next().ok_or_else(|| Error::InvalidCommand)?; + let coin_id = CoinId::new(coin_str.clone())?; + let coin_item = read_coin_from_registry(&coin_id)?; + + rust::new_evmchain::new_evmchain(&coin_item)?; + cpp::new_evmchain::new_evmchain(&coin_item)?; + + Ok(()) } fn generate_swift_bindings() -> Result<()> { diff --git a/codegen-v2/src/registry.rs b/codegen-v2/src/registry.rs new file mode 100644 index 00000000000..aaad3ff0330 --- /dev/null +++ b/codegen-v2/src/registry.rs @@ -0,0 +1,89 @@ +// 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::coin_id::CoinId; +use crate::{Error, Result}; +use convert_case::{Case, Casing}; +use std::path::PathBuf; +use std::{env, fs}; + +pub fn registry_json_path() -> PathBuf { + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("registry.json") +} + +#[derive(Clone, Deserialize)] +pub struct CoinExplorer { + pub url: String, + #[serde(rename = "txPath")] + pub tx_path: String, + #[serde(rename = "accountPath")] + pub account_path: String, + #[serde(rename = "sampleTx")] + #[serde(default)] + pub sample_tx: String, + #[serde(rename = "sampleAccount")] + #[serde(default)] + pub sample_account: String, +} + +#[derive(Clone, Deserialize)] +pub struct CoinItem { + pub id: CoinId, + pub name: String, + #[serde(rename = "coinId")] + pub coin_id_number: u32, + pub symbol: String, + pub decimals: u8, + pub blockchain: String, + #[serde(rename = "p2pkhPrefix")] + #[serde(default)] + pub p2pkh_prefix: u8, + #[serde(rename = "p2shPrefix")] + #[serde(default)] + pub p2sh_prefix: u8, + #[serde(rename = "staticPrefix")] + #[serde(default)] + pub static_prefix: u8, + pub explorer: CoinExplorer, +} + +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 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/utils.rs b/codegen-v2/src/utils.rs index bb4b01d2902..43e1bd84f81 100644 --- a/codegen-v2/src/utils.rs +++ b/codegen-v2/src/utils.rs @@ -31,6 +31,28 @@ impl FileContent { read_lines(&path).map(|lines| FileContent { path, lines }) } + pub fn find_region_with_prefix(&mut self, prefix: &str) -> Result> { + // Find the first line that starts with the `prefix`. + let region_starts_at = self + .lines + .iter() + .position(|line| line.starts_with(prefix)) + .ok_or_else(|| Error::io_error_other(format!("Cannot find the `{prefix}` region")))?; + + // Find the last line that starts with the `prefix`. + let region_ends_at = self + .lines + .iter() + .rposition(|line| line.starts_with(prefix)) + .ok_or_else(|| Error::io_error_other(format!("Cannot find the `{prefix}` region")))?; + + Ok(FileRegion { + lines: &mut self.lines, + region_starts_at, + region_ends_at, + }) + } + pub fn find_region_with_comments( &mut self, start_comment: &str, @@ -74,16 +96,12 @@ impl FileContent { 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 - )) - })?; + 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, @@ -104,10 +122,15 @@ pub struct FileRegion<'a> { impl<'a> FileRegion<'a> { pub fn push_line(&mut self, line: String) { self.lines.insert(self.region_ends_at + 1, line); + self.region_ends_at += 1; } pub fn sort(&mut self) { - self.lines[self.region_starts_at..self.region_ends_at].sort() + self.lines[self.region_starts_at..=self.region_ends_at].sort() + } + + pub fn count_lines(&self) -> usize { + self.region_ends_at - self.region_starts_at } } @@ -117,6 +140,19 @@ pub struct LinePointer<'a> { } impl<'a> LinePointer<'a> { + /// Please note that the line pointer will be shifted to the same line on which it pointed before. + pub fn push_line_before(&mut self, line: String) { + self.lines.insert(self.line_idx, line); + self.line_idx += 1; + } + + pub fn push_paragraph_before(&mut self, paragraph: String) { + for line in paragraph.split("\n") { + self.push_line_before(line.to_string()); + } + } + + /// Please note that the line pointer will not be shifted to the pushed element. pub fn push_line_after(&mut self, line: String) { self.lines.insert(self.line_idx + 1, line); } diff --git a/codegen/bin/newcoin b/codegen/bin/newcoin index aeeec609d73..ee14b241f7a 100755 --- a/codegen/bin/newcoin +++ b/codegen/bin/newcoin @@ -18,4 +18,4 @@ end coin_id = command_line_args[0] -generate_skeleton(coin_id, "") +generate_skeleton(coin_id, "full") diff --git a/codegen/bin/newcoin-mobile-tests b/codegen/bin/newcoin-mobile-tests new file mode 100755 index 00000000000..d6ea29a84c9 --- /dev/null +++ b/codegen/bin/newcoin-mobile-tests @@ -0,0 +1,22 @@ +#!/usr/bin/env ruby + +# Sript for creating new skeleton files for new coin mobile tests. See also `newcoin` or `newevmchain`. +# It is considered to be used by codegen-v2 tool until Swift and Android tests generating supported. +# 1. Add relevant entry to registry.json (in order to minimize merge conflict, don't add at the very end) +# 2. Invoke this script with the id of the coin, e.g.: codegen/bin/newcoin-mobile-tests ethereum + +require 'fileutils' + +CurrentDir = File.dirname(__FILE__) +$LOAD_PATH.unshift(File.join(CurrentDir, '..', 'lib')) +require 'coin_skeleton_gen' + +command_line_args = ARGV +if command_line_args.length < 1 + puts "Usage: newcoin-mobile-tests " + return +end + +coin_id = command_line_args[0] + +generate_skeleton(coin_id, "mobile-tests") diff --git a/codegen/lib/coin_skeleton_gen.rb b/codegen/lib/coin_skeleton_gen.rb index 29335e4fdb0..9a12fca1e9e 100755 --- a/codegen/lib/coin_skeleton_gen.rb +++ b/codegen/lib/coin_skeleton_gen.rb @@ -90,6 +90,36 @@ def self.insert_target_line(target_file, target_line, original_line) return true end +def generate_blockchain_files(coin) + name = format_name(coin) + + generate_file("newcoin/Address.h.erb", "src/#{name}", "Address.h", coin) + generate_file("newcoin/Address.cpp.erb", "src/#{name}", "Address.cpp", coin) + generate_file("newcoin/Entry.h.erb", "src/#{name}", "Entry.h", coin) + generate_file("newcoin/Entry.cpp.erb", "src/#{name}", "Entry.cpp", coin) + generate_file("newcoin/Proto.erb", "src/proto", "#{name}.proto", coin) + generate_file("newcoin/Signer.h.erb", "src/#{name}", "Signer.h", coin) + generate_file("newcoin/Signer.cpp.erb", "src/#{name}", "Signer.cpp", coin) + + generate_file("newcoin/AddressTests.cpp.erb", "tests/chains/#{name}", "AddressTests.cpp", coin) + generate_file("newcoin/SignerTests.cpp.erb", "tests/chains/#{name}", "SignerTests.cpp", coin) + generate_file("newcoin/TransactionCompilerTests.cpp.erb", "tests/chains/#{name}", "TransactionCompilerTests.cpp", coin) + generate_file("newcoin/TWAddressTests.cpp.erb", "tests/chains/#{name}", "TWAnyAddressTests.cpp", coin) + generate_file("newcoin/TWSignerTests.cpp.erb", "tests/chains/#{name}", "TWAnySignerTests.cpp", coin) +end + +def generate_mobile_tests(coin) + name = format_name(coin) + + generate_file("newcoin/AddressTests.kt.erb", "android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/#{format_name_lowercase(coin)}", "Test#{name}Address.kt", coin) + generate_file("newcoin/SignerTests.kt.erb", "android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/#{format_name_lowercase(coin)}", "Test#{name}Signer.kt", coin) + generate_file("newcoin/Tests.swift.erb", "swift/Tests/Blockchains", "#{name}Tests.swift", coin) +end + +def generate_coin_type_tests(coin) + coin_test_gen.generate_coin_test_file(coin, 'TWCoinTypeTests.cpp.erb', true) +end + def generate_skeleton(coin_id, mode) puts "New coin template for coin '#{coin_id}' #{mode} requested" @@ -112,32 +142,19 @@ def generate_skeleton(coin_id, mode) return end coin = coinSelect.first - name = format_name(coin) - - insert_coin_type(coin, mode) - if (mode != "evm") + if (mode == "full") + insert_coin_type(coin, mode) insert_coin_entry(coin) - - generate_file("newcoin/Address.h.erb", "src/#{name}", "Address.h", coin) - generate_file("newcoin/Address.cpp.erb", "src/#{name}", "Address.cpp", coin) - generate_file("newcoin/Entry.h.erb", "src/#{name}", "Entry.h", coin) - generate_file("newcoin/Entry.cpp.erb", "src/#{name}", "Entry.cpp", coin) - generate_file("newcoin/Proto.erb", "src/proto", "#{name}.proto", coin) - generate_file("newcoin/Signer.h.erb", "src/#{name}", "Signer.h", coin) - generate_file("newcoin/Signer.cpp.erb", "src/#{name}", "Signer.cpp", coin) - - generate_file("newcoin/AddressTests.cpp.erb", "tests/chains/#{name}", "AddressTests.cpp", coin) - generate_file("newcoin/SignerTests.cpp.erb", "tests/chains/#{name}", "SignerTests.cpp", coin) - generate_file("newcoin/TransactionCompilerTests.cpp.erb", "tests/chains/#{name}", "TransactionCompilerTests.cpp", coin) - generate_file("newcoin/TWAddressTests.cpp.erb", "tests/chains/#{name}", "TWAnyAddressTests.cpp", coin) - generate_file("newcoin/TWSignerTests.cpp.erb", "tests/chains/#{name}", "TWAnySignerTests.cpp", coin) - generate_file("newcoin/AddressTests.kt.erb", "android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/#{format_name_lowercase(coin)}", "Test#{name}Address.kt", coin) - generate_file("newcoin/SignerTests.kt.erb", "android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/#{format_name_lowercase(coin)}", "Test#{name}Signer.kt", coin) - generate_file("newcoin/Tests.swift.erb", "swift/Tests/Blockchains", "#{name}Tests.swift", coin) + generate_blockchain_files(coin) + generate_mobile_tests(coin) + generate_coin_type_tests(coin) + elsif (mode == "evm") + insert_coin_type(coin, mode) + generate_coin_type_tests(coin) + elsif (mode == "mobile-tests") + generate_mobile_tests(coin) end - coin_test_gen.generate_coin_test_file(coin, 'TWCoinTypeTests.cpp.erb', true) - puts "please tools/generate-files to generate Swift/Java/Protobuf files" end diff --git a/include/TrustWalletCore/TWBlockchain.h b/include/TrustWalletCore/TWBlockchain.h index 75da789d240..94187ca6899 100644 --- a/include/TrustWalletCore/TWBlockchain.h +++ b/include/TrustWalletCore/TWBlockchain.h @@ -65,8 +65,8 @@ enum TWBlockchain { TWBlockchainSui = 50, TWBlockchainGreenfield = 51, TWBlockchainInternetComputer = 52, - TWBlockchainNativeEvmos = 55, // Cosmos - TWBlockchainNativeInjective = 56, // Cosmos + TWBlockchainNativeEvmos = 53, // Cosmos + TWBlockchainNativeInjective = 54, // Cosmos }; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index cd2780d8a55..56df414d348 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -179,6 +179,7 @@ enum TWCoinType { TWCoinTypeZenEON = 7332, TWCoinTypeInternetComputer = 223, TWCoinTypeTia = 21000118, + // end_of_tw_coin_type_marker_do_not_modify }; /// Returns the blockchain for a coin type. diff --git a/rust/tw_any_coin/tests/coin_address_derivation_tests.rs b/rust/tw_any_coin/tests/coin_address_derivation_test.rs similarity index 96% rename from rust/tw_any_coin/tests/coin_address_derivation_tests.rs rename to rust/tw_any_coin/tests/coin_address_derivation_test.rs index 7ce749d3883..8320fd77d57 100644 --- a/rust/tw_any_coin/tests/coin_address_derivation_tests.rs +++ b/rust/tw_any_coin/tests/coin_address_derivation_test.rs @@ -85,8 +85,9 @@ fn test_coin_address_derivation() { | CoinType::Linea | CoinType::Greenfield | CoinType::Mantle - | CoinType::ZenEON => "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309", - // end_of_ethereum_coin_address_derivation - DO NOT REMOVE + | CoinType::ZenEON + // end_of_evm_address_derivation_tests_marker_do_not_modify + => "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309", CoinType::Bitcoin // TODO all Bitcoin-based blockchains should have different addresses. // It should be fixed when Bitcoin is finalized. @@ -143,7 +144,7 @@ fn test_coin_address_derivation() { CoinType::NativeInjective => "inj14s0vgnj0pjnazu4hsqlksdk7slah9vcfyrp6ct", CoinType::NativeCanto => "canto14s0vgnj0pjnazu4hsqlksdk7slah9vcfuuhw7m", CoinType::InternetComputer => "290cc7c359f44c8516fc169c5ed4f0f3ae2e24bf5de0d4c51f5e7545b5474faa", - // end_of_coin_address_derivation - DO NOT REMOVE + // end_of_coin_address_derivation_tests_marker_do_not_modify _ => panic!("{:?} must be covered", coin), }; diff --git a/rust/tw_coin_entry/src/coin_entry.rs b/rust/tw_coin_entry/src/coin_entry.rs index 70c9d24077d..785021d409b 100644 --- a/rust/tw_coin_entry/src/coin_entry.rs +++ b/rust/tw_coin_entry/src/coin_entry.rs @@ -26,6 +26,13 @@ pub trait CoinAddress: fmt::Display { fn data(&self) -> Data; } +/// The main coin entry trait. It is responsible for address management and the transaction signing. +/// +/// # Maintaining +/// +/// Please sync them with the code generator template if there is need to make any changes in this trait +/// (e.g adding/deleting a method or an associated type): +/// https://github.com/trustwallet/wallet-core/blob/master/codegen-v2/src/codegen/rust/templates/blockchain_crate/entry.rs pub trait CoinEntry { type AddressPrefix: Prefix; type Address: CoinAddress; diff --git a/rust/tw_coin_registry/src/dispatcher.rs b/rust/tw_coin_registry/src/dispatcher.rs index 02850a513d0..61e6f0d5998 100644 --- a/rust/tw_coin_registry/src/dispatcher.rs +++ b/rust/tw_coin_registry/src/dispatcher.rs @@ -47,9 +47,9 @@ pub fn blockchain_dispatcher(blockchain: BlockchainType) -> RegistryResult Ok(&NATIVE_EVMOS), BlockchainType::NativeInjective => Ok(&NATIVE_INJECTIVE), BlockchainType::Ronin => Ok(&RONIN), - BlockchainType::Unsupported => Err(RegistryError::Unsupported), BlockchainType::Thorchain => Ok(&THORCHAIN), // end_of_blockchain_dispatcher - USED TO GENERATE CODE + BlockchainType::Unsupported => Err(RegistryError::Unsupported), } } diff --git a/tests/common/CoinAddressDerivationTests.cpp b/tests/common/CoinAddressDerivationTests.cpp index d9a8736b834..f9b6c28e46b 100644 --- a/tests/common/CoinAddressDerivationTests.cpp +++ b/tests/common/CoinAddressDerivationTests.cpp @@ -84,6 +84,7 @@ TEST(Coin, DeriveAddress) { case TWCoinTypeGreenfield: case TWCoinTypeMantle: case TWCoinTypeZenEON: + // end_of_evm_address_derivation_tests_marker_do_not_modify EXPECT_EQ(address, "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"); break; @@ -384,6 +385,7 @@ TEST(Coin, DeriveAddress) { case TWCoinTypeTia: EXPECT_EQ(address, "celestia1hkfq3zahaqkkzx5mjnamwjsfpq2jk7z0g3wnkv"); break; + // end_of_coin_address_derivation_tests_marker_do_not_modify // no default branch here, intentionally, to better notice any missing coins } }