Skip to content

Commit

Permalink
[Cosmos]: Add NativeEvmos blockchain, cover with tests
Browse files Browse the repository at this point in the history
  • Loading branch information
satoshiotomakan committed Nov 13, 2023
1 parent b31e4b1 commit eae1347
Show file tree
Hide file tree
Showing 27 changed files with 359 additions and 32 deletions.
2 changes: 1 addition & 1 deletion registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -3943,7 +3943,7 @@
"coinId": 20009001,
"symbol": "EVMOS",
"decimals": 18,
"blockchain": "Cosmos",
"blockchain": "NativeEvmos",
"chainId": "evmos_9001-2",
"derivation": [
{
Expand Down
14 changes: 14 additions & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"chains/tw_cosmos",
"chains/tw_native_evmos",
"tw_any_coin",
"tw_bech32_address",
"tw_bitcoin",
Expand Down
14 changes: 14 additions & 0 deletions rust/chains/tw_native_evmos/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "tw_native_evmos"
version = "0.1.0"
edition = "2021"

[dependencies]
tw_coin_entry = { path = "../../tw_coin_entry" }
tw_cosmos_sdk = { path = "../../tw_cosmos_sdk" }
tw_keypair = { path = "../../tw_keypair" }
tw_memory = { path = "../../tw_memory" }
tw_proto = { path = "../../tw_proto" }

[dev-dependencies]
tw_cosmos_sdk = { path = "../../tw_cosmos_sdk", features = ["test-utils"] }
20 changes: 20 additions & 0 deletions rust/chains/tw_native_evmos/src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// 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::ethermint_public_key::EthermintEthSecp256PublicKey;
use tw_cosmos_sdk::address::Address;
use tw_cosmos_sdk::context::CosmosContext;
use tw_cosmos_sdk::hasher::keccak256_hasher::Keccak256Hasher;
use tw_cosmos_sdk::private_key::secp256k1::Secp256PrivateKey;

pub struct NativeEvmosContext;

impl CosmosContext for NativeEvmosContext {
type Address = Address;
type TxHasher = Keccak256Hasher;
type PrivateKey = Secp256PrivateKey;
type PublicKey = EthermintEthSecp256PublicKey;
}
74 changes: 74 additions & 0 deletions rust/chains/tw_native_evmos/src/entry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// 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::context::NativeEvmosContext;
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_cosmos_sdk::address::{Address, CosmosAddress};
use tw_cosmos_sdk::modules::signer::tw_signer::TWSigner;
use tw_keypair::tw;
use tw_proto::Cosmos::Proto;
use tw_proto::TxCompiler::Proto as CompilerProto;

pub struct NativeEvmosEntry;

impl CoinEntry for NativeEvmosEntry {
type AddressPrefix = NoPrefix;
type Address = Address;
type SigningInput<'a> = Proto::SigningInput<'a>;
type SigningOutput = Proto::SigningOutput<'static>;
type PreSigningOutput = CompilerProto::PreSigningOutput<'static>;
type JsonSigner = NoJsonSigner;
type PlanBuilder = NoPlanBuilder;
type MessageSigner = NoMessageSigner;

fn parse_address(
&self,
coin: &dyn CoinContext,
address: &str,
_prefix: Option<Self::AddressPrefix>,
) -> AddressResult<Self::Address> {
Address::from_str_with_coin(coin, address)
}

fn derive_address(
&self,
coin: &dyn CoinContext,
public_key: tw::PublicKey,
_derivation: Derivation,
_prefix: Option<Self::AddressPrefix>,
) -> AddressResult<Self::Address> {
Address::with_public_key_coin_context(coin, &public_key)
}

fn sign(&self, coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput {
TWSigner::<NativeEvmosContext>::sign(coin, input)
}

fn preimage_hashes(
&self,
_coin: &dyn CoinContext,
_input: Self::SigningInput<'_>,
) -> Self::PreSigningOutput {
todo!()
}

fn compile(
&self,
_coin: &dyn CoinContext,
_input: Self::SigningInput<'_>,
_signatures: Vec<SignatureBytes>,
_public_keys: Vec<PublicKeyBytes>,
) -> Self::SigningOutput {
todo!()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,44 @@
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

use crate::proto::ethermint;
use crate::public_key::secp256k1::prepare_secp256k1_public_key;
use crate::public_key::{CosmosPublicKey, JsonPublicKey, ProtobufPublicKey};
use tw_coin_entry::coin_context::CoinContext;
use tw_keypair::tw;
use tw_cosmos_sdk::proto::ethermint;
use tw_cosmos_sdk::public_key::{CosmosPublicKey, JsonPublicKey, ProtobufPublicKey};
use tw_keypair::ecdsa::secp256k1;
use tw_keypair::KeyPairResult;
use tw_keypair::{tw, KeyPairError};
use tw_memory::Data;
use tw_proto::{google, to_any};

// TODO move to tw_ethermint blockchain
pub struct EthermintEthSecp256PublicKey {
public_key: Data,
}

impl EthermintEthSecp256PublicKey {
pub fn new(public_key: &secp256k1::PublicKey) -> KeyPairResult<EthermintEthSecp256PublicKey> {
Ok(EthermintEthSecp256PublicKey {
// NativeEvmos chain requires the public key to be compressed.
// This trick is needed because `registry.json` contains extended public key type.
public_key: public_key.compressed().to_vec(),
})
}
}

impl CosmosPublicKey for EthermintEthSecp256PublicKey {
fn from_private_key(coin: &dyn CoinContext, private_key: &tw::PrivateKey) -> KeyPairResult<Self>
where
Self: Sized,
{
let public_key = private_key.get_public_key_by_type(coin.public_key_type())?;
Ok(EthermintEthSecp256PublicKey {
public_key: public_key.to_bytes(),
})
let tw_public_key = private_key.get_public_key_by_type(coin.public_key_type())?;
let secp256k1_key = tw_public_key
.to_secp256k1()
.ok_or(KeyPairError::InvalidPublicKey)?;
EthermintEthSecp256PublicKey::new(secp256k1_key)
}

fn from_bytes(coin: &dyn CoinContext, public_key_bytes: &[u8]) -> KeyPairResult<Self> {
let public_key = prepare_secp256k1_public_key(coin, public_key_bytes)?;
Ok(EthermintEthSecp256PublicKey { public_key })
fn from_bytes(_coin: &dyn CoinContext, public_key_bytes: &[u8]) -> KeyPairResult<Self> {
let public_key = secp256k1::PublicKey::try_from(public_key_bytes)?;
EthermintEthSecp256PublicKey::new(&public_key)
}

fn to_bytes(&self) -> Data {
Expand Down
9 changes: 9 additions & 0 deletions rust/chains/tw_native_evmos/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// 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.

pub mod context;
pub mod ethermint_public_key;
pub mod entry;
1 change: 1 addition & 0 deletions rust/tw_any_coin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ test-utils = []

[dev-dependencies]
tw_any_coin = { path = "./", features = ["test-utils"] }
tw_cosmos_sdk = { path = "../tw_cosmos_sdk", features = ["test-utils"] }
tw_encoding = { path = "../tw_encoding" }
tw_keypair = { path = "../tw_keypair", features = ["test-utils"] }
tw_memory = { path = "../tw_memory", features = ["test-utils"] }
Expand Down
7 changes: 7 additions & 0 deletions rust/tw_any_coin/tests/chain_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright © 2017-2023 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

mod chains;
7 changes: 7 additions & 0 deletions rust/tw_any_coin/tests/chains/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright © 2017-2023 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

mod native_evmos;
7 changes: 7 additions & 0 deletions rust/tw_any_coin/tests/chains/native_evmos/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright © 2017-2023 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

mod native_evmos_sign;
122 changes: 122 additions & 0 deletions rust/tw_any_coin/tests/chains/native_evmos/native_evmos_sign.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright © 2017-2023 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

use std::borrow::Cow;
use tw_any_coin::ffi::tw_any_signer::tw_any_signer_sign;
use tw_coin_entry::error::SigningErrorType;
use tw_cosmos_sdk::test_utils::proto_utils::{make_amount, make_fee, make_message};
use tw_encoding::hex::DecodeHex;
use tw_memory::test_utils::tw_data_helper::TWDataHelper;
use tw_proto::Cosmos::Proto;
use tw_proto::Cosmos::Proto::mod_Message::OneOfmessage_oneof as MessageEnum;
use tw_proto::{deserialize, serialize};

const NATIVE_EVMOS_COIN_TYPE: u32 = 20009001;

fn account_1037_private_key() -> Cow<'static, [u8]> {
"80e81ea269e66a0a05b11236df7919fb7fbeedba87452d667489d7403a02f005"
.decode_hex()
.unwrap()
.into()
}

fn account_2139877_private_key() -> Cow<'static, [u8]> {
"79bcbded1a5678ab34e6d9db9ad78e4e480e7b22723cc5fbf59e843732e1a8e5"
.decode_hex()
.unwrap()
.into()
}

#[test]
fn test_sign_native_evmos_tx_json() {
let send_msg = Proto::mod_Message::Send {
from_address: "evmos1hsk6jryyqjfhp5dhc55tc9jtckygx0ep4mur4z".into(),
to_address: "evmos1zt50azupanqlfam5afhv3hexwyutnuke45f6ye".into(),
amounts: vec![make_amount("muon", "1")],
..Proto::mod_Message::Send::default()
};
let input = Proto::SigningInput {
signing_mode: Proto::SigningMode::JSON,
account_number: 1037,
chain_id: "evmos_9001-2".into(),
sequence: 8,
fee: Some(make_fee(200000, make_amount("muon", "200"))),
private_key: account_1037_private_key(),
messages: vec![make_message(MessageEnum::send_coins_message(send_msg))],
..Proto::SigningInput::default()
};

let input_data = TWDataHelper::create(serialize(&input).unwrap());

let output =
TWDataHelper::wrap(unsafe { tw_any_signer_sign(input_data.ptr(), NATIVE_EVMOS_COIN_TYPE) })
.to_vec()
.expect("!tw_any_signer_sign returned nullptr");

let output: Proto::SigningOutput = deserialize(&output).unwrap();
assert_eq!(output.error, SigningErrorType::OK);
assert!(output.error_message.is_empty());

assert_eq!(
output.json,
r#"{"mode":"block","tx":{"fee":{"amount":[{"amount":"200","denom":"muon"}],"gas":"200000"},"memo":"","msg":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"1","denom":"muon"}],"from_address":"evmos1hsk6jryyqjfhp5dhc55tc9jtckygx0ep4mur4z","to_address":"evmos1zt50azupanqlfam5afhv3hexwyutnuke45f6ye"}}],"signatures":[{"pub_key":{"type":"ethermint/PubKeyEthSecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"1hMFtRqKjB8tiuyHYVYZundPdomebIIvHLC1gj9uXtFc+iO3UAHBysBjFB4brd9AD5yriS3uUDTAqqfg6fNGNg=="}]}}"#
);
assert_eq!(
output.signature_json,
r#"[{"pub_key":{"type":"ethermint/PubKeyEthSecp256k1","value":"AlcobsPzfTNVe7uqAAsndErJAjqplnyudaGB0f+R+p3F"},"signature":"1hMFtRqKjB8tiuyHYVYZundPdomebIIvHLC1gj9uXtFc+iO3UAHBysBjFB4brd9AD5yriS3uUDTAqqfg6fNGNg=="}]"#
);
}

/// CompoundingAuthz
#[test]
fn test_sign_native_evmos_tx_protobuf() {
use Proto::mod_Message::mod_AuthGrant::OneOfgrant_type as ProtoGrantType;
use Proto::mod_Message::mod_StakeAuthorization::OneOfvalidators as ProtoValidatorsType;

let allow_list = Proto::mod_Message::mod_StakeAuthorization::Validators {
address: vec!["evmosvaloper1umk407eed7af6anvut6llg2zevnf0dn0feqqny".into()],
};
let stake_authorization = Proto::mod_Message::StakeAuthorization {
authorization_type: Proto::mod_Message::AuthorizationType::UNDELEGATE,
validators: ProtoValidatorsType::allow_list(allow_list),
..Proto::mod_Message::StakeAuthorization::default()
};
let auth_grant = Proto::mod_Message::AuthGrant {
granter: "evmos12m9grgas60yk0kult076vxnsrqz8xpjy9rpf3e".into(),
grantee: "evmos18fzq4nac28gfma6gqfvkpwrgpm5ctar2z9mxf3".into(),
grant_type: ProtoGrantType::grant_stake(stake_authorization),
expiration: 1692309600,
};
let input = Proto::SigningInput {
signing_mode: Proto::SigningMode::Protobuf,
account_number: 2139877,
chain_id: "evmos_9001-2".into(),
sequence: 3,
fee: Some(make_fee(180859, make_amount("aevmos", "4521475000000000"))),
private_key: account_2139877_private_key(),
messages: vec![make_message(MessageEnum::auth_grant(auth_grant))],
..Proto::SigningInput::default()
};

let input_data = TWDataHelper::create(serialize(&input).unwrap());

let output =
TWDataHelper::wrap(unsafe { tw_any_signer_sign(input_data.ptr(), NATIVE_EVMOS_COIN_TYPE) })
.to_vec()
.expect("!tw_any_signer_sign returned nullptr");

let output: Proto::SigningOutput = deserialize(&output).unwrap();
assert_eq!(output.error, SigningErrorType::OK);
assert!(output.error_message.is_empty());

// Successfully broadcasted https://www.mintscan.io/evmos/txs/8D811CEC078420C41220F0B584EA0AC037763380FAC31E0E352E4BB4D1D18B79
// TODO the signature has been updated according to the serialization of the `StakeAuthorization` message.
// Previous: CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5EjUKM2V2bW9zdmFsb3BlcjF1bWs0MDdlZWQ3YWY2YW52dXQ2bGxnMnpldm5mMGRuMGZlcXFueSABEgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkAm17CZgB7m+CPVlITnrHosklMTL9zrUeGRs8FL8N0GcRami9zdJ+e3xuXOtJmwP7G6QNh85CRYjFj8a8lpmmJM
assert_eq!(
output.serialized,
r#"{"mode":"BROADCAST_MODE_BLOCK","tx_bytes":"CvUBCvIBCh4vY29zbW9zLmF1dGh6LnYxYmV0YTEuTXNnR3JhbnQSzwEKLGV2bW9zMTJtOWdyZ2FzNjB5azBrdWx0MDc2dnhuc3Jxejh4cGp5OXJwZjNlEixldm1vczE4ZnpxNG5hYzI4Z2ZtYTZncWZ2a3B3cmdwbTVjdGFyMno5bXhmMxpxCmcKKi9jb3Ntb3Muc3Rha2luZy52MWJldGExLlN0YWtlQXV0aG9yaXphdGlvbhI5IAISNQozZXZtb3N2YWxvcGVyMXVtazQwN2VlZDdhZjZhbnZ1dDZsbGcyemV2bmYwZG4wZmVxcW55EgYI4LD6pgYSfQpZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohA4B2WHbj6sH/GWE7z/YW5PRnXYFGaGRAov7gZZI2Fv2nEgQKAggBGAMSIAoaCgZhZXZtb3MSEDQ1MjE0NzUwMDAwMDAwMDAQ+4QLGkDu0sBTTeGfvUm+W6opsAcvBPLU51CFCtQOc1NCMrwRfGrUm+UBDN0NF3p1V8Ucn1tV9eLv7sXH13t/whbe7sTc"}"#
);
}
Loading

0 comments on commit eae1347

Please sign in to comment.