Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ZetaChain): Add support for NativeZetaChain #3669

Merged
merged 11 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -151,5 +151,6 @@ class CoinAddressDerivationTests {
SEI -> assertEquals("sei142j9u5eaduzd7faumygud6ruhdwme98qagm0sj", address)
INTERNETCOMPUTER -> assertEquals("b9a13d974ee9db036d5abc5b66ace23e513cb5676f3996626c7717c339a3ee87", address)
TIA -> assertEquals("celestia142j9u5eaduzd7faumygud6ruhdwme98qpwmfv7", address)
NATIVEZETACHAIN -> assertEquals("zeta13u6g7vqgw074mgmf2ze2cadzvkz9snlwywj304", address)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

package com.trustwallet.core.app.blockchains.nativezetachain

import com.trustwallet.core.app.utils.toHex
import com.trustwallet.core.app.utils.toHexByteArray
import org.junit.Assert.assertEquals
import org.junit.Test
import wallet.core.jni.*

class TestNativeZetaChainAddress {

init {
System.loadLibrary("TrustWalletCore")
}

@Test
fun testAddress() {
val key = PrivateKey("8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed".toHexByteArray())
val pubKey = key.getPublicKeySecp256k1(false)
val address = AnyAddress(pubKey, CoinType.NATIVEZETACHAIN)
val expected = AnyAddress("zeta14py36sx57ud82t9yrks9z6hdsrpn5x6kmxs0ne", CoinType.NATIVEZETACHAIN)

assertEquals(address.description(), expected.description())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

package com.trustwallet.core.app.blockchains.nativezetachain

import com.google.protobuf.ByteString
import com.trustwallet.core.app.utils.toHexByteArray
import org.junit.Assert.assertEquals
import org.junit.Test
import wallet.core.java.AnySigner
import wallet.core.jni.AnyAddress
import wallet.core.jni.CoinType
import wallet.core.jni.PrivateKey
import wallet.core.jni.proto.Cosmos

class TestNativeZetaChainSigner {

init {
System.loadLibrary("TrustWalletCore")
}

@Test
fun NativeZetaChainTransactionSigning() {
val key = PrivateKey("8d2a3bd62d300a148c89dc8635f87b7a24a951bd1c4e78675fe40e1a640d46ed".toHexByteArray())
val publicKey = key.getPublicKeySecp256k1(false)
val from = AnyAddress(publicKey, CoinType.NATIVEZETACHAIN).description()

val transferAmount = Cosmos.Amount.newBuilder().apply {
// 0.3 ZETA
amount = "300000000000000000"
denom = "azeta"
}.build()

val message = Cosmos.Message.newBuilder().apply {
sendCoinsMessage = Cosmos.Message.Send.newBuilder().apply {
fromAddress = from
toAddress = "zeta1cscf4ldnkkz7f0wpveur6dpd0d6p2zxnsuu70y"
addAllAmounts(listOf(transferAmount))
}.build()
}.build()

val transferFee = Cosmos.Fee.newBuilder().apply {
gas = 200000
}.build()

val signingInput = Cosmos.SigningInput.newBuilder().apply {
signingMode = Cosmos.SigningMode.Protobuf
accountNumber = 2726346
chainId = "athens_7001-1"
sequence = 2
fee = transferFee
privateKey = ByteString.copyFrom(key.data())
txHasher = Cosmos.TxHasher.Keccak256
signerInfo = Cosmos.SignerInfo.newBuilder().apply {
// Zetachain requires a compressed public key to sign a transaction,
// however an uncompressed public key is used to generate address.
publicKeyType = Cosmos.SignerPublicKeyType.Secp256k1
jsonType = "ethermint/PubKeyEthSecp256k1"
protobufType = "/ethermint.crypto.v1.ethsecp256k1.PubKey"
}.build()
addAllMessages(listOf(message))
}.build()

val output = AnySigner.sign(signingInput, CoinType.NATIVEZETACHAIN, Cosmos.SigningOutput.parser())

// Successfully broadcasted (testnet):
// https://explorer.zetachain.com/cosmos/tx/A2FC8816657856ED274C4418C3CAEAEE645561275F6C63AB5F8B1DCFB37341A0
assertEquals(output.serialized, "{\"mode\":\"BROADCAST_MODE_BLOCK\",\"tx_bytes\":\"CpoBCpcBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEncKK3pldGExNHB5MzZzeDU3dWQ4MnQ5eXJrczl6Nmhkc3JwbjV4NmtteHMwbmUSK3pldGExY3NjZjRsZG5ra3o3ZjB3cHZldXI2ZHBkMGQ2cDJ6eG5zdXU3MHkaGwoFYXpldGESEjMwMDAwMDAwMDAwMDAwMDAwMBJhClkKTwooL2V0aGVybWludC5jcnlwdG8udjEuZXRoc2VjcDI1NmsxLlB1YktleRIjCiECho5+FjRBfbKt/Z/jggW/oP6gGJin/TBWXRP3BWo3wGUSBAoCCAEYAhIEEMCaDBpAgGvqca0w2N9wnHnnxS9HiVud4aQ9lNCumzgNIW6wOR4kvPScacGS1G3kwCw7wyI2NJL8M1eVYjafFIt2FpKl3w==\"}")
}
}
8 changes: 8 additions & 0 deletions codegen-v2/src/codegen/cpp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::path::PathBuf;
pub mod blockchain_dispatcher_generator;
pub mod entry_generator;
pub mod new_blockchain;
pub mod new_cosmos_chain;
pub mod new_evmchain;
pub mod tw_any_address_tests_generator;
pub mod tw_any_signer_tests_generator;
Expand Down Expand Up @@ -41,3 +42,10 @@ pub fn coin_integration_tests_directory(coin: &CoinItem) -> PathBuf {
.join("chains")
.join(coin.coin_type())
}

pub fn cosmos_coin_integration_tests_directory(coin: &CoinItem) -> PathBuf {
integration_tests_directory()
.join("chains")
.join("Cosmos")
.join(coin.coin_type())
}
20 changes: 20 additions & 0 deletions codegen-v2/src/codegen/cpp/new_cosmos_chain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

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_cosmos_chain(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_coin_type_case(coin)?;

Ok(())
}
13 changes: 12 additions & 1 deletion codegen-v2/src/codegen/cpp/tw_coin_type_tests_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
//
// Copyright © 2017 Trust Wallet.

use crate::codegen::cpp::coin_integration_tests_directory;
use crate::codegen::cpp::{
coin_integration_tests_directory, cosmos_coin_integration_tests_directory,
};
use crate::codegen::template_generator::TemplateGenerator;
use crate::registry::CoinItem;
use crate::Result;
Expand All @@ -20,6 +22,15 @@ pub struct TWCoinTypeTestsGenerator;
impl TWCoinTypeTestsGenerator {
pub fn generate(coin: &CoinItem) -> Result<()> {
let coin_tests_dir = coin_integration_tests_directory(coin);
Self::generate_at(coin, coin_tests_dir)
}

pub fn generate_cosmos(coin: &CoinItem) -> Result<()> {
let cosmos_coin_tests_dir = cosmos_coin_integration_tests_directory(coin);
Self::generate_at(coin, cosmos_coin_tests_dir)
}

fn generate_at(coin: &CoinItem, coin_tests_dir: PathBuf) -> Result<()> {
let tw_coin_type_tests_path = coin_tests_dir.join("TWCoinTypeTests.cpp");

fs::create_dir(coin_tests_dir)?;
Expand Down
33 changes: 27 additions & 6 deletions codegen-v2/src/codegen/rust/coin_integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ use std::fs;
use std::path::PathBuf;

const ADDRESS_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/address_tests.rs");
const COSMOS_ADDRESS_TESTS_TEMPLATE: &str =
include_str!("templates/integration_tests/cosmos_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 MOD_ADDRESS_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/mod_address.rs");
const SIGN_TESTS_TEMPLATE: &str = include_str!("templates/integration_tests/sign_tests.rs");

pub fn chains_integration_tests_directory() -> PathBuf {
Expand Down Expand Up @@ -49,10 +52,28 @@ impl CoinIntegrationTests {
fs::create_dir_all(&blockchain_tests_path)?;

self.list_blockchain_in_chains_mod()?;
self.create_address_tests()?;
self.create_address_tests(ADDRESS_TESTS_TEMPLATE)?;
self.create_compile_tests()?;
self.create_sign_tests()?;
self.create_chain_tests_mod_rs()?;
self.create_chain_tests_mod_rs(MOD_TESTS_TEMPLATE)?;

Ok(blockchain_tests_path)
}

/// For a Cosmos chain, it's enough to create address tests only, but with additional Bech32 prefix tests.
pub fn create_cosmos(self) -> Result<PathBuf> {
let blockchain_tests_path = self.coin_tests_directory();
if blockchain_tests_path.exists() {
println!("[SKIP] integration tests already exists: {blockchain_tests_path:?}");
return Ok(blockchain_tests_path);
}

fs::create_dir_all(&blockchain_tests_path)?;

self.list_blockchain_in_chains_mod()?;
self.create_address_tests(COSMOS_ADDRESS_TESTS_TEMPLATE)?;
// `mod.rs` should contain `{COIN_TYPE}_address` module only.
self.create_chain_tests_mod_rs(MOD_ADDRESS_TESTS_TEMPLATE)?;

Ok(blockchain_tests_path)
}
Expand All @@ -61,14 +82,14 @@ impl CoinIntegrationTests {
coin_integration_tests_directory(&self.coin.id)
}

fn create_address_tests(&self) -> Result<()> {
fn create_address_tests(&self, template: &'static str) -> Result<()> {
let coin_id = self.coin.id.as_str();
let address_tests_path = self
.coin_tests_directory()
.join(format!("{coin_id}_address.rs"));

println!("[ADD] {address_tests_path:?}");
TemplateGenerator::new(ADDRESS_TESTS_TEMPLATE)
TemplateGenerator::new(template)
.write_to(address_tests_path)
.with_default_patterns(&self.coin)
.write()
Expand Down Expand Up @@ -100,11 +121,11 @@ impl CoinIntegrationTests {
.write()
}

fn create_chain_tests_mod_rs(&self) -> Result<()> {
fn create_chain_tests_mod_rs(&self, template: &'static str) -> Result<()> {
let blockchain_tests_mod_path = self.coin_tests_directory().join("mod.rs");

println!("[ADD] {blockchain_tests_mod_path:?}");
TemplateGenerator::new(MOD_TESTS_TEMPLATE)
TemplateGenerator::new(template)
.write_to(blockchain_tests_mod_path)
.with_default_patterns(&self.coin)
.write()
Expand Down
1 change: 1 addition & 0 deletions codegen-v2/src/codegen/rust/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod coin_crate;
pub mod coin_integration_tests;
pub mod coin_registry_manifest_generator;
pub mod new_blockchain;
pub mod new_cosmos_chain;
pub mod new_evmchain;
pub mod toml_editor;

Expand Down
16 changes: 16 additions & 0 deletions codegen-v2/src/codegen/rust/new_cosmos_chain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

use crate::codegen::rust::coin_address_derivation_test_generator::CoinAddressDerivationTestGenerator;
use crate::codegen::rust::coin_integration_tests::CoinIntegrationTests;
use crate::registry::CoinItem;
use crate::Result;

pub fn new_cosmos_chain(coin: &CoinItem) -> Result<()> {
// Create integration tests.
CoinIntegrationTests::new(coin.clone()).create_cosmos()?;
CoinAddressDerivationTestGenerator::generate_new_coin_type_case(coin)?;

Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

use tw_any_coin::test_utils::address_utils::{
test_address_bech32_is_valid, test_address_create_bech32_with_public_key,
test_address_get_data, test_address_invalid, test_address_normalization, test_address_valid,
AddressBech32IsValid, AddressCreateBech32WithPublicKey,
};
use tw_coin_registry::coin_type::CoinType;
use tw_keypair::tw::PublicKeyType;

#[test]
fn test_{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 {COIN_TYPE} ADDRESS");
}

#[test]
fn test_{COIN_ID}_address_invalid() {
test_address_invalid(CoinType::{COIN_TYPE}, "INVALID ADDRESS");
// Cosmos has a different `hrp`.
test_address_invalid(CoinType::Cosmos, "VALID {COIN_TYPE} ADDRESS");
}

#[test]
fn test_{COIN_ID}_address_get_data() {
test_address_get_data(CoinType::{COIN_TYPE}, "ADDRESS", "HEX(DATA)");
}

#[test]
fn test_{COIN_ID}_is_valid_bech32() {
// {COIN_TYPE} address must be valid if its Base32 prefix passed.
test_address_bech32_is_valid(AddressBech32IsValid {
coin: CoinType::{COIN_TYPE},
address: "{COIN_TYPE} ADDRESS",
hrp: "{HRP}",
});
// {COIN_TYPE} address must be valid for the standard Cosmos hub if its Base32 prefix passed.
test_address_bech32_is_valid(AddressBech32IsValid {
coin: CoinType::Cosmos,
address: "{COIN_TYPE} ADDRESS",
hrp: "{HRP}",
});
// Cosmos address must be valid with "cosmos" hrp.
test_address_bech32_is_valid(AddressBech32IsValid {
coin: CoinType::{COIN_TYPE},
address: "cosmos1hsk6jryyqjfhp5dhc55tc9jtckygx0eph6dd02",
hrp: "cosmos",
});
}

#[test]
fn test_any_address_create_bech32_with_public_key() {
test_address_create_bech32_with_public_key(AddressCreateBech32WithPublicKey {
// TODO consider using `CoinType::{COIN_TYPE}` if the chain's `addressHasher` is different from Cosmos's.
coin: CoinType::Cosmos,
private_key: "PRIVATE_KEY",
// TODO consider using another `PublicKeyType` if the chain's `publicKeyType` is different from Cosmos's.
public_key_type: PublicKeyType::Secp256k1,
hrp: "{HRP}",
expected: "{COIN_TYPE} ADDRESS",
});
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

mod {COIN_ID}_address;
1 change: 1 addition & 0 deletions codegen-v2/src/codegen/template_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ impl TemplateGenerator {
.add_pattern("{DECIMALS}", coin.decimals)
.add_pattern("{P2PKH_PREFIX}", coin.p2pkh_prefix)
.add_pattern("{P2SH_PREFIX}", coin.p2sh_prefix)
.add_pattern("{HRP}", coin.hrp.as_str())
.add_pattern("{STATIC_PREFIX}", coin.static_prefix)
.add_pattern("{EXPLORER_URL}", &coin.explorer.url)
.add_pattern("{EXPLORER_TX_PATH}", &coin.explorer.tx_path)
Expand Down
14 changes: 14 additions & 0 deletions codegen-v2/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ fn main() -> Result<()> {
"new-blockchain-rust" => new_blockchain_rust(&args[2..]),
"new-blockchain" => new_blockchain(&args[2..]),
"new-evmchain" => new_evmchain(&args[2..]),
"new-cosmos-chain" => new_cosmos_chain(&args[2..]),
"swift" => generate_swift_bindings(),
_ => Err(Error::InvalidCommand),
}
Expand Down Expand Up @@ -64,6 +65,19 @@ fn new_evmchain(args: &[String]) -> Result<()> {
Ok(())
}

fn new_cosmos_chain(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)?;

println!("New '{coin_str}' Cosmos chain template requested");

rust::new_cosmos_chain::new_cosmos_chain(&coin_item)?;
cpp::new_cosmos_chain::new_cosmos_chain(&coin_item)?;

Ok(())
}

fn generate_swift_bindings() -> Result<()> {
// NOTE: The paths will be configurable, eventually.
const OUT_DIR: &str = "bindings/";
Expand Down
2 changes: 2 additions & 0 deletions codegen-v2/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ pub struct CoinItem {
#[serde(rename = "staticPrefix")]
#[serde(default)]
pub static_prefix: u8,
#[serde(default)]
pub hrp: String,
pub explorer: CoinExplorer,
}

Expand Down
Loading
Loading