Skip to content

Commit

Permalink
feat(ZetaChain): Add support for NativeZetaChain (#3669)
Browse files Browse the repository at this point in the history
* feat(ZetaChain): Add `new-cosmos-chain` command

* feat(ZetaChain): Add `NativeZetaChain` mainnet

* feat(ZetaChain): Add support for custom Cosmos chain

* feat(ZetaChain): Add `SignerInfo` to the Cosmos.proto protocol

* feat(ZetaChain): Fix zetachain params in registry.json

* feat(ZetaChain): Add mobile tests

* [CI] Trigger CI

* feat(ZetaChain): Update JSON Public Key Type

* feat(ZetaChain): Fix `accountPath` in `registry.json`

* feat(ZetaChain): Fix rustfmt

* feat(ZetaChain): Slightly refactor NativeEvmos and NativeInjective public key types
  • Loading branch information
satoshiotomakan authored Jan 25, 2024
1 parent 43bf58c commit d02cb7d
Show file tree
Hide file tree
Showing 62 changed files with 991 additions and 289 deletions.
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

0 comments on commit d02cb7d

Please sign in to comment.