From d95ee6c6177d551e2167ca73773b24e65f988975 Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Mon, 2 Dec 2024 16:51:39 +0000 Subject: [PATCH 01/22] Add SCALE support for String --- rust/tw_scale/src/lib.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/rust/tw_scale/src/lib.rs b/rust/tw_scale/src/lib.rs index 2da23537483..fba5a4c9ec4 100644 --- a/rust/tw_scale/src/lib.rs +++ b/rust/tw_scale/src/lib.rs @@ -128,6 +128,12 @@ where } } +impl ToScale for String { + fn to_scale_into(&self, out: &mut Vec) { + self.as_bytes().to_scale_into(out) + } +} + #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct RawOwned(pub Vec); @@ -395,4 +401,18 @@ mod tests { &[0x18, 0x04, 0x00, 0x08, 0x00, 0x0f, 0x00, 0x10, 0x00, 0x17, 0x00, 0x2a, 0x00], ); } + + // Test SCALE encoding of String + #[test] + fn test_string() { + assert_eq!("".to_string().to_scale(), &[0x00]); + assert_eq!( + "hello".to_string().to_scale(), + &[0x14, 0x68, 0x65, 0x6c, 0x6c, 0x6f] + ); + assert_eq!( + "hello world".to_string().to_scale(), + &[0x2c, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64] + ); + } } From 08c3dbfdd2cdb5f29214a9f6bb07d9ab1dd716bb Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Tue, 3 Dec 2024 13:41:03 +0000 Subject: [PATCH 02/22] Add SCALE support for BTreeSet/Map. --- rust/tw_scale/src/lib.rs | 58 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/rust/tw_scale/src/lib.rs b/rust/tw_scale/src/lib.rs index fba5a4c9ec4..dec8344929b 100644 --- a/rust/tw_scale/src/lib.rs +++ b/rust/tw_scale/src/lib.rs @@ -134,6 +134,7 @@ impl ToScale for String { } } +/// RawOwned is used to wrap data that is already encoded in SCALE format. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct RawOwned(pub Vec); @@ -149,6 +150,35 @@ impl ToScale for RawOwned { } } +// Implement ToScale for BTreeSet collection. +impl ToScale for std::collections::BTreeSet +where + T: ToScale, +{ + fn to_scale_into(&self, out: &mut Vec) { + Compact(self.len()).to_scale_into(out); + for ts in self.iter() { + ts.to_scale_into(out); + } + } +} + +// Implement ToScale for BTreeMap collection. +impl ToScale for std::collections::BTreeMap +where + K: ToScale, + V: ToScale, +{ + fn to_scale_into(&self, out: &mut Vec) { + Compact(self.len()).to_scale_into(out); + for (k, v) in self.iter() { + k.to_scale_into(out); + v.to_scale_into(out); + } + } +} + +// Implement ToScale for Vec collection. impl ToScale for Vec where T: ToScale, @@ -158,6 +188,7 @@ where } } +// Implement ToScale for references to types that implement ToScale. impl ToScale for &T { fn to_scale_into(&self, out: &mut Vec) { (*self).to_scale_into(out) @@ -415,4 +446,31 @@ mod tests { &[0x2c, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64] ); } + + // Test SCALE encoding of BTreeSet + #[test] + fn test_btree_set() { + use std::collections::BTreeSet; + let mut set = BTreeSet::new(); + set.insert(10u8); + set.insert(30u8); + set.insert(20u8); + // The values are encoded in sorted order. + assert_eq!(set.to_scale(), &[0x0c, 10, 20, 30]); + } + + // Test SCALE encoding of BTreeMap + #[test] + fn test_btree_map() { + use std::collections::BTreeMap; + let mut map = BTreeMap::new(); + map.insert(30u8, 300u16); + map.insert(10u8, 100u16); + map.insert(20u8, 200u16); + // The keys/value pairs are encoded in sorted order (by key order). + assert_eq!( + map.to_scale(), + &[0x0c, 10, 0x64, 0x00, 20, 0xc8, 0x00, 30, 0x2c, 0x01] + ); + } } From 5c241b9bce063054d895a8bc2a08e76ea64e1708 Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Thu, 28 Nov 2024 22:22:46 +0800 Subject: [PATCH 03/22] Split Polymesh chain from Polkadot impl. --- docs/registry.md | 1 + include/TrustWalletCore/TWBlockchain.h | 1 + include/TrustWalletCore/TWCoinType.h | 1 + registry.json | 30 ++ rust/Cargo.lock | 17 + rust/Cargo.toml | 1 + .../tw_polkadot/src/call_encoder/mod.rs | 6 +- rust/chains/tw_polkadot/tests/extrinsic.rs | 143 +------- rust/chains/tw_polymesh/Cargo.toml | 16 + rust/chains/tw_polymesh/src/address.rs | 34 ++ .../tw_polymesh/src/call_encoder/mod.rs | 190 +++++++++++ .../src/call_encoder/polymesh.rs | 51 ++- rust/chains/tw_polymesh/src/compiler.rs | 50 +++ rust/chains/tw_polymesh/src/entry.rs | 143 ++++++++ rust/chains/tw_polymesh/src/lib.rs | 46 +++ rust/chains/tw_polymesh/src/signer.rs | 27 ++ rust/chains/tw_polymesh/tests/extrinsic.rs | 309 ++++++++++++++++++ rust/tw_coin_registry/Cargo.toml | 1 + rust/tw_coin_registry/src/blockchain_type.rs | 3 +- rust/tw_coin_registry/src/dispatcher.rs | 5 +- rust/tw_tests/tests/chains/polkadot/mod.rs | 2 - rust/tw_tests/tests/chains/polymesh/mod.rs | 16 + .../tests/chains/polymesh/polymesh_address.rs | 31 +- .../tests/chains/polymesh/polymesh_compile.rs | 85 +++++ .../tests/chains/polymesh/polymesh_sign.rs | 56 ++++ .../tests/coin_address_derivation_test.rs | 1 + src/Coin.cpp | 3 + src/Polymesh/Entry.h | 17 + tests/chains/Polkadot/TWAnyAddressTests.cpp | 22 -- tests/chains/Polkadot/TWAnySignerTests.cpp | 1 - tests/chains/Polymesh/TWAnyAddressTests.cpp | 100 ++++++ tests/chains/Polymesh/TWAnySignerTests.cpp | 185 +++++++++++ tests/chains/Polymesh/TWCoinTypeTests.cpp | 29 ++ tests/common/CoinAddressDerivationTests.cpp | 3 + 34 files changed, 1446 insertions(+), 180 deletions(-) create mode 100644 rust/chains/tw_polymesh/Cargo.toml create mode 100644 rust/chains/tw_polymesh/src/address.rs create mode 100644 rust/chains/tw_polymesh/src/call_encoder/mod.rs rename rust/chains/{tw_polkadot => tw_polymesh}/src/call_encoder/polymesh.rs (89%) create mode 100644 rust/chains/tw_polymesh/src/compiler.rs create mode 100644 rust/chains/tw_polymesh/src/entry.rs create mode 100644 rust/chains/tw_polymesh/src/lib.rs create mode 100644 rust/chains/tw_polymesh/src/signer.rs create mode 100644 rust/chains/tw_polymesh/tests/extrinsic.rs create mode 100644 rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs create mode 100644 rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs create mode 100644 src/Polymesh/Entry.h create mode 100644 tests/chains/Polymesh/TWAnyAddressTests.cpp create mode 100644 tests/chains/Polymesh/TWAnySignerTests.cpp create mode 100644 tests/chains/Polymesh/TWCoinTypeTests.cpp diff --git a/docs/registry.md b/docs/registry.md index 1473aa7b5ca..8d391648677 100644 --- a/docs/registry.md +++ b/docs/registry.md @@ -64,6 +64,7 @@ This list is generated from [./registry.json](../registry.json) | 508 | MultiversX | eGLD | | | | 529 | Secret | SCRT | | | | 564 | Agoric | BLD | | | +| 595 | Polymesh | POLYX | | | | 607 | TON | TON | | | | 637 | Aptos | APT | | | | 714 | BNB Beacon Chain | BNB | | | diff --git a/include/TrustWalletCore/TWBlockchain.h b/include/TrustWalletCore/TWBlockchain.h index eeb2f744474..eb66cbad9b4 100644 --- a/include/TrustWalletCore/TWBlockchain.h +++ b/include/TrustWalletCore/TWBlockchain.h @@ -67,6 +67,7 @@ enum TWBlockchain { TWBlockchainNativeInjective = 54, // Cosmos TWBlockchainBitcoinCash = 55, TWBlockchainPactus = 56, + TWBlockchainPolymesh = 57, // Substrate }; TW_EXTERN_C_END diff --git a/include/TrustWalletCore/TWCoinType.h b/include/TrustWalletCore/TWCoinType.h index 9d2f10e36c9..0ff955048be 100644 --- a/include/TrustWalletCore/TWCoinType.h +++ b/include/TrustWalletCore/TWCoinType.h @@ -187,6 +187,7 @@ enum TWCoinType { TWCoinTypeBounceBit = 6001, TWCoinTypeZkLinkNova = 810180, TWCoinTypePactus = 21888, + TWCoinTypePolymesh = 595, // end_of_tw_coin_type_marker_do_not_modify }; diff --git a/registry.json b/registry.json index 72a5e13ce3a..8033b095519 100644 --- a/registry.json +++ b/registry.json @@ -4814,5 +4814,35 @@ "rpc": "https://docs.pactus.org/api/http", "documentation": "https://docs.pactus.org" } + }, + { + "id": "polymesh", + "name": "Polymesh", + "coinId": 595, + "symbol": "POLYX", + "decimals": 6, + "blockchain": "Polymesh", + "derivation": [ + { + "path": "m/44'/595'/0'/0'/0'" + } + ], + "curve": "ed25519", + "publicKeyType": "ed25519", + "addressHasher": "keccak256", + "ss58Prefix": 12, + "explorer": { + "url": "https://polymesh.subscan.io", + "txPath": "/extrinsic/", + "accountPath": "/account/", + "sampleTx": "0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04", + "sampleAccount": "2E5u4xA1TqswQ3jMJH96zekxwr2itvKu79fDC1mmnVZRh6Uv" + }, + "info": { + "url": "https://polymesh.network", + "source": "https://github.com/PolymeshAssociation/Polymesh", + "rpc": "wss://rpc.polymesh.network/", + "documentation": "https://developers.polymesh.network/" + } } ] diff --git a/rust/Cargo.lock b/rust/Cargo.lock index a1c8d016f1d..e94dc44207f 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1864,6 +1864,7 @@ dependencies = [ "tw_native_injective", "tw_pactus", "tw_polkadot", + "tw_polymesh", "tw_ronin", "tw_solana", "tw_substrate", @@ -2116,6 +2117,22 @@ dependencies = [ "tw_substrate", ] +[[package]] +name = "tw_polymesh" +version = "0.1.0" +dependencies = [ + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_number", + "tw_proto", + "tw_scale", + "tw_ss58_address", + "tw_substrate", +] + [[package]] name = "tw_proto" version = "0.1.0" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 75b9d7761ab..34f6073d047 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -13,6 +13,7 @@ members = [ "chains/tw_native_injective", "chains/tw_pactus", "chains/tw_polkadot", + "chains/tw_polymesh", "chains/tw_ronin", "chains/tw_solana", "chains/tw_sui", diff --git a/rust/chains/tw_polkadot/src/call_encoder/mod.rs b/rust/chains/tw_polkadot/src/call_encoder/mod.rs index db5cb90c2b7..6b2548e3a38 100644 --- a/rust/chains/tw_polkadot/src/call_encoder/mod.rs +++ b/rust/chains/tw_polkadot/src/call_encoder/mod.rs @@ -1,4 +1,4 @@ -use crate::{ctx_from_tw, KUSAMA, POLKADOT, POLYMESH}; +use crate::{ctx_from_tw, KUSAMA, POLKADOT}; use tw_proto::Polkadot::Proto::{ self, mod_Balance::{BatchAssetTransfer, BatchTransfer, OneOfmessage_oneof as BalanceVariant}, @@ -19,9 +19,6 @@ use generic::*; pub mod polkadot; use polkadot::*; -pub mod polymesh; -use polymesh::*; - pub fn validate_call_index(call_index: &Option) -> EncodeResult { let index = match call_index { Some(CallIndices { @@ -56,7 +53,6 @@ impl CallEncoder { let encoder = match ctx.network { POLKADOT => PolkadotCallEncoder::new_boxed(ctx), KUSAMA => KusamaCallEncoder::new_boxed(ctx), - POLYMESH => PolymeshCallEncoder::new_boxed(ctx), _ => PolkadotCallEncoder::new_boxed(ctx), }; Ok(Self { encoder }) diff --git a/rust/chains/tw_polkadot/tests/extrinsic.rs b/rust/chains/tw_polkadot/tests/extrinsic.rs index f576982c449..1a379ff1d4d 100644 --- a/rust/chains/tw_polkadot/tests/extrinsic.rs +++ b/rust/chains/tw_polkadot/tests/extrinsic.rs @@ -5,7 +5,6 @@ use tw_encoding::hex::ToHex; use tw_number::U256; use tw_proto::Polkadot::Proto; use tw_proto::Polkadot::Proto::mod_Balance::{AssetTransfer, BatchAssetTransfer, Transfer}; -use tw_proto::Polkadot::Proto::mod_Identity::mod_AddAuthorization::{AuthData, Data}; use tw_proto::Polkadot::Proto::mod_Staking::{ Bond, BondExtra, Chill, Nominate, Rebond, Unbond, WithdrawUnbonded, }; @@ -27,37 +26,6 @@ fn custom_call_indices(module: u8, method: u8) -> Option { }) } -fn polymesh_identity_call( - call: Proto::mod_Identity::OneOfmessage_oneof, -) -> Proto::mod_SigningInput::OneOfmessage_oneof { - Proto::mod_SigningInput::OneOfmessage_oneof::polymesh_call(Proto::PolymeshCall { - message_oneof: Proto::mod_PolymeshCall::OneOfmessage_oneof::identity_call( - Proto::Identity { - message_oneof: call, - }, - ), - }) -} - -fn polymesh_add_auth_call( - add_auth: Proto::mod_Identity::AddAuthorization, -) -> Proto::mod_SigningInput::OneOfmessage_oneof { - polymesh_identity_call(Proto::mod_Identity::OneOfmessage_oneof::add_authorization( - add_auth, - )) -} - -fn polymesh_join_identity(auth_id: u64) -> Proto::mod_SigningInput::OneOfmessage_oneof<'static> { - polymesh_identity_call( - Proto::mod_Identity::OneOfmessage_oneof::join_identity_as_key( - Proto::mod_Identity::JoinIdentityAsKey { - call_indices: None, - auth_id, - }, - ), - ) -} - fn balance_call( call: Proto::mod_Balance::OneOfmessage_oneof, ) -> Proto::mod_SigningInput::OneOfmessage_oneof { @@ -75,122 +43,25 @@ fn staking_call( } #[test] -fn polymesh_encode_transfer_with_memo() { - // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x0501004c6c63e3dc083959f876788716b78885460b5f3c7ed9379f8d5f408e08639e0204014d454d4f20504144444544205749544820535041434553000000000000000000 - +fn polkadot_encode_transfer() { let input = Proto::SigningInput { - network: 12, + network: 0, multi_address: true, message_oneof: balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { - to_address: "2EB7wW2fYfFskkSx2d65ivn34ewpuEjcowfJYBL79ty5FsZF".into(), + to_address: "14ixj163bkk2UEKLEXsEWosuFNuijpqEWZbX5JzN4yMHbUVD".into(), value: Cow::Owned(U256::from(1u64).to_big_endian().to_vec()), - memo: "MEMO PADDED WITH SPACES".into(), - call_indices: custom_call_indices(0x05, 0x01), - })), - ..Default::default() - }; - - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!( - encoded.to_hex(), - "0501004c6c63e3dc083959f876788716b78885460b5f3c7ed9379f8d5f408e08639e0204014d454d4f20504144444544205749544820535041434553000000000000000000" - ); -} - -#[test] -fn polymesh_encode_authorization_join_identity() { - // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000 - - let input = Proto::SigningInput { - network: 12, - multi_address: true, - message_oneof: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { - target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), ..Default::default() - }), - ..Default::default() - }; - - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!( - encoded.to_hex(), - "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000" - ); -} - -#[test] -fn polymesh_encode_authorization_join_identity_with_zero_data() { - // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320501000100010000 - - let input = Proto::SigningInput { - network: 12, - multi_address: true, - message_oneof: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { - target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), - data: Some(AuthData { - asset: Some(Data { - data: (&[0x00]).into(), - }), - extrinsic: Some(Data { - data: (&[0x00]).into(), - }), - portfolio: Some(Data { - data: (&[0x00]).into(), - }), - }), - ..Default::default() - }), - ..Default::default() - }; - - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!( - encoded.to_hex(), - "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320501000100010000" - ); -} - -#[test] -fn polymesh_encode_authorization_join_identity_allowing_everything() { - // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000 - - let input = Proto::SigningInput { - network: 12, - multi_address: true, - message_oneof: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { - target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), - data: Some(AuthData { - asset: None, - extrinsic: None, - portfolio: None, - }), - ..Default::default() - }), + })), ..Default::default() }; let encoded = encode_input(&input).expect("error encoding call"); assert_eq!( encoded.to_hex(), - "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000" + "050000a4b558a0342ae6e379a7ed00d23ff505f1101646cb279844496ad608943eda0d04" ); } -#[test] -fn polymesh_encode_identity() { - // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x07040b13000000000000 - - let input = Proto::SigningInput { - network: 12, - multi_address: true, - message_oneof: polymesh_join_identity(4875), - ..Default::default() - }; - - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!(encoded.to_hex(), "07040b13000000000000"); -} - #[test] fn statemint_encode_asset_transfer() { // tx on mainnet @@ -318,7 +189,7 @@ fn encode_staking_chill() { #[test] fn encode_staking_bond_with_controller() { let input = Proto::SigningInput { - network: 12, + network: 0, multi_address: true, message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::bond(Bond { controller: "13wQDQTMM6E9g5WD27e6UsWWTwHLaW763FQxnkbVaoKmsBQy".into(), @@ -332,7 +203,7 @@ fn encode_staking_bond_with_controller() { let encoded = encode_input(&input).expect("error encoding call"); assert_eq!( encoded.to_hex(), - "11000081f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c914652310002" + "07000081f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c914652310002" ); } diff --git a/rust/chains/tw_polymesh/Cargo.toml b/rust/chains/tw_polymesh/Cargo.toml new file mode 100644 index 00000000000..3ccc62b213f --- /dev/null +++ b/rust/chains/tw_polymesh/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "tw_polymesh" +version = "0.1.0" +edition = "2021" + +[dependencies] +tw_coin_entry = { path = "../../tw_coin_entry" } +tw_encoding = { path = "../../tw_encoding" } +tw_scale = { path = "../../tw_scale" } +tw_substrate = { path = "../../frameworks/tw_substrate" } +tw_hash = { path = "../../tw_hash" } +tw_keypair = { path = "../../tw_keypair" } +tw_memory = { path = "../../tw_memory" } +tw_number = { path = "../../tw_number" } +tw_proto = { path = "../../tw_proto" } +tw_ss58_address = { path = "../../tw_ss58_address" } diff --git a/rust/chains/tw_polymesh/src/address.rs b/rust/chains/tw_polymesh/src/address.rs new file mode 100644 index 00000000000..5cb2c8216a9 --- /dev/null +++ b/rust/chains/tw_polymesh/src/address.rs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::fmt; +use std::str::FromStr; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::prelude::*; +use tw_memory::Data; + +pub struct PolymeshAddress { + // TODO add necessary fields. +} + +impl CoinAddress for PolymeshAddress { + #[inline] + fn data(&self) -> Data { + todo!() + } +} + +impl FromStr for PolymeshAddress { + type Err = AddressError; + + fn from_str(_s: &str) -> Result { + todo!() + } +} + +impl fmt::Display for PolymeshAddress { + fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { + todo!() + } +} diff --git a/rust/chains/tw_polymesh/src/call_encoder/mod.rs b/rust/chains/tw_polymesh/src/call_encoder/mod.rs new file mode 100644 index 00000000000..fd56d9d2d1e --- /dev/null +++ b/rust/chains/tw_polymesh/src/call_encoder/mod.rs @@ -0,0 +1,190 @@ +use crate::ctx_from_tw; +use tw_proto::Polkadot::Proto::{ + self, + mod_Balance::{BatchAssetTransfer, BatchTransfer, OneOfmessage_oneof as BalanceVariant}, + mod_CallIndices::OneOfvariant as CallIndicesVariant, + mod_SigningInput::OneOfmessage_oneof as SigningVariant, + mod_Staking::{ + Bond, BondAndNominate, Chill, ChillAndUnbond, Nominate, + OneOfmessage_oneof as StakingVariant, Unbond, + }, + Balance, CallIndices, Staking, +}; +use tw_scale::{RawOwned, ToScale}; +use tw_substrate::*; + +pub mod polymesh; +use polymesh::*; + +pub fn validate_call_index(call_index: &Option) -> EncodeResult { + let index = match call_index { + Some(CallIndices { + variant: CallIndicesVariant::custom(c), + }) => Some((c.module_index, c.method_index)), + _ => None, + }; + CallIndex::from_tw(index) +} + +pub fn required_call_index(call_index: &Option) -> EncodeResult { + let index = match call_index { + Some(CallIndices { + variant: CallIndicesVariant::custom(c), + }) => Some((c.module_index, c.method_index)), + _ => None, + }; + CallIndex::required_from_tw(index) +} + +pub struct CallEncoder { + encoder: PolymeshCallEncoder, +} + +impl CallEncoder { + pub fn from_ctx(ctx: &SubstrateContext) -> EncodeResult { + let encoder = PolymeshCallEncoder::new(ctx); + Ok(Self { encoder }) + } + + pub fn encode_input(input: &'_ Proto::SigningInput<'_>) -> EncodeResult { + let ctx = ctx_from_tw(input)?; + let encoder = Self::from_ctx(&ctx)?; + encoder.encode_call(&input.message_oneof) + } + + fn encode_batch_transfer(&self, bt: &BatchTransfer) -> EncodeResult { + let transfers = bt + .transfers + .iter() + .map(|t| { + let call = SigningVariant::balance_call(Proto::Balance { + message_oneof: BalanceVariant::transfer(t.clone()), + }); + self.encode_call(&call) + }) + .collect::>>()?; + + self.encode_batch(transfers, &bt.call_indices) + } + + fn encode_batch_asset_transfer(&self, bat: &BatchAssetTransfer) -> EncodeResult { + let transfers = bat + .transfers + .iter() + .map(|t| { + let call = SigningVariant::balance_call(Proto::Balance { + message_oneof: BalanceVariant::asset_transfer(t.clone()), + }); + self.encode_call(&call) + }) + .collect::>>()?; + + self.encode_batch(transfers, &bat.call_indices) + } + + fn encode_balance_batch_call(&self, b: &Balance) -> EncodeResult> { + match &b.message_oneof { + BalanceVariant::batchTransfer(bt) => { + let batch = self.encode_batch_transfer(bt)?; + Ok(Some(batch)) + }, + BalanceVariant::batch_asset_transfer(bat) => { + let batch = self.encode_batch_asset_transfer(bat)?; + Ok(Some(batch)) + }, + _ => Ok(None), + } + } + + fn encode_staking_bond_and_nominate(&self, ban: &BondAndNominate) -> EncodeResult { + // Encode a bond call + let first = self.encode_call(&SigningVariant::staking_call(Proto::Staking { + message_oneof: StakingVariant::bond(Bond { + controller: ban.controller.clone(), + value: ban.value.clone(), + reward_destination: ban.reward_destination, + // TODO: `BondAndNominate` needs 3 call_indices values to support this. + //call_indices: ban.call_indices.clone(), + call_indices: None, + }), + }))?; + + // Encode a nominate call + let second = self.encode_call(&SigningVariant::staking_call(Proto::Staking { + message_oneof: StakingVariant::nominate(Nominate { + nominators: ban.nominators.clone(), + // TODO: `BondAndNominate` needs 3 call_indices values to support this. + //call_indices: ban.call_indices.clone(), + call_indices: None, + }), + }))?; + + // Encode both calls as batched + self.encode_batch(vec![first, second], &ban.call_indices) + } + + fn encode_staking_chill_and_unbond(&self, cau: &ChillAndUnbond) -> EncodeResult { + let first = self.encode_call(&SigningVariant::staking_call(Proto::Staking { + message_oneof: StakingVariant::chill(Chill { + // TODO: `ChillAndUnbond` needs 3 call_indices values to support this. + //call_indices: cau.call_indices.clone(), + call_indices: None, + }), + }))?; + + let second = self.encode_call(&SigningVariant::staking_call(Proto::Staking { + message_oneof: StakingVariant::unbond(Unbond { + value: cau.value.clone(), + // TODO: `ChillAndUnbond` needs 3 call_indices values to support this. + //call_indices: cau.call_indices.clone(), + call_indices: None, + }), + }))?; + + // Encode both calls as batched + self.encode_batch(vec![first, second], &cau.call_indices) + } + + fn encode_staking_batch_call(&self, s: &Staking) -> EncodeResult> { + match &s.message_oneof { + StakingVariant::bond_and_nominate(ban) => { + let batch = self.encode_staking_bond_and_nominate(&ban)?; + Ok(Some(batch)) + }, + StakingVariant::chill_and_unbond(cau) => { + let batch = self.encode_staking_chill_and_unbond(&cau)?; + Ok(Some(batch)) + }, + _ => Ok(None), + } + } + + pub fn encode_call(&self, msg: &SigningVariant<'_>) -> EncodeResult { + // Special case for batches. + match msg { + SigningVariant::balance_call(b) => { + if let Some(batch) = self.encode_balance_batch_call(b)? { + return Ok(batch); + } + }, + SigningVariant::staking_call(s) => { + if let Some(batch) = self.encode_staking_batch_call(s)? { + return Ok(batch); + } + }, + _ => (), + } + // non-batch calls. + self.encoder.encode_call(msg) + } + + fn encode_batch( + &self, + calls: Vec, + ci: &Option, + ) -> EncodeResult { + let ci = validate_call_index(ci)?; + let call = ci.wrap(self.encoder.encode_batch(calls)?); + Ok(RawOwned(call.to_scale())) + } +} diff --git a/rust/chains/tw_polkadot/src/call_encoder/polymesh.rs b/rust/chains/tw_polymesh/src/call_encoder/polymesh.rs similarity index 89% rename from rust/chains/tw_polkadot/src/call_encoder/polymesh.rs rename to rust/chains/tw_polymesh/src/call_encoder/polymesh.rs index dde4a047308..79dbf4bef0d 100644 --- a/rust/chains/tw_polkadot/src/call_encoder/polymesh.rs +++ b/rust/chains/tw_polymesh/src/call_encoder/polymesh.rs @@ -185,6 +185,34 @@ impl PolymeshIdentity { } } +impl_enum_scale!( + #[derive(Clone, Debug)] + pub enum RewardDestination { + Staked = 0x00, + Stash = 0x01, + Controller = 0x02, + Account(AccountId) = 0x03, + None = 0x04, + } +); + +impl RewardDestination { + pub fn from_tw(dest: u8, account: &str) -> EncodeResult { + match dest { + 0 => Ok(Self::Staked), + 1 => Ok(Self::Stash), + 2 => Ok(Self::Controller), + 4 => { + let account = + SS58Address::from_str(account).map_err(|_| EncodeError::InvalidAddress)?; + Ok(Self::Account(SubstrateAddress(account))) + }, + 5 => Ok(Self::None), + _ => EncodeError::InvalidValue.tw_result(format!("Invalid reward destination: {dest}")), + } + } +} + impl_enum_scale!( #[derive(Clone, Debug)] pub enum PolymeshStaking { @@ -208,7 +236,7 @@ impl_enum_scale!( Chill = 0x06, Rebond { value: Compact, - } = 0x18, + } = 0x13, } ); @@ -307,26 +335,33 @@ impl PolymeshStaking { } } +impl_enum_scale!( + #[derive(Clone, Debug)] + pub enum PolymeshUtility { + BatchAll { calls: Vec } = 0x02, + } +); + impl_enum_scale!( #[derive(Clone, Debug)] pub enum PolymeshCall { Balances(PolymeshBalances) = 0x05, Identity(PolymeshIdentity) = 0x07, Staking(PolymeshStaking) = 0x11, - Utility(GenericUtility) = 0x29, + Utility(PolymeshUtility) = 0x29, } ); pub struct PolymeshCallEncoder; impl PolymeshCallEncoder { - pub fn new_boxed(_ctx: &SubstrateContext) -> Box { - Box::new(Self) + pub fn new(_ctx: &SubstrateContext) -> Self { + Self } } -impl TWPolkadotCallEncoder for PolymeshCallEncoder { - fn encode_call(&self, msg: &SigningVariant<'_>) -> EncodeResult { +impl PolymeshCallEncoder { + pub fn encode_call(&self, msg: &SigningVariant<'_>) -> EncodeResult { let call = match msg { SigningVariant::balance_call(b) => { PolymeshBalances::encode_call(b)?.map(PolymeshCall::Balances) @@ -353,8 +388,8 @@ impl TWPolkadotCallEncoder for PolymeshCallEncoder { Ok(RawOwned(call.to_scale())) } - fn encode_batch(&self, calls: Vec) -> EncodeResult { - let call = PolymeshCall::Utility(GenericUtility::BatchAll { calls }); + pub fn encode_batch(&self, calls: Vec) -> EncodeResult { + let call = PolymeshCall::Utility(PolymeshUtility::BatchAll { calls }); Ok(RawOwned(call.to_scale())) } } diff --git a/rust/chains/tw_polymesh/src/compiler.rs b/rust/chains/tw_polymesh/src/compiler.rs new file mode 100644 index 00000000000..a70571c1b1d --- /dev/null +++ b/rust/chains/tw_polymesh/src/compiler.rs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_proto::Polymesh::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct PolymeshCompiler; + +impl PolymeshCompiler { + #[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/rust/chains/tw_polymesh/src/entry.rs b/rust/chains/tw_polymesh/src/entry.rs new file mode 100644 index 00000000000..79bed742a65 --- /dev/null +++ b/rust/chains/tw_polymesh/src/entry.rs @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::ctx_from_tw; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_keypair::ed25519::sha512::{KeyPair, PublicKey}; +use tw_number::U256; +use tw_proto::Polkadot::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; +use tw_scale::{RawOwned, ToScale}; +use tw_ss58_address::SS58Address; +use tw_substrate::*; + +use crate::call_encoder::CallEncoder; + +pub struct PolymeshEntry; + +impl PolymeshEntry { + #[inline] + fn get_keypair_impl( + &self, + _coin: &dyn CoinContext, + input: &Proto::SigningInput<'_>, + ) -> SigningResult { + Ok(KeyPair::try_from(input.private_key.as_ref())?) + } + + #[inline] + fn build_transaction_impl( + &self, + _coin: &dyn CoinContext, + public_key: Option, + input: &Proto::SigningInput<'_>, + ) -> EncodeResult { + let ctx = ctx_from_tw(&input)?; + let encoder = CallEncoder::from_ctx(&ctx)?; + let call = encoder.encode_call(&input.message_oneof)?; + let era = match &input.era { + Some(era) => Era::mortal(era.period, era.block_number), + None => Era::immortal(), + }; + let genesis_hash = input.genesis_hash.as_ref().try_into().unwrap_or_default(); + let current_hash = input.block_hash.as_ref().try_into().unwrap_or_default(); + let tip = U256::from_big_endian_slice(&input.tip) + .map_err(|_| EncodeError::InvalidValue)? + .try_into() + .map_err(|_| EncodeError::InvalidValue)?; + + let mut builder = TransactionBuilder::new(ctx.multi_address, call); + // Add chain extensions. + builder.extension(CheckVersion(input.spec_version)); + builder.extension(CheckVersion(input.transaction_version)); + builder.extension(CheckGenesis(genesis_hash)); + builder.extension(CheckEra { era, current_hash }); + builder.extension(CheckNonce::new(input.nonce as u32)); + builder.extension(ChargeTransactionPayment::new(tip)); + if let Some(public_key) = public_key { + let account = SubstrateAddress( + SS58Address::from_public_key(&public_key, ctx.network) + .map_err(|e| EncodeError::InvalidAddress.with_error(e))?, + ); + builder.set_account(account); + } + Ok(builder) + } + + #[inline] + fn signing_output_impl( + &self, + _coin: &dyn CoinContext, + result: SigningResult, + ) -> SigningResult> { + let encoded = result?.to_scale(); + Ok(Proto::SigningOutput { + encoded: encoded.into(), + ..Default::default() + }) + } + + #[inline] + fn presigning_output_impl( + &self, + _coin: &dyn CoinContext, + result: SigningResult, + ) -> SigningResult> { + let pre_image = result?.to_scale(); + Ok(CompilerProto::PreSigningOutput { + // `pre_image` is already hashed if it is larger then 256 bytes. + data_hash: pre_image.clone().into(), + data: pre_image.into(), + ..Default::default() + }) + } +} + +impl SubstrateCoinEntry for PolymeshEntry { + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + #[inline] + fn get_keypair( + &self, + coin: &dyn CoinContext, + input: &Proto::SigningInput<'_>, + ) -> SigningResult { + self.get_keypair_impl(coin, input) + } + + #[inline] + fn build_transaction( + &self, + coin: &dyn CoinContext, + public_key: Option, + input: &Self::SigningInput<'_>, + ) -> SigningResult { + self.build_transaction_impl(coin, public_key, input) + .map_err(|e| e.map_err(SigningErrorType::from)) + } + + #[inline] + fn signing_output( + &self, + coin: &dyn CoinContext, + result: SigningResult, + ) -> Self::SigningOutput { + self.signing_output_impl(coin, result) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + #[inline] + fn presigning_output( + &self, + coin: &dyn CoinContext, + result: SigningResult, + ) -> Self::PreSigningOutput { + self.presigning_output_impl(coin, result) + .unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e)) + } +} diff --git a/rust/chains/tw_polymesh/src/lib.rs b/rust/chains/tw_polymesh/src/lib.rs new file mode 100644 index 00000000000..0cc3a216fe8 --- /dev/null +++ b/rust/chains/tw_polymesh/src/lib.rs @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_proto::Polkadot::Proto::{ + self, mod_Balance::OneOfmessage_oneof as BalanceVariant, + mod_SigningInput::OneOfmessage_oneof as SigningVariant, +}; +use tw_ss58_address::NetworkId; +use tw_substrate::*; + +pub mod call_encoder; +pub mod entry; + +pub const POLYMESH_PREFIX: u16 = 12; +pub const POLYMESH: NetworkId = NetworkId::new_unchecked(POLYMESH_PREFIX); + +pub fn network_id_from_tw(input: &'_ Proto::SigningInput<'_>) -> EncodeResult { + Ok(NetworkId::try_from(input.network as u16).map_err(|_| EncodeError::InvalidNetworkId)?) +} + +pub fn fee_asset_id_from_tw(input: &'_ Proto::SigningInput<'_>) -> Option { + // Special case for batches. + match &input.message_oneof { + SigningVariant::balance_call(b) => match &b.message_oneof { + BalanceVariant::asset_transfer(at) => Some(at.fee_asset_id), + BalanceVariant::batch_asset_transfer(bat) => Some(bat.fee_asset_id), + _ => None, + }, + _ => None, + } +} + +pub fn ctx_from_tw(input: &'_ Proto::SigningInput<'_>) -> EncodeResult { + let network = + NetworkId::try_from(input.network as u16).map_err(|_| EncodeError::InvalidNetworkId)?; + let spec_version = input.spec_version; + + Ok(SubstrateContext { + multi_address: true, + network, + spec_version, + transaction_version: input.transaction_version, + fee_asset_id: None, + }) +} diff --git a/rust/chains/tw_polymesh/src/signer.rs b/rust/chains/tw_polymesh/src/signer.rs new file mode 100644 index 00000000000..21c344aa210 --- /dev/null +++ b/rust/chains/tw_polymesh/src/signer.rs @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::signing_output_error; +use tw_proto::Polymesh::Proto; + +pub struct PolymeshSigner; + +impl PolymeshSigner { + 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/rust/chains/tw_polymesh/tests/extrinsic.rs b/rust/chains/tw_polymesh/tests/extrinsic.rs new file mode 100644 index 00000000000..7c8b7033664 --- /dev/null +++ b/rust/chains/tw_polymesh/tests/extrinsic.rs @@ -0,0 +1,309 @@ +use std::borrow::Cow; +use std::default::Default; + +use tw_encoding::hex::ToHex; +use tw_number::U256; +use tw_proto::Polkadot::Proto; +use tw_proto::Polkadot::Proto::mod_Balance::Transfer; +use tw_proto::Polkadot::Proto::mod_Identity::mod_AddAuthorization::{AuthData, Data}; +use tw_proto::Polkadot::Proto::mod_Staking::{ + Bond, BondExtra, Chill, Nominate, Rebond, Unbond, WithdrawUnbonded, +}; +use tw_substrate::EncodeResult; + +use tw_polymesh::call_encoder::CallEncoder; + +fn encode_input(input: &Proto::SigningInput<'_>) -> EncodeResult> { + let encoded = CallEncoder::encode_input(input)?; + Ok(encoded.0) +} + +fn polymesh_identity_call( + call: Proto::mod_Identity::OneOfmessage_oneof, +) -> Proto::mod_SigningInput::OneOfmessage_oneof { + Proto::mod_SigningInput::OneOfmessage_oneof::polymesh_call(Proto::PolymeshCall { + message_oneof: Proto::mod_PolymeshCall::OneOfmessage_oneof::identity_call( + Proto::Identity { + message_oneof: call, + }, + ), + }) +} + +fn polymesh_add_auth_call( + add_auth: Proto::mod_Identity::AddAuthorization, +) -> Proto::mod_SigningInput::OneOfmessage_oneof { + polymesh_identity_call(Proto::mod_Identity::OneOfmessage_oneof::add_authorization( + add_auth, + )) +} + +fn polymesh_join_identity(auth_id: u64) -> Proto::mod_SigningInput::OneOfmessage_oneof<'static> { + polymesh_identity_call( + Proto::mod_Identity::OneOfmessage_oneof::join_identity_as_key( + Proto::mod_Identity::JoinIdentityAsKey { + call_indices: None, + auth_id, + }, + ), + ) +} + +fn balance_call( + call: Proto::mod_Balance::OneOfmessage_oneof, +) -> Proto::mod_SigningInput::OneOfmessage_oneof { + Proto::mod_SigningInput::OneOfmessage_oneof::balance_call(Proto::Balance { + message_oneof: call, + }) +} + +fn staking_call( + call: Proto::mod_Staking::OneOfmessage_oneof, +) -> Proto::mod_SigningInput::OneOfmessage_oneof { + Proto::mod_SigningInput::OneOfmessage_oneof::staking_call(Proto::Staking { + message_oneof: call, + }) +} + +#[test] +fn polymesh_encode_transfer_with_memo() { + // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x0501004c6c63e3dc083959f876788716b78885460b5f3c7ed9379f8d5f408e08639e0204014d454d4f20504144444544205749544820535041434553000000000000000000 + + let input = Proto::SigningInput { + network: 12, + multi_address: true, + message_oneof: balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: "2EB7wW2fYfFskkSx2d65ivn34ewpuEjcowfJYBL79ty5FsZF".into(), + value: Cow::Owned(U256::from(1u64).to_big_endian().to_vec()), + memo: "MEMO PADDED WITH SPACES".into(), + ..Default::default() + })), + ..Default::default() + }; + + let encoded = encode_input(&input).expect("error encoding call"); + assert_eq!( + encoded.to_hex(), + "0501004c6c63e3dc083959f876788716b78885460b5f3c7ed9379f8d5f408e08639e0204014d454d4f20504144444544205749544820535041434553000000000000000000" + ); +} + +#[test] +fn polymesh_encode_authorization_join_identity() { + // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000 + + let input = Proto::SigningInput { + network: 12, + multi_address: true, + message_oneof: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { + target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), + ..Default::default() + }), + ..Default::default() + }; + + let encoded = encode_input(&input).expect("error encoding call"); + assert_eq!( + encoded.to_hex(), + "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000" + ); +} + +#[test] +fn polymesh_encode_authorization_join_identity_with_zero_data() { + // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320501000100010000 + + let input = Proto::SigningInput { + network: 12, + multi_address: true, + message_oneof: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { + target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), + data: Some(AuthData { + asset: Some(Data { + data: (&[0x00]).into(), + }), + extrinsic: Some(Data { + data: (&[0x00]).into(), + }), + portfolio: Some(Data { + data: (&[0x00]).into(), + }), + }), + ..Default::default() + }), + ..Default::default() + }; + + let encoded = encode_input(&input).expect("error encoding call"); + assert_eq!( + encoded.to_hex(), + "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320501000100010000" + ); +} + +#[test] +fn polymesh_encode_authorization_join_identity_allowing_everything() { + // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000 + + let input = Proto::SigningInput { + network: 12, + multi_address: true, + message_oneof: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { + target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), + data: Some(AuthData { + asset: None, + extrinsic: None, + portfolio: None, + }), + ..Default::default() + }), + ..Default::default() + }; + + let encoded = encode_input(&input).expect("error encoding call"); + assert_eq!( + encoded.to_hex(), + "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000" + ); +} + +#[test] +fn polymesh_encode_identity() { + // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x07040b13000000000000 + + let input = Proto::SigningInput { + network: 12, + multi_address: true, + message_oneof: polymesh_join_identity(4875), + ..Default::default() + }; + + let encoded = encode_input(&input).expect("error encoding call"); + assert_eq!(encoded.to_hex(), "07040b13000000000000"); +} + +#[test] +fn encode_staking_nominate() { + let input = Proto::SigningInput { + network: 12, + multi_address: true, + message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::nominate(Nominate { + nominators: vec![ + "2DxgKKS53wsAeETAZXhmT5A1bTt7h1aV4bKdtkMDwwSzSMXm".into(), + "2HqjMm2goapWvXQBqjjEdVaTZsUmunWwEq1TSToDR1pDzQ1F".into(), + ], + call_indices: None, + })), + ..Default::default() + }; + + let encoded = encode_input(&input).expect("error encoding call"); + assert_eq!( + encoded.to_hex(), + "1105080042ef301451c7f596f974daec8ca1234f66809905d13e16c18e23896b0c57e53e00ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e67148" + ); +} + +#[test] +fn encode_staking_chill() { + let input = Proto::SigningInput { + network: 12, + multi_address: true, + message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::chill(Chill { + call_indices: None, + })), + ..Default::default() + }; + + let encoded = encode_input(&input).expect("error encoding call"); + assert_eq!(encoded.to_hex(), "1106"); +} + +#[test] +fn encode_staking_bond() { + let input = Proto::SigningInput { + network: 12, + multi_address: true, + message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::bond(Bond { + controller: "2HqjMm2goapWvXQBqjjEdVaTZsUmunWwEq1TSToDR1pDzQ1F".into(), + value: U256::from(808081u64).to_big_endian().to_vec().into(), + reward_destination: Proto::RewardDestination::STAKED, + call_indices: None, + })), + ..Default::default() + }; + + let encoded = encode_input(&input).expect("error encoding call"); + assert_eq!( + encoded.to_hex(), + "110000ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e671484652310000" + ); +} + +#[test] +fn encode_staking_bond_extra() { + let input = Proto::SigningInput { + network: 12, + multi_address: true, + message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::bond_extra( + BondExtra { + value: U256::from(808081u64).to_big_endian().to_vec().into(), + call_indices: None, + }, + )), + ..Default::default() + }; + + let encoded = encode_input(&input).expect("error encoding call"); + assert_eq!(encoded.to_hex(), "110146523100"); +} + +#[test] +fn encode_staking_rebond() { + let input = Proto::SigningInput { + network: 12, + multi_address: true, + message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::rebond(Rebond { + value: U256::from(808081u64).to_big_endian().to_vec().into(), + call_indices: None, + })), + ..Default::default() + }; + + let encoded = encode_input(&input).expect("error encoding call"); + assert_eq!(encoded.to_hex(), "111346523100"); +} + +#[test] +fn encode_staking_unbond() { + let input = Proto::SigningInput { + network: 12, + multi_address: true, + message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::unbond(Unbond { + value: U256::from(808081u64).to_big_endian().to_vec().into(), + call_indices: None, + })), + ..Default::default() + }; + + let encoded = encode_input(&input).expect("error encoding call"); + assert_eq!(encoded.to_hex(), "110246523100"); +} + +#[test] +fn encode_staking_withdraw_unbonded() { + let input = Proto::SigningInput { + network: 12, + multi_address: true, + message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::withdraw_unbonded( + WithdrawUnbonded { + slashing_spans: 84, + call_indices: None, + }, + )), + ..Default::default() + }; + + let encoded = encode_input(&input).expect("error encoding call"); + assert_eq!(encoded.to_hex(), "110354000000"); +} diff --git a/rust/tw_coin_registry/Cargo.toml b/rust/tw_coin_registry/Cargo.toml index ce69b29fde6..a257822548c 100644 --- a/rust/tw_coin_registry/Cargo.toml +++ b/rust/tw_coin_registry/Cargo.toml @@ -27,6 +27,7 @@ tw_native_evmos = { path = "../chains/tw_native_evmos" } tw_native_injective = { path = "../chains/tw_native_injective" } tw_pactus = { path = "../chains/tw_pactus" } tw_polkadot = { path = "../chains/tw_polkadot" } +tw_polymesh = { path = "../chains/tw_polymesh" } tw_ronin = { path = "../chains/tw_ronin" } tw_solana = { path = "../chains/tw_solana" } tw_substrate = { path = "../frameworks/tw_substrate" } diff --git a/rust/tw_coin_registry/src/blockchain_type.rs b/rust/tw_coin_registry/src/blockchain_type.rs index 92420c6491a..b611ecbf5c6 100644 --- a/rust/tw_coin_registry/src/blockchain_type.rs +++ b/rust/tw_coin_registry/src/blockchain_type.rs @@ -17,11 +17,12 @@ pub enum BlockchainType { Ethereum, Greenfield, InternetComputer, + Kusama, NativeEvmos, NativeInjective, Pactus, Polkadot, - Kusama, + Polymesh, Ronin, Solana, Sui, diff --git a/rust/tw_coin_registry/src/dispatcher.rs b/rust/tw_coin_registry/src/dispatcher.rs index ee3e958ea37..2941d42293e 100644 --- a/rust/tw_coin_registry/src/dispatcher.rs +++ b/rust/tw_coin_registry/src/dispatcher.rs @@ -21,6 +21,7 @@ use tw_native_evmos::entry::NativeEvmosEntry; use tw_native_injective::entry::NativeInjectiveEntry; use tw_pactus::entry::PactusEntry; use tw_polkadot::entry::PolkadotEntry; +use tw_polymesh::entry::PolymeshEntry; use tw_ronin::entry::RoninEntry; use tw_solana::entry::SolanaEntry; use tw_substrate::entry::SubstrateEntry; @@ -44,6 +45,7 @@ const NATIVE_EVMOS: NativeEvmosEntry = NativeEvmosEntry; const NATIVE_INJECTIVE: NativeInjectiveEntry = NativeInjectiveEntry; const PACTUS: PactusEntry = PactusEntry; const POLKADOT: SubstrateEntry = SubstrateEntry(PolkadotEntry); +const POLYMESH: SubstrateEntry = SubstrateEntry(PolymeshEntry); const RONIN: RoninEntry = RoninEntry; const SOLANA: SolanaEntry = SolanaEntry; const SUI: SuiEntry = SuiEntry; @@ -62,11 +64,12 @@ pub fn blockchain_dispatcher(blockchain: BlockchainType) -> RegistryResult Ok(ÐEREUM), BlockchainType::Greenfield => Ok(&GREENFIELD), BlockchainType::InternetComputer => Ok(&INTERNET_COMPUTER), + BlockchainType::Kusama => Ok(&POLKADOT), BlockchainType::NativeEvmos => Ok(&NATIVE_EVMOS), BlockchainType::NativeInjective => Ok(&NATIVE_INJECTIVE), BlockchainType::Pactus => Ok(&PACTUS), BlockchainType::Polkadot => Ok(&POLKADOT), - BlockchainType::Kusama => Ok(&POLKADOT), + BlockchainType::Polymesh => Ok(&POLYMESH), BlockchainType::Ronin => Ok(&RONIN), BlockchainType::Solana => Ok(&SOLANA), BlockchainType::Sui => Ok(&SUI), diff --git a/rust/tw_tests/tests/chains/polkadot/mod.rs b/rust/tw_tests/tests/chains/polkadot/mod.rs index 5e57ab33dc0..9ef70e5c95c 100644 --- a/rust/tw_tests/tests/chains/polkadot/mod.rs +++ b/rust/tw_tests/tests/chains/polkadot/mod.rs @@ -17,8 +17,6 @@ mod polkadot_compile; mod polkadot_sign; const GENESIS_HASH: &str = "91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"; -const POLYMESH_GENESIS_HASH: &str = - "6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063"; const PRIVATE_KEY: &str = "abf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115"; const PRIVATE_KEY_IOS: &str = "37932b086586a6675e66e562fe68bd3eeea4177d066619c602fe3efc290ada62"; const PRIVATE_KEY_2: &str = "70a794d4f1019c3ce002f33062f45029c4f930a56b3d20ec477f7668c6bbc37f"; diff --git a/rust/tw_tests/tests/chains/polymesh/mod.rs b/rust/tw_tests/tests/chains/polymesh/mod.rs index 157974d25e6..8c1fe6c8cf6 100644 --- a/rust/tw_tests/tests/chains/polymesh/mod.rs +++ b/rust/tw_tests/tests/chains/polymesh/mod.rs @@ -1 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + mod polymesh_address; +mod polymesh_compile; +mod polymesh_sign; + +pub const GENESIS_HASH: &str = "0x6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063"; +/// Private key for testing. DO NOT USE, since this is public. +pub const PRIVATE_KEY_1: &str = + "0x790a0a01ec2e7c7db4abcaffc92ce70a960ef9ad3021dbe3bf327c1c6343aee4"; +pub const PUBLIC_KEY_1: &str = "2EANwBfNsFu9KV8JsW5sbhF6ft8bzvw5EW1LCrgHhrqtK6Ys"; +pub const PUBLIC_KEY_HEX_1: &str = + "0x4bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c"; + +pub const PUBLIC_KEY_2: &str = "2CpqFh8VnwJAjenw4xSUWCaaJ2QwGdhnCikoSEczMhjgqyj7"; diff --git a/rust/tw_tests/tests/chains/polymesh/polymesh_address.rs b/rust/tw_tests/tests/chains/polymesh/polymesh_address.rs index 15957c6c549..4712270ef18 100644 --- a/rust/tw_tests/tests/chains/polymesh/polymesh_address.rs +++ b/rust/tw_tests/tests/chains/polymesh/polymesh_address.rs @@ -2,32 +2,55 @@ // // Copyright © 2017 Trust Wallet. +use crate::chains::polymesh::{PRIVATE_KEY_1, PUBLIC_KEY_1, PUBLIC_KEY_2, PUBLIC_KEY_HEX_1}; use tw_any_coin::test_utils::address_utils::{ - test_address_ss58_is_invalid, test_address_ss58_is_valid, + test_address_derive, test_address_get_data, test_address_invalid, test_address_normalization, + test_address_ss58_is_invalid, test_address_ss58_is_valid, test_address_valid, }; use tw_coin_registry::coin_type::CoinType; +#[test] +fn test_polymesh_address_derive() { + test_address_derive(CoinType::Polymesh, PRIVATE_KEY_1, PUBLIC_KEY_1); +} + +#[test] +fn test_polymesh_address_normalization() { + test_address_normalization(CoinType::Polymesh, PUBLIC_KEY_1, PUBLIC_KEY_1); +} + #[test] fn test_polymesh_address_is_valid() { // Polymesh test_address_ss58_is_valid( - CoinType::Polkadot, + CoinType::Polymesh, "2DxwekgWwK7sqVeuXGmaXLZUvwnewLTs2rvU2CFKLgvvYwCG", 12, // Polymesh ss58 ); + test_address_valid(CoinType::Polymesh, PUBLIC_KEY_1); + test_address_valid(CoinType::Polymesh, PUBLIC_KEY_2); } #[test] fn test_polymesh_address_invalid() { // Substrate ed25519 test_address_ss58_is_invalid( - CoinType::Polkadot, + CoinType::Polymesh, "5FqqU2rytGPhcwQosKRtW1E3ha6BJKAjHgtcodh71dSyXhoZ", 12, // Polymesh ss58 ); test_address_ss58_is_invalid( - CoinType::Polkadot, + CoinType::Polymesh, "JCViCkwMdGWKpf7Wogb8EFtDmaYTEZGEg6ah4svUPGnnpc7A", 12, // Polymesh ss58 ); + test_address_invalid( + CoinType::Polymesh, + "5HUUBD9nsjaKKUVB3XV87CcjcEDu7sDH2G32NAj6uNqgWp9G", + ); +} + +#[test] +fn test_polymesh_address_get_data() { + test_address_get_data(CoinType::Polymesh, PUBLIC_KEY_1, PUBLIC_KEY_HEX_1); } diff --git a/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs b/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs new file mode 100644 index 00000000000..70da2314f55 --- /dev/null +++ b/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::chains::polkadot::balance_call; +use crate::chains::polymesh::{GENESIS_HASH, PUBLIC_KEY_2, PUBLIC_KEY_HEX_1}; +use std::borrow::Cow; +use tw_any_coin::test_utils::sign_utils::{CompilerHelper, PreImageHelper}; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_keypair::ed25519::{sha512::PublicKey, Signature}; +use tw_keypair::traits::VerifyingKeyTrait; +use tw_number::U256; +use tw_proto::Common::Proto::SigningError; +use tw_proto::Polkadot::Proto; +use tw_proto::Polkadot::Proto::mod_Balance::Transfer; +use tw_proto::TxCompiler::Proto as CompilerProto; + +#[test] +fn test_polymesh_compile_transfer() { + // https://polymesh.subscan.io/extrinsic/0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04 + + // Step 1: Prepare input. + let block_hash = "77d32517dcc7b74501096afdcff3af72008a2c489e17083f56629d195e5c6a1d" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + let value = 1_000_000u64; // 1.0 POLYX + + let input = Proto::SigningInput { + network: 12, + nonce: 1, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 7_000_005, + transaction_version: 7, + era: Some(Proto::Era { + block_number: 16_102_106, + period: 64, + }), + message_oneof: balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: PUBLIC_KEY_2.into(), + value: Cow::Owned(U256::from(value).to_big_endian().to_vec()), + ..Default::default() + })), + ..Default::default() + }; + + // Step 2: Obtain preimage hash + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(CoinType::Polymesh, &input); + + assert_eq!(preimage_output.error, SigningError::OK); + + assert_eq!( + preimage_output.data.to_hex(), + "05000010b713ceeb165c1ac7c450f5b138a6da0eba50bb18849f5b8e83985daa45a87e02093d00a5010400c5cf6a00070000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f406377d32517dcc7b74501096afdcff3af72008a2c489e17083f56629d195e5c6a1d" + ); + + // Step 3: Compile transaction info + + // Simulate signature, normally obtained from signature server + let signature_bytes = "e9b4742a2b66931e0cf29f6811e4d44545b4f278a667b9eb1217c4b2de8763c8037e4501dd4a21179b737beb33415f458788f2d1093b527cae8bee8b2d55210b".decode_hex().unwrap(); + let signature = Signature::try_from(signature_bytes.as_slice()).unwrap(); + let public_key = PUBLIC_KEY_HEX_1.decode_hex().unwrap(); + let public = PublicKey::try_from(public_key.as_slice()).unwrap(); + + // Verify signature (pubkey & hash & signature) + assert!(public.verify(signature, preimage_output.data.into())); + + // Compile transaction info + let mut compiler = CompilerHelper::::default(); + let output = compiler.compile( + CoinType::Polymesh, + &input, + vec![signature_bytes], + vec![public_key], + ); + assert_eq!(output.error, SigningError::OK); + + assert_eq!( + output.encoded.to_hex(), + "390284004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c00e9b4742a2b66931e0cf29f6811e4d44545b4f278a667b9eb1217c4b2de8763c8037e4501dd4a21179b737beb33415f458788f2d1093b527cae8bee8b2d55210ba501040005000010b713ceeb165c1ac7c450f5b138a6da0eba50bb18849f5b8e83985daa45a87e02093d00" + ); +} diff --git a/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs b/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs new file mode 100644 index 00000000000..fee07e43b90 --- /dev/null +++ b/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use crate::chains::polkadot::balance_call; +use crate::chains::polymesh::{GENESIS_HASH, PRIVATE_KEY_1, PUBLIC_KEY_2}; +use std::borrow::Cow; +use tw_any_coin::test_utils::sign_utils::AnySignerHelper; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_number::U256; +use tw_proto::Common::Proto::SigningError; +use tw_proto::Polkadot::Proto; +use tw_proto::Polkadot::Proto::mod_Balance::Transfer; + +#[test] +fn test_polymesh_sign_transfer() { + // https://polymesh.subscan.io/extrinsic/0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04 + + // Step 1: Prepare input. + let private_key = PRIVATE_KEY_1.decode_hex().unwrap(); + let block_hash = "77d32517dcc7b74501096afdcff3af72008a2c489e17083f56629d195e5c6a1d" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + let value = 1_000_000u64; // 1.0 POLYX + + let input = Proto::SigningInput { + network: 12, + private_key: private_key.into(), + nonce: 1, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 7_000_005, + transaction_version: 7, + era: Some(Proto::Era { + block_number: 16_102_106, + period: 64, + }), + message_oneof: balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: PUBLIC_KEY_2.into(), + value: Cow::Owned(U256::from(value).to_big_endian().to_vec()), + ..Default::default() + })), + ..Default::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Polymesh, input); + + assert_eq!(output.error, SigningError::OK); + assert_eq!( + output.encoded.to_hex(), + "390284004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c00e9b4742a2b66931e0cf29f6811e4d44545b4f278a667b9eb1217c4b2de8763c8037e4501dd4a21179b737beb33415f458788f2d1093b527cae8bee8b2d55210ba501040005000010b713ceeb165c1ac7c450f5b138a6da0eba50bb18849f5b8e83985daa45a87e02093d00" + ); +} diff --git a/rust/tw_tests/tests/coin_address_derivation_test.rs b/rust/tw_tests/tests/coin_address_derivation_test.rs index 1b8b9f19aac..933adbc67e0 100644 --- a/rust/tw_tests/tests/coin_address_derivation_test.rs +++ b/rust/tw_tests/tests/coin_address_derivation_test.rs @@ -157,6 +157,7 @@ fn test_coin_address_derivation() { CoinType::Polkadot => "12dyy3fArMPDXLsnRtapTqZsC2KCEimeqs1dop4AEERaKC6x", CoinType::Acala => "22WaYy5ChG8V5vvRVDP4ErE7esk8nZ4rjGYwxeVArnNT8dU3", CoinType::Kusama => "EDJV2jycw8fqTgiExLsDe6iUzbnM62hDk7u3BLm9wcYswkY", + CoinType::Polymesh => "2E5u4xA1TqswQ3jMJH96zekxwr2itvKu79fDC1mmnVZRh6Uv", // end_of_coin_address_derivation_tests_marker_do_not_modify _ => panic!("{:?} must be covered", coin), }; diff --git a/src/Coin.cpp b/src/Coin.cpp index 447c5c2f5a0..80e87baf92a 100644 --- a/src/Coin.cpp +++ b/src/Coin.cpp @@ -68,6 +68,7 @@ #include "NativeInjective/Entry.h" #include "BitcoinCash/Entry.h" #include "Pactus/Entry.h" +#include "Polymesh/Entry.h" // end_of_coin_includes_marker_do_not_modify using namespace TW; @@ -129,6 +130,7 @@ NativeEvmos::Entry NativeEvmosDP; NativeInjective::Entry NativeInjectiveDP; BitcoinCash::Entry BitcoinCashDP; Pactus::Entry PactusDP; +Polymesh::Entry PolymeshDP; // end_of_coin_dipatcher_declarations_marker_do_not_modify CoinEntry* coinDispatcher(TWCoinType coinType) { @@ -192,6 +194,7 @@ CoinEntry* coinDispatcher(TWCoinType coinType) { case TWBlockchainNativeInjective: entry = &NativeInjectiveDP; break; case TWBlockchainBitcoinCash: entry = &BitcoinCashDP; break; case TWBlockchainPactus: entry = &PactusDP; break; + case TWBlockchainPolymesh: entry = &PolymeshDP; break; // end_of_coin_dipatcher_switch_marker_do_not_modify default: entry = nullptr; break; diff --git a/src/Polymesh/Entry.h b/src/Polymesh/Entry.h new file mode 100644 index 00000000000..e32f339be46 --- /dev/null +++ b/src/Polymesh/Entry.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#pragma once + +#include "rust/RustCoinEntry.h" + +namespace TW::Polymesh { + +/// Entry point for Polymesh 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::Polymesh + diff --git a/tests/chains/Polkadot/TWAnyAddressTests.cpp b/tests/chains/Polkadot/TWAnyAddressTests.cpp index 5b57279f2e3..2a18ba00c41 100644 --- a/tests/chains/Polkadot/TWAnyAddressTests.cpp +++ b/tests/chains/Polkadot/TWAnyAddressTests.cpp @@ -18,7 +18,6 @@ namespace TW::Polkadot::tests { extern uint32_t polkadotPrefix; extern uint32_t kusamaPrefix; extern uint32_t astarPrefix; -extern uint32_t polymeshPrefix; extern uint32_t parallelPrefix; TEST(PolkadotAddress, Validation) { @@ -40,10 +39,6 @@ TEST(PolkadotAddress, Validation) { ASSERT_TRUE(TWAnyAddressIsValidSS58(STRING("cEYtw6AVMB27hFUs4gVukajLM7GqxwxUfJkbPY3rNToHMcCgb").get(), TWCoinTypePolkadot, 64)); ASSERT_FALSE(TWAnyAddressIsValidSS58(STRING("JCViCkwMdGWKpf7Wogb8EFtDmaYTEZGEg6ah4svUPGnnpc7A").get(), TWCoinTypePolkadot, 64)); - - // Polymesh - ASSERT_TRUE(TWAnyAddressIsValidSS58(STRING("2DxwekgWwK7sqVeuXGmaXLZUvwnewLTs2rvU2CFKLgvvYwCG").get(), TWCoinTypePolkadot, polymeshPrefix)); - ASSERT_FALSE(TWAnyAddressIsValidSS58(STRING("JCViCkwMdGWKpf7Wogb8EFtDmaYTEZGEg6ah4svUPGnnpc7A").get(), TWCoinTypePolkadot, polymeshPrefix)); } TEST(PolkadotAddress, FromPrivateKey) { @@ -85,15 +80,6 @@ TEST(PolkadotAddress, FromPublicKeyWithPrefix) { const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); EXPECT_TRUE(TWStringEqual(addressStr.get(), addressParallel.get())); } - - // polymesh - publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA("849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f83").get(), TWPublicKeyTypeED25519)); - const auto addressPolymesh = STRING("2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW"); - { - const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateSS58WithPublicKey(publicKey.get(), TWCoinTypePolkadot, polymeshPrefix)); - const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); - EXPECT_TRUE(TWStringEqual(addressStr.get(), addressPolymesh.get())); - } } TEST(PolkadotAddress, FromString) { @@ -117,14 +103,6 @@ TEST(PolkadotAddress, FromStringWithPrefix) { const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); EXPECT_TRUE(TWStringEqual(addressStr.get(), addressParallel.get())); } - - // polymesh - auto addressPolymesh = STRING("2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW"); - { - const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateSS58(addressPolymesh.get(), TWCoinTypePolkadot, polymeshPrefix)); - const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); - EXPECT_TRUE(TWStringEqual(addressStr.get(), addressPolymesh.get())); - } } } // namespace TW::Polkadot::tests diff --git a/tests/chains/Polkadot/TWAnySignerTests.cpp b/tests/chains/Polkadot/TWAnySignerTests.cpp index 8cff6ef551b..7c6328d4d98 100644 --- a/tests/chains/Polkadot/TWAnySignerTests.cpp +++ b/tests/chains/Polkadot/TWAnySignerTests.cpp @@ -24,7 +24,6 @@ namespace TW::Polkadot::tests { uint32_t polkadotPrefix = ss58Prefix(TWCoinTypePolkadot); uint32_t kusamaPrefix = ss58Prefix(TWCoinTypeKusama); uint32_t astarPrefix = 5; -uint32_t polymeshPrefix = 12; uint32_t parallelPrefix = 172; auto privateKey = PrivateKey(parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115")); diff --git a/tests/chains/Polymesh/TWAnyAddressTests.cpp b/tests/chains/Polymesh/TWAnyAddressTests.cpp new file mode 100644 index 00000000000..1602ff3e1a0 --- /dev/null +++ b/tests/chains/Polymesh/TWAnyAddressTests.cpp @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include +#include +#include + +#include "TestUtilities.h" +#include +#include + +using namespace TW; + +namespace TW::Polymesh::tests { +extern uint32_t polymeshPrefix; + +TEST(TWPolymesh, Address) { + auto string = STRING("2E5u4xA1TqswQ3jMJH96zekxwr2itvKu79fDC1mmnVZRh6Uv"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypePolymesh)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867"); +} + +TEST(PolymeshAddress, Validation) { + // Substrate ed25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("5FqqU2rytGPhcwQosKRtW1E3ha6BJKAjHgtcodh71dSyXhoZ").get(), TWCoinTypePolymesh)); + // Bitcoin + ASSERT_FALSE(TWAnyAddressIsValid(STRING("1ES14c7qLb5CYhLMUekctxLgc1FV2Ti9DA").get(), TWCoinTypePolymesh)); + // Kusama ed25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("FHKAe66mnbk8ke8zVWE9hFVFrJN1mprFPVmD5rrevotkcDZ").get(), TWCoinTypePolymesh)); + // Kusama secp256k1 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("FxQFyTorsjVsjjMyjdgq8w5vGx8LiA1qhWbRYcFijxKKchx").get(), TWCoinTypePolymesh)); + // Kusama sr25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("EJ5UJ12GShfh7EWrcNZFLiYU79oogdtXFUuDDZzk7Wb2vCe").get(), TWCoinTypePolymesh)); + + // Polkadot ed25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("15KRsCq9LLNmCxNFhGk55s5bEyazKefunDxUH24GFZwsTxyu").get(), TWCoinTypePolymesh)); + // Polkadot sr25519 + ASSERT_FALSE(TWAnyAddressIsValid(STRING("15AeCjMpcSt3Fwa47jJBd7JzQ395Kr2cuyF5Zp4UBf1g9ony").get(), TWCoinTypePolymesh)); + + // Polymesh + ASSERT_TRUE(TWAnyAddressIsValid(STRING("2DxwekgWwK7sqVeuXGmaXLZUvwnewLTs2rvU2CFKLgvvYwCG").get(), TWCoinTypePolymesh)); + ASSERT_FALSE(TWAnyAddressIsValid(STRING("JCViCkwMdGWKpf7Wogb8EFtDmaYTEZGEg6ah4svUPGnnpc7A").get(), TWCoinTypePolymesh)); + ASSERT_TRUE(TWAnyAddressIsValidSS58(STRING("2DxwekgWwK7sqVeuXGmaXLZUvwnewLTs2rvU2CFKLgvvYwCG").get(), TWCoinTypePolymesh, polymeshPrefix)); + ASSERT_FALSE(TWAnyAddressIsValidSS58(STRING("JCViCkwMdGWKpf7Wogb8EFtDmaYTEZGEg6ah4svUPGnnpc7A").get(), TWCoinTypePolymesh, polymeshPrefix)); +} + +TEST(PolymeshAddress, FromPrivateKey) { + // subkey phrase `chief menu kingdom stereo hope hazard into island bag trick egg route` + const auto privateKey = WRAP(TWPrivateKey, TWPrivateKeyCreateWithData(DATA("0x612d82bc053d1b4729057688ecb1ebf62745d817ddd9b595bc822f5f2ba0e41a").get())); + const auto publicKey = WRAP(TWPublicKey, TWPrivateKeyGetPublicKey(privateKey.get(), TWCoinTypePolymesh)); + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypePolymesh)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), STRING("2GmLy7KywpsV5fDpZfJMcgGgzoJWyrEA3Wc3fDmsoq5iqtBT").get())); +} + +TEST(PolymeshAddress, FromPublicKey) { + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA("0xbeff0e5d6f6e6e6d573d3044f3e2bfb353400375dc281da3337468d4aa527908").get(), TWPublicKeyTypeED25519)); + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypePolymesh)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), STRING("2GmLy7KywpsV5fDpZfJMcgGgzoJWyrEA3Wc3fDmsoq5iqtBT").get())); +} + +TEST(PolymeshAddress, FromPublicKeyWithPrefix) { + auto publicKey = WRAP(TWPublicKey, TWPublicKeyCreateWithData(DATA("849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f83").get(), TWPublicKeyTypeED25519)); + const auto addressPolymesh = STRING("2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW"); + { + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateSS58WithPublicKey(publicKey.get(), TWCoinTypePolymesh, polymeshPrefix)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), addressPolymesh.get())); + } +} + +TEST(PolymeshAddress, FromString) { + auto string = STRING("2E5u4xA1TqswQ3jMJH96zekxwr2itvKu79fDC1mmnVZRh6Uv"); + auto addr = WRAP(TWAnyAddress, TWAnyAddressCreateWithString(string.get(), TWCoinTypePolymesh)); + auto string2 = WRAPS(TWAnyAddressDescription(addr.get())); + EXPECT_TRUE(TWStringEqual(string.get(), string2.get())); + auto keyHash = WRAPD(TWAnyAddressData(addr.get())); + assertHexEqual(keyHash, "4870d56d074c50e891506d78faa4fb69ca039cc5f131eb491e166b975880e867"); +} + +TEST(PolymeshAddress, FromStringWithPrefix) { + // polymesh + auto addressPolymesh = STRING("2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW"); + { + const auto address = WRAP(TWAnyAddress, TWAnyAddressCreateSS58(addressPolymesh.get(), TWCoinTypePolymesh, polymeshPrefix)); + const auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); + EXPECT_TRUE(TWStringEqual(addressStr.get(), addressPolymesh.get())); + } +} + +} // namespace TW::Polymesh::tests \ No newline at end of file diff --git a/tests/chains/Polymesh/TWAnySignerTests.cpp b/tests/chains/Polymesh/TWAnySignerTests.cpp new file mode 100644 index 00000000000..a1e176ef572 --- /dev/null +++ b/tests/chains/Polymesh/TWAnySignerTests.cpp @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "AnyAddress.h" +#include "Coin.h" +#include "HexCoding.h" +#include "PrivateKey.h" +#include "PublicKey.h" +#include "proto/Polkadot.pb.h" +#include "proto/TransactionCompiler.pb.h" +#include "uint256.h" + +#include +#include +#include +#include +#include + +#include "TestUtilities.h" +#include +#include + +using namespace TW; +using namespace TW::Polkadot; + +namespace TW::Polymesh::tests { +auto polymeshPrefix = 12; + +Data helper_encodeTransaction(TWCoinType coin, const Proto::SigningInput& input, const Data& pubKey, const Data& signature) { + auto txInputData = data(input.SerializeAsString()); + auto txInputDataPtr = WRAPD(TWDataCreateWithBytes(txInputData.data(), txInputData.size())); + + const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( + coin, txInputDataPtr.get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&pubKey)).get())); + + Polkadot::Proto::SigningOutput output; + output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get())); + EXPECT_EQ(output.error(), Common::Proto::OK); + + return data(output.encoded()); +} + +TEST(TWAnySignerPolymesh, PolymeshEncodeAndSign) { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x9a4283cc38f7e769c53ad2d1c5cf292fc85a740ec1c1aa80c180847e51928650 + + /// Step 1: Prepare transaction input (protobuf) + const auto coin = TWCoinTypePolymesh; + + Polkadot::Proto::SigningInput input; + input.set_network(12); + input.set_multi_address(true); + auto blockHash = parse_hex("898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102"); + auto vGenesisHash = parse_hex("6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(vGenesisHash.begin(), vGenesisHash.end())); + input.set_nonce(1UL); + input.set_spec_version(3010u); + input.set_transaction_version(2u); + + auto* era = input.mutable_era(); + era->set_block_number(4298130UL); + era->set_period(64UL); + + auto* transfer = input.mutable_balance_call()->mutable_transfer(); + transfer->set_to_address("2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW"); + auto value = store(1000000); + transfer->set_value(std::string(value.begin(), value.end())); + transfer->set_memo("MEMO PADDED WITH SPACES"); + + auto* callIndices = transfer->mutable_call_indices()->mutable_custom(); + callIndices->set_module_index(0x05); + callIndices->set_method_index(0x01); + + /// Step 2: Obtain preimage hash + auto txInputData = data(input.SerializeAsString()); + auto txInputDataPtr = WRAPD(TWDataCreateWithBytes(txInputData.data(), txInputData.size())); + const auto preImageHashes = WRAPD(TWTransactionCompilerPreImageHashes(coin, txInputDataPtr.get())); + auto preImageHash = data(TWDataBytes(preImageHashes.get()), TWDataSize(preImageHashes.get())); + + TxCompiler::Proto::PreSigningOutput preSigningOutput; + ASSERT_TRUE(preSigningOutput.ParseFromArray(preImageHash.data(), int(preImageHash.size()))); + ASSERT_EQ(preSigningOutput.error(), Common::Proto::OK); + + const auto preImage = data(preSigningOutput.data()); + + ASSERT_EQ(hex(preImage), "050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f2050414444454420574954482053504143455300000000000000000025010400c20b0000020000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102"); + + auto pubKey = parse_hex("4322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee"); + auto signature = parse_hex("0791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f"); + + /// Step 3: Compile transaction info + const auto outputData = WRAPD(TWTransactionCompilerCompileWithSignatures( + coin, txInputDataPtr.get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), + WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&pubKey)).get())); + + const auto ExpectedTx = + "bd0284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee000791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f25010400050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f20504144444544205749544820535041434553000000000000000000"; + { + Polkadot::Proto::SigningOutput output; + ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), + (int)TWDataSize(outputData.get()))); + + EXPECT_EQ(hex(output.encoded()), ExpectedTx); + } +} + +TEST(TWAnySignerPolymesh, encodeTransaction_Add_authorization) { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x7d9b9109027b36b72d37ba0648cb70e5254524d3d6752cc6b41601f4bdfb1af0 + + Polkadot::Proto::SigningInput input; + input.set_network(12); + input.set_multi_address(true); + auto blockHash = parse_hex("ce0c2109db498e45abf8fd447580dcfa7b7a07ffc2bfb1a0fbdd1af3e8816d2b"); + auto vGenesisHash = parse_hex("6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(vGenesisHash.begin(), vGenesisHash.end())); + input.set_nonce(5UL); + input.set_spec_version(3010U); + input.set_transaction_version(2U); + + auto* era = input.mutable_era(); + era->set_block_number(4395451UL); + era->set_period(64UL); + + auto* addAuthorization = input.mutable_polymesh_call()->mutable_identity_call()->mutable_add_authorization(); + addAuthorization->set_target("2HEVN4PHYKj7B1krQ9bctAQXZxHQQkANVNCcfbdYk2gZ4cBR"); + auto* authData = addAuthorization->mutable_data(); + // Set empty "These". + auto* assets = authData->mutable_asset(); + auto empty = parse_hex("00"); + assets->set_data(std::string(empty.begin(), empty.end())); + auto* extrinsics = authData->mutable_extrinsic(); + extrinsics->set_data(std::string(empty.begin(), empty.end())); + auto* portfolios = authData->mutable_portfolio(); + portfolios->set_data(std::string(empty.begin(), empty.end())); + + auto* callIndices = addAuthorization->mutable_call_indices()->mutable_custom(); + callIndices->set_module_index(0x07); + callIndices->set_method_index(0x0d); + + auto pubKey = parse_hex("4322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee"); + auto signature = parse_hex("81e6561e4391862b5da961d7033baced1c4b25f0e27f938b02321af1118e0b859e1c2bd5607576a258f2c2befbc5f397ea4adb62938f30eb73c8060ab0eabf01"); + auto encoded = helper_encodeTransaction(TWCoinTypePolymesh, input, pubKey, signature); + ASSERT_EQ(hex(encoded), "490284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee0081e6561e4391862b5da961d7033baced1c4b25f0e27f938b02321af1118e0b859e1c2bd5607576a258f2c2befbc5f397ea4adb62938f30eb73c8060ab0eabf01b5031400070d01d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d0610540501000100010000"); +} + +TEST(TWAnySignerPolymesh, encodeTransaction_JoinIdentityAsKey) { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x9d7297d8b38af5668861996cb115f321ed681989e87024fda64eae748c2dc542 + + Polkadot::Proto::SigningInput input; + input.set_network(12); + input.set_multi_address(true); + auto blockHash = parse_hex("45c80153c47f5d16acc7a66d473870e8d4574437a7d8c813f47da74cae3812c2"); + auto vGenesisHash = parse_hex("6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063"); + input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); + input.set_genesis_hash(std::string(vGenesisHash.begin(), vGenesisHash.end())); + input.set_nonce(0UL); + input.set_spec_version(3010U); + input.set_transaction_version(2U); + + auto* era = input.mutable_era(); + era->set_block_number(4395527UL); + era->set_period(64UL); + + auto* key = input.mutable_polymesh_call()->mutable_identity_call()->mutable_join_identity_as_key(); + key->set_auth_id(21435); + auto* callIndices = key->mutable_call_indices()->mutable_custom(); + callIndices->set_module_index(0x07); + callIndices->set_method_index(0x05); + + auto pubKey = parse_hex("d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d061054"); + auto signature = parse_hex("7f5adbb2749e2f0ace29b409c41dd717681495b1f22dc5358311646a9fb8af8a173fc47f1b19748fb56831c2128773e2976986685adee83c741ab49934d80006"); + auto encoded = helper_encodeTransaction(TWCoinTypePolymesh, input, pubKey, signature); + ASSERT_EQ(hex(encoded), "c5018400d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d061054007f5adbb2749e2f0ace29b409c41dd717681495b1f22dc5358311646a9fb8af8a173fc47f1b19748fb56831c2128773e2976986685adee83c741ab49934d80006750000000705bb53000000000000"); +} + +} // namespace TW::Polymesh::tests \ No newline at end of file diff --git a/tests/chains/Polymesh/TWCoinTypeTests.cpp b/tests/chains/Polymesh/TWCoinTypeTests.cpp new file mode 100644 index 00000000000..4271fff9bbe --- /dev/null +++ b/tests/chains/Polymesh/TWCoinTypeTests.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +#include "TestUtilities.h" +#include +#include + +TEST(TWPolymeshCoinType, TWCoinType) { + const auto coin = TWCoinTypePolymesh; + const auto symbol = WRAPS(TWCoinTypeConfigurationGetSymbol(coin)); + const auto id = WRAPS(TWCoinTypeConfigurationGetID(coin)); + const auto name = WRAPS(TWCoinTypeConfigurationGetName(coin)); + const auto txId = WRAPS(TWStringCreateWithUTF8Bytes("0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04")); + const auto txUrl = WRAPS(TWCoinTypeConfigurationGetTransactionURL(coin, txId.get())); + const auto accId = WRAPS(TWStringCreateWithUTF8Bytes("2E5u4xA1TqswQ3jMJH96zekxwr2itvKu79fDC1mmnVZRh6Uv")); + const auto accUrl = WRAPS(TWCoinTypeConfigurationGetAccountURL(coin, accId.get())); + + assertStringsEqual(id, "polymesh"); + assertStringsEqual(name, "Polymesh"); + assertStringsEqual(symbol, "POLYX"); + ASSERT_EQ(TWCoinTypeConfigurationGetDecimals(coin), 6); + ASSERT_EQ(TWCoinTypeBlockchain(coin), TWBlockchainPolymesh); + ASSERT_EQ(TWCoinTypeP2pkhPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeP2shPrefix(coin), 0); + ASSERT_EQ(TWCoinTypeStaticPrefix(coin), 0); + assertStringsEqual(txUrl, "https://polymesh.subscan.io/extrinsic/0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04"); + assertStringsEqual(accUrl, "https://polymesh.subscan.io/account/2E5u4xA1TqswQ3jMJH96zekxwr2itvKu79fDC1mmnVZRh6Uv"); +} diff --git a/tests/common/CoinAddressDerivationTests.cpp b/tests/common/CoinAddressDerivationTests.cpp index 6df5336c5c0..489265a1eec 100644 --- a/tests/common/CoinAddressDerivationTests.cpp +++ b/tests/common/CoinAddressDerivationTests.cpp @@ -399,6 +399,9 @@ TEST(Coin, DeriveAddress) { case TWCoinTypePactus: EXPECT_EQ(address, "pc1rehvlc6tfn79z0zjqqaj8zas5j5h9c2fe59a4ff"); break; + case TWCoinTypePolymesh: + EXPECT_EQ(address, "2HqjMm2goapWvXQBqjjEdVaTZsUmunWwEq1TSToDR1pDzQ1F"); + break; // end_of_coin_address_derivation_tests_marker_do_not_modify // no default branch here, intentionally, to better notice any missing coins } From 8cbe0278d9309d8af3a5be4d764d5b624f150dbf Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Fri, 29 Nov 2024 23:47:18 +0800 Subject: [PATCH 04/22] Some cleanup of the Polymesh call encoder. --- .../tw_polymesh/src/call_encoder/mod.rs | 46 +++++++++++-------- .../tw_polymesh/src/call_encoder/polymesh.rs | 44 +----------------- 2 files changed, 29 insertions(+), 61 deletions(-) diff --git a/rust/chains/tw_polymesh/src/call_encoder/mod.rs b/rust/chains/tw_polymesh/src/call_encoder/mod.rs index fd56d9d2d1e..094348d08dd 100644 --- a/rust/chains/tw_polymesh/src/call_encoder/mod.rs +++ b/rust/chains/tw_polymesh/src/call_encoder/mod.rs @@ -3,6 +3,7 @@ use tw_proto::Polkadot::Proto::{ self, mod_Balance::{BatchAssetTransfer, BatchTransfer, OneOfmessage_oneof as BalanceVariant}, mod_CallIndices::OneOfvariant as CallIndicesVariant, + mod_PolymeshCall::OneOfmessage_oneof as PolymeshVariant, mod_SigningInput::OneOfmessage_oneof as SigningVariant, mod_Staking::{ Bond, BondAndNominate, Chill, ChillAndUnbond, Nominate, @@ -26,24 +27,11 @@ pub fn validate_call_index(call_index: &Option) -> EncodeResult) -> EncodeResult { - let index = match call_index { - Some(CallIndices { - variant: CallIndicesVariant::custom(c), - }) => Some((c.module_index, c.method_index)), - _ => None, - }; - CallIndex::required_from_tw(index) -} - -pub struct CallEncoder { - encoder: PolymeshCallEncoder, -} +pub struct CallEncoder; impl CallEncoder { - pub fn from_ctx(ctx: &SubstrateContext) -> EncodeResult { - let encoder = PolymeshCallEncoder::new(ctx); - Ok(Self { encoder }) + pub fn from_ctx(_ctx: &SubstrateContext) -> EncodeResult { + Ok(Self) } pub fn encode_input(input: &'_ Proto::SigningInput<'_>) -> EncodeResult { @@ -175,7 +163,28 @@ impl CallEncoder { _ => (), } // non-batch calls. - self.encoder.encode_call(msg) + let call = match msg { + SigningVariant::balance_call(b) => { + PolymeshBalances::encode_call(b)?.map(PolymeshCall::Balances) + }, + SigningVariant::polymesh_call(msg) => match &msg.message_oneof { + PolymeshVariant::identity_call(msg) => { + PolymeshIdentity::encode_call(msg)?.map(PolymeshCall::Identity) + }, + PolymeshVariant::None => { + return EncodeError::NotSupported + .tw_result("Polymesh call variant is None".to_string()); + }, + }, + SigningVariant::staking_call(s) => { + PolymeshStaking::encode_call(s)?.map(PolymeshCall::Staking) + }, + SigningVariant::None => { + return EncodeError::NotSupported + .tw_result("Staking call variant is None".to_string()); + }, + }; + Ok(RawOwned(call.to_scale())) } fn encode_batch( @@ -184,7 +193,8 @@ impl CallEncoder { ci: &Option, ) -> EncodeResult { let ci = validate_call_index(ci)?; - let call = ci.wrap(self.encoder.encode_batch(calls)?); + let call = PolymeshCall::Utility(PolymeshUtility::BatchAll { calls }); + let call = ci.wrap(RawOwned(call.to_scale())); Ok(RawOwned(call.to_scale())) } } diff --git a/rust/chains/tw_polymesh/src/call_encoder/polymesh.rs b/rust/chains/tw_polymesh/src/call_encoder/polymesh.rs index 79dbf4bef0d..308e788ecbc 100644 --- a/rust/chains/tw_polymesh/src/call_encoder/polymesh.rs +++ b/rust/chains/tw_polymesh/src/call_encoder/polymesh.rs @@ -6,14 +6,13 @@ use tw_number::U256; use tw_proto::Polkadot::Proto::{ mod_Balance::{OneOfmessage_oneof as BalanceVariant, Transfer}, mod_Identity::{AddAuthorization, JoinIdentityAsKey, OneOfmessage_oneof as IdentityVariant}, - mod_PolymeshCall::OneOfmessage_oneof as PolymeshVariant, mod_Staking::{ Bond, BondExtra, Chill, Nominate, OneOfmessage_oneof as StakingVariant, Rebond, Unbond, WithdrawUnbonded, }, Balance, Identity, Staking, }; -use tw_scale::{impl_enum_scale, impl_struct_scale, Compact, RawOwned, ToScale}; +use tw_scale::{impl_enum_scale, impl_struct_scale, Compact, RawOwned}; use tw_ss58_address::SS58Address; use tw_substrate::address::SubstrateAddress; @@ -352,44 +351,3 @@ impl_enum_scale!( } ); -pub struct PolymeshCallEncoder; - -impl PolymeshCallEncoder { - pub fn new(_ctx: &SubstrateContext) -> Self { - Self - } -} - -impl PolymeshCallEncoder { - pub fn encode_call(&self, msg: &SigningVariant<'_>) -> EncodeResult { - let call = match msg { - SigningVariant::balance_call(b) => { - PolymeshBalances::encode_call(b)?.map(PolymeshCall::Balances) - }, - SigningVariant::polymesh_call(msg) => match &msg.message_oneof { - PolymeshVariant::identity_call(msg) => { - PolymeshIdentity::encode_call(msg)?.map(PolymeshCall::Identity) - }, - PolymeshVariant::None => { - return Err(EncodeError::NotSupported) - .into_tw() - .context("Polymesh call variant is None"); - }, - }, - SigningVariant::staking_call(s) => { - PolymeshStaking::encode_call(s)?.map(PolymeshCall::Staking) - }, - SigningVariant::None => { - return Err(EncodeError::NotSupported) - .into_tw() - .context("Staking call variant is None"); - }, - }; - Ok(RawOwned(call.to_scale())) - } - - pub fn encode_batch(&self, calls: Vec) -> EncodeResult { - let call = PolymeshCall::Utility(PolymeshUtility::BatchAll { calls }); - Ok(RawOwned(call.to_scale())) - } -} From 6c8e3a4b9012d11d3851c973f648398c270187bf Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Thu, 28 Nov 2024 23:28:11 +0800 Subject: [PATCH 05/22] Use Polymesh protobuf instead of Polkadot. --- .../tw_polymesh/src/call_encoder/mod.rs | 13 +- .../tw_polymesh/src/call_encoder/polymesh.rs | 2 +- rust/chains/tw_polymesh/src/entry.rs | 2 +- rust/chains/tw_polymesh/src/lib.rs | 2 +- rust/chains/tw_polymesh/tests/extrinsic.rs | 18 +- rust/tw_tests/tests/chains/polymesh/mod.rs | 18 + .../tests/chains/polymesh/polymesh_compile.rs | 199 ++++++++++- .../tests/chains/polymesh/polymesh_sign.rs | 53 ++- src/proto/Polkadot.proto | 57 +--- src/proto/Polymesh.proto | 310 ++++++++++++++++++ tests/chains/Polymesh/TWAnySignerTests.cpp | 18 +- 11 files changed, 595 insertions(+), 97 deletions(-) create mode 100644 src/proto/Polymesh.proto diff --git a/rust/chains/tw_polymesh/src/call_encoder/mod.rs b/rust/chains/tw_polymesh/src/call_encoder/mod.rs index 094348d08dd..e0337ab3599 100644 --- a/rust/chains/tw_polymesh/src/call_encoder/mod.rs +++ b/rust/chains/tw_polymesh/src/call_encoder/mod.rs @@ -1,9 +1,8 @@ use crate::ctx_from_tw; -use tw_proto::Polkadot::Proto::{ +use tw_proto::Polymesh::Proto::{ self, mod_Balance::{BatchAssetTransfer, BatchTransfer, OneOfmessage_oneof as BalanceVariant}, mod_CallIndices::OneOfvariant as CallIndicesVariant, - mod_PolymeshCall::OneOfmessage_oneof as PolymeshVariant, mod_SigningInput::OneOfmessage_oneof as SigningVariant, mod_Staking::{ Bond, BondAndNominate, Chill, ChillAndUnbond, Nominate, @@ -167,14 +166,8 @@ impl CallEncoder { SigningVariant::balance_call(b) => { PolymeshBalances::encode_call(b)?.map(PolymeshCall::Balances) }, - SigningVariant::polymesh_call(msg) => match &msg.message_oneof { - PolymeshVariant::identity_call(msg) => { - PolymeshIdentity::encode_call(msg)?.map(PolymeshCall::Identity) - }, - PolymeshVariant::None => { - return EncodeError::NotSupported - .tw_result("Polymesh call variant is None".to_string()); - }, + SigningVariant::identity_call(msg) => { + PolymeshIdentity::encode_call(msg)?.map(PolymeshCall::Identity) }, SigningVariant::staking_call(s) => { PolymeshStaking::encode_call(s)?.map(PolymeshCall::Staking) diff --git a/rust/chains/tw_polymesh/src/call_encoder/polymesh.rs b/rust/chains/tw_polymesh/src/call_encoder/polymesh.rs index 308e788ecbc..214c5bd908d 100644 --- a/rust/chains/tw_polymesh/src/call_encoder/polymesh.rs +++ b/rust/chains/tw_polymesh/src/call_encoder/polymesh.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use tw_coin_entry::error::prelude::*; use tw_hash::H256; use tw_number::U256; -use tw_proto::Polkadot::Proto::{ +use tw_proto::Polymesh::Proto::{ mod_Balance::{OneOfmessage_oneof as BalanceVariant, Transfer}, mod_Identity::{AddAuthorization, JoinIdentityAsKey, OneOfmessage_oneof as IdentityVariant}, mod_Staking::{ diff --git a/rust/chains/tw_polymesh/src/entry.rs b/rust/chains/tw_polymesh/src/entry.rs index 79bed742a65..96d0ed08409 100644 --- a/rust/chains/tw_polymesh/src/entry.rs +++ b/rust/chains/tw_polymesh/src/entry.rs @@ -8,7 +8,7 @@ use tw_coin_entry::error::prelude::*; use tw_coin_entry::signing_output_error; use tw_keypair::ed25519::sha512::{KeyPair, PublicKey}; use tw_number::U256; -use tw_proto::Polkadot::Proto; +use tw_proto::Polymesh::Proto; use tw_proto::TxCompiler::Proto as CompilerProto; use tw_scale::{RawOwned, ToScale}; use tw_ss58_address::SS58Address; diff --git a/rust/chains/tw_polymesh/src/lib.rs b/rust/chains/tw_polymesh/src/lib.rs index 0cc3a216fe8..9f576cad779 100644 --- a/rust/chains/tw_polymesh/src/lib.rs +++ b/rust/chains/tw_polymesh/src/lib.rs @@ -2,7 +2,7 @@ // // Copyright © 2017 Trust Wallet. -use tw_proto::Polkadot::Proto::{ +use tw_proto::Polymesh::Proto::{ self, mod_Balance::OneOfmessage_oneof as BalanceVariant, mod_SigningInput::OneOfmessage_oneof as SigningVariant, }; diff --git a/rust/chains/tw_polymesh/tests/extrinsic.rs b/rust/chains/tw_polymesh/tests/extrinsic.rs index 7c8b7033664..95d75219ff9 100644 --- a/rust/chains/tw_polymesh/tests/extrinsic.rs +++ b/rust/chains/tw_polymesh/tests/extrinsic.rs @@ -3,11 +3,11 @@ use std::default::Default; use tw_encoding::hex::ToHex; use tw_number::U256; -use tw_proto::Polkadot::Proto; -use tw_proto::Polkadot::Proto::mod_Balance::Transfer; -use tw_proto::Polkadot::Proto::mod_Identity::mod_AddAuthorization::{AuthData, Data}; -use tw_proto::Polkadot::Proto::mod_Staking::{ - Bond, BondExtra, Chill, Nominate, Rebond, Unbond, WithdrawUnbonded, +use tw_proto::Polymesh::Proto::{ + self, + mod_Balance::Transfer, + mod_Identity::mod_AddAuthorization::{AuthData, Data}, + mod_Staking::{Bond, BondExtra, Chill, Nominate, Rebond, Unbond, WithdrawUnbonded}, }; use tw_substrate::EncodeResult; @@ -21,12 +21,8 @@ fn encode_input(input: &Proto::SigningInput<'_>) -> EncodeResult> { fn polymesh_identity_call( call: Proto::mod_Identity::OneOfmessage_oneof, ) -> Proto::mod_SigningInput::OneOfmessage_oneof { - Proto::mod_SigningInput::OneOfmessage_oneof::polymesh_call(Proto::PolymeshCall { - message_oneof: Proto::mod_PolymeshCall::OneOfmessage_oneof::identity_call( - Proto::Identity { - message_oneof: call, - }, - ), + Proto::mod_SigningInput::OneOfmessage_oneof::identity_call(Proto::Identity { + message_oneof: call, }) } diff --git a/rust/tw_tests/tests/chains/polymesh/mod.rs b/rust/tw_tests/tests/chains/polymesh/mod.rs index 8c1fe6c8cf6..62ffa86bd7a 100644 --- a/rust/tw_tests/tests/chains/polymesh/mod.rs +++ b/rust/tw_tests/tests/chains/polymesh/mod.rs @@ -2,6 +2,8 @@ // // Copyright © 2017 Trust Wallet. +use tw_proto::Polymesh::Proto; + mod polymesh_address; mod polymesh_compile; mod polymesh_sign; @@ -15,3 +17,19 @@ pub const PUBLIC_KEY_HEX_1: &str = "0x4bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c"; pub const PUBLIC_KEY_2: &str = "2CpqFh8VnwJAjenw4xSUWCaaJ2QwGdhnCikoSEczMhjgqyj7"; + +pub fn balance_call( + call: Proto::mod_Balance::OneOfmessage_oneof, +) -> Proto::mod_SigningInput::OneOfmessage_oneof { + Proto::mod_SigningInput::OneOfmessage_oneof::balance_call(Proto::Balance { + message_oneof: call, + }) +} + +pub fn identity_call( + call: Proto::mod_Identity::OneOfmessage_oneof, +) -> Proto::mod_SigningInput::OneOfmessage_oneof { + Proto::mod_SigningInput::OneOfmessage_oneof::identity_call(Proto::Identity { + message_oneof: call, + }) +} diff --git a/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs b/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs index 70da2314f55..81a3c298571 100644 --- a/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs +++ b/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs @@ -2,8 +2,9 @@ // // Copyright © 2017 Trust Wallet. -use crate::chains::polkadot::balance_call; -use crate::chains::polymesh::{GENESIS_HASH, PUBLIC_KEY_2, PUBLIC_KEY_HEX_1}; +use crate::chains::polymesh::{ + balance_call, identity_call, GENESIS_HASH, PUBLIC_KEY_1, PUBLIC_KEY_2, PUBLIC_KEY_HEX_1, +}; use std::borrow::Cow; use tw_any_coin::test_utils::sign_utils::{CompilerHelper, PreImageHelper}; use tw_coin_registry::coin_type::CoinType; @@ -12,10 +13,132 @@ use tw_keypair::ed25519::{sha512::PublicKey, Signature}; use tw_keypair::traits::VerifyingKeyTrait; use tw_number::U256; use tw_proto::Common::Proto::SigningError; -use tw_proto::Polkadot::Proto; -use tw_proto::Polkadot::Proto::mod_Balance::Transfer; +use tw_proto::Polymesh::Proto::{ + self, + mod_Balance::Transfer, + mod_Identity::{ + mod_AddAuthorization::{AuthData, Data}, + AddAuthorization, JoinIdentityAsKey, + }, +}; use tw_proto::TxCompiler::Proto as CompilerProto; +// Test compile of AddAuthorization: JoinIdentity +#[test] +fn test_polymesh_compile_add_authorization() { + // https://polymesh.subscan.io/extrinsic/16102080-1 + + // Step 1: Prepare input. + let block_hash = "b569a6fcba97252a9987f7beac2fe6dbb560b78a45e623be1e2f54fe18778512" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + let data = Data { + data: "0x00".decode_hex().unwrap().into(), + }; + + let input = Proto::SigningInput { + network: 12, + nonce: 11, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 7_000_005, + transaction_version: 7, + era: Some(Proto::Era { + block_number: 16_102_074, + period: 64, + }), + message_oneof: identity_call(Proto::mod_Identity::OneOfmessage_oneof::add_authorization( + AddAuthorization { + target: PUBLIC_KEY_1.into(), + data: Some(AuthData { + asset: Some(data.clone()), + portfolio: Some(data.clone()), + extrinsic: Some(data), + }), + ..Default::default() + }, + )), + ..Default::default() + }; + + // Step 2: Obtain preimage hash + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(CoinType::Polymesh, &input); + + assert_eq!(preimage_output.error, SigningError::OK); + + assert_eq!(preimage_output.data.to_hex(), "070a014bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c0501000100010000a5032c00c5cf6a00070000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063b569a6fcba97252a9987f7beac2fe6dbb560b78a45e623be1e2f54fe18778512"); + + // SR25519 signature is not supported yet. +} + +// Test compile of JoinIdentityAsKey transaction. +#[test] +fn test_polymesh_compile_join_identity() { + // https://polymesh.subscan.io/extrinsic/16102090-1 + + // Step 1: Prepare input. + let block_hash = "cd19ce1ee3d725d5a62f29c41925d25f0655043e579231d24fb0175268b7e340" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + nonce: 0, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 7_000_005, + transaction_version: 7, + era: Some(Proto::Era { + block_number: 16_102_087, + period: 64, + }), + message_oneof: identity_call( + Proto::mod_Identity::OneOfmessage_oneof::join_identity_as_key(JoinIdentityAsKey { + auth_id: 52_188, + ..Default::default() + }), + ), + ..Default::default() + }; + + // Step 2: Obtain preimage hash + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(CoinType::Polymesh, &input); + + assert_eq!(preimage_output.error, SigningError::OK); + + assert_eq!(preimage_output.data.to_hex(), "0704dccb00000000000075000000c5cf6a00070000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063cd19ce1ee3d725d5a62f29c41925d25f0655043e579231d24fb0175268b7e340"); + + // Step 3: Compile transaction info + + // Simulate signature, normally obtained from signature server + let signature_bytes = "b40292db45bc8f910b580a586ff81f6c1655fc928d0bf0f41929385b26fda364985d9dee576dec47712a215bb7f70f4c926d1853533cdb693a45c65e8c017904".decode_hex().unwrap(); + let signature = Signature::try_from(signature_bytes.as_slice()).unwrap(); + let public_key = PUBLIC_KEY_HEX_1.decode_hex().unwrap(); + let public = PublicKey::try_from(public_key.as_slice()).unwrap(); + + // Verify signature (pubkey & hash & signature) + assert!(public.verify(signature, preimage_output.data.into())); + + // Compile transaction info + let mut compiler = CompilerHelper::::default(); + let output = compiler.compile( + CoinType::Polymesh, + &input, + vec![signature_bytes], + vec![public_key], + ); + assert_eq!(output.error, SigningError::OK); + + assert_eq!( + output.encoded.to_hex(), + "c50184004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c00b40292db45bc8f910b580a586ff81f6c1655fc928d0bf0f41929385b26fda364985d9dee576dec47712a215bb7f70f4c926d1853533cdb693a45c65e8c017904750000000704dccb000000000000" + ); +} + #[test] fn test_polymesh_compile_transfer() { // https://polymesh.subscan.io/extrinsic/0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04 @@ -83,3 +206,71 @@ fn test_polymesh_compile_transfer() { "390284004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c00e9b4742a2b66931e0cf29f6811e4d44545b4f278a667b9eb1217c4b2de8763c8037e4501dd4a21179b737beb33415f458788f2d1093b527cae8bee8b2d55210ba501040005000010b713ceeb165c1ac7c450f5b138a6da0eba50bb18849f5b8e83985daa45a87e02093d00" ); } + +// Test Leave identity transaction. +#[test] +#[ignore] +fn test_polymesh_compile_leave_identity() { + // https://polymesh.subscan.io/extrinsic/16102113-1 + + // Step 1: Prepare input. + let block_hash = "77d32517dcc7b74501096afdcff3af72008a2c489e17083f56629d195e5c6a1d" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + nonce: 1, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 7_000_005, + transaction_version: 7, + era: Some(Proto::Era { + block_number: 16_102_110, + period: 64, + }), + // TODO: This is not correct, need to fix. This is a placeholder. + message_oneof: identity_call( + Proto::mod_Identity::OneOfmessage_oneof::join_identity_as_key(JoinIdentityAsKey { + auth_id: 52_188, + ..Default::default() + }), + ), + ..Default::default() + }; + + // Step 2: Obtain preimage hash + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(CoinType::Polymesh, &input); + + assert_eq!(preimage_output.error, SigningError::OK); + + assert_eq!(preimage_output.data.to_hex(), "0705..TODO...."); + + // Step 3: Compile transaction info + + // Simulate signature, normally obtained from signature server + let signature_bytes = "57232b54338939c6d9742f7a982cc668b45933bbabcb1df100f5e25ec0879eed803c04bcea28734f5e4e034f0f02aac0a8b81dcc860ddcc6b910458fc8cddb08".decode_hex().unwrap(); + let signature = Signature::try_from(signature_bytes.as_slice()).unwrap(); + let public_key = PUBLIC_KEY_HEX_1.decode_hex().unwrap(); + let public = PublicKey::try_from(public_key.as_slice()).unwrap(); + + // Verify signature (pubkey & hash & signature) + assert!(public.verify(signature, preimage_output.data.into())); + + // Compile transaction info + let mut compiler = CompilerHelper::::default(); + let output = compiler.compile( + CoinType::Polymesh, + &input, + vec![signature_bytes], + vec![public_key], + ); + assert_eq!(output.error, SigningError::OK); + + assert_eq!( + output.encoded.to_hex(), + "a50184004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c0057232b54338939c6d9742f7a982cc668b45933bbabcb1df100f5e25ec0879eed803c04bcea28734f5e4e034f0f02aac0a8b81dcc860ddcc6b910458fc8cddb08e50108000705" + ); +} diff --git a/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs b/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs index fee07e43b90..87f157a51ed 100644 --- a/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs +++ b/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs @@ -2,17 +2,62 @@ // // Copyright © 2017 Trust Wallet. -use crate::chains::polkadot::balance_call; -use crate::chains::polymesh::{GENESIS_HASH, PRIVATE_KEY_1, PUBLIC_KEY_2}; +use crate::chains::polymesh::{ + balance_call, identity_call, GENESIS_HASH, PRIVATE_KEY_1, PUBLIC_KEY_2, +}; use std::borrow::Cow; use tw_any_coin::test_utils::sign_utils::AnySignerHelper; use tw_coin_registry::coin_type::CoinType; use tw_encoding::hex::{DecodeHex, ToHex}; use tw_number::U256; use tw_proto::Common::Proto::SigningError; -use tw_proto::Polkadot::Proto; -use tw_proto::Polkadot::Proto::mod_Balance::Transfer; +use tw_proto::Polymesh::Proto::{self, mod_Balance::Transfer, mod_Identity::JoinIdentityAsKey}; +/// Test a join identity as key transaction. +#[test] +fn test_polymesh_sign_join_identity() { + // join identity + // https://polymesh.subscan.io/extrinsic/16102090-1 + + // Step 1: Prepare input. + let private_key = PRIVATE_KEY_1.decode_hex().unwrap(); + let block_hash = "cd19ce1ee3d725d5a62f29c41925d25f0655043e579231d24fb0175268b7e340" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + private_key: private_key.into(), + nonce: 0, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 7_000_005, + transaction_version: 7, + era: Some(Proto::Era { + block_number: 16_102_087, + period: 64, + }), + message_oneof: identity_call( + Proto::mod_Identity::OneOfmessage_oneof::join_identity_as_key(JoinIdentityAsKey { + auth_id: 52_188, + ..Default::default() + }), + ), + ..Default::default() + }; + + let mut signer = AnySignerHelper::::default(); + let output = signer.sign(CoinType::Polymesh, input); + + assert_eq!(output.error, SigningError::OK); + assert_eq!( + output.encoded.to_hex(), + "c50184004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c00b40292db45bc8f910b580a586ff81f6c1655fc928d0bf0f41929385b26fda364985d9dee576dec47712a215bb7f70f4c926d1853533cdb693a45c65e8c017904750000000704dccb000000000000" + ); +} + +/// Test a simple POLYX transfer. #[test] fn test_polymesh_sign_transfer() { // https://polymesh.subscan.io/extrinsic/0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04 diff --git a/src/proto/Polkadot.proto b/src/proto/Polkadot.proto index 719ccdddef5..59f8aa00ba4 100644 --- a/src/proto/Polkadot.proto +++ b/src/proto/Polkadot.proto @@ -218,60 +218,6 @@ message Staking { } } -// Identity module -message Identity { - // Identity::join_identity_as_key call - message JoinIdentityAsKey { - // call indices - CallIndices call_indices = 1; - - // auth id - uint64 auth_id = 2; - } - - // Identity::add_authorization call - message AddAuthorization { - message Data { - bytes data = 1; - } - - message AuthData { - // authorization data, empty means all permissions, null means no permissions - Data asset = 1; - - // authorization data, empty means all permissions, null means no permissions - Data extrinsic = 2; - - // authorization data, empty means all permissions, null means no permissions - Data portfolio = 3; - } - - // call indices - CallIndices call_indices = 1; - - // address that will be added to the Identity - string target = 2; - - // authorization data, null means all permissions - AuthData data = 3; - - // expire time, unix seconds - uint64 expiry = 4; - } - - oneof message_oneof { - JoinIdentityAsKey join_identity_as_key = 1; - AddAuthorization add_authorization = 2; - } -} - -// Polymesh call -message PolymeshCall { - oneof message_oneof { - Identity identity_call = 2; - } -} - // Input data necessary to create a signed transaction. message SigningInput { // Recent block hash, or genesis hash if era is not set @@ -308,7 +254,6 @@ message SigningInput { oneof message_oneof { Balance balance_call = 11; Staking staking_call = 12; - PolymeshCall polymesh_call = 13; } } @@ -322,4 +267,4 @@ message SigningOutput { // error code description string error_message = 3; -} +} \ No newline at end of file diff --git a/src/proto/Polymesh.proto b/src/proto/Polymesh.proto new file mode 100644 index 00000000000..dc7925c973c --- /dev/null +++ b/src/proto/Polymesh.proto @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +syntax = "proto3"; + +package TW.Polymesh.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +// Destination options for reward +enum RewardDestination { + STAKED = 0; + STASH = 1; + CONTROLLER = 2; +} + +// An era, a period defined by a starting block and length +message Era { + // recent block number (called phase in polkadot code), should match block hash + uint64 block_number = 1; + + // length of period, calculated from block number, e.g. 64 + uint64 period = 2; +} + +// Readable decoded call indices can be found at https://www.subscan.io/ +message CustomCallIndices { + // Module index. + int32 module_index = 4; + + // Method index. + int32 method_index = 5; +} + +// Optional call indices. +// Must be set if `SigningInput::network` is different from `Polymesh`. +message CallIndices { + oneof variant { + CustomCallIndices custom = 1; + } +} + +// Balance transfer transaction +message Balance { + // transfer + message Transfer { + // destination address + string to_address = 1; + + // amount (uint256, serialized big endian) + bytes value = 2; + + // max 32 chars + string memo = 3; + + // call indices + CallIndices call_indices = 4; + } + // batch tranfer + message BatchTransfer { + // call indices + CallIndices call_indices = 1; + + repeated Transfer transfers = 2; + } + // asset transfer + message AssetTransfer { + // call indices + CallIndices call_indices = 1; + + // destination + string to_address = 2; + + // value - BigInteger + bytes value = 3; + + // asset identifier + uint32 asset_id = 4; + + // fee asset identifier + uint32 fee_asset_id = 5; + } + + // batch asset transfer + message BatchAssetTransfer { + // call indices + CallIndices call_indices = 1; + + // fee asset identifier + uint32 fee_asset_id = 2; + + repeated AssetTransfer transfers = 3; + } + + oneof message_oneof { + Transfer transfer = 1; + BatchTransfer batchTransfer = 2; + AssetTransfer asset_transfer = 3; + BatchAssetTransfer batch_asset_transfer = 4; + } +} + +// Staking transaction +message Staking { + // Bond to a controller + message Bond { + // controller ID (optional) + string controller = 1; + + // amount (uint256, serialized big endian) + bytes value = 2; + + // destination for rewards + RewardDestination reward_destination = 3; + + // call indices + CallIndices call_indices = 4; + } + + // Bond to a controller, with nominators + message BondAndNominate { + // controller ID (optional) + string controller = 1; + + // amount (uint256, serialized big endian) + bytes value = 2; + + // destination for rewards + RewardDestination reward_destination = 3; + + // list of nominators + repeated string nominators = 4; + + // call indices + CallIndices call_indices = 5; + } + + // Bond extra amount + message BondExtra { + // amount (uint256, serialized big endian) + bytes value = 1; + + // call indices + CallIndices call_indices = 2; + } + + // Unbond + message Unbond { + // amount (uint256, serialized big endian) + bytes value = 1; + + // call indices + CallIndices call_indices = 2; + } + + // Rebond + message Rebond { + // amount (uint256, serialized big endian) + bytes value = 1; + + // call indices + CallIndices call_indices = 2; + } + + // Withdraw unbonded amounts + message WithdrawUnbonded { + int32 slashing_spans = 1; + + // call indices + CallIndices call_indices = 2; + } + + // Nominate + message Nominate { + // list of nominators + repeated string nominators = 1; + + // call indices + CallIndices call_indices = 2; + } + + // Chill and unbound + message ChillAndUnbond { + // amount (uint256, serialized big endian) + bytes value = 1; + + // call indices + CallIndices call_indices = 2; + } + + // Chill + message Chill { + // call indices + CallIndices call_indices = 1; + } + + // Payload messsage + oneof message_oneof { + Bond bond = 1; + BondAndNominate bond_and_nominate = 2; + BondExtra bond_extra = 3; + Unbond unbond = 4; + WithdrawUnbonded withdraw_unbonded = 5; + Nominate nominate = 6; + Chill chill = 7; + ChillAndUnbond chill_and_unbond = 8; + Rebond rebond = 9; + } +} + +// Identity module +message Identity { + // Identity::join_identity_as_key call + message JoinIdentityAsKey { + // call indices + CallIndices call_indices = 1; + + // auth id + uint64 auth_id = 2; + } + + // Identity::add_authorization call + message AddAuthorization { + message Data { + bytes data = 1; + } + + message AuthData { + // authorization data, empty means all permissions, null means no permissions + Data asset = 1; + + // authorization data, empty means all permissions, null means no permissions + Data extrinsic = 2; + + // authorization data, empty means all permissions, null means no permissions + Data portfolio = 3; + } + + // call indices + CallIndices call_indices = 1; + + // address that will be added to the Identity + string target = 2; + + // authorization data, null means all permissions + AuthData data = 3; + + // expire time, unix seconds + uint64 expiry = 4; + } + + oneof message_oneof { + JoinIdentityAsKey join_identity_as_key = 1; + AddAuthorization add_authorization = 2; + } +} + +// Input data necessary to create a signed transaction. +message SigningInput { + // Recent block hash, or genesis hash if era is not set + bytes block_hash = 1; + + // Genesis block hash (identifies the chain) + bytes genesis_hash = 2; + + // Current account nonce + uint64 nonce = 3; + + // Specification version, e.g. 26. + uint32 spec_version = 4; + + // Transaction version, e.g. 5. + uint32 transaction_version = 5; + + // Optional tip to pay, big integer + bytes tip = 6; + + // Optional time validity limit, recommended, for replay-protection. Empty means Immortal. + Era era = 7; + + // The secret private key used for signing (32 bytes). + bytes private_key = 8; + + // Network type + uint32 network = 9; + + // Whether enable MultiAddress + bool multi_address = 10; + + // Payload message + oneof message_oneof { + Balance balance_call = 11; + Staking staking_call = 12; + Identity identity_call = 13; + } +} + +// Result containing the signed and encoded transaction. +message SigningOutput { + // Signed and encoded transaction bytes. + bytes encoded = 1; + + // error code, 0 is ok, other codes will be treated as errors + Common.Proto.SigningError error = 2; + + // error code description + string error_message = 3; +} \ No newline at end of file diff --git a/tests/chains/Polymesh/TWAnySignerTests.cpp b/tests/chains/Polymesh/TWAnySignerTests.cpp index a1e176ef572..e3ab1c71203 100644 --- a/tests/chains/Polymesh/TWAnySignerTests.cpp +++ b/tests/chains/Polymesh/TWAnySignerTests.cpp @@ -7,7 +7,7 @@ #include "HexCoding.h" #include "PrivateKey.h" #include "PublicKey.h" -#include "proto/Polkadot.pb.h" +#include "proto/Polymesh.pb.h" #include "proto/TransactionCompiler.pb.h" #include "uint256.h" @@ -22,7 +22,7 @@ #include using namespace TW; -using namespace TW::Polkadot; +using namespace TW::Polymesh; namespace TW::Polymesh::tests { auto polymeshPrefix = 12; @@ -36,7 +36,7 @@ Data helper_encodeTransaction(TWCoinType coin, const Proto::SigningInput& input, WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&signature)).get(), WRAP(TWDataVector, TWDataVectorCreateWithData((TWData*)&pubKey)).get())); - Polkadot::Proto::SigningOutput output; + Polymesh::Proto::SigningOutput output; output.ParseFromArray(TWDataBytes(outputData.get()), (int)TWDataSize(outputData.get())); EXPECT_EQ(output.error(), Common::Proto::OK); @@ -51,7 +51,7 @@ TEST(TWAnySignerPolymesh, PolymeshEncodeAndSign) { /// Step 1: Prepare transaction input (protobuf) const auto coin = TWCoinTypePolymesh; - Polkadot::Proto::SigningInput input; + Polymesh::Proto::SigningInput input; input.set_network(12); input.set_multi_address(true); auto blockHash = parse_hex("898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102"); @@ -102,7 +102,7 @@ TEST(TWAnySignerPolymesh, PolymeshEncodeAndSign) { const auto ExpectedTx = "bd0284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee000791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f25010400050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f20504144444544205749544820535041434553000000000000000000"; { - Polkadot::Proto::SigningOutput output; + Polymesh::Proto::SigningOutput output; ASSERT_TRUE(output.ParseFromArray(TWDataBytes(outputData.get()), (int)TWDataSize(outputData.get()))); @@ -114,7 +114,7 @@ TEST(TWAnySignerPolymesh, encodeTransaction_Add_authorization) { // tx on mainnet // https://polymesh.subscan.io/extrinsic/0x7d9b9109027b36b72d37ba0648cb70e5254524d3d6752cc6b41601f4bdfb1af0 - Polkadot::Proto::SigningInput input; + Polymesh::Proto::SigningInput input; input.set_network(12); input.set_multi_address(true); auto blockHash = parse_hex("ce0c2109db498e45abf8fd447580dcfa7b7a07ffc2bfb1a0fbdd1af3e8816d2b"); @@ -129,7 +129,7 @@ TEST(TWAnySignerPolymesh, encodeTransaction_Add_authorization) { era->set_block_number(4395451UL); era->set_period(64UL); - auto* addAuthorization = input.mutable_polymesh_call()->mutable_identity_call()->mutable_add_authorization(); + auto* addAuthorization = input.mutable_identity_call()->mutable_add_authorization(); addAuthorization->set_target("2HEVN4PHYKj7B1krQ9bctAQXZxHQQkANVNCcfbdYk2gZ4cBR"); auto* authData = addAuthorization->mutable_data(); // Set empty "These". @@ -155,7 +155,7 @@ TEST(TWAnySignerPolymesh, encodeTransaction_JoinIdentityAsKey) { // tx on mainnet // https://polymesh.subscan.io/extrinsic/0x9d7297d8b38af5668861996cb115f321ed681989e87024fda64eae748c2dc542 - Polkadot::Proto::SigningInput input; + Polymesh::Proto::SigningInput input; input.set_network(12); input.set_multi_address(true); auto blockHash = parse_hex("45c80153c47f5d16acc7a66d473870e8d4574437a7d8c813f47da74cae3812c2"); @@ -170,7 +170,7 @@ TEST(TWAnySignerPolymesh, encodeTransaction_JoinIdentityAsKey) { era->set_block_number(4395527UL); era->set_period(64UL); - auto* key = input.mutable_polymesh_call()->mutable_identity_call()->mutable_join_identity_as_key(); + auto* key = input.mutable_identity_call()->mutable_join_identity_as_key(); key->set_auth_id(21435); auto* callIndices = key->mutable_call_indices()->mutable_custom(); callIndices->set_module_index(0x07); From eb411acaa6b1154861941c609a73e6a40eb899f9 Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Mon, 2 Dec 2024 18:34:35 +0000 Subject: [PATCH 06/22] Add permissions support. --- .../tw_polymesh/src/call_encoder/polymesh.rs | 337 ++++++++++++++++-- rust/chains/tw_polymesh/tests/extrinsic.rs | 50 ++- .../tests/chains/polymesh/polymesh_compile.rs | 31 +- src/proto/Polymesh.proto | 88 ++++- tests/chains/Polymesh/TWAnySignerTests.cpp | 15 +- 5 files changed, 437 insertions(+), 84 deletions(-) diff --git a/rust/chains/tw_polymesh/src/call_encoder/polymesh.rs b/rust/chains/tw_polymesh/src/call_encoder/polymesh.rs index 214c5bd908d..e744db562c3 100644 --- a/rust/chains/tw_polymesh/src/call_encoder/polymesh.rs +++ b/rust/chains/tw_polymesh/src/call_encoder/polymesh.rs @@ -1,16 +1,28 @@ -use std::str::FromStr; +use std::{ + collections::{BTreeMap, BTreeSet}, + str::FromStr, +}; -use tw_coin_entry::error::prelude::*; -use tw_hash::H256; +use tw_coin_entry::error::prelude::TWError; +use tw_hash::{Hash, H256}; use tw_number::U256; use tw_proto::Polymesh::Proto::{ mod_Balance::{OneOfmessage_oneof as BalanceVariant, Transfer}, - mod_Identity::{AddAuthorization, JoinIdentityAsKey, OneOfmessage_oneof as IdentityVariant}, + mod_Identity::{ + mod_AddAuthorization::mod_Authorization::OneOfauth_oneof as AuthVariant, AddAuthorization, + JoinIdentityAsKey, OneOfmessage_oneof as IdentityVariant, + }, + mod_SecondaryKeyPermissions::{ + AssetPermissions as TWAssetPermissions, ExtrinsicPermissions as TWExtrinsicPermissions, + PalletPermissions as TWPalletPermissions, PortfolioPermissions as TWPortfolioPermissions, + RestrictionKind as TWRestrictionKind, + }, mod_Staking::{ Bond, BondExtra, Chill, Nominate, OneOfmessage_oneof as StakingVariant, Rebond, Unbond, WithdrawUnbonded, }, - Balance, Identity, Staking, + AssetId as TWAssetId, Balance, Identity, IdentityId as TWIdentityId, + PortfolioId as TWPortfolioId, SecondaryKeyPermissions, Staking, }; use tw_scale::{impl_enum_scale, impl_struct_scale, Compact, RawOwned}; use tw_ss58_address::SS58Address; @@ -34,11 +46,78 @@ impl Memo { } } +pub type H128 = Hash<16>; + impl_struct_scale!( - #[derive(Clone, Debug)] + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] + pub struct AssetId(H128); +); + +impl TryFrom<&TWAssetId<'_>> for AssetId { + type Error = TWError; + + fn try_from(id: &TWAssetId) -> Result { + let did = H128::try_from(id.id.as_ref()).map_err(|_| { + EncodeError::InvalidValue.with_context(format!("Expected 16 byte AssetId")) + })?; + Ok(Self(did)) + } +} + +impl_struct_scale!( + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] pub struct IdentityId(H256); ); +impl TryFrom<&TWIdentityId<'_>> for IdentityId { + type Error = TWError; + + fn try_from(id: &TWIdentityId) -> Result { + let did = H256::try_from(id.id.as_ref()).map_err(|_| { + EncodeError::InvalidValue.with_context(format!("Expected 32 byte IdentityId")) + })?; + Ok(Self(did)) + } +} + +impl_enum_scale!( + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] + pub enum PortfolioKind { + #[default] + Default = 0x00, + User(u64) = 0x01, + } +); + +impl_struct_scale!( + #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] + pub struct PortfolioId { + did: IdentityId, + kind: PortfolioKind, + } +); + +impl TryFrom<&TWPortfolioId<'_>> for PortfolioId { + type Error = TWError; + + fn try_from(portfolio: &TWPortfolioId) -> Result { + Ok(Self { + did: portfolio + .identity + .as_ref() + .ok_or_else(|| { + EncodeError::InvalidValue.with_context(format!("Missing portfolio identity")) + })? + .try_into()?, + kind: if portfolio.default { + PortfolioKind::Default + } else { + PortfolioKind::User(portfolio.user) + }, + }) + } +} + impl_enum_scale!( #[derive(Clone, Debug)] pub enum PolymeshBalances { @@ -96,13 +175,209 @@ impl_enum_scale!( } ); +impl_enum_scale!( + #[derive(Clone, Debug, Default, PartialEq, Eq)] + pub enum RestrictionKind { + #[default] + Whole = 0x00, + These = 0x01, + Except = 0x02, + } +); + +impl From for RestrictionKind { + fn from(kind: TWRestrictionKind) -> Self { + match kind { + TWRestrictionKind::Whole => Self::Whole, + TWRestrictionKind::These => Self::These, + TWRestrictionKind::Except => Self::Except, + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct AssetPermissions { + kind: RestrictionKind, + assets: BTreeSet, +} + +impl AssetPermissions { + /// Empty permissions means no access. + pub fn empty() -> Self { + Self { + kind: RestrictionKind::These, + assets: BTreeSet::new(), + } + } +} + +impl ToScale for AssetPermissions { + fn to_scale_into(&self, data: &mut Vec) { + self.kind.to_scale_into(data); + if self.kind != RestrictionKind::Whole { + self.assets.to_scale_into(data); + } + } +} + +impl TryFrom<&TWAssetPermissions<'_>> for AssetPermissions { + type Error = TWError; + + fn try_from(perms: &TWAssetPermissions) -> Result { + Ok(Self { + kind: perms.kind.into(), + assets: perms + .assets + .iter() + .map(|asset| Ok(asset.try_into()?)) + .collect::>>()?, + }) + } +} + +#[derive(Clone, Debug, Default)] +pub struct PortfolioPermissions { + kind: RestrictionKind, + portfolios: BTreeSet, +} + +impl PortfolioPermissions { + /// Empty permissions means no access. + pub fn empty() -> Self { + Self { + kind: RestrictionKind::These, + portfolios: BTreeSet::new(), + } + } +} + +impl ToScale for PortfolioPermissions { + fn to_scale_into(&self, data: &mut Vec) { + self.kind.to_scale_into(data); + if self.kind != RestrictionKind::Whole { + self.portfolios.to_scale_into(data); + } + } +} + +impl TryFrom<&TWPortfolioPermissions<'_>> for PortfolioPermissions { + type Error = TWError; + + fn try_from(perms: &TWPortfolioPermissions) -> Result { + Ok(Self { + kind: perms.kind.into(), + portfolios: perms + .portfolios + .iter() + .map(|portfolio| Ok(portfolio.try_into()?)) + .collect::>>()?, + }) + } +} + +#[derive(Clone, Debug, Default)] +pub struct PalletPermissions { + kind: RestrictionKind, + extrinsic_names: BTreeSet, +} + +impl ToScale for PalletPermissions { + fn to_scale_into(&self, data: &mut Vec) { + self.kind.to_scale_into(data); + if self.kind != RestrictionKind::Whole { + self.extrinsic_names.to_scale_into(data); + } + } +} + +impl From<&TWPalletPermissions<'_>> for PalletPermissions { + fn from(perms: &TWPalletPermissions) -> Self { + Self { + kind: perms.kind.into(), + extrinsic_names: perms + .extrinsic_names + .iter() + .map(|name| name.to_string()) + .collect(), + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct ExtrinsicPermissions { + kind: RestrictionKind, + pallets: BTreeMap, +} + +impl ExtrinsicPermissions { + /// Empty permissions means no access. + pub fn empty() -> Self { + Self { + kind: RestrictionKind::These, + pallets: BTreeMap::new(), + } + } +} + +impl ToScale for ExtrinsicPermissions { + fn to_scale_into(&self, data: &mut Vec) { + self.kind.to_scale_into(data); + if self.kind != RestrictionKind::Whole { + self.pallets.to_scale_into(data); + } + } +} + +impl From<&TWExtrinsicPermissions<'_>> for ExtrinsicPermissions { + fn from(perms: &TWExtrinsicPermissions) -> Self { + Self { + kind: perms.kind.into(), + pallets: perms + .pallets + .iter() + .map(|pallet| (pallet.pallet_name.to_string(), pallet.into())) + .collect(), + } + } +} + +impl_struct_scale!( + #[derive(Clone, Debug, Default)] + pub struct Permissions { + asset: AssetPermissions, + extrinsic: ExtrinsicPermissions, + portfolio: PortfolioPermissions, + } +); + +impl TryFrom<&SecondaryKeyPermissions<'_>> for Permissions { + type Error = TWError; + + fn try_from(perms: &SecondaryKeyPermissions) -> Result { + Ok(Self { + asset: if let Some(perms) = &perms.asset { + perms.try_into()? + } else { + AssetPermissions::default() + }, + extrinsic: if let Some(perms) = &perms.extrinsic { + perms.into() + } else { + ExtrinsicPermissions::default() + }, + portfolio: if let Some(perms) = &perms.portfolio { + perms.try_into()? + } else { + PortfolioPermissions::default() + }, + }) + } +} + impl_enum_scale!( #[derive(Clone, Debug)] pub enum AuthorizationData { - JoinIdentity { - // TODO: Polymesh permissions. - permissions: RawOwned, - } = 0x05, + JoinIdentity { permissions: Permissions } = 0x05, } ); @@ -132,39 +407,23 @@ impl PolymeshIdentity { let ci = validate_call_index(&auth.call_indices)?; let target = SS58Address::from_str(&auth.target).map_err(|_| EncodeError::InvalidAddress)?; - let mut data = Vec::new(); - if let Some(auth_data) = &auth.data { - if let Some(asset) = &auth_data.asset { - data.push(0x01); - data.extend_from_slice(&asset.data); - } else { - data.push(0x00); - } - - if let Some(extrinsic) = &auth_data.extrinsic { - data.push(0x01); - data.extend_from_slice(&extrinsic.data); - } else { - data.push(0x00); - } - - if let Some(portfolio) = &auth_data.portfolio { - data.push(0x01); - data.extend_from_slice(&portfolio.data); - } else { - data.push(0x00); + let data = if let Some(auth) = &auth.authorization { + match &auth.auth_oneof { + AuthVariant::join_identity(perms) => AuthorizationData::JoinIdentity { + permissions: perms.try_into().map_err(|_| EncodeError::InvalidValue)?, + }, + AuthVariant::None => { + return EncodeError::NotSupported + .tw_result("Unsupported Authorization".to_string()); + }, } } else { - // Mark everything as authorized (asset, extrinsic, portfolio) - data.push(0x00); - data.push(0x00); - data.push(0x00); - } + return EncodeError::NotSupported.tw_result("Missing Authorization".to_string()); + }; + Ok(ci.wrap(Self::AddAuthorization { target: Signatory::Account(SubstrateAddress(target)), - data: AuthorizationData::JoinIdentity { - permissions: RawOwned(data), - }, + data, expiry: if auth.expiry > 0 { Some(auth.expiry) } else { diff --git a/rust/chains/tw_polymesh/tests/extrinsic.rs b/rust/chains/tw_polymesh/tests/extrinsic.rs index 95d75219ff9..1c6f2765f78 100644 --- a/rust/chains/tw_polymesh/tests/extrinsic.rs +++ b/rust/chains/tw_polymesh/tests/extrinsic.rs @@ -6,8 +6,14 @@ use tw_number::U256; use tw_proto::Polymesh::Proto::{ self, mod_Balance::Transfer, - mod_Identity::mod_AddAuthorization::{AuthData, Data}, + mod_Identity::mod_AddAuthorization::{ + mod_Authorization::OneOfauth_oneof as AuthVariant, Authorization, + }, + mod_SecondaryKeyPermissions::{ + AssetPermissions, ExtrinsicPermissions, PortfolioPermissions, RestrictionKind, + }, mod_Staking::{Bond, BondExtra, Chill, Nominate, Rebond, Unbond, WithdrawUnbonded}, + SecondaryKeyPermissions, }; use tw_substrate::EncodeResult; @@ -93,6 +99,9 @@ fn polymesh_encode_authorization_join_identity() { multi_address: true, message_oneof: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), + authorization: Some(Authorization { + auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions::default()), + }), ..Default::default() }), ..Default::default() @@ -114,15 +123,23 @@ fn polymesh_encode_authorization_join_identity_with_zero_data() { multi_address: true, message_oneof: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), - data: Some(AuthData { - asset: Some(Data { - data: (&[0x00]).into(), - }), - extrinsic: Some(Data { - data: (&[0x00]).into(), - }), - portfolio: Some(Data { - data: (&[0x00]).into(), + authorization: Some(Authorization { + auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions { + // No asset permissions. + asset: Some(AssetPermissions { + kind: RestrictionKind::These, + assets: vec![], + }), + // No extrinsic permissions. + extrinsic: Some(ExtrinsicPermissions { + kind: RestrictionKind::These, + pallets: vec![], + }), + // No portfolio permissions. + portfolio: Some(PortfolioPermissions { + kind: RestrictionKind::These, + portfolios: vec![], + }), }), }), ..Default::default() @@ -146,10 +163,15 @@ fn polymesh_encode_authorization_join_identity_allowing_everything() { multi_address: true, message_oneof: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), - data: Some(AuthData { - asset: None, - extrinsic: None, - portfolio: None, + authorization: Some(Authorization { + auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions { + // All asset permissions. + asset: None, + // All extrinsic permissions. + extrinsic: None, + // All portfolio permissions. + portfolio: None, + }), }), ..Default::default() }), diff --git a/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs b/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs index 81a3c298571..1165f6a80c4 100644 --- a/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs +++ b/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs @@ -17,9 +17,13 @@ use tw_proto::Polymesh::Proto::{ self, mod_Balance::Transfer, mod_Identity::{ - mod_AddAuthorization::{AuthData, Data}, + mod_AddAuthorization::{mod_Authorization::OneOfauth_oneof as AuthVariant, Authorization}, AddAuthorization, JoinIdentityAsKey, }, + mod_SecondaryKeyPermissions::{ + AssetPermissions, ExtrinsicPermissions, PortfolioPermissions, RestrictionKind, + }, + SecondaryKeyPermissions, }; use tw_proto::TxCompiler::Proto as CompilerProto; @@ -33,9 +37,6 @@ fn test_polymesh_compile_add_authorization() { .decode_hex() .unwrap(); let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); - let data = Data { - data: "0x00".decode_hex().unwrap().into(), - }; let input = Proto::SigningInput { network: 12, @@ -51,10 +52,24 @@ fn test_polymesh_compile_add_authorization() { message_oneof: identity_call(Proto::mod_Identity::OneOfmessage_oneof::add_authorization( AddAuthorization { target: PUBLIC_KEY_1.into(), - data: Some(AuthData { - asset: Some(data.clone()), - portfolio: Some(data.clone()), - extrinsic: Some(data), + authorization: Some(Authorization { + auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions { + // No asset permissions. + asset: Some(AssetPermissions { + kind: RestrictionKind::These, + assets: vec![], + }), + // No extrinsic permissions. + extrinsic: Some(ExtrinsicPermissions { + kind: RestrictionKind::These, + pallets: vec![], + }), + // No portfolio permissions. + portfolio: Some(PortfolioPermissions { + kind: RestrictionKind::These, + portfolios: vec![], + }), + }), }), ..Default::default() }, diff --git a/src/proto/Polymesh.proto b/src/proto/Polymesh.proto index dc7925c973c..3701d207054 100644 --- a/src/proto/Polymesh.proto +++ b/src/proto/Polymesh.proto @@ -210,6 +210,63 @@ message Staking { } } +message IdentityId { + // 32 byte identity id. + bytes id = 1; +} + +message AssetId { + // 16 byte asset id. + bytes id = 1; +} + +message PortfolioId { + // IdentityId of the portfolio owner. + IdentityId identity = 1; + // If `default` is true ignore the `user` field. + bool default = 2; + // The users portfolio number. (ignored if `default = true`) + uint64 user = 3; +} + +message SecondaryKeyPermissions { + enum RestrictionKind { + Whole = 0; + These = 1; + Except = 2; + } + + message AssetPermissions { + RestrictionKind kind = 1; + repeated AssetId assets = 2; + } + + message PortfolioPermissions { + RestrictionKind kind = 1; + repeated PortfolioId portfolios = 2; + } + + message PalletPermissions { + string pallet_name = 1; + RestrictionKind kind = 2; + repeated string extrinsic_names = 3; + } + + message ExtrinsicPermissions { + RestrictionKind kind = 1; + repeated PalletPermissions pallets = 2; + } + + // The assets permissions of the secondary key. + AssetPermissions asset = 1; + + // The pallet/extrinsic permissions of the secondary key. + ExtrinsicPermissions extrinsic = 2; + + // The portfolio permissions of the secondary key. + PortfolioPermissions portfolio = 3; +} + // Identity module message Identity { // Identity::join_identity_as_key call @@ -223,19 +280,20 @@ message Identity { // Identity::add_authorization call message AddAuthorization { - message Data { - bytes data = 1; - } - - message AuthData { - // authorization data, empty means all permissions, null means no permissions - Data asset = 1; - - // authorization data, empty means all permissions, null means no permissions - Data extrinsic = 2; - - // authorization data, empty means all permissions, null means no permissions - Data portfolio = 3; + message Authorization { + // Authorization data. + oneof auth_oneof { + // AttestPrimaryKeyRotation(IdentityId) = 1 + // RotatePrimaryKey = 2 + // TransferTicker(Ticker) = 3 + // AddMultiSigSigner(AccountId) = 4 + // TransferAssetOwnership(AssetId) = 5 + SecondaryKeyPermissions join_identity = 6; + // PortfolioCustody(PortfolioId) = 7 + // BecomeAgent(AssetId, AgentGroup) = 8 + // AddRelayerPayingKey(AccountId, AccountId, Balance) = 9 + // RotatePrimaryKeyToSecondary(Permissions) = 10 + } } // call indices @@ -244,8 +302,8 @@ message Identity { // address that will be added to the Identity string target = 2; - // authorization data, null means all permissions - AuthData data = 3; + // Authorization. + Authorization authorization = 3; // expire time, unix seconds uint64 expiry = 4; diff --git a/tests/chains/Polymesh/TWAnySignerTests.cpp b/tests/chains/Polymesh/TWAnySignerTests.cpp index e3ab1c71203..2e7e8062cde 100644 --- a/tests/chains/Polymesh/TWAnySignerTests.cpp +++ b/tests/chains/Polymesh/TWAnySignerTests.cpp @@ -131,15 +131,14 @@ TEST(TWAnySignerPolymesh, encodeTransaction_Add_authorization) { auto* addAuthorization = input.mutable_identity_call()->mutable_add_authorization(); addAuthorization->set_target("2HEVN4PHYKj7B1krQ9bctAQXZxHQQkANVNCcfbdYk2gZ4cBR"); - auto* authData = addAuthorization->mutable_data(); + auto* keyPerms = addAuthorization->mutable_authorization()->mutable_join_identity(); // Set empty "These". - auto* assets = authData->mutable_asset(); - auto empty = parse_hex("00"); - assets->set_data(std::string(empty.begin(), empty.end())); - auto* extrinsics = authData->mutable_extrinsic(); - extrinsics->set_data(std::string(empty.begin(), empty.end())); - auto* portfolios = authData->mutable_portfolio(); - portfolios->set_data(std::string(empty.begin(), empty.end())); + auto* assets = keyPerms->mutable_asset(); + assets->set_kind(Polymesh::Proto::SecondaryKeyPermissions_RestrictionKind_These); + auto* extrinsics = keyPerms->mutable_extrinsic(); + extrinsics->set_kind(Polymesh::Proto::SecondaryKeyPermissions_RestrictionKind_These); + auto* portfolios = keyPerms->mutable_portfolio(); + portfolios->set_kind(Polymesh::Proto::SecondaryKeyPermissions_RestrictionKind_These); auto* callIndices = addAuthorization->mutable_call_indices()->mutable_custom(); callIndices->set_module_index(0x07); From 6ee2d6354c3f39321272986ee6b9de0d750e0434 Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Tue, 3 Dec 2024 22:00:46 +0800 Subject: [PATCH 07/22] Refactor. Move types into different module. --- rust/chains/tw_polymesh/src/call_encoder.rs | 453 ++++++++++++++++++ .../tw_polymesh/src/call_encoder/mod.rs | 193 -------- rust/chains/tw_polymesh/src/lib.rs | 1 + .../{call_encoder/polymesh.rs => types.rs} | 267 +---------- 4 files changed, 457 insertions(+), 457 deletions(-) create mode 100644 rust/chains/tw_polymesh/src/call_encoder.rs delete mode 100644 rust/chains/tw_polymesh/src/call_encoder/mod.rs rename rust/chains/tw_polymesh/src/{call_encoder/polymesh.rs => types.rs} (51%) diff --git a/rust/chains/tw_polymesh/src/call_encoder.rs b/rust/chains/tw_polymesh/src/call_encoder.rs new file mode 100644 index 00000000000..d7a13fd2dfa --- /dev/null +++ b/rust/chains/tw_polymesh/src/call_encoder.rs @@ -0,0 +1,453 @@ +use std::str::FromStr; + +use crate::ctx_from_tw; +use crate::types::*; +use tw_number::U256; +use tw_proto::Polymesh::Proto::{ + self, + mod_Balance::{ + BatchAssetTransfer, BatchTransfer, OneOfmessage_oneof as BalanceVariant, Transfer, + }, + mod_CallIndices::OneOfvariant as CallIndicesVariant, + mod_Identity::{ + mod_AddAuthorization::mod_Authorization::OneOfauth_oneof as AuthVariant, AddAuthorization, + JoinIdentityAsKey, OneOfmessage_oneof as IdentityVariant, + }, + mod_SigningInput::OneOfmessage_oneof as SigningVariant, + mod_Staking::{ + Bond, BondAndNominate, BondExtra, Chill, ChillAndUnbond, Nominate, + OneOfmessage_oneof as StakingVariant, Rebond, Unbond, WithdrawUnbonded, + }, + Balance, CallIndices, Identity, Staking, +}; +use tw_scale::{impl_enum_scale, Compact, RawOwned, ToScale}; +use tw_ss58_address::SS58Address; +use tw_substrate::address::SubstrateAddress; +use tw_substrate::*; + +fn validate_call_index(call_index: &Option) -> EncodeResult { + let index = match call_index { + Some(CallIndices { + variant: CallIndicesVariant::custom(c), + }) => Some((c.module_index, c.method_index)), + _ => None, + }; + CallIndex::from_tw(index) +} + +impl_enum_scale!( + #[derive(Clone, Debug)] + pub enum PolymeshBalances { + Transfer { + dest: MultiAddress, + value: Compact, + } = 0x00, + TransferWithMemo { + dest: MultiAddress, + value: Compact, + memo: Option, + } = 0x01, + } +); + +impl PolymeshBalances { + fn encode_transfer(t: &Transfer) -> WithCallIndexResult { + let ci = validate_call_index(&t.call_indices)?; + let address = + SS58Address::from_str(&t.to_address).map_err(|_| EncodeError::InvalidAddress)?; + let value = U256::from_big_endian_slice(&t.value) + .map_err(|_| EncodeError::InvalidValue)? + .try_into() + .map_err(|_| EncodeError::InvalidValue)?; + + if !t.memo.is_empty() { + Ok(ci.wrap(Self::TransferWithMemo { + dest: address.into(), + value: Compact(value), + memo: Some(Memo::new(&t.memo)), + })) + } else { + Ok(ci.wrap(Self::Transfer { + dest: address.into(), + value: Compact(value), + })) + } + } + + pub fn encode_call(b: &Balance) -> WithCallIndexResult { + match &b.message_oneof { + BalanceVariant::transfer(t) => Self::encode_transfer(t), + _ => EncodeError::NotSupported.tw_result("Unsupported balance call".to_string()), + } + } +} + +impl_enum_scale!( + #[derive(Clone, Debug)] + pub enum AuthorizationData { + JoinIdentity { permissions: Permissions } = 0x05, + } +); + +impl_enum_scale!( + #[derive(Clone, Debug)] + pub enum PolymeshIdentity { + JoinIdentity { + auth_id: u64, + } = 0x04, + AddAuthorization { + target: Signatory, + data: AuthorizationData, + expiry: Option, + } = 0x0a, + } +); + +impl PolymeshIdentity { + fn encode_join_identity(join: &JoinIdentityAsKey) -> WithCallIndexResult { + let ci = validate_call_index(&join.call_indices)?; + Ok(ci.wrap(Self::JoinIdentity { + auth_id: join.auth_id, + })) + } + + fn encode_add_authorization(auth: &AddAuthorization) -> WithCallIndexResult { + let ci = validate_call_index(&auth.call_indices)?; + let target = + SS58Address::from_str(&auth.target).map_err(|_| EncodeError::InvalidAddress)?; + let data = if let Some(auth) = &auth.authorization { + match &auth.auth_oneof { + AuthVariant::join_identity(perms) => AuthorizationData::JoinIdentity { + permissions: perms.try_into().map_err(|_| EncodeError::InvalidValue)?, + }, + AuthVariant::None => { + return EncodeError::NotSupported + .tw_result("Unsupported Authorization".to_string()); + }, + } + } else { + return EncodeError::NotSupported.tw_result("Missing Authorization".to_string()); + }; + + Ok(ci.wrap(Self::AddAuthorization { + target: Signatory::Account(SubstrateAddress(target.into())), + data, + expiry: if auth.expiry > 0 { + Some(auth.expiry) + } else { + None + }, + })) + } + + pub fn encode_call(ident: &Identity) -> WithCallIndexResult { + match &ident.message_oneof { + IdentityVariant::join_identity_as_key(t) => Self::encode_join_identity(t), + IdentityVariant::add_authorization(a) => Self::encode_add_authorization(a), + _ => EncodeError::NotSupported.tw_result("Unsupported identity call".to_string()), + } + } +} + +impl_enum_scale!( + #[derive(Clone, Debug)] + pub enum PolymeshStaking { + Bond { + controller: MultiAddress, + value: Compact, + reward: RewardDestination, + } = 0x00, + BondExtra { + max_additional: Compact, + } = 0x01, + Unbond { + value: Compact, + } = 0x02, + WithdrawUnbonded { + num_slashing_spans: u32, + } = 0x03, + Nominate { + targets: Vec, + } = 0x05, + Chill = 0x06, + Rebond { + value: Compact, + } = 0x13, + } +); + +impl PolymeshStaking { + fn encode_bond(b: &Bond) -> WithCallIndexResult { + let ci = validate_call_index(&b.call_indices)?; + let value = U256::from_big_endian_slice(&b.value) + .map_err(|_| EncodeError::InvalidValue)? + .try_into() + .map_err(|_| EncodeError::InvalidValue)?; + let controller = + SS58Address::from_str(&b.controller).map_err(|_| EncodeError::InvalidAddress)?; + + Ok(ci.wrap(Self::Bond { + controller: controller.into(), + value: Compact(value), + reward: RewardDestination::from_tw(b.reward_destination as u8, &b.controller)?, + })) + } + + fn encode_bond_extra(b: &BondExtra) -> WithCallIndexResult { + let ci = validate_call_index(&b.call_indices)?; + let value = U256::from_big_endian_slice(&b.value) + .map_err(|_| EncodeError::InvalidValue)? + .try_into() + .map_err(|_| EncodeError::InvalidValue)?; + + Ok(ci.wrap(Self::BondExtra { + max_additional: Compact(value), + })) + } + + fn encode_chill(c: &Chill) -> WithCallIndexResult { + let ci = validate_call_index(&c.call_indices)?; + Ok(ci.wrap(Self::Chill)) + } + + fn encode_unbond(b: &Unbond) -> WithCallIndexResult { + let ci = validate_call_index(&b.call_indices)?; + let value = U256::from_big_endian_slice(&b.value) + .map_err(|_| EncodeError::InvalidValue)? + .try_into() + .map_err(|_| EncodeError::InvalidValue)?; + + Ok(ci.wrap(Self::Unbond { + value: Compact(value), + })) + } + + fn encode_rebond(b: &Rebond) -> WithCallIndexResult { + let ci = validate_call_index(&b.call_indices)?; + let value = U256::from_big_endian_slice(&b.value) + .map_err(|_| EncodeError::InvalidValue)? + .try_into() + .map_err(|_| EncodeError::InvalidValue)?; + + Ok(ci.wrap(Self::Rebond { + value: Compact(value), + })) + } + + fn encode_withdraw_unbonded(b: &WithdrawUnbonded) -> WithCallIndexResult { + let ci = validate_call_index(&b.call_indices)?; + Ok(ci.wrap(Self::WithdrawUnbonded { + num_slashing_spans: b.slashing_spans as u32, + })) + } + + fn encode_nominate(b: &Nominate) -> WithCallIndexResult { + let ci = validate_call_index(&b.call_indices)?; + let targets = b + .nominators + .iter() + .map(|target| { + let account = + SS58Address::from_str(&target).map_err(|_| EncodeError::InvalidAddress)?; + Ok(account.into()) + }) + .collect::>>()?; + Ok(ci.wrap(Self::Nominate { targets })) + } + + pub fn encode_call(s: &Staking) -> WithCallIndexResult { + match &s.message_oneof { + StakingVariant::bond(b) => Self::encode_bond(b), + StakingVariant::bond_extra(b) => Self::encode_bond_extra(b), + StakingVariant::chill(b) => Self::encode_chill(b), + StakingVariant::unbond(b) => Self::encode_unbond(b), + StakingVariant::withdraw_unbonded(b) => Self::encode_withdraw_unbonded(b), + StakingVariant::rebond(b) => Self::encode_rebond(b), + StakingVariant::nominate(b) => Self::encode_nominate(b), + _ => EncodeError::NotSupported.tw_result("Unsupported staking call".to_string()), + } + } +} + +impl_enum_scale!( + #[derive(Clone, Debug)] + pub enum PolymeshUtility { + BatchAll { calls: Vec } = 0x02, + } +); + +impl_enum_scale!( + #[derive(Clone, Debug)] + pub enum PolymeshCall { + Balances(PolymeshBalances) = 0x05, + Identity(PolymeshIdentity) = 0x07, + Staking(PolymeshStaking) = 0x11, + Utility(PolymeshUtility) = 0x29, + } +); + +pub struct CallEncoder; + +impl CallEncoder { + pub fn from_ctx(_ctx: &SubstrateContext) -> EncodeResult { + Ok(Self) + } + + pub fn encode_input(input: &'_ Proto::SigningInput<'_>) -> EncodeResult { + let ctx = ctx_from_tw(input)?; + let encoder = Self::from_ctx(&ctx)?; + encoder.encode_call(&input.message_oneof) + } + + fn encode_batch_transfer(&self, bt: &BatchTransfer) -> EncodeResult { + let transfers = bt + .transfers + .iter() + .map(|t| { + let call = SigningVariant::balance_call(Proto::Balance { + message_oneof: BalanceVariant::transfer(t.clone()), + }); + self.encode_call(&call) + }) + .collect::>>()?; + + self.encode_batch(transfers, &bt.call_indices) + } + + fn encode_batch_asset_transfer(&self, bat: &BatchAssetTransfer) -> EncodeResult { + let transfers = bat + .transfers + .iter() + .map(|t| { + let call = SigningVariant::balance_call(Proto::Balance { + message_oneof: BalanceVariant::asset_transfer(t.clone()), + }); + self.encode_call(&call) + }) + .collect::>>()?; + + self.encode_batch(transfers, &bat.call_indices) + } + + fn encode_balance_batch_call(&self, b: &Balance) -> EncodeResult> { + match &b.message_oneof { + BalanceVariant::batchTransfer(bt) => { + let batch = self.encode_batch_transfer(bt)?; + Ok(Some(batch)) + }, + BalanceVariant::batch_asset_transfer(bat) => { + let batch = self.encode_batch_asset_transfer(bat)?; + Ok(Some(batch)) + }, + _ => Ok(None), + } + } + + fn encode_staking_bond_and_nominate(&self, ban: &BondAndNominate) -> EncodeResult { + // Encode a bond call + let first = self.encode_call(&SigningVariant::staking_call(Proto::Staking { + message_oneof: StakingVariant::bond(Bond { + controller: ban.controller.clone(), + value: ban.value.clone(), + reward_destination: ban.reward_destination, + // TODO: `BondAndNominate` needs 3 call_indices values to support this. + //call_indices: ban.call_indices.clone(), + call_indices: None, + }), + }))?; + + // Encode a nominate call + let second = self.encode_call(&SigningVariant::staking_call(Proto::Staking { + message_oneof: StakingVariant::nominate(Nominate { + nominators: ban.nominators.clone(), + // TODO: `BondAndNominate` needs 3 call_indices values to support this. + //call_indices: ban.call_indices.clone(), + call_indices: None, + }), + }))?; + + // Encode both calls as batched + self.encode_batch(vec![first, second], &ban.call_indices) + } + + fn encode_staking_chill_and_unbond(&self, cau: &ChillAndUnbond) -> EncodeResult { + let first = self.encode_call(&SigningVariant::staking_call(Proto::Staking { + message_oneof: StakingVariant::chill(Chill { + // TODO: `ChillAndUnbond` needs 3 call_indices values to support this. + //call_indices: cau.call_indices.clone(), + call_indices: None, + }), + }))?; + + let second = self.encode_call(&SigningVariant::staking_call(Proto::Staking { + message_oneof: StakingVariant::unbond(Unbond { + value: cau.value.clone(), + // TODO: `ChillAndUnbond` needs 3 call_indices values to support this. + //call_indices: cau.call_indices.clone(), + call_indices: None, + }), + }))?; + + // Encode both calls as batched + self.encode_batch(vec![first, second], &cau.call_indices) + } + + fn encode_staking_batch_call(&self, s: &Staking) -> EncodeResult> { + match &s.message_oneof { + StakingVariant::bond_and_nominate(ban) => { + let batch = self.encode_staking_bond_and_nominate(&ban)?; + Ok(Some(batch)) + }, + StakingVariant::chill_and_unbond(cau) => { + let batch = self.encode_staking_chill_and_unbond(&cau)?; + Ok(Some(batch)) + }, + _ => Ok(None), + } + } + + pub fn encode_call(&self, msg: &SigningVariant<'_>) -> EncodeResult { + // Special case for batches. + match msg { + SigningVariant::balance_call(b) => { + if let Some(batch) = self.encode_balance_batch_call(b)? { + return Ok(batch); + } + }, + SigningVariant::staking_call(s) => { + if let Some(batch) = self.encode_staking_batch_call(s)? { + return Ok(batch); + } + }, + _ => (), + } + // non-batch calls. + let call = match msg { + SigningVariant::balance_call(b) => { + PolymeshBalances::encode_call(b)?.map(PolymeshCall::Balances) + }, + SigningVariant::identity_call(msg) => { + PolymeshIdentity::encode_call(msg)?.map(PolymeshCall::Identity) + }, + SigningVariant::staking_call(s) => { + PolymeshStaking::encode_call(s)?.map(PolymeshCall::Staking) + }, + SigningVariant::None => { + return EncodeError::NotSupported + .tw_result("Staking call variant is None".to_string()); + }, + }; + Ok(RawOwned(call.to_scale())) + } + + fn encode_batch( + &self, + calls: Vec, + ci: &Option, + ) -> EncodeResult { + let ci = validate_call_index(ci)?; + let call = PolymeshCall::Utility(PolymeshUtility::BatchAll { calls }); + let call = ci.wrap(RawOwned(call.to_scale())); + Ok(RawOwned(call.to_scale())) + } +} diff --git a/rust/chains/tw_polymesh/src/call_encoder/mod.rs b/rust/chains/tw_polymesh/src/call_encoder/mod.rs deleted file mode 100644 index e0337ab3599..00000000000 --- a/rust/chains/tw_polymesh/src/call_encoder/mod.rs +++ /dev/null @@ -1,193 +0,0 @@ -use crate::ctx_from_tw; -use tw_proto::Polymesh::Proto::{ - self, - mod_Balance::{BatchAssetTransfer, BatchTransfer, OneOfmessage_oneof as BalanceVariant}, - mod_CallIndices::OneOfvariant as CallIndicesVariant, - mod_SigningInput::OneOfmessage_oneof as SigningVariant, - mod_Staking::{ - Bond, BondAndNominate, Chill, ChillAndUnbond, Nominate, - OneOfmessage_oneof as StakingVariant, Unbond, - }, - Balance, CallIndices, Staking, -}; -use tw_scale::{RawOwned, ToScale}; -use tw_substrate::*; - -pub mod polymesh; -use polymesh::*; - -pub fn validate_call_index(call_index: &Option) -> EncodeResult { - let index = match call_index { - Some(CallIndices { - variant: CallIndicesVariant::custom(c), - }) => Some((c.module_index, c.method_index)), - _ => None, - }; - CallIndex::from_tw(index) -} - -pub struct CallEncoder; - -impl CallEncoder { - pub fn from_ctx(_ctx: &SubstrateContext) -> EncodeResult { - Ok(Self) - } - - pub fn encode_input(input: &'_ Proto::SigningInput<'_>) -> EncodeResult { - let ctx = ctx_from_tw(input)?; - let encoder = Self::from_ctx(&ctx)?; - encoder.encode_call(&input.message_oneof) - } - - fn encode_batch_transfer(&self, bt: &BatchTransfer) -> EncodeResult { - let transfers = bt - .transfers - .iter() - .map(|t| { - let call = SigningVariant::balance_call(Proto::Balance { - message_oneof: BalanceVariant::transfer(t.clone()), - }); - self.encode_call(&call) - }) - .collect::>>()?; - - self.encode_batch(transfers, &bt.call_indices) - } - - fn encode_batch_asset_transfer(&self, bat: &BatchAssetTransfer) -> EncodeResult { - let transfers = bat - .transfers - .iter() - .map(|t| { - let call = SigningVariant::balance_call(Proto::Balance { - message_oneof: BalanceVariant::asset_transfer(t.clone()), - }); - self.encode_call(&call) - }) - .collect::>>()?; - - self.encode_batch(transfers, &bat.call_indices) - } - - fn encode_balance_batch_call(&self, b: &Balance) -> EncodeResult> { - match &b.message_oneof { - BalanceVariant::batchTransfer(bt) => { - let batch = self.encode_batch_transfer(bt)?; - Ok(Some(batch)) - }, - BalanceVariant::batch_asset_transfer(bat) => { - let batch = self.encode_batch_asset_transfer(bat)?; - Ok(Some(batch)) - }, - _ => Ok(None), - } - } - - fn encode_staking_bond_and_nominate(&self, ban: &BondAndNominate) -> EncodeResult { - // Encode a bond call - let first = self.encode_call(&SigningVariant::staking_call(Proto::Staking { - message_oneof: StakingVariant::bond(Bond { - controller: ban.controller.clone(), - value: ban.value.clone(), - reward_destination: ban.reward_destination, - // TODO: `BondAndNominate` needs 3 call_indices values to support this. - //call_indices: ban.call_indices.clone(), - call_indices: None, - }), - }))?; - - // Encode a nominate call - let second = self.encode_call(&SigningVariant::staking_call(Proto::Staking { - message_oneof: StakingVariant::nominate(Nominate { - nominators: ban.nominators.clone(), - // TODO: `BondAndNominate` needs 3 call_indices values to support this. - //call_indices: ban.call_indices.clone(), - call_indices: None, - }), - }))?; - - // Encode both calls as batched - self.encode_batch(vec![first, second], &ban.call_indices) - } - - fn encode_staking_chill_and_unbond(&self, cau: &ChillAndUnbond) -> EncodeResult { - let first = self.encode_call(&SigningVariant::staking_call(Proto::Staking { - message_oneof: StakingVariant::chill(Chill { - // TODO: `ChillAndUnbond` needs 3 call_indices values to support this. - //call_indices: cau.call_indices.clone(), - call_indices: None, - }), - }))?; - - let second = self.encode_call(&SigningVariant::staking_call(Proto::Staking { - message_oneof: StakingVariant::unbond(Unbond { - value: cau.value.clone(), - // TODO: `ChillAndUnbond` needs 3 call_indices values to support this. - //call_indices: cau.call_indices.clone(), - call_indices: None, - }), - }))?; - - // Encode both calls as batched - self.encode_batch(vec![first, second], &cau.call_indices) - } - - fn encode_staking_batch_call(&self, s: &Staking) -> EncodeResult> { - match &s.message_oneof { - StakingVariant::bond_and_nominate(ban) => { - let batch = self.encode_staking_bond_and_nominate(&ban)?; - Ok(Some(batch)) - }, - StakingVariant::chill_and_unbond(cau) => { - let batch = self.encode_staking_chill_and_unbond(&cau)?; - Ok(Some(batch)) - }, - _ => Ok(None), - } - } - - pub fn encode_call(&self, msg: &SigningVariant<'_>) -> EncodeResult { - // Special case for batches. - match msg { - SigningVariant::balance_call(b) => { - if let Some(batch) = self.encode_balance_batch_call(b)? { - return Ok(batch); - } - }, - SigningVariant::staking_call(s) => { - if let Some(batch) = self.encode_staking_batch_call(s)? { - return Ok(batch); - } - }, - _ => (), - } - // non-batch calls. - let call = match msg { - SigningVariant::balance_call(b) => { - PolymeshBalances::encode_call(b)?.map(PolymeshCall::Balances) - }, - SigningVariant::identity_call(msg) => { - PolymeshIdentity::encode_call(msg)?.map(PolymeshCall::Identity) - }, - SigningVariant::staking_call(s) => { - PolymeshStaking::encode_call(s)?.map(PolymeshCall::Staking) - }, - SigningVariant::None => { - return EncodeError::NotSupported - .tw_result("Staking call variant is None".to_string()); - }, - }; - Ok(RawOwned(call.to_scale())) - } - - fn encode_batch( - &self, - calls: Vec, - ci: &Option, - ) -> EncodeResult { - let ci = validate_call_index(ci)?; - let call = PolymeshCall::Utility(PolymeshUtility::BatchAll { calls }); - let call = ci.wrap(RawOwned(call.to_scale())); - Ok(RawOwned(call.to_scale())) - } -} diff --git a/rust/chains/tw_polymesh/src/lib.rs b/rust/chains/tw_polymesh/src/lib.rs index 9f576cad779..af8f7ec8b0d 100644 --- a/rust/chains/tw_polymesh/src/lib.rs +++ b/rust/chains/tw_polymesh/src/lib.rs @@ -11,6 +11,7 @@ use tw_substrate::*; pub mod call_encoder; pub mod entry; +pub mod types; pub const POLYMESH_PREFIX: u16 = 12; pub const POLYMESH: NetworkId = NetworkId::new_unchecked(POLYMESH_PREFIX); diff --git a/rust/chains/tw_polymesh/src/call_encoder/polymesh.rs b/rust/chains/tw_polymesh/src/types.rs similarity index 51% rename from rust/chains/tw_polymesh/src/call_encoder/polymesh.rs rename to rust/chains/tw_polymesh/src/types.rs index e744db562c3..066c7cc5252 100644 --- a/rust/chains/tw_polymesh/src/call_encoder/polymesh.rs +++ b/rust/chains/tw_polymesh/src/types.rs @@ -5,26 +5,16 @@ use std::{ use tw_coin_entry::error::prelude::TWError; use tw_hash::{Hash, H256}; -use tw_number::U256; use tw_proto::Polymesh::Proto::{ - mod_Balance::{OneOfmessage_oneof as BalanceVariant, Transfer}, - mod_Identity::{ - mod_AddAuthorization::mod_Authorization::OneOfauth_oneof as AuthVariant, AddAuthorization, - JoinIdentityAsKey, OneOfmessage_oneof as IdentityVariant, - }, mod_SecondaryKeyPermissions::{ AssetPermissions as TWAssetPermissions, ExtrinsicPermissions as TWExtrinsicPermissions, PalletPermissions as TWPalletPermissions, PortfolioPermissions as TWPortfolioPermissions, RestrictionKind as TWRestrictionKind, }, - mod_Staking::{ - Bond, BondExtra, Chill, Nominate, OneOfmessage_oneof as StakingVariant, Rebond, Unbond, - WithdrawUnbonded, - }, - AssetId as TWAssetId, Balance, Identity, IdentityId as TWIdentityId, - PortfolioId as TWPortfolioId, SecondaryKeyPermissions, Staking, + AssetId as TWAssetId, IdentityId as TWIdentityId, PortfolioId as TWPortfolioId, + SecondaryKeyPermissions, }; -use tw_scale::{impl_enum_scale, impl_struct_scale, Compact, RawOwned}; +use tw_scale::{impl_enum_scale, impl_struct_scale, ToScale}; use tw_ss58_address::SS58Address; use tw_substrate::address::SubstrateAddress; @@ -118,55 +108,6 @@ impl TryFrom<&TWPortfolioId<'_>> for PortfolioId { } } -impl_enum_scale!( - #[derive(Clone, Debug)] - pub enum PolymeshBalances { - Transfer { - dest: MultiAddress, - value: Compact, - } = 0x00, - TransferWithMemo { - dest: MultiAddress, - value: Compact, - memo: Option, - } = 0x01, - } -); - -impl PolymeshBalances { - fn encode_transfer(t: &Transfer) -> WithCallIndexResult { - let ci = validate_call_index(&t.call_indices)?; - let address = - SS58Address::from_str(&t.to_address).map_err(|_| EncodeError::InvalidAddress)?; - let value = U256::from_big_endian_slice(&t.value) - .map_err(|_| EncodeError::InvalidValue)? - .try_into() - .map_err(|_| EncodeError::InvalidValue)?; - - if !t.memo.is_empty() { - Ok(ci.wrap(Self::TransferWithMemo { - dest: address.into(), - value: Compact(value), - memo: Some(Memo::new(&t.memo)), - })) - } else { - Ok(ci.wrap(Self::Transfer { - dest: address.into(), - value: Compact(value), - })) - } - } - - pub fn encode_call(b: &Balance) -> WithCallIndexResult { - match &b.message_oneof { - BalanceVariant::transfer(t) => Self::encode_transfer(t), - _ => Err(EncodeError::NotSupported) - .into_tw() - .context("Unsupported balance call"), - } - } -} - impl_enum_scale!( #[derive(Clone, Debug)] pub enum Signatory { @@ -381,68 +322,6 @@ impl_enum_scale!( } ); -impl_enum_scale!( - #[derive(Clone, Debug)] - pub enum PolymeshIdentity { - JoinIdentity { - auth_id: u64, - } = 0x04, - AddAuthorization { - target: Signatory, - data: AuthorizationData, - expiry: Option, - } = 0x0a, - } -); - -impl PolymeshIdentity { - fn encode_join_identity(join: &JoinIdentityAsKey) -> WithCallIndexResult { - let ci = validate_call_index(&join.call_indices)?; - Ok(ci.wrap(Self::JoinIdentity { - auth_id: join.auth_id, - })) - } - - fn encode_add_authorization(auth: &AddAuthorization) -> WithCallIndexResult { - let ci = validate_call_index(&auth.call_indices)?; - let target = - SS58Address::from_str(&auth.target).map_err(|_| EncodeError::InvalidAddress)?; - let data = if let Some(auth) = &auth.authorization { - match &auth.auth_oneof { - AuthVariant::join_identity(perms) => AuthorizationData::JoinIdentity { - permissions: perms.try_into().map_err(|_| EncodeError::InvalidValue)?, - }, - AuthVariant::None => { - return EncodeError::NotSupported - .tw_result("Unsupported Authorization".to_string()); - }, - } - } else { - return EncodeError::NotSupported.tw_result("Missing Authorization".to_string()); - }; - - Ok(ci.wrap(Self::AddAuthorization { - target: Signatory::Account(SubstrateAddress(target)), - data, - expiry: if auth.expiry > 0 { - Some(auth.expiry) - } else { - None - }, - })) - } - - pub fn encode_call(ident: &Identity) -> WithCallIndexResult { - match &ident.message_oneof { - IdentityVariant::join_identity_as_key(t) => Self::encode_join_identity(t), - IdentityVariant::add_authorization(a) => Self::encode_add_authorization(a), - _ => Err(EncodeError::NotSupported) - .into_tw() - .context("Unsupported identity call"), - } - } -} - impl_enum_scale!( #[derive(Clone, Debug)] pub enum RewardDestination { @@ -470,143 +349,3 @@ impl RewardDestination { } } } - -impl_enum_scale!( - #[derive(Clone, Debug)] - pub enum PolymeshStaking { - Bond { - controller: MultiAddress, - value: Compact, - reward: RewardDestination, - } = 0x00, - BondExtra { - max_additional: Compact, - } = 0x01, - Unbond { - value: Compact, - } = 0x02, - WithdrawUnbonded { - num_slashing_spans: u32, - } = 0x03, - Nominate { - targets: Vec, - } = 0x05, - Chill = 0x06, - Rebond { - value: Compact, - } = 0x13, - } -); - -impl PolymeshStaking { - fn encode_bond(b: &Bond) -> WithCallIndexResult { - let ci = validate_call_index(&b.call_indices)?; - let value = U256::from_big_endian_slice(&b.value) - .map_err(|_| EncodeError::InvalidValue)? - .try_into() - .map_err(|_| EncodeError::InvalidValue)?; - let controller = - SS58Address::from_str(&b.controller).map_err(|_| EncodeError::InvalidAddress)?; - - Ok(ci.wrap(Self::Bond { - controller: controller.into(), - value: Compact(value), - reward: RewardDestination::from_tw(&b.reward_destination)?, - })) - } - - fn encode_bond_extra(b: &BondExtra) -> WithCallIndexResult { - let ci = validate_call_index(&b.call_indices)?; - let value = U256::from_big_endian_slice(&b.value) - .map_err(|_| EncodeError::InvalidValue)? - .try_into() - .map_err(|_| EncodeError::InvalidValue)?; - - Ok(ci.wrap(Self::BondExtra { - max_additional: Compact(value), - })) - } - - fn encode_chill(c: &Chill) -> WithCallIndexResult { - let ci = validate_call_index(&c.call_indices)?; - Ok(ci.wrap(Self::Chill)) - } - - fn encode_unbond(b: &Unbond) -> WithCallIndexResult { - let ci = validate_call_index(&b.call_indices)?; - let value = U256::from_big_endian_slice(&b.value) - .map_err(|_| EncodeError::InvalidValue)? - .try_into() - .map_err(|_| EncodeError::InvalidValue)?; - - Ok(ci.wrap(Self::Unbond { - value: Compact(value), - })) - } - - fn encode_rebond(b: &Rebond) -> WithCallIndexResult { - let ci = validate_call_index(&b.call_indices)?; - let value = U256::from_big_endian_slice(&b.value) - .map_err(|_| EncodeError::InvalidValue)? - .try_into() - .map_err(|_| EncodeError::InvalidValue)?; - - Ok(ci.wrap(Self::Rebond { - value: Compact(value), - })) - } - - fn encode_withdraw_unbonded(b: &WithdrawUnbonded) -> WithCallIndexResult { - let ci = validate_call_index(&b.call_indices)?; - Ok(ci.wrap(Self::WithdrawUnbonded { - num_slashing_spans: b.slashing_spans as u32, - })) - } - - fn encode_nominate(b: &Nominate) -> WithCallIndexResult { - let ci = validate_call_index(&b.call_indices)?; - let targets = b - .nominators - .iter() - .map(|target| { - let account = - SS58Address::from_str(target).map_err(|_| EncodeError::InvalidAddress)?; - Ok(account.into()) - }) - .collect::>>()?; - Ok(ci.wrap(Self::Nominate { targets })) - } - - pub fn encode_call(s: &Staking) -> WithCallIndexResult { - match &s.message_oneof { - StakingVariant::bond(b) => Self::encode_bond(b), - StakingVariant::bond_extra(b) => Self::encode_bond_extra(b), - StakingVariant::chill(b) => Self::encode_chill(b), - StakingVariant::unbond(b) => Self::encode_unbond(b), - StakingVariant::withdraw_unbonded(b) => Self::encode_withdraw_unbonded(b), - StakingVariant::rebond(b) => Self::encode_rebond(b), - StakingVariant::nominate(b) => Self::encode_nominate(b), - _ => Err(EncodeError::NotSupported) - .into_tw() - .context("Unsupported staking call"), - } - } -} - -impl_enum_scale!( - #[derive(Clone, Debug)] - pub enum PolymeshUtility { - BatchAll { calls: Vec } = 0x02, - } -); - -impl_enum_scale!( - #[derive(Clone, Debug)] - pub enum PolymeshCall { - Balances(PolymeshBalances) = 0x05, - Identity(PolymeshIdentity) = 0x07, - Staking(PolymeshStaking) = 0x11, - Utility(PolymeshUtility) = 0x29, - } -); - From d4bd1b59597b2b16fed66b8ff68d95c2b82a28b5 Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Tue, 3 Dec 2024 14:16:31 +0000 Subject: [PATCH 08/22] Add support for leave identity as key. --- rust/chains/tw_polymesh/src/call_encoder.rs | 28 +++++++++++-------- .../tests/chains/polymesh/polymesh_compile.rs | 13 ++++----- src/proto/Polymesh.proto | 7 +++++ 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/rust/chains/tw_polymesh/src/call_encoder.rs b/rust/chains/tw_polymesh/src/call_encoder.rs index d7a13fd2dfa..bdf3c76fef6 100644 --- a/rust/chains/tw_polymesh/src/call_encoder.rs +++ b/rust/chains/tw_polymesh/src/call_encoder.rs @@ -11,7 +11,7 @@ use tw_proto::Polymesh::Proto::{ mod_CallIndices::OneOfvariant as CallIndicesVariant, mod_Identity::{ mod_AddAuthorization::mod_Authorization::OneOfauth_oneof as AuthVariant, AddAuthorization, - JoinIdentityAsKey, OneOfmessage_oneof as IdentityVariant, + JoinIdentityAsKey, LeaveIdentityAsKey, OneOfmessage_oneof as IdentityVariant, }, mod_SigningInput::OneOfmessage_oneof as SigningVariant, mod_Staking::{ @@ -95,6 +95,7 @@ impl_enum_scale!( JoinIdentity { auth_id: u64, } = 0x04, + LeaveIdentity = 0x05, AddAuthorization { target: Signatory, data: AuthorizationData, @@ -104,18 +105,22 @@ impl_enum_scale!( ); impl PolymeshIdentity { - fn encode_join_identity(join: &JoinIdentityAsKey) -> WithCallIndexResult { - let ci = validate_call_index(&join.call_indices)?; + fn encode_join_identity(msg: &JoinIdentityAsKey) -> WithCallIndexResult { + let ci = validate_call_index(&msg.call_indices)?; Ok(ci.wrap(Self::JoinIdentity { - auth_id: join.auth_id, + auth_id: msg.auth_id, })) } - fn encode_add_authorization(auth: &AddAuthorization) -> WithCallIndexResult { - let ci = validate_call_index(&auth.call_indices)?; - let target = - SS58Address::from_str(&auth.target).map_err(|_| EncodeError::InvalidAddress)?; - let data = if let Some(auth) = &auth.authorization { + fn encode_leave_identity(msg: &LeaveIdentityAsKey) -> WithCallIndexResult { + let ci = validate_call_index(&msg.call_indices)?; + Ok(ci.wrap(Self::LeaveIdentity)) + } + + fn encode_add_authorization(msg: &AddAuthorization) -> WithCallIndexResult { + let ci = validate_call_index(&msg.call_indices)?; + let target = SS58Address::from_str(&msg.target).map_err(|_| EncodeError::InvalidAddress)?; + let data = if let Some(auth) = &msg.authorization { match &auth.auth_oneof { AuthVariant::join_identity(perms) => AuthorizationData::JoinIdentity { permissions: perms.try_into().map_err(|_| EncodeError::InvalidValue)?, @@ -132,8 +137,8 @@ impl PolymeshIdentity { Ok(ci.wrap(Self::AddAuthorization { target: Signatory::Account(SubstrateAddress(target.into())), data, - expiry: if auth.expiry > 0 { - Some(auth.expiry) + expiry: if msg.expiry > 0 { + Some(msg.expiry) } else { None }, @@ -143,6 +148,7 @@ impl PolymeshIdentity { pub fn encode_call(ident: &Identity) -> WithCallIndexResult { match &ident.message_oneof { IdentityVariant::join_identity_as_key(t) => Self::encode_join_identity(t), + IdentityVariant::leave_identity_as_key(t) => Self::encode_leave_identity(t), IdentityVariant::add_authorization(a) => Self::encode_add_authorization(a), _ => EncodeError::NotSupported.tw_result("Unsupported identity call".to_string()), } diff --git a/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs b/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs index 1165f6a80c4..2dd04398b70 100644 --- a/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs +++ b/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs @@ -18,7 +18,7 @@ use tw_proto::Polymesh::Proto::{ mod_Balance::Transfer, mod_Identity::{ mod_AddAuthorization::{mod_Authorization::OneOfauth_oneof as AuthVariant, Authorization}, - AddAuthorization, JoinIdentityAsKey, + AddAuthorization, JoinIdentityAsKey, LeaveIdentityAsKey, }, mod_SecondaryKeyPermissions::{ AssetPermissions, ExtrinsicPermissions, PortfolioPermissions, RestrictionKind, @@ -224,19 +224,18 @@ fn test_polymesh_compile_transfer() { // Test Leave identity transaction. #[test] -#[ignore] fn test_polymesh_compile_leave_identity() { // https://polymesh.subscan.io/extrinsic/16102113-1 // Step 1: Prepare input. - let block_hash = "77d32517dcc7b74501096afdcff3af72008a2c489e17083f56629d195e5c6a1d" + let block_hash = "6651325ae8f7c1726f8a610827b5e4300a504081d5fc85c17199d95bb6d9605c" .decode_hex() .unwrap(); let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); let input = Proto::SigningInput { network: 12, - nonce: 1, + nonce: 2, block_hash: block_hash.into(), genesis_hash: genesis_hash.into(), spec_version: 7_000_005, @@ -245,10 +244,8 @@ fn test_polymesh_compile_leave_identity() { block_number: 16_102_110, period: 64, }), - // TODO: This is not correct, need to fix. This is a placeholder. message_oneof: identity_call( - Proto::mod_Identity::OneOfmessage_oneof::join_identity_as_key(JoinIdentityAsKey { - auth_id: 52_188, + Proto::mod_Identity::OneOfmessage_oneof::leave_identity_as_key(LeaveIdentityAsKey { ..Default::default() }), ), @@ -261,7 +258,7 @@ fn test_polymesh_compile_leave_identity() { assert_eq!(preimage_output.error, SigningError::OK); - assert_eq!(preimage_output.data.to_hex(), "0705..TODO...."); + assert_eq!(preimage_output.data.to_hex(), "0705e5010800c5cf6a00070000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f40636651325ae8f7c1726f8a610827b5e4300a504081d5fc85c17199d95bb6d9605c"); // Step 3: Compile transaction info diff --git a/src/proto/Polymesh.proto b/src/proto/Polymesh.proto index 3701d207054..083e776d695 100644 --- a/src/proto/Polymesh.proto +++ b/src/proto/Polymesh.proto @@ -278,6 +278,12 @@ message Identity { uint64 auth_id = 2; } + // Identity::leave_identity_as_key call + message LeaveIdentityAsKey { + // call indices + CallIndices call_indices = 1; + } + // Identity::add_authorization call message AddAuthorization { message Authorization { @@ -312,6 +318,7 @@ message Identity { oneof message_oneof { JoinIdentityAsKey join_identity_as_key = 1; AddAuthorization add_authorization = 2; + LeaveIdentityAsKey leave_identity_as_key = 3; } } From 8d1fe4578ac68ee2032fb3892d9b087e0d32b3c7 Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Tue, 3 Dec 2024 14:27:06 +0000 Subject: [PATCH 09/22] Fix custom call indices for staking batch calls. --- rust/chains/tw_polymesh/src/call_encoder.rs | 16 ++++------------ src/proto/Polymesh.proto | 12 ++++++++++++ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/rust/chains/tw_polymesh/src/call_encoder.rs b/rust/chains/tw_polymesh/src/call_encoder.rs index bdf3c76fef6..5cb89e3b780 100644 --- a/rust/chains/tw_polymesh/src/call_encoder.rs +++ b/rust/chains/tw_polymesh/src/call_encoder.rs @@ -356,9 +356,7 @@ impl CallEncoder { controller: ban.controller.clone(), value: ban.value.clone(), reward_destination: ban.reward_destination, - // TODO: `BondAndNominate` needs 3 call_indices values to support this. - //call_indices: ban.call_indices.clone(), - call_indices: None, + call_indices: ban.bond_call_indices.clone(), }), }))?; @@ -366,9 +364,7 @@ impl CallEncoder { let second = self.encode_call(&SigningVariant::staking_call(Proto::Staking { message_oneof: StakingVariant::nominate(Nominate { nominators: ban.nominators.clone(), - // TODO: `BondAndNominate` needs 3 call_indices values to support this. - //call_indices: ban.call_indices.clone(), - call_indices: None, + call_indices: ban.nominate_call_indices.clone(), }), }))?; @@ -379,18 +375,14 @@ impl CallEncoder { fn encode_staking_chill_and_unbond(&self, cau: &ChillAndUnbond) -> EncodeResult { let first = self.encode_call(&SigningVariant::staking_call(Proto::Staking { message_oneof: StakingVariant::chill(Chill { - // TODO: `ChillAndUnbond` needs 3 call_indices values to support this. - //call_indices: cau.call_indices.clone(), - call_indices: None, + call_indices: cau.chill_call_indices.clone(), }), }))?; let second = self.encode_call(&SigningVariant::staking_call(Proto::Staking { message_oneof: StakingVariant::unbond(Unbond { value: cau.value.clone(), - // TODO: `ChillAndUnbond` needs 3 call_indices values to support this. - //call_indices: cau.call_indices.clone(), - call_indices: None, + call_indices: cau.unbond_call_indices.clone(), }), }))?; diff --git a/src/proto/Polymesh.proto b/src/proto/Polymesh.proto index 083e776d695..3d4ae6af2d1 100644 --- a/src/proto/Polymesh.proto +++ b/src/proto/Polymesh.proto @@ -135,6 +135,12 @@ message Staking { // call indices CallIndices call_indices = 5; + + // Staking.Bond call indices + CallIndices bond_call_indices = 6; + + // Staking.Nominate call indices + CallIndices nominate_call_indices = 7; } // Bond extra amount @@ -188,6 +194,12 @@ message Staking { // call indices CallIndices call_indices = 2; + + // Staking.Chill call indices + CallIndices chill_call_indices = 3; + + // Staking.Unbond call indices + CallIndices unbond_call_indices = 4; } // Chill From 7de6610cde28f7f2f995c73a47806fd576973a8e Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Wed, 4 Dec 2024 12:57:28 +0000 Subject: [PATCH 10/22] Refactor batched call support. --- rust/chains/tw_polymesh/src/call_encoder.rs | 202 +++++------------- rust/chains/tw_polymesh/src/entry.rs | 7 +- rust/chains/tw_polymesh/src/lib.rs | 17 +- rust/chains/tw_polymesh/tests/extrinsic.rs | 56 ++--- rust/tw_tests/tests/chains/polymesh/mod.rs | 16 +- .../tests/chains/polymesh/polymesh_compile.rs | 8 +- .../tests/chains/polymesh/polymesh_sign.rs | 4 +- src/proto/Polymesh.proto | 149 +++++-------- tests/chains/Polymesh/TWAnySignerTests.cpp | 6 +- 9 files changed, 169 insertions(+), 296 deletions(-) diff --git a/rust/chains/tw_polymesh/src/call_encoder.rs b/rust/chains/tw_polymesh/src/call_encoder.rs index 5cb89e3b780..a4173c72439 100644 --- a/rust/chains/tw_polymesh/src/call_encoder.rs +++ b/rust/chains/tw_polymesh/src/call_encoder.rs @@ -5,20 +5,19 @@ use crate::types::*; use tw_number::U256; use tw_proto::Polymesh::Proto::{ self, - mod_Balance::{ - BatchAssetTransfer, BatchTransfer, OneOfmessage_oneof as BalanceVariant, Transfer, - }, + mod_Balance::{OneOfmessage_oneof as BalanceVariant, Transfer}, mod_CallIndices::OneOfvariant as CallIndicesVariant, mod_Identity::{ mod_AddAuthorization::mod_Authorization::OneOfauth_oneof as AuthVariant, AddAuthorization, JoinIdentityAsKey, LeaveIdentityAsKey, OneOfmessage_oneof as IdentityVariant, }, - mod_SigningInput::OneOfmessage_oneof as SigningVariant, + mod_RuntimeCall::OneOfpallet_oneof as RuntimeCallVariant, mod_Staking::{ - Bond, BondAndNominate, BondExtra, Chill, ChillAndUnbond, Nominate, - OneOfmessage_oneof as StakingVariant, Rebond, Unbond, WithdrawUnbonded, + Bond, BondExtra, Chill, Nominate, OneOfmessage_oneof as StakingVariant, Rebond, Unbond, + WithdrawUnbonded, }, - Balance, CallIndices, Identity, Staking, + mod_Utility::{BatchKind, OneOfmessage_oneof as UtilityVariant}, + Balance, CallIndices, Identity, Staking, Utility, }; use tw_scale::{impl_enum_scale, Compact, RawOwned, ToScale}; use tw_ss58_address::SS58Address; @@ -278,10 +277,40 @@ impl PolymeshStaking { impl_enum_scale!( #[derive(Clone, Debug)] pub enum PolymeshUtility { + Batch { calls: Vec } = 0x01, BatchAll { calls: Vec } = 0x02, + ForceBatch { calls: Vec } = 0x03, } ); +impl PolymeshUtility { + pub fn encode_call(encoder: &mut CallEncoder, u: &Utility) -> WithCallIndexResult { + if encoder.batch_depth > 0 { + return EncodeError::NotSupported + .tw_result("Nested batch calls not allowed".to_string()); + } + encoder.batch_depth += 1; + match &u.message_oneof { + UtilityVariant::batch(b) => { + let ci = validate_call_index(&b.call_indices)?; + let calls = b + .calls + .iter() + .map(|call| encoder.encode_runtime_call(call)) + .collect::>>()?; + encoder.batch_depth -= 1; + let batch = match b.kind { + BatchKind::StopOnError => Self::Batch { calls }, + BatchKind::Atomic => Self::BatchAll { calls }, + BatchKind::Optimistic => Self::ForceBatch { calls }, + }; + Ok(ci.wrap(batch)) + }, + _ => EncodeError::NotSupported.tw_result("Unsupported utility call".to_string()), + } + } +} + impl_enum_scale!( #[derive(Clone, Debug)] pub enum PolymeshCall { @@ -292,160 +321,43 @@ impl_enum_scale!( } ); -pub struct CallEncoder; +pub struct CallEncoder { + pub batch_depth: u32, +} impl CallEncoder { pub fn from_ctx(_ctx: &SubstrateContext) -> EncodeResult { - Ok(Self) + Ok(Self { batch_depth: 0 }) } pub fn encode_input(input: &'_ Proto::SigningInput<'_>) -> EncodeResult { let ctx = ctx_from_tw(input)?; - let encoder = Self::from_ctx(&ctx)?; - encoder.encode_call(&input.message_oneof) + let mut encoder = Self::from_ctx(&ctx)?; + let call = input.runtime_call.as_ref().ok_or_else(|| { + EncodeError::InvalidValue.with_context("Missing runtime call".to_string()) + })?; + encoder.encode_runtime_call(&call) } - fn encode_batch_transfer(&self, bt: &BatchTransfer) -> EncodeResult { - let transfers = bt - .transfers - .iter() - .map(|t| { - let call = SigningVariant::balance_call(Proto::Balance { - message_oneof: BalanceVariant::transfer(t.clone()), - }); - self.encode_call(&call) - }) - .collect::>>()?; - - self.encode_batch(transfers, &bt.call_indices) - } - - fn encode_batch_asset_transfer(&self, bat: &BatchAssetTransfer) -> EncodeResult { - let transfers = bat - .transfers - .iter() - .map(|t| { - let call = SigningVariant::balance_call(Proto::Balance { - message_oneof: BalanceVariant::asset_transfer(t.clone()), - }); - self.encode_call(&call) - }) - .collect::>>()?; - - self.encode_batch(transfers, &bat.call_indices) - } - - fn encode_balance_batch_call(&self, b: &Balance) -> EncodeResult> { - match &b.message_oneof { - BalanceVariant::batchTransfer(bt) => { - let batch = self.encode_batch_transfer(bt)?; - Ok(Some(batch)) + pub fn encode_runtime_call(&mut self, call: &Proto::RuntimeCall) -> EncodeResult { + let call = match &call.pallet_oneof { + RuntimeCallVariant::balance_call(msg) => { + PolymeshBalances::encode_call(msg)?.map(PolymeshCall::Balances) }, - BalanceVariant::batch_asset_transfer(bat) => { - let batch = self.encode_batch_asset_transfer(bat)?; - Ok(Some(batch)) - }, - _ => Ok(None), - } - } - - fn encode_staking_bond_and_nominate(&self, ban: &BondAndNominate) -> EncodeResult { - // Encode a bond call - let first = self.encode_call(&SigningVariant::staking_call(Proto::Staking { - message_oneof: StakingVariant::bond(Bond { - controller: ban.controller.clone(), - value: ban.value.clone(), - reward_destination: ban.reward_destination, - call_indices: ban.bond_call_indices.clone(), - }), - }))?; - - // Encode a nominate call - let second = self.encode_call(&SigningVariant::staking_call(Proto::Staking { - message_oneof: StakingVariant::nominate(Nominate { - nominators: ban.nominators.clone(), - call_indices: ban.nominate_call_indices.clone(), - }), - }))?; - - // Encode both calls as batched - self.encode_batch(vec![first, second], &ban.call_indices) - } - - fn encode_staking_chill_and_unbond(&self, cau: &ChillAndUnbond) -> EncodeResult { - let first = self.encode_call(&SigningVariant::staking_call(Proto::Staking { - message_oneof: StakingVariant::chill(Chill { - call_indices: cau.chill_call_indices.clone(), - }), - }))?; - - let second = self.encode_call(&SigningVariant::staking_call(Proto::Staking { - message_oneof: StakingVariant::unbond(Unbond { - value: cau.value.clone(), - call_indices: cau.unbond_call_indices.clone(), - }), - }))?; - - // Encode both calls as batched - self.encode_batch(vec![first, second], &cau.call_indices) - } - - fn encode_staking_batch_call(&self, s: &Staking) -> EncodeResult> { - match &s.message_oneof { - StakingVariant::bond_and_nominate(ban) => { - let batch = self.encode_staking_bond_and_nominate(&ban)?; - Ok(Some(batch)) - }, - StakingVariant::chill_and_unbond(cau) => { - let batch = self.encode_staking_chill_and_unbond(&cau)?; - Ok(Some(batch)) - }, - _ => Ok(None), - } - } - - pub fn encode_call(&self, msg: &SigningVariant<'_>) -> EncodeResult { - // Special case for batches. - match msg { - SigningVariant::balance_call(b) => { - if let Some(batch) = self.encode_balance_batch_call(b)? { - return Ok(batch); - } - }, - SigningVariant::staking_call(s) => { - if let Some(batch) = self.encode_staking_batch_call(s)? { - return Ok(batch); - } - }, - _ => (), - } - // non-batch calls. - let call = match msg { - SigningVariant::balance_call(b) => { - PolymeshBalances::encode_call(b)?.map(PolymeshCall::Balances) - }, - SigningVariant::identity_call(msg) => { + RuntimeCallVariant::identity_call(msg) => { PolymeshIdentity::encode_call(msg)?.map(PolymeshCall::Identity) }, - SigningVariant::staking_call(s) => { - PolymeshStaking::encode_call(s)?.map(PolymeshCall::Staking) + RuntimeCallVariant::staking_call(msg) => { + PolymeshStaking::encode_call(msg)?.map(PolymeshCall::Staking) + }, + RuntimeCallVariant::utility_call(msg) => { + PolymeshUtility::encode_call(self, msg)?.map(PolymeshCall::Utility) }, - SigningVariant::None => { + RuntimeCallVariant::None => { return EncodeError::NotSupported - .tw_result("Staking call variant is None".to_string()); + .tw_result("Runtime call variant is None".to_string()); }, }; Ok(RawOwned(call.to_scale())) } - - fn encode_batch( - &self, - calls: Vec, - ci: &Option, - ) -> EncodeResult { - let ci = validate_call_index(ci)?; - let call = PolymeshCall::Utility(PolymeshUtility::BatchAll { calls }); - let call = ci.wrap(RawOwned(call.to_scale())); - Ok(RawOwned(call.to_scale())) - } } diff --git a/rust/chains/tw_polymesh/src/entry.rs b/rust/chains/tw_polymesh/src/entry.rs index 96d0ed08409..8446aeecd0c 100644 --- a/rust/chains/tw_polymesh/src/entry.rs +++ b/rust/chains/tw_polymesh/src/entry.rs @@ -36,8 +36,11 @@ impl PolymeshEntry { input: &Proto::SigningInput<'_>, ) -> EncodeResult { let ctx = ctx_from_tw(&input)?; - let encoder = CallEncoder::from_ctx(&ctx)?; - let call = encoder.encode_call(&input.message_oneof)?; + let mut encoder = CallEncoder::from_ctx(&ctx)?; + let call = input.runtime_call.as_ref().ok_or_else(|| { + EncodeError::InvalidValue.with_context("Missing runtime call".to_string()) + })?; + let call = encoder.encode_runtime_call(&call)?; let era = match &input.era { Some(era) => Era::mortal(era.period, era.block_number), None => Era::immortal(), diff --git a/rust/chains/tw_polymesh/src/lib.rs b/rust/chains/tw_polymesh/src/lib.rs index af8f7ec8b0d..79bf6b59111 100644 --- a/rust/chains/tw_polymesh/src/lib.rs +++ b/rust/chains/tw_polymesh/src/lib.rs @@ -2,10 +2,7 @@ // // Copyright © 2017 Trust Wallet. -use tw_proto::Polymesh::Proto::{ - self, mod_Balance::OneOfmessage_oneof as BalanceVariant, - mod_SigningInput::OneOfmessage_oneof as SigningVariant, -}; +use tw_proto::Polymesh::Proto; use tw_ss58_address::NetworkId; use tw_substrate::*; @@ -20,18 +17,6 @@ pub fn network_id_from_tw(input: &'_ Proto::SigningInput<'_>) -> EncodeResult) -> Option { - // Special case for batches. - match &input.message_oneof { - SigningVariant::balance_call(b) => match &b.message_oneof { - BalanceVariant::asset_transfer(at) => Some(at.fee_asset_id), - BalanceVariant::batch_asset_transfer(bat) => Some(bat.fee_asset_id), - _ => None, - }, - _ => None, - } -} - pub fn ctx_from_tw(input: &'_ Proto::SigningInput<'_>) -> EncodeResult { let network = NetworkId::try_from(input.network as u16).map_err(|_| EncodeError::InvalidNetworkId)?; diff --git a/rust/chains/tw_polymesh/tests/extrinsic.rs b/rust/chains/tw_polymesh/tests/extrinsic.rs index 1c6f2765f78..cc33736dcd4 100644 --- a/rust/chains/tw_polymesh/tests/extrinsic.rs +++ b/rust/chains/tw_polymesh/tests/extrinsic.rs @@ -26,21 +26,23 @@ fn encode_input(input: &Proto::SigningInput<'_>) -> EncodeResult> { fn polymesh_identity_call( call: Proto::mod_Identity::OneOfmessage_oneof, -) -> Proto::mod_SigningInput::OneOfmessage_oneof { - Proto::mod_SigningInput::OneOfmessage_oneof::identity_call(Proto::Identity { - message_oneof: call, +) -> Option> { + Some(Proto::RuntimeCall { + pallet_oneof: Proto::mod_RuntimeCall::OneOfpallet_oneof::identity_call(Proto::Identity { + message_oneof: call, + }), }) } fn polymesh_add_auth_call( add_auth: Proto::mod_Identity::AddAuthorization, -) -> Proto::mod_SigningInput::OneOfmessage_oneof { +) -> Option> { polymesh_identity_call(Proto::mod_Identity::OneOfmessage_oneof::add_authorization( add_auth, )) } -fn polymesh_join_identity(auth_id: u64) -> Proto::mod_SigningInput::OneOfmessage_oneof<'static> { +fn polymesh_join_identity(auth_id: u64) -> Option> { polymesh_identity_call( Proto::mod_Identity::OneOfmessage_oneof::join_identity_as_key( Proto::mod_Identity::JoinIdentityAsKey { @@ -51,19 +53,19 @@ fn polymesh_join_identity(auth_id: u64) -> Proto::mod_SigningInput::OneOfmessage ) } -fn balance_call( - call: Proto::mod_Balance::OneOfmessage_oneof, -) -> Proto::mod_SigningInput::OneOfmessage_oneof { - Proto::mod_SigningInput::OneOfmessage_oneof::balance_call(Proto::Balance { - message_oneof: call, +fn balance_call(call: Proto::mod_Balance::OneOfmessage_oneof) -> Option> { + Some(Proto::RuntimeCall { + pallet_oneof: Proto::mod_RuntimeCall::OneOfpallet_oneof::balance_call(Proto::Balance { + message_oneof: call, + }), }) } -fn staking_call( - call: Proto::mod_Staking::OneOfmessage_oneof, -) -> Proto::mod_SigningInput::OneOfmessage_oneof { - Proto::mod_SigningInput::OneOfmessage_oneof::staking_call(Proto::Staking { - message_oneof: call, +fn staking_call(call: Proto::mod_Staking::OneOfmessage_oneof) -> Option> { + Some(Proto::RuntimeCall { + pallet_oneof: Proto::mod_RuntimeCall::OneOfpallet_oneof::staking_call(Proto::Staking { + message_oneof: call, + }), }) } @@ -74,7 +76,7 @@ fn polymesh_encode_transfer_with_memo() { let input = Proto::SigningInput { network: 12, multi_address: true, - message_oneof: balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + runtime_call: balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { to_address: "2EB7wW2fYfFskkSx2d65ivn34ewpuEjcowfJYBL79ty5FsZF".into(), value: Cow::Owned(U256::from(1u64).to_big_endian().to_vec()), memo: "MEMO PADDED WITH SPACES".into(), @@ -97,7 +99,7 @@ fn polymesh_encode_authorization_join_identity() { let input = Proto::SigningInput { network: 12, multi_address: true, - message_oneof: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { + runtime_call: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), authorization: Some(Authorization { auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions::default()), @@ -121,7 +123,7 @@ fn polymesh_encode_authorization_join_identity_with_zero_data() { let input = Proto::SigningInput { network: 12, multi_address: true, - message_oneof: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { + runtime_call: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), authorization: Some(Authorization { auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions { @@ -161,7 +163,7 @@ fn polymesh_encode_authorization_join_identity_allowing_everything() { let input = Proto::SigningInput { network: 12, multi_address: true, - message_oneof: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { + runtime_call: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), authorization: Some(Authorization { auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions { @@ -192,7 +194,7 @@ fn polymesh_encode_identity() { let input = Proto::SigningInput { network: 12, multi_address: true, - message_oneof: polymesh_join_identity(4875), + runtime_call: polymesh_join_identity(4875), ..Default::default() }; @@ -205,7 +207,7 @@ fn encode_staking_nominate() { let input = Proto::SigningInput { network: 12, multi_address: true, - message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::nominate(Nominate { + runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::nominate(Nominate { nominators: vec![ "2DxgKKS53wsAeETAZXhmT5A1bTt7h1aV4bKdtkMDwwSzSMXm".into(), "2HqjMm2goapWvXQBqjjEdVaTZsUmunWwEq1TSToDR1pDzQ1F".into(), @@ -227,7 +229,7 @@ fn encode_staking_chill() { let input = Proto::SigningInput { network: 12, multi_address: true, - message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::chill(Chill { + runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::chill(Chill { call_indices: None, })), ..Default::default() @@ -242,7 +244,7 @@ fn encode_staking_bond() { let input = Proto::SigningInput { network: 12, multi_address: true, - message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::bond(Bond { + runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::bond(Bond { controller: "2HqjMm2goapWvXQBqjjEdVaTZsUmunWwEq1TSToDR1pDzQ1F".into(), value: U256::from(808081u64).to_big_endian().to_vec().into(), reward_destination: Proto::RewardDestination::STAKED, @@ -263,7 +265,7 @@ fn encode_staking_bond_extra() { let input = Proto::SigningInput { network: 12, multi_address: true, - message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::bond_extra( + runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::bond_extra( BondExtra { value: U256::from(808081u64).to_big_endian().to_vec().into(), call_indices: None, @@ -281,7 +283,7 @@ fn encode_staking_rebond() { let input = Proto::SigningInput { network: 12, multi_address: true, - message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::rebond(Rebond { + runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::rebond(Rebond { value: U256::from(808081u64).to_big_endian().to_vec().into(), call_indices: None, })), @@ -297,7 +299,7 @@ fn encode_staking_unbond() { let input = Proto::SigningInput { network: 12, multi_address: true, - message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::unbond(Unbond { + runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::unbond(Unbond { value: U256::from(808081u64).to_big_endian().to_vec().into(), call_indices: None, })), @@ -313,7 +315,7 @@ fn encode_staking_withdraw_unbonded() { let input = Proto::SigningInput { network: 12, multi_address: true, - message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::withdraw_unbonded( + runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::withdraw_unbonded( WithdrawUnbonded { slashing_spans: 84, call_indices: None, diff --git a/rust/tw_tests/tests/chains/polymesh/mod.rs b/rust/tw_tests/tests/chains/polymesh/mod.rs index 62ffa86bd7a..68cdd22f228 100644 --- a/rust/tw_tests/tests/chains/polymesh/mod.rs +++ b/rust/tw_tests/tests/chains/polymesh/mod.rs @@ -20,16 +20,20 @@ pub const PUBLIC_KEY_2: &str = "2CpqFh8VnwJAjenw4xSUWCaaJ2QwGdhnCikoSEczMhjgqyj7 pub fn balance_call( call: Proto::mod_Balance::OneOfmessage_oneof, -) -> Proto::mod_SigningInput::OneOfmessage_oneof { - Proto::mod_SigningInput::OneOfmessage_oneof::balance_call(Proto::Balance { - message_oneof: call, +) -> Option> { + Some(Proto::RuntimeCall { + pallet_oneof: Proto::mod_RuntimeCall::OneOfpallet_oneof::balance_call(Proto::Balance { + message_oneof: call, + }), }) } pub fn identity_call( call: Proto::mod_Identity::OneOfmessage_oneof, -) -> Proto::mod_SigningInput::OneOfmessage_oneof { - Proto::mod_SigningInput::OneOfmessage_oneof::identity_call(Proto::Identity { - message_oneof: call, +) -> Option> { + Some(Proto::RuntimeCall { + pallet_oneof: Proto::mod_RuntimeCall::OneOfpallet_oneof::identity_call(Proto::Identity { + message_oneof: call, + }), }) } diff --git a/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs b/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs index 2dd04398b70..988c8ecfaa5 100644 --- a/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs +++ b/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs @@ -49,7 +49,7 @@ fn test_polymesh_compile_add_authorization() { block_number: 16_102_074, period: 64, }), - message_oneof: identity_call(Proto::mod_Identity::OneOfmessage_oneof::add_authorization( + runtime_call: identity_call(Proto::mod_Identity::OneOfmessage_oneof::add_authorization( AddAuthorization { target: PUBLIC_KEY_1.into(), authorization: Some(Authorization { @@ -110,7 +110,7 @@ fn test_polymesh_compile_join_identity() { block_number: 16_102_087, period: 64, }), - message_oneof: identity_call( + runtime_call: identity_call( Proto::mod_Identity::OneOfmessage_oneof::join_identity_as_key(JoinIdentityAsKey { auth_id: 52_188, ..Default::default() @@ -176,7 +176,7 @@ fn test_polymesh_compile_transfer() { block_number: 16_102_106, period: 64, }), - message_oneof: balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + runtime_call: balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { to_address: PUBLIC_KEY_2.into(), value: Cow::Owned(U256::from(value).to_big_endian().to_vec()), ..Default::default() @@ -244,7 +244,7 @@ fn test_polymesh_compile_leave_identity() { block_number: 16_102_110, period: 64, }), - message_oneof: identity_call( + runtime_call: identity_call( Proto::mod_Identity::OneOfmessage_oneof::leave_identity_as_key(LeaveIdentityAsKey { ..Default::default() }), diff --git a/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs b/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs index 87f157a51ed..047091fdeee 100644 --- a/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs +++ b/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs @@ -38,7 +38,7 @@ fn test_polymesh_sign_join_identity() { block_number: 16_102_087, period: 64, }), - message_oneof: identity_call( + runtime_call: identity_call( Proto::mod_Identity::OneOfmessage_oneof::join_identity_as_key(JoinIdentityAsKey { auth_id: 52_188, ..Default::default() @@ -82,7 +82,7 @@ fn test_polymesh_sign_transfer() { block_number: 16_102_106, period: 64, }), - message_oneof: balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + runtime_call: balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { to_address: PUBLIC_KEY_2.into(), value: Cow::Owned(U256::from(value).to_big_endian().to_vec()), ..Default::default() diff --git a/src/proto/Polymesh.proto b/src/proto/Polymesh.proto index 3d4ae6af2d1..f60396b68c3 100644 --- a/src/proto/Polymesh.proto +++ b/src/proto/Polymesh.proto @@ -58,47 +58,9 @@ message Balance { // call indices CallIndices call_indices = 4; } - // batch tranfer - message BatchTransfer { - // call indices - CallIndices call_indices = 1; - - repeated Transfer transfers = 2; - } - // asset transfer - message AssetTransfer { - // call indices - CallIndices call_indices = 1; - - // destination - string to_address = 2; - - // value - BigInteger - bytes value = 3; - - // asset identifier - uint32 asset_id = 4; - - // fee asset identifier - uint32 fee_asset_id = 5; - } - - // batch asset transfer - message BatchAssetTransfer { - // call indices - CallIndices call_indices = 1; - - // fee asset identifier - uint32 fee_asset_id = 2; - - repeated AssetTransfer transfers = 3; - } oneof message_oneof { Transfer transfer = 1; - BatchTransfer batchTransfer = 2; - AssetTransfer asset_transfer = 3; - BatchAssetTransfer batch_asset_transfer = 4; } } @@ -119,30 +81,6 @@ message Staking { CallIndices call_indices = 4; } - // Bond to a controller, with nominators - message BondAndNominate { - // controller ID (optional) - string controller = 1; - - // amount (uint256, serialized big endian) - bytes value = 2; - - // destination for rewards - RewardDestination reward_destination = 3; - - // list of nominators - repeated string nominators = 4; - - // call indices - CallIndices call_indices = 5; - - // Staking.Bond call indices - CallIndices bond_call_indices = 6; - - // Staking.Nominate call indices - CallIndices nominate_call_indices = 7; - } - // Bond extra amount message BondExtra { // amount (uint256, serialized big endian) @@ -187,21 +125,6 @@ message Staking { CallIndices call_indices = 2; } - // Chill and unbound - message ChillAndUnbond { - // amount (uint256, serialized big endian) - bytes value = 1; - - // call indices - CallIndices call_indices = 2; - - // Staking.Chill call indices - CallIndices chill_call_indices = 3; - - // Staking.Unbond call indices - CallIndices unbond_call_indices = 4; - } - // Chill message Chill { // call indices @@ -211,14 +134,12 @@ message Staking { // Payload messsage oneof message_oneof { Bond bond = 1; - BondAndNominate bond_and_nominate = 2; - BondExtra bond_extra = 3; - Unbond unbond = 4; - WithdrawUnbonded withdraw_unbonded = 5; - Nominate nominate = 6; - Chill chill = 7; - ChillAndUnbond chill_and_unbond = 8; - Rebond rebond = 9; + BondExtra bond_extra = 2; + Unbond unbond = 3; + WithdrawUnbonded withdraw_unbonded = 4; + Nominate nominate = 5; + Chill chill = 6; + Rebond rebond = 7; } } @@ -334,6 +255,56 @@ message Identity { } } +// Utility pallet transaction +message Utility { + enum BatchKind { + // Batch multiple calls, stoping on the first error. + // + // Each call in the batch is executed in its own transaction. + // When one call fails only that transaction will be rolled back + // and any following calls in the batch will be skipped. + StopOnError = 0; + // Batch multiple calls and execute them in a single atomic transaction. + // The whole transaction will rollback if any of the calls fail. + Atomic = 1; + // Batch multiple calls. Unlike `Batch` this will continue even + // if one of the calls failed. + // + // Each call in the batch is executed in its own transaction. + // When a call fails its transaction will be rolled back and the error + // will be emitted in an event. + // + // Execution will continue until all calls in the batch have been executed. + Optimistic = 2; + } + + message Batch { + // The type of batch. + BatchKind kind = 1; + + // batched calls. + repeated RuntimeCall calls = 2; + + // call indices + CallIndices call_indices = 3; + } + + oneof message_oneof { + Batch batch = 1; + } +} + +// Polymesh runtime call. +message RuntimeCall { + // Top-level pallets. + oneof pallet_oneof { + Balance balance_call = 1; + Staking staking_call = 2; + Identity identity_call = 3; + Utility utility_call = 4; + } +} + // Input data necessary to create a signed transaction. message SigningInput { // Recent block hash, or genesis hash if era is not set @@ -366,12 +337,8 @@ message SigningInput { // Whether enable MultiAddress bool multi_address = 10; - // Payload message - oneof message_oneof { - Balance balance_call = 11; - Staking staking_call = 12; - Identity identity_call = 13; - } + // Payload call + RuntimeCall runtime_call = 11; } // Result containing the signed and encoded transaction. diff --git a/tests/chains/Polymesh/TWAnySignerTests.cpp b/tests/chains/Polymesh/TWAnySignerTests.cpp index 2e7e8062cde..cd4856468a7 100644 --- a/tests/chains/Polymesh/TWAnySignerTests.cpp +++ b/tests/chains/Polymesh/TWAnySignerTests.cpp @@ -66,7 +66,7 @@ TEST(TWAnySignerPolymesh, PolymeshEncodeAndSign) { era->set_block_number(4298130UL); era->set_period(64UL); - auto* transfer = input.mutable_balance_call()->mutable_transfer(); + auto* transfer = input.mutable_runtime_call()->mutable_balance_call()->mutable_transfer(); transfer->set_to_address("2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW"); auto value = store(1000000); transfer->set_value(std::string(value.begin(), value.end())); @@ -129,7 +129,7 @@ TEST(TWAnySignerPolymesh, encodeTransaction_Add_authorization) { era->set_block_number(4395451UL); era->set_period(64UL); - auto* addAuthorization = input.mutable_identity_call()->mutable_add_authorization(); + auto* addAuthorization = input.mutable_runtime_call()->mutable_identity_call()->mutable_add_authorization(); addAuthorization->set_target("2HEVN4PHYKj7B1krQ9bctAQXZxHQQkANVNCcfbdYk2gZ4cBR"); auto* keyPerms = addAuthorization->mutable_authorization()->mutable_join_identity(); // Set empty "These". @@ -169,7 +169,7 @@ TEST(TWAnySignerPolymesh, encodeTransaction_JoinIdentityAsKey) { era->set_block_number(4395527UL); era->set_period(64UL); - auto* key = input.mutable_identity_call()->mutable_join_identity_as_key(); + auto* key = input.mutable_runtime_call()->mutable_identity_call()->mutable_join_identity_as_key(); key->set_auth_id(21435); auto* callIndices = key->mutable_call_indices()->mutable_custom(); callIndices->set_module_index(0x07); From e4865f897599b8a76464f0071ae273e0a35e0af8 Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Wed, 4 Dec 2024 12:59:35 +0000 Subject: [PATCH 11/22] Remove dead code. --- rust/Cargo.lock | 2 + rust/chains/tw_polymesh/Cargo.toml | 4 +- rust/chains/tw_polymesh/src/address.rs | 34 ----------------- rust/chains/tw_polymesh/src/compiler.rs | 50 ------------------------- rust/chains/tw_polymesh/src/lib.rs | 4 -- rust/chains/tw_polymesh/src/signer.rs | 27 ------------- 6 files changed, 4 insertions(+), 117 deletions(-) delete mode 100644 rust/chains/tw_polymesh/src/address.rs delete mode 100644 rust/chains/tw_polymesh/src/compiler.rs delete mode 100644 rust/chains/tw_polymesh/src/signer.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index e94dc44207f..754561e711d 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2199,6 +2199,7 @@ name = "tw_substrate" version = "0.1.0" dependencies = [ "tw_coin_entry", + "tw_encoding", "tw_hash", "tw_keypair", "tw_memory", @@ -2221,6 +2222,7 @@ dependencies = [ "tw_hash", "tw_keypair", "tw_memory", + "tw_misc", "tw_proto", ] diff --git a/rust/chains/tw_polymesh/Cargo.toml b/rust/chains/tw_polymesh/Cargo.toml index 3ccc62b213f..1b9e2cfefd9 100644 --- a/rust/chains/tw_polymesh/Cargo.toml +++ b/rust/chains/tw_polymesh/Cargo.toml @@ -6,11 +6,11 @@ edition = "2021" [dependencies] tw_coin_entry = { path = "../../tw_coin_entry" } tw_encoding = { path = "../../tw_encoding" } -tw_scale = { path = "../../tw_scale" } -tw_substrate = { path = "../../frameworks/tw_substrate" } tw_hash = { path = "../../tw_hash" } tw_keypair = { path = "../../tw_keypair" } tw_memory = { path = "../../tw_memory" } tw_number = { path = "../../tw_number" } tw_proto = { path = "../../tw_proto" } +tw_scale = { path = "../../tw_scale" } tw_ss58_address = { path = "../../tw_ss58_address" } +tw_substrate = { path = "../../frameworks/tw_substrate" } diff --git a/rust/chains/tw_polymesh/src/address.rs b/rust/chains/tw_polymesh/src/address.rs deleted file mode 100644 index 5cb2c8216a9..00000000000 --- a/rust/chains/tw_polymesh/src/address.rs +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -use std::fmt; -use std::str::FromStr; -use tw_coin_entry::coin_entry::CoinAddress; -use tw_coin_entry::error::prelude::*; -use tw_memory::Data; - -pub struct PolymeshAddress { - // TODO add necessary fields. -} - -impl CoinAddress for PolymeshAddress { - #[inline] - fn data(&self) -> Data { - todo!() - } -} - -impl FromStr for PolymeshAddress { - type Err = AddressError; - - fn from_str(_s: &str) -> Result { - todo!() - } -} - -impl fmt::Display for PolymeshAddress { - fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { - todo!() - } -} diff --git a/rust/chains/tw_polymesh/src/compiler.rs b/rust/chains/tw_polymesh/src/compiler.rs deleted file mode 100644 index a70571c1b1d..00000000000 --- a/rust/chains/tw_polymesh/src/compiler.rs +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -use tw_coin_entry::coin_context::CoinContext; -use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; -use tw_coin_entry::error::prelude::*; -use tw_coin_entry::signing_output_error; -use tw_proto::Polymesh::Proto; -use tw_proto::TxCompiler::Proto as CompilerProto; - -pub struct PolymeshCompiler; - -impl PolymeshCompiler { - #[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/rust/chains/tw_polymesh/src/lib.rs b/rust/chains/tw_polymesh/src/lib.rs index 79bf6b59111..838cbe4f522 100644 --- a/rust/chains/tw_polymesh/src/lib.rs +++ b/rust/chains/tw_polymesh/src/lib.rs @@ -13,10 +13,6 @@ pub mod types; pub const POLYMESH_PREFIX: u16 = 12; pub const POLYMESH: NetworkId = NetworkId::new_unchecked(POLYMESH_PREFIX); -pub fn network_id_from_tw(input: &'_ Proto::SigningInput<'_>) -> EncodeResult { - Ok(NetworkId::try_from(input.network as u16).map_err(|_| EncodeError::InvalidNetworkId)?) -} - pub fn ctx_from_tw(input: &'_ Proto::SigningInput<'_>) -> EncodeResult { let network = NetworkId::try_from(input.network as u16).map_err(|_| EncodeError::InvalidNetworkId)?; diff --git a/rust/chains/tw_polymesh/src/signer.rs b/rust/chains/tw_polymesh/src/signer.rs deleted file mode 100644 index 21c344aa210..00000000000 --- a/rust/chains/tw_polymesh/src/signer.rs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -use tw_coin_entry::coin_context::CoinContext; -use tw_coin_entry::error::prelude::*; -use tw_coin_entry::signing_output_error; -use tw_proto::Polymesh::Proto; - -pub struct PolymeshSigner; - -impl PolymeshSigner { - 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!() - } -} From 71b8cbc4ea5a6f8977d117bbdeb6a4f19e5f1a37 Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Wed, 4 Dec 2024 13:03:34 +0000 Subject: [PATCH 12/22] Allow enum variants to re-use the same index for backwards compatibility. --- rust/tw_scale/src/macros.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rust/tw_scale/src/macros.rs b/rust/tw_scale/src/macros.rs index 3e5e345ba8d..3340790eda1 100644 --- a/rust/tw_scale/src/macros.rs +++ b/rust/tw_scale/src/macros.rs @@ -117,7 +117,7 @@ macro_rules! impl_enum_scale { $( $variant_field_name : $variant_field_ty ),+ - })? = $variant_index, + })?, )* } @@ -200,6 +200,8 @@ mod tests { Variant10 = 10, /// Struct variant. Struct { id: u8, id2: u8 } = 11, + /// Struct variant v2. Variants can use the same index. This allows for backwards compatibility. + StructV2 { id: u8, id2: u16 } = 11, } ); @@ -212,5 +214,9 @@ mod tests { TestEnum::Struct { id: 1, id2: 2 }.to_scale(), &[0x0B, 0x01, 0x02] ); + assert_eq!( + TestEnum::StructV2 { id: 1, id2: 2 }.to_scale(), + &[0x0B, 0x01, 0x02, 0x00] + ); } } From 795bc8c44a6f1bb996d3bda81ea873c46313f1da Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Wed, 4 Dec 2024 14:35:04 +0000 Subject: [PATCH 13/22] Remove `multi_address` flag. Only support MultiAddress. --- rust/chains/tw_polymesh/src/entry.rs | 2 +- rust/chains/tw_polymesh/tests/extrinsic.rs | 12 ------------ src/proto/Polymesh.proto | 5 +---- tests/chains/Polymesh/TWAnySignerTests.cpp | 3 --- 4 files changed, 2 insertions(+), 20 deletions(-) diff --git a/rust/chains/tw_polymesh/src/entry.rs b/rust/chains/tw_polymesh/src/entry.rs index 8446aeecd0c..2383d5c42a3 100644 --- a/rust/chains/tw_polymesh/src/entry.rs +++ b/rust/chains/tw_polymesh/src/entry.rs @@ -52,7 +52,7 @@ impl PolymeshEntry { .try_into() .map_err(|_| EncodeError::InvalidValue)?; - let mut builder = TransactionBuilder::new(ctx.multi_address, call); + let mut builder = TransactionBuilder::new(true, call); // Add chain extensions. builder.extension(CheckVersion(input.spec_version)); builder.extension(CheckVersion(input.transaction_version)); diff --git a/rust/chains/tw_polymesh/tests/extrinsic.rs b/rust/chains/tw_polymesh/tests/extrinsic.rs index cc33736dcd4..86bc2127fde 100644 --- a/rust/chains/tw_polymesh/tests/extrinsic.rs +++ b/rust/chains/tw_polymesh/tests/extrinsic.rs @@ -75,7 +75,6 @@ fn polymesh_encode_transfer_with_memo() { let input = Proto::SigningInput { network: 12, - multi_address: true, runtime_call: balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { to_address: "2EB7wW2fYfFskkSx2d65ivn34ewpuEjcowfJYBL79ty5FsZF".into(), value: Cow::Owned(U256::from(1u64).to_big_endian().to_vec()), @@ -98,7 +97,6 @@ fn polymesh_encode_authorization_join_identity() { let input = Proto::SigningInput { network: 12, - multi_address: true, runtime_call: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), authorization: Some(Authorization { @@ -122,7 +120,6 @@ fn polymesh_encode_authorization_join_identity_with_zero_data() { let input = Proto::SigningInput { network: 12, - multi_address: true, runtime_call: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), authorization: Some(Authorization { @@ -162,7 +159,6 @@ fn polymesh_encode_authorization_join_identity_allowing_everything() { let input = Proto::SigningInput { network: 12, - multi_address: true, runtime_call: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), authorization: Some(Authorization { @@ -193,7 +189,6 @@ fn polymesh_encode_identity() { let input = Proto::SigningInput { network: 12, - multi_address: true, runtime_call: polymesh_join_identity(4875), ..Default::default() }; @@ -206,7 +201,6 @@ fn polymesh_encode_identity() { fn encode_staking_nominate() { let input = Proto::SigningInput { network: 12, - multi_address: true, runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::nominate(Nominate { nominators: vec![ "2DxgKKS53wsAeETAZXhmT5A1bTt7h1aV4bKdtkMDwwSzSMXm".into(), @@ -228,7 +222,6 @@ fn encode_staking_nominate() { fn encode_staking_chill() { let input = Proto::SigningInput { network: 12, - multi_address: true, runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::chill(Chill { call_indices: None, })), @@ -243,7 +236,6 @@ fn encode_staking_chill() { fn encode_staking_bond() { let input = Proto::SigningInput { network: 12, - multi_address: true, runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::bond(Bond { controller: "2HqjMm2goapWvXQBqjjEdVaTZsUmunWwEq1TSToDR1pDzQ1F".into(), value: U256::from(808081u64).to_big_endian().to_vec().into(), @@ -264,7 +256,6 @@ fn encode_staking_bond() { fn encode_staking_bond_extra() { let input = Proto::SigningInput { network: 12, - multi_address: true, runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::bond_extra( BondExtra { value: U256::from(808081u64).to_big_endian().to_vec().into(), @@ -282,7 +273,6 @@ fn encode_staking_bond_extra() { fn encode_staking_rebond() { let input = Proto::SigningInput { network: 12, - multi_address: true, runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::rebond(Rebond { value: U256::from(808081u64).to_big_endian().to_vec().into(), call_indices: None, @@ -298,7 +288,6 @@ fn encode_staking_rebond() { fn encode_staking_unbond() { let input = Proto::SigningInput { network: 12, - multi_address: true, runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::unbond(Unbond { value: U256::from(808081u64).to_big_endian().to_vec().into(), call_indices: None, @@ -314,7 +303,6 @@ fn encode_staking_unbond() { fn encode_staking_withdraw_unbonded() { let input = Proto::SigningInput { network: 12, - multi_address: true, runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::withdraw_unbonded( WithdrawUnbonded { slashing_spans: 84, diff --git a/src/proto/Polymesh.proto b/src/proto/Polymesh.proto index f60396b68c3..54d1562a95d 100644 --- a/src/proto/Polymesh.proto +++ b/src/proto/Polymesh.proto @@ -334,11 +334,8 @@ message SigningInput { // Network type uint32 network = 9; - // Whether enable MultiAddress - bool multi_address = 10; - // Payload call - RuntimeCall runtime_call = 11; + RuntimeCall runtime_call = 10; } // Result containing the signed and encoded transaction. diff --git a/tests/chains/Polymesh/TWAnySignerTests.cpp b/tests/chains/Polymesh/TWAnySignerTests.cpp index cd4856468a7..8fc6cdbde4b 100644 --- a/tests/chains/Polymesh/TWAnySignerTests.cpp +++ b/tests/chains/Polymesh/TWAnySignerTests.cpp @@ -53,7 +53,6 @@ TEST(TWAnySignerPolymesh, PolymeshEncodeAndSign) { Polymesh::Proto::SigningInput input; input.set_network(12); - input.set_multi_address(true); auto blockHash = parse_hex("898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102"); auto vGenesisHash = parse_hex("6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063"); input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); @@ -116,7 +115,6 @@ TEST(TWAnySignerPolymesh, encodeTransaction_Add_authorization) { Polymesh::Proto::SigningInput input; input.set_network(12); - input.set_multi_address(true); auto blockHash = parse_hex("ce0c2109db498e45abf8fd447580dcfa7b7a07ffc2bfb1a0fbdd1af3e8816d2b"); auto vGenesisHash = parse_hex("6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063"); input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); @@ -156,7 +154,6 @@ TEST(TWAnySignerPolymesh, encodeTransaction_JoinIdentityAsKey) { Polymesh::Proto::SigningInput input; input.set_network(12); - input.set_multi_address(true); auto blockHash = parse_hex("45c80153c47f5d16acc7a66d473870e8d4574437a7d8c813f47da74cae3812c2"); auto vGenesisHash = parse_hex("6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063"); input.set_block_hash(std::string(blockHash.begin(), blockHash.end())); From 3d7df67e64b49abb181d84c9c01b0fdfcfa99324 Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Wed, 4 Dec 2024 16:00:17 +0000 Subject: [PATCH 14/22] Fix batch call index. --- rust/chains/tw_polymesh/src/call_encoder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/chains/tw_polymesh/src/call_encoder.rs b/rust/chains/tw_polymesh/src/call_encoder.rs index a4173c72439..e1ec4f9e2f1 100644 --- a/rust/chains/tw_polymesh/src/call_encoder.rs +++ b/rust/chains/tw_polymesh/src/call_encoder.rs @@ -277,9 +277,9 @@ impl PolymeshStaking { impl_enum_scale!( #[derive(Clone, Debug)] pub enum PolymeshUtility { - Batch { calls: Vec } = 0x01, + Batch { calls: Vec } = 0x00, BatchAll { calls: Vec } = 0x02, - ForceBatch { calls: Vec } = 0x03, + ForceBatch { calls: Vec } = 0x04, } ); From 8ef62f0f5f1821378c5c6e6f96c9d4730f6490ff Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Wed, 4 Dec 2024 16:44:56 +0000 Subject: [PATCH 15/22] Add some batch tests. --- rust/chains/tw_polymesh/tests/extrinsic.rs | 499 ++++++++++++--------- 1 file changed, 299 insertions(+), 200 deletions(-) diff --git a/rust/chains/tw_polymesh/tests/extrinsic.rs b/rust/chains/tw_polymesh/tests/extrinsic.rs index 86bc2127fde..a7bf864e192 100644 --- a/rust/chains/tw_polymesh/tests/extrinsic.rs +++ b/rust/chains/tw_polymesh/tests/extrinsic.rs @@ -5,313 +5,412 @@ use tw_encoding::hex::ToHex; use tw_number::U256; use tw_proto::Polymesh::Proto::{ self, - mod_Balance::Transfer, - mod_Identity::mod_AddAuthorization::{ - mod_Authorization::OneOfauth_oneof as AuthVariant, Authorization, + mod_Balance::{OneOfmessage_oneof as BalanceVariant, Transfer}, + mod_Identity::{ + mod_AddAuthorization::{mod_Authorization::OneOfauth_oneof as AuthVariant, Authorization}, + AddAuthorization, JoinIdentityAsKey, OneOfmessage_oneof as IdentityVariant, }, + mod_RuntimeCall::OneOfpallet_oneof as CallVariant, mod_SecondaryKeyPermissions::{ AssetPermissions, ExtrinsicPermissions, PortfolioPermissions, RestrictionKind, }, - mod_Staking::{Bond, BondExtra, Chill, Nominate, Rebond, Unbond, WithdrawUnbonded}, - SecondaryKeyPermissions, + mod_Staking::{ + Bond, BondExtra, Chill, Nominate, OneOfmessage_oneof as StakingVariant, Rebond, Unbond, + WithdrawUnbonded, + }, + mod_Utility::{Batch, BatchKind, OneOfmessage_oneof as UtilityVariant}, + Balance, Identity, RuntimeCall, SecondaryKeyPermissions, Staking, Utility, }; -use tw_substrate::EncodeResult; use tw_polymesh::call_encoder::CallEncoder; -fn encode_input(input: &Proto::SigningInput<'_>) -> EncodeResult> { - let encoded = CallEncoder::encode_input(input)?; - Ok(encoded.0) +fn expect_encoded(input: &Proto::SigningInput<'_>, expected_value: &str) { + let encoded = CallEncoder::encode_input(input).expect("error encoding call"); + assert_eq!(encoded.0.to_hex(), expected_value); } -fn polymesh_identity_call( - call: Proto::mod_Identity::OneOfmessage_oneof, -) -> Option> { - Some(Proto::RuntimeCall { - pallet_oneof: Proto::mod_RuntimeCall::OneOfpallet_oneof::identity_call(Proto::Identity { +fn polymesh_identity_call(call: IdentityVariant) -> RuntimeCall<'_> { + RuntimeCall { + pallet_oneof: CallVariant::identity_call(Identity { message_oneof: call, }), - }) + } } -fn polymesh_add_auth_call( - add_auth: Proto::mod_Identity::AddAuthorization, -) -> Option> { - polymesh_identity_call(Proto::mod_Identity::OneOfmessage_oneof::add_authorization( - add_auth, - )) +fn polymesh_add_auth_call(add_auth: AddAuthorization) -> RuntimeCall<'_> { + polymesh_identity_call(IdentityVariant::add_authorization(add_auth)) } -fn polymesh_join_identity(auth_id: u64) -> Option> { - polymesh_identity_call( - Proto::mod_Identity::OneOfmessage_oneof::join_identity_as_key( - Proto::mod_Identity::JoinIdentityAsKey { - call_indices: None, - auth_id, - }, - ), - ) +fn polymesh_join_identity(auth_id: u64) -> RuntimeCall<'static> { + polymesh_identity_call(IdentityVariant::join_identity_as_key(JoinIdentityAsKey { + call_indices: None, + auth_id, + })) } -fn balance_call(call: Proto::mod_Balance::OneOfmessage_oneof) -> Option> { - Some(Proto::RuntimeCall { - pallet_oneof: Proto::mod_RuntimeCall::OneOfpallet_oneof::balance_call(Proto::Balance { +fn balance_call(call: BalanceVariant) -> RuntimeCall<'_> { + RuntimeCall { + pallet_oneof: CallVariant::balance_call(Balance { message_oneof: call, }), - }) + } } -fn staking_call(call: Proto::mod_Staking::OneOfmessage_oneof) -> Option> { - Some(Proto::RuntimeCall { - pallet_oneof: Proto::mod_RuntimeCall::OneOfpallet_oneof::staking_call(Proto::Staking { +fn staking_call(call: StakingVariant) -> RuntimeCall<'_> { + RuntimeCall { + pallet_oneof: CallVariant::staking_call(Staking { message_oneof: call, }), - }) + } +} + +fn batch_calls(kind: BatchKind, calls: Vec>) -> RuntimeCall<'static> { + RuntimeCall { + pallet_oneof: CallVariant::utility_call(Utility { + message_oneof: UtilityVariant::batch(Batch { + kind, + calls, + ..Default::default() + }), + }), + } } +fn build_input(runtime_call: RuntimeCall<'_>) -> Proto::SigningInput<'_> { + Proto::SigningInput { + network: 12, + transaction_version: 7, + runtime_call: Some(runtime_call), + ..Default::default() + } +} + +/// Test POLYX transfer with memo. #[test] fn polymesh_encode_transfer_with_memo() { // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x0501004c6c63e3dc083959f876788716b78885460b5f3c7ed9379f8d5f408e08639e0204014d454d4f20504144444544205749544820535041434553000000000000000000 - let input = Proto::SigningInput { - network: 12, - runtime_call: balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + let input = build_input(balance_call( + Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { to_address: "2EB7wW2fYfFskkSx2d65ivn34ewpuEjcowfJYBL79ty5FsZF".into(), value: Cow::Owned(U256::from(1u64).to_big_endian().to_vec()), + // The memo field is padded with nulls. memo: "MEMO PADDED WITH SPACES".into(), ..Default::default() - })), - ..Default::default() - }; + }), + )); - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!( - encoded.to_hex(), - "0501004c6c63e3dc083959f876788716b78885460b5f3c7ed9379f8d5f408e08639e0204014d454d4f20504144444544205749544820535041434553000000000000000000" - ); + expect_encoded(&input, "0501004c6c63e3dc083959f876788716b78885460b5f3c7ed9379f8d5f408e08639e0204014d454d4f20504144444544205749544820535041434553000000000000000000"); } +/// Test add authorization to join identity with default permissions (`Whole` meaning all permissions). #[test] fn polymesh_encode_authorization_join_identity() { // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000 - let input = Proto::SigningInput { - network: 12, - runtime_call: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { - target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), - authorization: Some(Authorization { - auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions::default()), - }), - ..Default::default() + let input = build_input(polymesh_add_auth_call(AddAuthorization { + target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), + authorization: Some(Authorization { + auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions::default()), }), ..Default::default() - }; + })); - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!( - encoded.to_hex(), - "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000" + expect_encoded( + &input, + "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000", ); } +/// Test add authorization to join identity with no permissions. #[test] fn polymesh_encode_authorization_join_identity_with_zero_data() { // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320501000100010000 - let input = Proto::SigningInput { - network: 12, - runtime_call: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { - target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), - authorization: Some(Authorization { - auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions { - // No asset permissions. - asset: Some(AssetPermissions { - kind: RestrictionKind::These, - assets: vec![], - }), - // No extrinsic permissions. - extrinsic: Some(ExtrinsicPermissions { - kind: RestrictionKind::These, - pallets: vec![], - }), - // No portfolio permissions. - portfolio: Some(PortfolioPermissions { - kind: RestrictionKind::These, - portfolios: vec![], - }), + let input = build_input(polymesh_add_auth_call(AddAuthorization { + target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), + authorization: Some(Authorization { + auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions { + // No asset permissions. + asset: Some(AssetPermissions { + kind: RestrictionKind::These, + assets: vec![], + }), + // No extrinsic permissions. + extrinsic: Some(ExtrinsicPermissions { + kind: RestrictionKind::These, + pallets: vec![], + }), + // No portfolio permissions. + portfolio: Some(PortfolioPermissions { + kind: RestrictionKind::These, + portfolios: vec![], }), }), - ..Default::default() }), ..Default::default() - }; + })); - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!( - encoded.to_hex(), - "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320501000100010000" + expect_encoded( + &input, + "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320501000100010000", ); } +/// Test add authorization to join identity with all permissions. Each permission is set to `None`, which defaults to `Whole`. #[test] fn polymesh_encode_authorization_join_identity_allowing_everything() { // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000 - let input = Proto::SigningInput { - network: 12, - runtime_call: polymesh_add_auth_call(Proto::mod_Identity::AddAuthorization { - target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), - authorization: Some(Authorization { - auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions { - // All asset permissions. - asset: None, - // All extrinsic permissions. - extrinsic: None, - // All portfolio permissions. - portfolio: None, - }), + let input = build_input(polymesh_add_auth_call(AddAuthorization { + target: "2FM6FpjQ6r5HTt7FGYSzskDNkwUyFsonMtwBpsnr9vwmCjhc".into(), + authorization: Some(Authorization { + auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions { + // All asset permissions. + asset: None, + // All extrinsic permissions. + extrinsic: None, + // All portfolio permissions. + portfolio: None, }), - ..Default::default() }), ..Default::default() - }; + })); - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!( - encoded.to_hex(), - "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000" + expect_encoded( + &input, + "070a0180436894d47a18e0bcfea6940bd90226f7104fbd037a259aeff6b47b8257c1320500000000", ); } +/// Test accepting a join identity authorization. #[test] -fn polymesh_encode_identity() { +fn polymesh_encode_identity_join_identity() { // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x07040b13000000000000 - let input = Proto::SigningInput { - network: 12, - runtime_call: polymesh_join_identity(4875), - ..Default::default() - }; + let input = build_input(polymesh_join_identity(4875)); - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!(encoded.to_hex(), "07040b13000000000000"); + expect_encoded(&input, "07040b13000000000000"); } +/// Test staking nominate. #[test] fn encode_staking_nominate() { - let input = Proto::SigningInput { - network: 12, - runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::nominate(Nominate { - nominators: vec![ - "2DxgKKS53wsAeETAZXhmT5A1bTt7h1aV4bKdtkMDwwSzSMXm".into(), - "2HqjMm2goapWvXQBqjjEdVaTZsUmunWwEq1TSToDR1pDzQ1F".into(), - ], - call_indices: None, - })), - ..Default::default() - }; - - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!( - encoded.to_hex(), - "1105080042ef301451c7f596f974daec8ca1234f66809905d13e16c18e23896b0c57e53e00ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e67148" - ); + let input = build_input(staking_call(StakingVariant::nominate(Nominate { + nominators: vec![ + "2DxgKKS53wsAeETAZXhmT5A1bTt7h1aV4bKdtkMDwwSzSMXm".into(), + "2HqjMm2goapWvXQBqjjEdVaTZsUmunWwEq1TSToDR1pDzQ1F".into(), + ], + call_indices: None, + }))); + + expect_encoded(&input, "1105080042ef301451c7f596f974daec8ca1234f66809905d13e16c18e23896b0c57e53e00ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e67148"); } +/// Test staking chill. #[test] fn encode_staking_chill() { - let input = Proto::SigningInput { - network: 12, - runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::chill(Chill { - call_indices: None, - })), - ..Default::default() - }; + let input = build_input(staking_call(StakingVariant::chill(Chill { + call_indices: None, + }))); - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!(encoded.to_hex(), "1106"); + expect_encoded(&input, "1106"); } +/// Test staking bond. #[test] fn encode_staking_bond() { - let input = Proto::SigningInput { - network: 12, - runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::bond(Bond { - controller: "2HqjMm2goapWvXQBqjjEdVaTZsUmunWwEq1TSToDR1pDzQ1F".into(), - value: U256::from(808081u64).to_big_endian().to_vec().into(), - reward_destination: Proto::RewardDestination::STAKED, - call_indices: None, - })), - ..Default::default() - }; - - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!( - encoded.to_hex(), - "110000ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e671484652310000" + let input = build_input(staking_call(StakingVariant::bond(Bond { + controller: "2HqjMm2goapWvXQBqjjEdVaTZsUmunWwEq1TSToDR1pDzQ1F".into(), + value: U256::from(808081u64).to_big_endian().to_vec().into(), + reward_destination: Proto::RewardDestination::STAKED, + call_indices: None, + }))); + + expect_encoded( + &input, + "110000ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e671484652310000", ); } +/// Test staking bond extra. #[test] fn encode_staking_bond_extra() { - let input = Proto::SigningInput { - network: 12, - runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::bond_extra( - BondExtra { - value: U256::from(808081u64).to_big_endian().to_vec().into(), - call_indices: None, - }, - )), - ..Default::default() - }; + let input = build_input(staking_call(StakingVariant::bond_extra(BondExtra { + value: U256::from(808081u64).to_big_endian().to_vec().into(), + call_indices: None, + }))); - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!(encoded.to_hex(), "110146523100"); + expect_encoded(&input, "110146523100"); } +/// Test staking rebond. #[test] fn encode_staking_rebond() { - let input = Proto::SigningInput { - network: 12, - runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::rebond(Rebond { - value: U256::from(808081u64).to_big_endian().to_vec().into(), - call_indices: None, - })), - ..Default::default() - }; + let input = build_input(staking_call(StakingVariant::rebond(Rebond { + value: U256::from(808081u64).to_big_endian().to_vec().into(), + call_indices: None, + }))); - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!(encoded.to_hex(), "111346523100"); + expect_encoded(&input, "111346523100"); } +/// Test staking unbond. #[test] fn encode_staking_unbond() { - let input = Proto::SigningInput { - network: 12, - runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::unbond(Unbond { - value: U256::from(808081u64).to_big_endian().to_vec().into(), - call_indices: None, - })), - ..Default::default() - }; + let input = build_input(staking_call(StakingVariant::unbond(Unbond { + value: U256::from(808081u64).to_big_endian().to_vec().into(), + call_indices: None, + }))); - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!(encoded.to_hex(), "110246523100"); + expect_encoded(&input, "110246523100"); } +/// Test staking withdraw unbonded. #[test] fn encode_staking_withdraw_unbonded() { - let input = Proto::SigningInput { - network: 12, - runtime_call: staking_call(Proto::mod_Staking::OneOfmessage_oneof::withdraw_unbonded( - WithdrawUnbonded { - slashing_spans: 84, + let input = build_input(staking_call(StakingVariant::withdraw_unbonded( + WithdrawUnbonded { + slashing_spans: 84, + call_indices: None, + }, + ))); + + expect_encoded(&input, "110354000000"); +} + +/// Test atomic batching staking calls bond and nominate. +#[test] +fn encode_staking_batch_bond_and_nominate() { + let input = build_input(batch_calls( + BatchKind::Atomic, + vec![ + staking_call(StakingVariant::bond(Bond { + controller: "2HqjMm2goapWvXQBqjjEdVaTZsUmunWwEq1TSToDR1pDzQ1F".into(), + value: U256::from(808081u64).to_big_endian().to_vec().into(), + reward_destination: Proto::RewardDestination::STAKED, call_indices: None, - }, - )), - ..Default::default() - }; + })), + staking_call(StakingVariant::nominate(Nominate { + nominators: vec![ + "2DxgKKS53wsAeETAZXhmT5A1bTt7h1aV4bKdtkMDwwSzSMXm".into(), + "2HqjMm2goapWvXQBqjjEdVaTZsUmunWwEq1TSToDR1pDzQ1F".into(), + ], + call_indices: None, + })), + ], + )); - let encoded = encode_input(&input).expect("error encoding call"); - assert_eq!(encoded.to_hex(), "110354000000"); + expect_encoded( + &input, + "290208110000ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e6714846523100001105080042ef301451c7f596f974daec8ca1234f66809905d13e16c18e23896b0c57e53e00ee93a4f66f8d16b819bb9beb9ffccdfcdc1412e87fee6a324c2a99a1e0e67148" + ); +} + +/// Test atomic batching of staking calls chill and unbond. +#[test] +fn encode_staking_batch_chill_and_unbond() { + let input = build_input(batch_calls( + BatchKind::Atomic, + vec![ + staking_call(StakingVariant::chill(Chill { call_indices: None })), + staking_call(StakingVariant::unbond(Unbond { + value: U256::from(808081u64).to_big_endian().to_vec().into(), + call_indices: None, + })), + ], + )); + + expect_encoded(&input, "2902081106110246523100"); +} + +/// Test optimistic batch of POLYX transfers. +#[test] +fn encode_batch_transfers() { + let input = build_input(batch_calls( + BatchKind::Optimistic, + vec![ + balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: "2EB7wW2fYfFskkSx2d65ivn34ewpuEjcowfJYBL79ty5FsZF".into(), + value: Cow::Owned(U256::from(1u64).to_big_endian().to_vec()), + ..Default::default() + })), + balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: "2EANwBfNsFu9KV8JsW5sbhF6ft8bzvw5EW1LCrgHhrqtK6Ys".into(), + value: Cow::Owned(U256::from(2u64).to_big_endian().to_vec()), + // The memo field is padded with nulls. + memo: "MEMO PADDED WITH SPACES".into(), + ..Default::default() + })), + ], + )); + + expect_encoded( + &input, + "2904080500004c6c63e3dc083959f876788716b78885460b5f3c7ed9379f8d5f408e08639e02040501004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c08014d454d4f20504144444544205749544820535041434553000000000000000000", + ); +} + +/// Test stop on first error batch of POLYX transfers. +#[test] +fn encode_batch_transfers_stop_on_first_error() { + let input = build_input(batch_calls( + BatchKind::StopOnError, + vec![ + balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: "2EB7wW2fYfFskkSx2d65ivn34ewpuEjcowfJYBL79ty5FsZF".into(), + value: Cow::Owned(U256::from(1u64).to_big_endian().to_vec()), + ..Default::default() + })), + balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: "2EANwBfNsFu9KV8JsW5sbhF6ft8bzvw5EW1LCrgHhrqtK6Ys".into(), + value: Cow::Owned(U256::from(2u64).to_big_endian().to_vec()), + // The memo field is padded with nulls. + memo: "MEMO PADDED WITH SPACES".into(), + ..Default::default() + })), + ], + )); + + expect_encoded( + &input, + "2900080500004c6c63e3dc083959f876788716b78885460b5f3c7ed9379f8d5f408e08639e02040501004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c08014d454d4f20504144444544205749544820535041434553000000000000000000", + ); +} + +/// Test that nesting of batch calls is not allowed. +#[test] +fn encode_nested_batch_calls() { + let input = build_input(batch_calls( + BatchKind::Atomic, + vec![ + balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: "2EB7wW2fYfFskkSx2d65ivn34ewpuEjcowfJYBL79ty5FsZF".into(), + value: Cow::Owned(U256::from(1u64).to_big_endian().to_vec()), + ..Default::default() + })), + batch_calls( + BatchKind::Atomic, + vec![balance_call( + Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: "2EANwBfNsFu9KV8JsW5sbhF6ft8bzvw5EW1LCrgHhrqtK6Ys".into(), + value: Cow::Owned(U256::from(2u64).to_big_endian().to_vec()), + // The memo field is padded with nulls. + memo: "MEMO PADDED WITH SPACES".into(), + ..Default::default() + }), + )], + ), + ], + )); + + let tw_err = + CallEncoder::encode_input(&input).expect_err("nested batch calls should not be allowed"); + assert_eq!( + tw_err.error_type(), + &tw_substrate::EncodeError::NotSupported + ); + // Ensure the error message contains the expected context. + let context = format!("{:?}", tw_err); + assert!(context.contains("Nested batch calls not allowed")); } From df79c4229e3761fb5162f7d67d5e4d0c9d625fb2 Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Wed, 4 Dec 2024 16:57:21 +0000 Subject: [PATCH 16/22] Code cleanup. --- rust/tw_tests/tests/chains/polkadot/mod.rs | 2 +- rust/tw_tests/tests/chains/polymesh/mod.rs | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/rust/tw_tests/tests/chains/polkadot/mod.rs b/rust/tw_tests/tests/chains/polkadot/mod.rs index 9ef70e5c95c..58b11f0cdcb 100644 --- a/rust/tw_tests/tests/chains/polkadot/mod.rs +++ b/rust/tw_tests/tests/chains/polkadot/mod.rs @@ -92,7 +92,7 @@ pub fn helper_encode_and_compile( (preimage, signed) } -pub fn balance_call( +fn balance_call( call: Proto::mod_Balance::OneOfmessage_oneof, ) -> Proto::mod_SigningInput::OneOfmessage_oneof { Proto::mod_SigningInput::OneOfmessage_oneof::balance_call(Proto::Balance { diff --git a/rust/tw_tests/tests/chains/polymesh/mod.rs b/rust/tw_tests/tests/chains/polymesh/mod.rs index 68cdd22f228..b3f87898b3f 100644 --- a/rust/tw_tests/tests/chains/polymesh/mod.rs +++ b/rust/tw_tests/tests/chains/polymesh/mod.rs @@ -18,9 +18,7 @@ pub const PUBLIC_KEY_HEX_1: &str = pub const PUBLIC_KEY_2: &str = "2CpqFh8VnwJAjenw4xSUWCaaJ2QwGdhnCikoSEczMhjgqyj7"; -pub fn balance_call( - call: Proto::mod_Balance::OneOfmessage_oneof, -) -> Option> { +fn balance_call(call: Proto::mod_Balance::OneOfmessage_oneof) -> Option> { Some(Proto::RuntimeCall { pallet_oneof: Proto::mod_RuntimeCall::OneOfpallet_oneof::balance_call(Proto::Balance { message_oneof: call, @@ -28,9 +26,7 @@ pub fn balance_call( }) } -pub fn identity_call( - call: Proto::mod_Identity::OneOfmessage_oneof, -) -> Option> { +fn identity_call(call: Proto::mod_Identity::OneOfmessage_oneof) -> Option> { Some(Proto::RuntimeCall { pallet_oneof: Proto::mod_RuntimeCall::OneOfpallet_oneof::identity_call(Proto::Identity { message_oneof: call, From 2637bfe167546d6938f306a98a85843ce8e9a175 Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Thu, 5 Dec 2024 17:47:06 +0000 Subject: [PATCH 17/22] Add mobile tests. --- .../blockchains/CoinAddressDerivationTests.kt | 1 + .../polymesh/TestPolymeshAddress.kt | 30 +++++++++ .../polymesh/TestPolymeshSigner.kt | 63 +++++++++++++++++++ swift/Tests/Blockchains/PolymeshTests.swift | 52 +++++++++++++++ swift/Tests/CoinAddressDerivationTests.swift | 3 + 5 files changed, 149 insertions(+) create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshAddress.kt create mode 100644 android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshSigner.kt create mode 100644 swift/Tests/Blockchains/PolymeshTests.swift diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt index f99db4e987c..b9cb1fe23c8 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt @@ -102,6 +102,7 @@ class CoinAddressDerivationTests { ACALA -> assertEquals("25GGezx3LWFQj6HZpYzoWoVzLsHojGtybef3vthC9nd19ms3", address) KUSAMA -> assertEquals("G9xV2EatmrjRC1FLPexc3ddqNRRzCsAdURU8RFiAAJX6ppY", address) POLKADOT -> assertEquals("13nN6BGAoJwd7Nw1XxeBCx5YcBXuYnL94Mh7i3xBprqVSsFk", address) + POLYMESH -> assertEquals("2DHK8VhBpacs9quk78AVP9TmmcG5iXi2oKtZqneSNsVXxCKw", address) PIVX -> assertEquals("D81AqC8zKma3Cht4TbVuh4jyVVyLkZULCm", address) KAVA -> assertEquals("kava1drpa0x9ptz0fql3frv562rcrhj2nstuz3pas87", address) CARDANO -> assertEquals("addr1qyr8jjfnypp95eq74aqzn7ss687ehxclgj7mu6gratmg3mul2040vt35dypp042awzsjk5xm3zr3zm5qh7454uwdv08s84ray2", address) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshAddress.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshAddress.kt new file mode 100644 index 00000000000..a7e77d9a3e0 --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshAddress.kt @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.polymesh + +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 TestPolymeshAddress { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testAddress() { + + val key = PrivateKey("0x790a0a01ec2e7c7db4abcaffc92ce70a960ef9ad3021dbe3bf327c1c6343aee4".toHexByteArray()) + val pubkey = key.publicKeyEd25519 + val address = AnyAddress(pubkey, CoinType.POLYMESH) + val expected = AnyAddress("2EANwBfNsFu9KV8JsW5sbhF6ft8bzvw5EW1LCrgHhrqtK6Ys", CoinType.POLYMESH) + + assertEquals(pubkey.data().toHex(), "0x4bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c") + assertEquals(address.description(), expected.description()) + } +} diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshSigner.kt new file mode 100644 index 00000000000..4adc768d4ad --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/polymesh/TestPolymeshSigner.kt @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +package com.trustwallet.core.app.blockchains.polymesh + +import com.trustwallet.core.app.utils.Numeric +import com.trustwallet.core.app.utils.toHexBytesInByteString +import org.junit.Assert.assertEquals +import org.junit.Test +import wallet.core.java.AnySigner +import wallet.core.jni.CoinType.POLYMESH +import wallet.core.jni.proto.Polymesh +import wallet.core.jni.proto.Polymesh.SigningOutput + +class TestPolymeshSigner { + + init { + System.loadLibrary("TrustWalletCore") + } + + val genesisHashStr = "0x6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063".toHexBytesInByteString() + // Private key for testing. DO NOT USE, since this is public. + val TestKey1 = "0x790a0a01ec2e7c7db4abcaffc92ce70a960ef9ad3021dbe3bf327c1c6343aee4".toHexBytesInByteString() + + @Test + fun PolymeshTransactionSigning() { + // https://polymesh.subscan.io/extrinsic/0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04 + + // Step 1: Prepare input. + val blockHashStr = "77d32517dcc7b74501096afdcff3af72008a2c489e17083f56629d195e5c6a1d".toHexBytesInByteString() + + var call = Polymesh.Balance.Transfer.newBuilder().apply { + toAddress = "2CpqFh8VnwJAjenw4xSUWCaaJ2QwGdhnCikoSEczMhjgqyj7" + value = "0x0F4240".toHexBytesInByteString() + } + + val input = Polymesh.SigningInput.newBuilder().apply { + genesisHash = genesisHashStr + blockHash = blockHashStr + era = Polymesh.Era.newBuilder().apply { + blockNumber = 16_102_106 + period = 64 + }.build() + network = POLYMESH.ss58Prefix() + nonce = 1 + specVersion = 7000005 + transactionVersion = 7 + privateKey = TestKey1 + runtimeCall = Polymesh.RuntimeCall.newBuilder().apply { + balanceCall = Polymesh.Balance.newBuilder().apply { + transfer = call.build() + }.build() + }.build() + } + + val output = AnySigner.sign(input.build(), POLYMESH, SigningOutput.parser()) + val encoded = Numeric.toHexString(output.encoded.toByteArray()) + + val expected = "0x390284004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c00e9b4742a2b66931e0cf29f6811e4d44545b4f278a667b9eb1217c4b2de8763c8037e4501dd4a21179b737beb33415f458788f2d1093b527cae8bee8b2d55210ba501040005000010b713ceeb165c1ac7c450f5b138a6da0eba50bb18849f5b8e83985daa45a87e02093d00" + assertEquals(encoded, expected) + } +} diff --git a/swift/Tests/Blockchains/PolymeshTests.swift b/swift/Tests/Blockchains/PolymeshTests.swift new file mode 100644 index 00000000000..ec4db4bcfd7 --- /dev/null +++ b/swift/Tests/Blockchains/PolymeshTests.swift @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +import WalletCore +import XCTest + +class PolymeshTests: XCTestCase { + let genesisHash = Data(hexString: "0x6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063")! + // Private key for testing. DO NOT USE, since this is public. + let testKey1 = Data(hexString: "0x790a0a01ec2e7c7db4abcaffc92ce70a960ef9ad3021dbe3bf327c1c6343aee4")! + + func testAddress() { + let key = PrivateKey(data: Data(hexString: "0x790a0a01ec2e7c7db4abcaffc92ce70a960ef9ad3021dbe3bf327c1c6343aee4")!)! + let pubkey = key.getPublicKeyEd25519() + let address = AnyAddress(publicKey: pubkey, coin: .polymesh) + let addressFromString = AnyAddress(string: "2EANwBfNsFu9KV8JsW5sbhF6ft8bzvw5EW1LCrgHhrqtK6Ys", coin: .polymesh)! + + XCTAssertEqual(pubkey.data.hexString, "0x4bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c") + XCTAssertEqual(address.description, addressFromString.description) + } + + func testSignTransfer() { + // https://polymesh.subscan.io/extrinsic/0x98cb5e33d8ff3dd5838c384e2ef9e291314ed8db13f5d4f42cdd70bad54a5e04 + + // Step 1: Prepare input. + let blockHash = Data(hexString: "77d32517dcc7b74501096afdcff3af72008a2c489e17083f56629d195e5c6a1d")! + + let input = PolymeshSigningInput.with { + $0.genesisHash = genesisHash + $0.blockHash = blockHash + $0.nonce = 1 + $0.specVersion = 7000005 + $0.network = CoinType.polymesh.ss58Prefix + $0.transactionVersion = 7 + $0.privateKey = testKey1 + $0.era = PolymeshEra.with { + $0.blockNumber = 16102106 + $0.period = 64 + } + $0.runtimeCall = PolymeshRuntimeCall.with { + $0.balanceCall = PolymeshBalance.Transfer.with { + $0.toAddress = "2CpqFh8VnwJAjenw4xSUWCaaJ2QwGdhnCikoSEczMhjgqyj7" + $0.value = Data(hexString: "0x0F4240")! // 1.0 POLYX + } + } + } + let output: PolymeshSigningOutput = AnySigner.sign(input: input, coin: .polkadot) + + XCTAssertEqual(output.encoded.hexString, "390284004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c00e9b4742a2b66931e0cf29f6811e4d44545b4f278a667b9eb1217c4b2de8763c8037e4501dd4a21179b737beb33415f458788f2d1093b527cae8bee8b2d55210ba501040005000010b713ceeb165c1ac7c450f5b138a6da0eba50bb18849f5b8e83985daa45a87e02093d00") + } +} diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index 72c611f4083..8d57ad595d8 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -203,6 +203,9 @@ class CoinAddressDerivationTests: XCTestCase { case .polkadot: let expectedResult = "13nN6BGAoJwd7Nw1XxeBCx5YcBXuYnL94Mh7i3xBprqVSsFk" assertCoinDerivation(coin, expectedResult, derivedAddress, address) + case .polymesh: + let expectedResult = "2DHK8VhBpacs9quk78AVP9TmmcG5iXi2oKtZqneSNsVXxCKw" + assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .qtum: let expectedResult = "QhceuaTdeCZtcxmVc6yyEDEJ7Riu5gWFoF" assertCoinDerivation(coin, expectedResult, derivedAddress, address) From 0f77590e3ce1603c2fa2842f3e95e4d92d74083c Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Tue, 17 Dec 2024 12:34:30 +0000 Subject: [PATCH 18/22] Cleanup error conversion code. --- rust/chains/tw_polymesh/src/call_encoder.rs | 45 ++++++++++++++------- rust/chains/tw_polymesh/src/entry.rs | 14 ++++--- rust/chains/tw_polymesh/src/types.rs | 22 +++++----- 3 files changed, 52 insertions(+), 29 deletions(-) diff --git a/rust/chains/tw_polymesh/src/call_encoder.rs b/rust/chains/tw_polymesh/src/call_encoder.rs index e1ec4f9e2f1..f09c5156b64 100644 --- a/rust/chains/tw_polymesh/src/call_encoder.rs +++ b/rust/chains/tw_polymesh/src/call_encoder.rs @@ -2,6 +2,7 @@ use std::str::FromStr; use crate::ctx_from_tw; use crate::types::*; +use tw_coin_entry::error::prelude::*; use tw_number::U256; use tw_proto::Polymesh::Proto::{ self, @@ -76,7 +77,9 @@ impl PolymeshBalances { pub fn encode_call(b: &Balance) -> WithCallIndexResult { match &b.message_oneof { BalanceVariant::transfer(t) => Self::encode_transfer(t), - _ => EncodeError::NotSupported.tw_result("Unsupported balance call".to_string()), + _ => Err(EncodeError::NotSupported) + .into_tw() + .context("Unsupported balance call".to_string()), } } } @@ -125,12 +128,15 @@ impl PolymeshIdentity { permissions: perms.try_into().map_err(|_| EncodeError::InvalidValue)?, }, AuthVariant::None => { - return EncodeError::NotSupported - .tw_result("Unsupported Authorization".to_string()); + return Err(EncodeError::NotSupported) + .into_tw() + .context("Unsupported Authorization".to_string()); }, } } else { - return EncodeError::NotSupported.tw_result("Missing Authorization".to_string()); + return Err(EncodeError::NotSupported) + .into_tw() + .context("Missing Authorization".to_string()); }; Ok(ci.wrap(Self::AddAuthorization { @@ -149,7 +155,9 @@ impl PolymeshIdentity { IdentityVariant::join_identity_as_key(t) => Self::encode_join_identity(t), IdentityVariant::leave_identity_as_key(t) => Self::encode_leave_identity(t), IdentityVariant::add_authorization(a) => Self::encode_add_authorization(a), - _ => EncodeError::NotSupported.tw_result("Unsupported identity call".to_string()), + _ => Err(EncodeError::NotSupported) + .into_tw() + .context("Unsupported identity call".to_string()), } } } @@ -269,7 +277,9 @@ impl PolymeshStaking { StakingVariant::withdraw_unbonded(b) => Self::encode_withdraw_unbonded(b), StakingVariant::rebond(b) => Self::encode_rebond(b), StakingVariant::nominate(b) => Self::encode_nominate(b), - _ => EncodeError::NotSupported.tw_result("Unsupported staking call".to_string()), + _ => Err(EncodeError::NotSupported) + .into_tw() + .context("Unsupported staking call".to_string()), } } } @@ -286,8 +296,9 @@ impl_enum_scale!( impl PolymeshUtility { pub fn encode_call(encoder: &mut CallEncoder, u: &Utility) -> WithCallIndexResult { if encoder.batch_depth > 0 { - return EncodeError::NotSupported - .tw_result("Nested batch calls not allowed".to_string()); + return Err(EncodeError::NotSupported) + .into_tw() + .context("Nested batch calls not allowed"); } encoder.batch_depth += 1; match &u.message_oneof { @@ -306,7 +317,9 @@ impl PolymeshUtility { }; Ok(ci.wrap(batch)) }, - _ => EncodeError::NotSupported.tw_result("Unsupported utility call".to_string()), + _ => Err(EncodeError::NotSupported) + .into_tw() + .context("Unsupported utility call"), } } } @@ -333,9 +346,12 @@ impl CallEncoder { pub fn encode_input(input: &'_ Proto::SigningInput<'_>) -> EncodeResult { let ctx = ctx_from_tw(input)?; let mut encoder = Self::from_ctx(&ctx)?; - let call = input.runtime_call.as_ref().ok_or_else(|| { - EncodeError::InvalidValue.with_context("Missing runtime call".to_string()) - })?; + let call = input + .runtime_call + .as_ref() + .ok_or(EncodeError::InvalidValue) + .into_tw() + .context("Missing runtime call")?; encoder.encode_runtime_call(&call) } @@ -354,8 +370,9 @@ impl CallEncoder { PolymeshUtility::encode_call(self, msg)?.map(PolymeshCall::Utility) }, RuntimeCallVariant::None => { - return EncodeError::NotSupported - .tw_result("Runtime call variant is None".to_string()); + return Err(EncodeError::NotSupported) + .into_tw() + .context("Runtime call variant is None".to_string()); }, }; Ok(RawOwned(call.to_scale())) diff --git a/rust/chains/tw_polymesh/src/entry.rs b/rust/chains/tw_polymesh/src/entry.rs index 2383d5c42a3..5df9eb39f95 100644 --- a/rust/chains/tw_polymesh/src/entry.rs +++ b/rust/chains/tw_polymesh/src/entry.rs @@ -37,9 +37,12 @@ impl PolymeshEntry { ) -> EncodeResult { let ctx = ctx_from_tw(&input)?; let mut encoder = CallEncoder::from_ctx(&ctx)?; - let call = input.runtime_call.as_ref().ok_or_else(|| { - EncodeError::InvalidValue.with_context("Missing runtime call".to_string()) - })?; + let call = input + .runtime_call + .as_ref() + .ok_or(EncodeError::InvalidValue) + .into_tw() + .context("Missing runtime call")?; let call = encoder.encode_runtime_call(&call)?; let era = match &input.era { Some(era) => Era::mortal(era.period, era.block_number), @@ -62,8 +65,9 @@ impl PolymeshEntry { builder.extension(ChargeTransactionPayment::new(tip)); if let Some(public_key) = public_key { let account = SubstrateAddress( - SS58Address::from_public_key(&public_key, ctx.network) - .map_err(|e| EncodeError::InvalidAddress.with_error(e))?, + SS58Address::from_public_key(&public_key, ctx.network).map_err(|e| { + TWError::new(EncodeError::InvalidAddress).context(format!("{e:?}")) + })?, ); builder.set_account(account); } diff --git a/rust/chains/tw_polymesh/src/types.rs b/rust/chains/tw_polymesh/src/types.rs index 066c7cc5252..cb6f54e2c3d 100644 --- a/rust/chains/tw_polymesh/src/types.rs +++ b/rust/chains/tw_polymesh/src/types.rs @@ -3,7 +3,7 @@ use std::{ str::FromStr, }; -use tw_coin_entry::error::prelude::TWError; +use tw_coin_entry::error::prelude::*; use tw_hash::{Hash, H256}; use tw_proto::Polymesh::Proto::{ mod_SecondaryKeyPermissions::{ @@ -47,9 +47,10 @@ impl TryFrom<&TWAssetId<'_>> for AssetId { type Error = TWError; fn try_from(id: &TWAssetId) -> Result { - let did = H128::try_from(id.id.as_ref()).map_err(|_| { - EncodeError::InvalidValue.with_context(format!("Expected 16 byte AssetId")) - })?; + let did = H128::try_from(id.id.as_ref()) + .map_err(|_| EncodeError::InvalidValue) + .into_tw() + .context("Expected 16 byte AssetId")?; Ok(Self(did)) } } @@ -63,9 +64,10 @@ impl TryFrom<&TWIdentityId<'_>> for IdentityId { type Error = TWError; fn try_from(id: &TWIdentityId) -> Result { - let did = H256::try_from(id.id.as_ref()).map_err(|_| { - EncodeError::InvalidValue.with_context(format!("Expected 32 byte IdentityId")) - })?; + let did = H256::try_from(id.id.as_ref()) + .map_err(|_| EncodeError::InvalidValue) + .into_tw() + .context("Expected 32 byte IdentityId")?; Ok(Self(did)) } } @@ -95,9 +97,9 @@ impl TryFrom<&TWPortfolioId<'_>> for PortfolioId { did: portfolio .identity .as_ref() - .ok_or_else(|| { - EncodeError::InvalidValue.with_context(format!("Missing portfolio identity")) - })? + .ok_or(EncodeError::InvalidValue) + .into_tw() + .context("Missing portfolio identity")? .try_into()?, kind: if portfolio.default { PortfolioKind::Default From 49505c958a07327d67bb654f1023c3f01e7747d2 Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Tue, 17 Dec 2024 12:35:28 +0000 Subject: [PATCH 19/22] Fix RewardDestination. --- rust/chains/tw_polymesh/src/call_encoder.rs | 2 +- rust/chains/tw_polymesh/src/types.rs | 24 ++++++--------------- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/rust/chains/tw_polymesh/src/call_encoder.rs b/rust/chains/tw_polymesh/src/call_encoder.rs index f09c5156b64..38d69d6a170 100644 --- a/rust/chains/tw_polymesh/src/call_encoder.rs +++ b/rust/chains/tw_polymesh/src/call_encoder.rs @@ -202,7 +202,7 @@ impl PolymeshStaking { Ok(ci.wrap(Self::Bond { controller: controller.into(), value: Compact(value), - reward: RewardDestination::from_tw(b.reward_destination as u8, &b.controller)?, + reward: RewardDestination::from_tw(&b.reward_destination)?, })) } diff --git a/rust/chains/tw_polymesh/src/types.rs b/rust/chains/tw_polymesh/src/types.rs index cb6f54e2c3d..b7d2b47adad 100644 --- a/rust/chains/tw_polymesh/src/types.rs +++ b/rust/chains/tw_polymesh/src/types.rs @@ -1,7 +1,4 @@ -use std::{ - collections::{BTreeMap, BTreeSet}, - str::FromStr, -}; +use std::collections::{BTreeMap, BTreeSet}; use tw_coin_entry::error::prelude::*; use tw_hash::{Hash, H256}; @@ -12,11 +9,9 @@ use tw_proto::Polymesh::Proto::{ RestrictionKind as TWRestrictionKind, }, AssetId as TWAssetId, IdentityId as TWIdentityId, PortfolioId as TWPortfolioId, - SecondaryKeyPermissions, + RewardDestination as TWRewardDestination, SecondaryKeyPermissions, }; use tw_scale::{impl_enum_scale, impl_struct_scale, ToScale}; -use tw_ss58_address::SS58Address; -use tw_substrate::address::SubstrateAddress; use super::*; @@ -336,18 +331,11 @@ impl_enum_scale!( ); impl RewardDestination { - pub fn from_tw(dest: u8, account: &str) -> EncodeResult { + pub fn from_tw(dest: &TWRewardDestination) -> EncodeResult { match dest { - 0 => Ok(Self::Staked), - 1 => Ok(Self::Stash), - 2 => Ok(Self::Controller), - 4 => { - let account = - SS58Address::from_str(account).map_err(|_| EncodeError::InvalidAddress)?; - Ok(Self::Account(SubstrateAddress(account))) - }, - 5 => Ok(Self::None), - _ => EncodeError::InvalidValue.tw_result(format!("Invalid reward destination: {dest}")), + TWRewardDestination::STAKED => Ok(Self::Staked), + TWRewardDestination::STASH => Ok(Self::Stash), + TWRewardDestination::CONTROLLER => Ok(Self::Controller), } } } From 5fcd32ec505be4d85300b702bed75da3fc5659fc Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Tue, 17 Dec 2024 12:36:03 +0000 Subject: [PATCH 20/22] Add missing check_metadata field. --- rust/chains/tw_polymesh/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/chains/tw_polymesh/src/lib.rs b/rust/chains/tw_polymesh/src/lib.rs index 838cbe4f522..cc814de9bbd 100644 --- a/rust/chains/tw_polymesh/src/lib.rs +++ b/rust/chains/tw_polymesh/src/lib.rs @@ -24,5 +24,6 @@ pub fn ctx_from_tw(input: &'_ Proto::SigningInput<'_>) -> EncodeResult Date: Tue, 17 Dec 2024 13:32:47 +0000 Subject: [PATCH 21/22] Move Polymesh signing tests over from Polkadot tests. --- rust/tw_tests/tests/chains/polkadot/mod.rs | 12 - .../tests/chains/polkadot/polkadot_sign.rs | 238 +-------------- rust/tw_tests/tests/chains/polymesh/mod.rs | 130 +++++++- .../tests/chains/polymesh/polymesh_compile.rs | 26 +- .../tests/chains/polymesh/polymesh_sign.rs | 286 ++++++++++++++++-- 5 files changed, 406 insertions(+), 286 deletions(-) diff --git a/rust/tw_tests/tests/chains/polkadot/mod.rs b/rust/tw_tests/tests/chains/polkadot/mod.rs index 58b11f0cdcb..ff4f5541cc5 100644 --- a/rust/tw_tests/tests/chains/polkadot/mod.rs +++ b/rust/tw_tests/tests/chains/polkadot/mod.rs @@ -107,15 +107,3 @@ pub fn staking_call( message_oneof: call, }) } - -pub fn polymesh_call( - call: Proto::mod_Identity::OneOfmessage_oneof, -) -> Proto::mod_SigningInput::OneOfmessage_oneof { - Proto::mod_SigningInput::OneOfmessage_oneof::polymesh_call(Proto::PolymeshCall { - message_oneof: Proto::mod_PolymeshCall::OneOfmessage_oneof::identity_call( - Proto::Identity { - message_oneof: call, - }, - ), - }) -} diff --git a/rust/tw_tests/tests/chains/polkadot/polkadot_sign.rs b/rust/tw_tests/tests/chains/polkadot/polkadot_sign.rs index 98b8631c1d0..d12a3ac2a5a 100644 --- a/rust/tw_tests/tests/chains/polkadot/polkadot_sign.rs +++ b/rust/tw_tests/tests/chains/polkadot/polkadot_sign.rs @@ -3,9 +3,8 @@ // Copyright © 2017 Trust Wallet. use crate::chains::polkadot::{ - balance_call, helper_encode, helper_encode_and_maybe_sign, helper_sign, polymesh_call, - staking_call, ACCOUNT_2, GENESIS_HASH, POLYMESH_GENESIS_HASH, PRIVATE_KEY, PRIVATE_KEY_2, - PRIVATE_KEY_IOS, PRIVATE_KEY_POLKADOT, + balance_call, helper_encode, helper_encode_and_maybe_sign, helper_sign, staking_call, + ACCOUNT_2, GENESIS_HASH, PRIVATE_KEY, PRIVATE_KEY_2, PRIVATE_KEY_IOS, PRIVATE_KEY_POLKADOT, }; use std::borrow::Cow; use tw_any_coin::any_address::AnyAddress; @@ -427,148 +426,6 @@ fn test_polkadot_sign_chill_and_unbond() { ); } -// TEST(TWAnySignerPolkadot, PolymeshEncodeAndSign) -#[test] -fn test_polymesh_encode_and_sign() { - // tx on mainnet - // https://polymesh.subscan.io/extrinsic/0x9a4283cc38f7e769c53ad2d1c5cf292fc85a740ec1c1aa80c180847e51928650 - - let block_hash = "898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102" - .decode_hex() - .unwrap(); - let genesis_hash = POLYMESH_GENESIS_HASH.decode_hex().unwrap(); - - let input = Proto::SigningInput { - network: 12, - multi_address: true, - nonce: 1, - block_hash: block_hash.into(), - genesis_hash: genesis_hash.into(), - spec_version: 3010, - transaction_version: 2, - era: Some(Proto::Era { - block_number: 4298130, - period: 64, - }), - message_oneof: balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { - to_address: "2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW".into(), - value: Cow::Owned(U256::from(1000000u64).to_big_endian().to_vec()), - // The original C++ test had the wrong memo, since it didn't space pad the memo to 32 bytes. - memo: "MEMO PADDED WITH SPACES ".into(), - call_indices: custom_call_indices(0x05, 0x01), - ..Default::default() - })), - ..Default::default() - }; - - let public_key = "4322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee"; - let signature = "0791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f"; - - // Compile and verify the ED25519 signature. - let (preimage, signed) = - helper_encode_and_compile(CoinType::Polkadot, input, signature, public_key, true); - - assert_eq!(preimage, "050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f2050414444454420574954482053504143455320202020202020202025010400c20b0000020000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102"); - // This signed tranaction is different from the original C++ test, but matches the transaction on Polymesh. - assert_eq!(signed, "bd0284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee000791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f25010400050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f20504144444544205749544820535041434553202020202020202020"); -} - -// TEST(TWAnySignerPolkadot, PolymeshEncodeBondAndNominate) -#[test] -fn test_polymesh_encode_bond_and_nominate() { - // tx on mainnet - // https://polymesh.subscan.io/extrinsic/0xd516d4cb1f5ade29e557586e370e98c141c90d87a0b7547d98c6580eb2afaeeb - - let block_hash = "ab67744c78f1facfec9e517810a47ae23bc438315a01dac5ffee46beed5ad3d8" - .decode_hex() - .unwrap(); - let genesis_hash = POLYMESH_GENESIS_HASH.decode_hex().unwrap(); - - let input = Proto::SigningInput { - network: 12, - multi_address: true, - nonce: 0, - block_hash: block_hash.into(), - genesis_hash: genesis_hash.into(), - spec_version: 6003050, - transaction_version: 4, - era: Some(Proto::Era { - block_number: 15742961, - period: 64, - }), - message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::bond_and_nominate( - Proto::mod_Staking::BondAndNominate { - controller: "2EYbDVDVWiFbXZWJgqGDJsiH5MfNeLr5fxqH3tX84LQZaETG".into(), - value: Cow::Owned(U256::from(4000000u64).to_big_endian().to_vec()), // 4.0 POLYX - reward_destination: Proto::RewardDestination::STAKED.into(), - nominators: vec!["2Gw8mSc4CUMxXMKEDqEsumQEXE5yTF8ACq2KdHGuigyXkwtz".into()], - call_indices: custom_call_indices(0x29, 0x02), - bond_call_indices: custom_call_indices(0x11, 0x00), - nominate_call_indices: custom_call_indices(0x11, 0x05), - ..Default::default() - }, - )), - ..Default::default() - }; - - let preimage = helper_encode(CoinType::Polkadot, &input); - - assert_eq!(preimage, "2902081100005ccc5c9276ab7976e7c93c70c190fbf1761578c07b892d0d1fe65972f6a290610224f4000011050400c6766ff780e1f506e41622f7798ec9323ab3b8bea43767d8c107e1e920581958150300006a995b00040000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063ab67744c78f1facfec9e517810a47ae23bc438315a01dac5ffee46beed5ad3d8"); - - // Can't compile a transaction with an SR25519 signature. - /* - // The public key is an SR25519 key and the signature is an SR25519 signature. - let public_key = "5ccc5c9276ab7976e7c93c70c190fbf1761578c07b892d0d1fe65972f6a29061"; - let signature = "685a2fd4b1bdf7775c55eb97302a0f86b0c10848fd9db3a7f6bbe912c4c2fa28bed16f6032852ec14f27f0553523dd2fc181a6dca79f19f9c7ed6cb660cf6480"; - - let (preimage, signed) = - helper_encode_and_compile(CoinType::Polkadot, input, signature, public_key, true); - assert_eq!(signed, "d90284005ccc5c9276ab7976e7c93c70c190fbf1761578c07b892d0d1fe65972f6a2906101685a2fd4b1bdf7775c55eb97302a0f86b0c10848fd9db3a7f6bbe912c4c2fa28bed16f6032852ec14f27f0553523dd2fc181a6dca79f19f9c7ed6cb660cf6480150300002902081100005ccc5c9276ab7976e7c93c70c190fbf1761578c07b892d0d1fe65972f6a290610224f4000011050400c6766ff780e1f506e41622f7798ec9323ab3b8bea43767d8c107e1e920581958"); - */ -} - -// TEST(TWAnySignerPolkadot, PolymeshEncodeChillAndUnbond) -#[test] -fn test_polymesh_encode_chill_and_unbond() { - // extrinsic on mainnet - // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x29020811061102027a030a - - let block_hash = "ab67744c78f1facfec9e517810a47ae23bc438315a01dac5ffee46beed5ad3d8" - .decode_hex() - .unwrap(); - let genesis_hash = POLYMESH_GENESIS_HASH.decode_hex().unwrap(); - - let input = Proto::SigningInput { - network: 12, - multi_address: true, - nonce: 0, - block_hash: block_hash.into(), - genesis_hash: genesis_hash.into(), - spec_version: 6003050, - transaction_version: 4, - era: Some(Proto::Era { - block_number: 15742961, - period: 64, - }), - message_oneof: staking_call(Proto::mod_Staking::OneOfmessage_oneof::chill_and_unbond( - Proto::mod_Staking::ChillAndUnbond { - value: Cow::Owned(U256::from(42000000u64).to_big_endian().to_vec()), // 42.0 POLYX - call_indices: custom_call_indices(0x29, 0x02), - chill_call_indices: custom_call_indices(0x11, 0x06), - unbond_call_indices: custom_call_indices(0x11, 0x02), - ..Default::default() - }, - )), - ..Default::default() - }; - - let preimage = helper_encode(CoinType::Polkadot, &input); - - assert_eq!( - preimage, - "29020811061102027a030a150300006a995b00040000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063ab67744c78f1facfec9e517810a47ae23bc438315a01dac5ffee46beed5ad3d8"); -} - // TEST(TWAnySignerPolkadot, Statemint_encodeTransaction_transfer) #[test] fn test_statemint_encode_transaction_transfer() { @@ -802,97 +659,6 @@ fn test_statemint_encode_transaction_usdt_transfer_keep_alive() { assert_eq!(signed, "5102840081f5dd1432e5dd60aa71819e1141ad5e54d6f4277d7d128030154114444b8c9100d22583408806c005a24caf16f2084691f4c6dcb6015e6645adc86fc1474369b0e0b7dbcc0ef25b17eae43844aff6fb42a0b279a19e822c76043cac015be5e40a00200001c00700003206011f0050e47b3c8aef60bc4fc744d8d979cb0eb2d45fa25c2e9da74e1e5ebd9e117518821a0600"); } -// TEST(TWAnySignerPolkadot, encodeTransaction_Add_authorization) -#[test] -fn test_encode_transaction_add_authorization() { - // tx on mainnet - // https://polymesh.subscan.io/extrinsic/0x7d9b9109027b36b72d37ba0648cb70e5254524d3d6752cc6b41601f4bdfb1af0 - - let block_hash = "ce0c2109db498e45abf8fd447580dcfa7b7a07ffc2bfb1a0fbdd1af3e8816d2b" - .decode_hex() - .unwrap(); - let genesis_hash = POLYMESH_GENESIS_HASH.decode_hex().unwrap(); - - // Set empty "These". - let empty = Proto::mod_Identity::mod_AddAuthorization::Data { - data: vec![0x00u8].into(), - }; - let input = Proto::SigningInput { - network: 12, - multi_address: true, - nonce: 5, - block_hash: block_hash.into(), - genesis_hash: genesis_hash.into(), - spec_version: 3010, - transaction_version: 2, - era: Some(Proto::Era { - block_number: 4395451, - period: 64, - }), - message_oneof: polymesh_call(Proto::mod_Identity::OneOfmessage_oneof::add_authorization( - Proto::mod_Identity::AddAuthorization { - target: "2HEVN4PHYKj7B1krQ9bctAQXZxHQQkANVNCcfbdYk2gZ4cBR".into(), - data: Some(Proto::mod_Identity::mod_AddAuthorization::AuthData { - asset: Some(empty.clone()), - extrinsic: Some(empty.clone()), - portfolio: Some(empty.clone()), - }), - call_indices: custom_call_indices(0x07, 0x0d), - ..Default::default() - }, - )), - ..Default::default() - }; - - let public_key = "4322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee"; - let signature = "81e6561e4391862b5da961d7033baced1c4b25f0e27f938b02321af1118e0b859e1c2bd5607576a258f2c2befbc5f397ea4adb62938f30eb73c8060ab0eabf01"; - let (_preimage, signed) = - helper_encode_and_compile(CoinType::Polkadot, input, signature, public_key, true); - assert_eq!(signed, "490284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee0081e6561e4391862b5da961d7033baced1c4b25f0e27f938b02321af1118e0b859e1c2bd5607576a258f2c2befbc5f397ea4adb62938f30eb73c8060ab0eabf01b5031400070d01d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d0610540501000100010000"); -} - -// TEST(TWAnySignerPolkadot, encodeTransaction_JoinIdentityAsKey) -#[test] -fn test_encode_transaction_join_identity_as_key() { - // tx on mainnet - // https://polymesh.subscan.io/extrinsic/0x9d7297d8b38af5668861996cb115f321ed681989e87024fda64eae748c2dc542 - - let block_hash = "45c80153c47f5d16acc7a66d473870e8d4574437a7d8c813f47da74cae3812c2" - .decode_hex() - .unwrap(); - let genesis_hash = POLYMESH_GENESIS_HASH.decode_hex().unwrap(); - - let input = Proto::SigningInput { - network: 12, - multi_address: true, - nonce: 0, - block_hash: block_hash.into(), - genesis_hash: genesis_hash.into(), - spec_version: 3010, - transaction_version: 2, - era: Some(Proto::Era { - block_number: 4395527, - period: 64, - }), - message_oneof: polymesh_call( - Proto::mod_Identity::OneOfmessage_oneof::join_identity_as_key( - Proto::mod_Identity::JoinIdentityAsKey { - auth_id: 21435, - call_indices: custom_call_indices(0x07, 0x05), - ..Default::default() - }, - ), - ), - ..Default::default() - }; - - let public_key = "d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d061054"; - let signature = "7f5adbb2749e2f0ace29b409c41dd717681495b1f22dc5358311646a9fb8af8a173fc47f1b19748fb56831c2128773e2976986685adee83c741ab49934d80006"; - let (_preimage, signed) = - helper_encode_and_compile(CoinType::Polkadot, input, signature, public_key, true); - assert_eq!(signed, "c5018400d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d061054007f5adbb2749e2f0ace29b409c41dd717681495b1f22dc5358311646a9fb8af8a173fc47f1b19748fb56831c2128773e2976986685adee83c741ab49934d80006750000000705bb53000000000000"); -} - // TEST(TWAnySignerPolkadot, Kusama_SignBond_NoController) #[test] fn test_kusama_sign_bond_no_controller() { diff --git a/rust/tw_tests/tests/chains/polymesh/mod.rs b/rust/tw_tests/tests/chains/polymesh/mod.rs index b3f87898b3f..578833cc23f 100644 --- a/rust/tw_tests/tests/chains/polymesh/mod.rs +++ b/rust/tw_tests/tests/chains/polymesh/mod.rs @@ -2,7 +2,15 @@ // // Copyright © 2017 Trust Wallet. -use tw_proto::Polymesh::Proto; +use tw_any_coin::test_utils::sign_utils::AnySignerHelper; +use tw_any_coin::test_utils::sign_utils::{CompilerHelper, PreImageHelper}; +use tw_coin_registry::coin_type::CoinType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_keypair::ed25519::{sha512::PublicKey, Signature}; +use tw_keypair::traits::VerifyingKeyTrait; +use tw_proto::Common::Proto::SigningError; +use tw_proto::Polymesh::Proto::{self, SigningInput}; +use tw_proto::TxCompiler::Proto::{self as CompilerProto, PreSigningOutput}; mod polymesh_address; mod polymesh_compile; @@ -18,18 +26,126 @@ pub const PUBLIC_KEY_HEX_1: &str = pub const PUBLIC_KEY_2: &str = "2CpqFh8VnwJAjenw4xSUWCaaJ2QwGdhnCikoSEczMhjgqyj7"; -fn balance_call(call: Proto::mod_Balance::OneOfmessage_oneof) -> Option> { - Some(Proto::RuntimeCall { +fn custom_call_indices(module: u8, method: u8) -> Option { + Some(Proto::CallIndices { + variant: Proto::mod_CallIndices::OneOfvariant::custom(Proto::CustomCallIndices { + module_index: module as i32, + method_index: method as i32, + }), + }) +} + +fn helper_sign(coin: CoinType, input: SigningInput<'_>) -> String { + let mut signer = AnySignerHelper::::default(); + let signed_output = signer.sign(coin, input); + assert_eq!(signed_output.error, SigningError::OK); + + signed_output.encoded.to_hex() +} + +fn helper_encode(coin: CoinType, input: &SigningInput<'_>) -> String { + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(coin, input); + + assert_eq!(preimage_output.error, SigningError::OK); + preimage_output.data.to_hex() +} + +fn helper_encode_and_compile( + coin: CoinType, + input: Proto::SigningInput, + signature: &str, + public_key: &str, + ed25519: bool, +) -> (String, String) { + // Step 1: Obtain preimage hash + let mut pre_imager = PreImageHelper::::default(); + let preimage_output = pre_imager.pre_image_hashes(coin, &input); + + assert_eq!(preimage_output.error, SigningError::OK); + let preimage = preimage_output.data.to_hex(); + + // Step 2: Compile transaction info + + // Simulate signature, normally obtained from signature server + let signature_bytes = signature.decode_hex().unwrap(); + let public_key = public_key.decode_hex().unwrap(); + + // Verify signature (pubkey & hash & signature) + if !ed25519 { + let signature = Signature::try_from(signature_bytes.as_slice()).unwrap(); + let public = PublicKey::try_from(public_key.as_slice()).unwrap(); + assert!(public.verify(signature, preimage_output.data.into())); + } + + // Compile transaction info + let mut compiler = CompilerHelper::::default(); + let output = compiler.compile(coin, &input, vec![signature_bytes], vec![public_key]); + assert_eq!(output.error, SigningError::OK); + let signed = output.encoded.to_hex(); + + (preimage, signed) +} + +fn balance_call(call: Proto::mod_Balance::OneOfmessage_oneof) -> Proto::RuntimeCall<'_> { + Proto::RuntimeCall { pallet_oneof: Proto::mod_RuntimeCall::OneOfpallet_oneof::balance_call(Proto::Balance { message_oneof: call, }), - }) + } } -fn identity_call(call: Proto::mod_Identity::OneOfmessage_oneof) -> Option> { - Some(Proto::RuntimeCall { +fn identity_call(call: Proto::mod_Identity::OneOfmessage_oneof) -> Proto::RuntimeCall<'_> { + Proto::RuntimeCall { pallet_oneof: Proto::mod_RuntimeCall::OneOfpallet_oneof::identity_call(Proto::Identity { message_oneof: call, }), - }) + } +} + +fn identity_add_auth_call( + add_auth: Proto::mod_Identity::AddAuthorization, +) -> Proto::RuntimeCall<'_> { + identity_call(Proto::mod_Identity::OneOfmessage_oneof::add_authorization( + add_auth, + )) +} + +fn identity_join_identity( + auth_id: u64, + call_indices: Option, +) -> Proto::RuntimeCall<'static> { + identity_call( + Proto::mod_Identity::OneOfmessage_oneof::join_identity_as_key( + Proto::mod_Identity::JoinIdentityAsKey { + call_indices, + auth_id, + }, + ), + ) +} + +fn staking_call(call: Proto::mod_Staking::OneOfmessage_oneof) -> Proto::RuntimeCall<'_> { + Proto::RuntimeCall { + pallet_oneof: Proto::mod_RuntimeCall::OneOfpallet_oneof::staking_call(Proto::Staking { + message_oneof: call, + }), + } +} + +fn batch_calls( + kind: Proto::mod_Utility::BatchKind, + calls: Vec>, +) -> Proto::RuntimeCall<'static> { + Proto::RuntimeCall { + pallet_oneof: Proto::mod_RuntimeCall::OneOfpallet_oneof::utility_call(Proto::Utility { + message_oneof: Proto::mod_Utility::OneOfmessage_oneof::batch( + Proto::mod_Utility::Batch { + kind, + calls, + ..Default::default() + }, + ), + }), + } } diff --git a/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs b/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs index 988c8ecfaa5..29b4a82c5f3 100644 --- a/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs +++ b/rust/tw_tests/tests/chains/polymesh/polymesh_compile.rs @@ -49,8 +49,8 @@ fn test_polymesh_compile_add_authorization() { block_number: 16_102_074, period: 64, }), - runtime_call: identity_call(Proto::mod_Identity::OneOfmessage_oneof::add_authorization( - AddAuthorization { + runtime_call: Some(identity_call( + Proto::mod_Identity::OneOfmessage_oneof::add_authorization(AddAuthorization { target: PUBLIC_KEY_1.into(), authorization: Some(Authorization { auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions { @@ -72,7 +72,7 @@ fn test_polymesh_compile_add_authorization() { }), }), ..Default::default() - }, + }), )), ..Default::default() }; @@ -110,12 +110,12 @@ fn test_polymesh_compile_join_identity() { block_number: 16_102_087, period: 64, }), - runtime_call: identity_call( + runtime_call: Some(identity_call( Proto::mod_Identity::OneOfmessage_oneof::join_identity_as_key(JoinIdentityAsKey { auth_id: 52_188, ..Default::default() }), - ), + )), ..Default::default() }; @@ -176,11 +176,13 @@ fn test_polymesh_compile_transfer() { block_number: 16_102_106, period: 64, }), - runtime_call: balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { - to_address: PUBLIC_KEY_2.into(), - value: Cow::Owned(U256::from(value).to_big_endian().to_vec()), - ..Default::default() - })), + runtime_call: Some(balance_call( + Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + to_address: PUBLIC_KEY_2.into(), + value: Cow::Owned(U256::from(value).to_big_endian().to_vec()), + ..Default::default() + }), + )), ..Default::default() }; @@ -244,11 +246,11 @@ fn test_polymesh_compile_leave_identity() { block_number: 16_102_110, period: 64, }), - runtime_call: identity_call( + runtime_call: Some(identity_call( Proto::mod_Identity::OneOfmessage_oneof::leave_identity_as_key(LeaveIdentityAsKey { ..Default::default() }), - ), + )), ..Default::default() }; diff --git a/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs b/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs index 047091fdeee..ac2a0685dc5 100644 --- a/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs +++ b/rust/tw_tests/tests/chains/polymesh/polymesh_sign.rs @@ -3,15 +3,28 @@ // Copyright © 2017 Trust Wallet. use crate::chains::polymesh::{ - balance_call, identity_call, GENESIS_HASH, PRIVATE_KEY_1, PUBLIC_KEY_2, + balance_call, batch_calls, custom_call_indices, helper_encode, helper_encode_and_compile, + helper_sign, identity_add_auth_call, identity_call, identity_join_identity, staking_call, + GENESIS_HASH, PRIVATE_KEY_1, PUBLIC_KEY_2, }; use std::borrow::Cow; -use tw_any_coin::test_utils::sign_utils::AnySignerHelper; use tw_coin_registry::coin_type::CoinType; -use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_encoding::hex::DecodeHex; use tw_number::U256; -use tw_proto::Common::Proto::SigningError; -use tw_proto::Polymesh::Proto::{self, mod_Balance::Transfer, mod_Identity::JoinIdentityAsKey}; +use tw_proto::Polymesh::Proto::{ + self, + mod_Balance::{OneOfmessage_oneof as BalanceVariant, Transfer}, + mod_Identity::{ + mod_AddAuthorization::{mod_Authorization::OneOfauth_oneof as AuthVariant, Authorization}, + AddAuthorization, JoinIdentityAsKey, OneOfmessage_oneof as IdentityVariant, + }, + mod_SecondaryKeyPermissions::{ + AssetPermissions, ExtrinsicPermissions, PortfolioPermissions, RestrictionKind, + }, + mod_Staking::{Bond, Chill, Nominate, OneOfmessage_oneof as StakingVariant, Unbond}, + mod_Utility::BatchKind, + SecondaryKeyPermissions, +}; /// Test a join identity as key transaction. #[test] @@ -38,21 +51,19 @@ fn test_polymesh_sign_join_identity() { block_number: 16_102_087, period: 64, }), - runtime_call: identity_call( - Proto::mod_Identity::OneOfmessage_oneof::join_identity_as_key(JoinIdentityAsKey { + runtime_call: Some(identity_call(IdentityVariant::join_identity_as_key( + JoinIdentityAsKey { auth_id: 52_188, ..Default::default() - }), - ), + }, + ))), ..Default::default() }; - let mut signer = AnySignerHelper::::default(); - let output = signer.sign(CoinType::Polymesh, input); + let signed = helper_sign(CoinType::Polymesh, input); - assert_eq!(output.error, SigningError::OK); assert_eq!( - output.encoded.to_hex(), + signed, "c50184004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c00b40292db45bc8f910b580a586ff81f6c1655fc928d0bf0f41929385b26fda364985d9dee576dec47712a215bb7f70f4c926d1853533cdb693a45c65e8c017904750000000704dccb000000000000" ); } @@ -82,20 +93,257 @@ fn test_polymesh_sign_transfer() { block_number: 16_102_106, period: 64, }), - runtime_call: balance_call(Proto::mod_Balance::OneOfmessage_oneof::transfer(Transfer { + runtime_call: Some(balance_call(BalanceVariant::transfer(Transfer { to_address: PUBLIC_KEY_2.into(), value: Cow::Owned(U256::from(value).to_big_endian().to_vec()), ..Default::default() - })), + }))), ..Default::default() }; - let mut signer = AnySignerHelper::::default(); - let output = signer.sign(CoinType::Polymesh, input); + let signed = helper_sign(CoinType::Polymesh, input); - assert_eq!(output.error, SigningError::OK); assert_eq!( - output.encoded.to_hex(), + signed, "390284004bdb9ef424035e1621e228bd11c5917d7d1dac5965d244c4c72fc91170244f0c00e9b4742a2b66931e0cf29f6811e4d44545b4f278a667b9eb1217c4b2de8763c8037e4501dd4a21179b737beb33415f458788f2d1093b527cae8bee8b2d55210ba501040005000010b713ceeb165c1ac7c450f5b138a6da0eba50bb18849f5b8e83985daa45a87e02093d00" ); } + +// TEST(TWAnySignerPolkadot, PolymeshEncodeAndSign) +#[test] +fn test_polymesh_encode_and_sign() { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x9a4283cc38f7e769c53ad2d1c5cf292fc85a740ec1c1aa80c180847e51928650 + + let block_hash = "898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + nonce: 1, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 3010, + transaction_version: 2, + era: Some(Proto::Era { + block_number: 4298130, + period: 64, + }), + runtime_call: Some(balance_call(BalanceVariant::transfer(Transfer { + to_address: "2FSoQykVV3uWe5ChZuazMDHBoaZmCPPuoYx5KHL5VqXooDQW".into(), + value: Cow::Owned(U256::from(1000000u64).to_big_endian().to_vec()), + // The original C++ test had the wrong memo, since it didn't space pad the memo to 32 bytes. + memo: "MEMO PADDED WITH SPACES ".into(), + ..Default::default() + }))), + ..Default::default() + }; + + let public_key = "4322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee"; + let signature = "0791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f"; + + // Compile and verify the ED25519 signature. + let (preimage, signed) = + helper_encode_and_compile(CoinType::Polymesh, input, signature, public_key, true); + + assert_eq!(preimage, "050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f2050414444454420574954482053504143455320202020202020202025010400c20b0000020000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063898bba6413c38f79a284aec8749f297f6c8734c501f67517b5a6aadc338d1102"); + // This signed tranaction is different from the original C++ test, but matches the transaction on Polymesh. + assert_eq!(signed, "bd0284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee000791ee378775eaff34ef7e529ab742f0d81d281fdf20ace0aa765ca484f5909c4eea0a59c8dbbc534c832704924b424ba3230c38acd0ad5360cef023ca2a420f25010400050100849e2f6b165d4b28b39ef3d98f86c0520d82bc349536324365c10af08f323f8302093d00014d454d4f20504144444544205749544820535041434553202020202020202020"); +} + +// TEST(TWAnySignerPolkadot, PolymeshEncodeBondAndNominate) +#[test] +fn test_polymesh_encode_bond_and_nominate() { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0xd516d4cb1f5ade29e557586e370e98c141c90d87a0b7547d98c6580eb2afaeeb + + let block_hash = "ab67744c78f1facfec9e517810a47ae23bc438315a01dac5ffee46beed5ad3d8" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + nonce: 0, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 6003050, + transaction_version: 4, + era: Some(Proto::Era { + block_number: 15742961, + period: 64, + }), + runtime_call: Some(batch_calls( + BatchKind::Atomic, + vec![ + staking_call(StakingVariant::bond(Bond { + controller: "2EYbDVDVWiFbXZWJgqGDJsiH5MfNeLr5fxqH3tX84LQZaETG".into(), + value: Cow::Owned(U256::from(4000000u64).to_big_endian().to_vec()), // 4.0 POLYX + reward_destination: Proto::RewardDestination::STAKED.into(), + call_indices: None, + })), + staking_call(StakingVariant::nominate(Nominate { + nominators: vec!["2Gw8mSc4CUMxXMKEDqEsumQEXE5yTF8ACq2KdHGuigyXkwtz".into()], + call_indices: None, + })), + ], + )), + ..Default::default() + }; + + let preimage = helper_encode(CoinType::Polymesh, &input); + + assert_eq!(preimage, "2902081100005ccc5c9276ab7976e7c93c70c190fbf1761578c07b892d0d1fe65972f6a290610224f4000011050400c6766ff780e1f506e41622f7798ec9323ab3b8bea43767d8c107e1e920581958150300006a995b00040000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063ab67744c78f1facfec9e517810a47ae23bc438315a01dac5ffee46beed5ad3d8"); + + // Can't compile a transaction with an SR25519 signature. + /* + // The public key is an SR25519 key and the signature is an SR25519 signature. + let public_key = "5ccc5c9276ab7976e7c93c70c190fbf1761578c07b892d0d1fe65972f6a29061"; + let signature = "685a2fd4b1bdf7775c55eb97302a0f86b0c10848fd9db3a7f6bbe912c4c2fa28bed16f6032852ec14f27f0553523dd2fc181a6dca79f19f9c7ed6cb660cf6480"; + + let (preimage, signed) = + helper_encode_and_compile(CoinType::Polymesh, input, signature, public_key, true); + assert_eq!(signed, "d90284005ccc5c9276ab7976e7c93c70c190fbf1761578c07b892d0d1fe65972f6a2906101685a2fd4b1bdf7775c55eb97302a0f86b0c10848fd9db3a7f6bbe912c4c2fa28bed16f6032852ec14f27f0553523dd2fc181a6dca79f19f9c7ed6cb660cf6480150300002902081100005ccc5c9276ab7976e7c93c70c190fbf1761578c07b892d0d1fe65972f6a290610224f4000011050400c6766ff780e1f506e41622f7798ec9323ab3b8bea43767d8c107e1e920581958"); + */ +} + +// TEST(TWAnySignerPolkadot, PolymeshEncodeChillAndUnbond) +#[test] +fn test_polymesh_encode_chill_and_unbond() { + // extrinsic on mainnet + // https://mainnet-app.polymesh.network/#/extrinsics/decode/0x29020811061102027a030a + + let block_hash = "ab67744c78f1facfec9e517810a47ae23bc438315a01dac5ffee46beed5ad3d8" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + nonce: 0, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 6003050, + transaction_version: 4, + era: Some(Proto::Era { + block_number: 15742961, + period: 64, + }), + runtime_call: Some(batch_calls( + BatchKind::Atomic, + vec![ + staking_call(StakingVariant::chill(Chill { call_indices: None })), + staking_call(StakingVariant::unbond(Unbond { + value: Cow::Owned(U256::from(42000000u64).to_big_endian().to_vec()), // 42.0 POLYX + call_indices: None, + })), + ], + )), + ..Default::default() + }; + + let preimage = helper_encode(CoinType::Polymesh, &input); + + assert_eq!( + preimage, + "29020811061102027a030a150300006a995b00040000006fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063ab67744c78f1facfec9e517810a47ae23bc438315a01dac5ffee46beed5ad3d8"); +} + +// TEST(TWAnySignerPolkadot, encodeTransaction_Add_authorization) +#[test] +fn test_encode_transaction_add_authorization() { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x7d9b9109027b36b72d37ba0648cb70e5254524d3d6752cc6b41601f4bdfb1af0 + + let block_hash = "ce0c2109db498e45abf8fd447580dcfa7b7a07ffc2bfb1a0fbdd1af3e8816d2b" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + nonce: 5, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 3010, + transaction_version: 2, + era: Some(Proto::Era { + block_number: 4395451, + period: 64, + }), + runtime_call: Some(identity_add_auth_call(AddAuthorization { + target: "2HEVN4PHYKj7B1krQ9bctAQXZxHQQkANVNCcfbdYk2gZ4cBR".into(), + authorization: Some(Authorization { + auth_oneof: AuthVariant::join_identity(SecondaryKeyPermissions { + // No asset permissions. + asset: Some(AssetPermissions { + kind: RestrictionKind::These, + // Set empty "These". + assets: vec![], + }), + // No extrinsic permissions. + extrinsic: Some(ExtrinsicPermissions { + kind: RestrictionKind::These, + // Set empty "These". + pallets: vec![], + }), + // No portfolio permissions. + portfolio: Some(PortfolioPermissions { + kind: RestrictionKind::These, + // Set empty "These". + portfolios: vec![], + }), + }), + }), + // Old Polymesh v4.x call indices. + call_indices: custom_call_indices(0x07, 0x0d), + ..Default::default() + })), + ..Default::default() + }; + + let public_key = "4322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee"; + let signature = "81e6561e4391862b5da961d7033baced1c4b25f0e27f938b02321af1118e0b859e1c2bd5607576a258f2c2befbc5f397ea4adb62938f30eb73c8060ab0eabf01"; + let (_preimage, signed) = + helper_encode_and_compile(CoinType::Polymesh, input, signature, public_key, true); + assert_eq!(signed, "490284004322cf71da08f9d56181a707af7c0c437dfcb93e6caac9825a5aba57548142ee0081e6561e4391862b5da961d7033baced1c4b25f0e27f938b02321af1118e0b859e1c2bd5607576a258f2c2befbc5f397ea4adb62938f30eb73c8060ab0eabf01b5031400070d01d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d0610540501000100010000"); +} + +// TEST(TWAnySignerPolkadot, encodeTransaction_JoinIdentityAsKey) +#[test] +fn test_encode_transaction_join_identity_as_key() { + // tx on mainnet + // https://polymesh.subscan.io/extrinsic/0x9d7297d8b38af5668861996cb115f321ed681989e87024fda64eae748c2dc542 + + let block_hash = "45c80153c47f5d16acc7a66d473870e8d4574437a7d8c813f47da74cae3812c2" + .decode_hex() + .unwrap(); + let genesis_hash = GENESIS_HASH.decode_hex().unwrap(); + + let input = Proto::SigningInput { + network: 12, + nonce: 0, + block_hash: block_hash.into(), + genesis_hash: genesis_hash.into(), + spec_version: 3010, + transaction_version: 2, + era: Some(Proto::Era { + block_number: 4395527, + period: 64, + }), + runtime_call: Some(identity_join_identity( + 21435, + // Old Polymesh v4.x call indices. + custom_call_indices(0x07, 0x05), + )), + ..Default::default() + }; + + let public_key = "d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d061054"; + let signature = "7f5adbb2749e2f0ace29b409c41dd717681495b1f22dc5358311646a9fb8af8a173fc47f1b19748fb56831c2128773e2976986685adee83c741ab49934d80006"; + let (_preimage, signed) = + helper_encode_and_compile(CoinType::Polymesh, input, signature, public_key, true); + assert_eq!(signed, "c5018400d3b2f1c41b9b4522eb3e23329b81aca6cc0231167ecfa3580c5a71ff6d061054007f5adbb2749e2f0ace29b409c41dd717681495b1f22dc5358311646a9fb8af8a173fc47f1b19748fb56831c2128773e2976986685adee83c741ab49934d80006750000000705bb53000000000000"); +} From 5674d42369e2747d542e86d1c6d147506f81db0a Mon Sep 17 00:00:00 2001 From: "Robert G. Jakabosky" Date: Mon, 13 Jan 2025 23:10:02 +0800 Subject: [PATCH 22/22] cargo clippy. --- rust/chains/tw_polymesh/src/call_encoder.rs | 6 +++--- rust/chains/tw_polymesh/src/entry.rs | 4 ++-- rust/chains/tw_polymesh/src/types.rs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rust/chains/tw_polymesh/src/call_encoder.rs b/rust/chains/tw_polymesh/src/call_encoder.rs index 38d69d6a170..2f4e9f56e5e 100644 --- a/rust/chains/tw_polymesh/src/call_encoder.rs +++ b/rust/chains/tw_polymesh/src/call_encoder.rs @@ -140,7 +140,7 @@ impl PolymeshIdentity { }; Ok(ci.wrap(Self::AddAuthorization { - target: Signatory::Account(SubstrateAddress(target.into())), + target: Signatory::Account(SubstrateAddress(target)), data, expiry: if msg.expiry > 0 { Some(msg.expiry) @@ -261,7 +261,7 @@ impl PolymeshStaking { .iter() .map(|target| { let account = - SS58Address::from_str(&target).map_err(|_| EncodeError::InvalidAddress)?; + SS58Address::from_str(target).map_err(|_| EncodeError::InvalidAddress)?; Ok(account.into()) }) .collect::>>()?; @@ -352,7 +352,7 @@ impl CallEncoder { .ok_or(EncodeError::InvalidValue) .into_tw() .context("Missing runtime call")?; - encoder.encode_runtime_call(&call) + encoder.encode_runtime_call(call) } pub fn encode_runtime_call(&mut self, call: &Proto::RuntimeCall) -> EncodeResult { diff --git a/rust/chains/tw_polymesh/src/entry.rs b/rust/chains/tw_polymesh/src/entry.rs index 5df9eb39f95..603efb21872 100644 --- a/rust/chains/tw_polymesh/src/entry.rs +++ b/rust/chains/tw_polymesh/src/entry.rs @@ -35,7 +35,7 @@ impl PolymeshEntry { public_key: Option, input: &Proto::SigningInput<'_>, ) -> EncodeResult { - let ctx = ctx_from_tw(&input)?; + let ctx = ctx_from_tw(input)?; let mut encoder = CallEncoder::from_ctx(&ctx)?; let call = input .runtime_call @@ -43,7 +43,7 @@ impl PolymeshEntry { .ok_or(EncodeError::InvalidValue) .into_tw() .context("Missing runtime call")?; - let call = encoder.encode_runtime_call(&call)?; + let call = encoder.encode_runtime_call(call)?; let era = match &input.era { Some(era) => Era::mortal(era.period, era.block_number), None => Era::immortal(), diff --git a/rust/chains/tw_polymesh/src/types.rs b/rust/chains/tw_polymesh/src/types.rs index b7d2b47adad..a4798a2a11c 100644 --- a/rust/chains/tw_polymesh/src/types.rs +++ b/rust/chains/tw_polymesh/src/types.rs @@ -167,7 +167,7 @@ impl TryFrom<&TWAssetPermissions<'_>> for AssetPermissions { assets: perms .assets .iter() - .map(|asset| Ok(asset.try_into()?)) + .map(|asset| asset.try_into()) .collect::>>()?, }) } @@ -207,7 +207,7 @@ impl TryFrom<&TWPortfolioPermissions<'_>> for PortfolioPermissions { portfolios: perms .portfolios .iter() - .map(|portfolio| Ok(portfolio.try_into()?)) + .map(|portfolio| portfolio.try_into()) .collect::>>()?, }) }