From b619d3f991acae90c4c3f654104e313353e85b09 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Mon, 7 Oct 2024 17:21:01 +0300 Subject: [PATCH 01/20] Start working on stacks verify message handler. --- ampd/src/config.rs | 15 +- ampd/src/handlers/config.rs | 22 ++ ampd/src/handlers/mod.rs | 1 + ampd/src/handlers/stacks_verify_msg.rs | 135 +++++++++ ampd/src/lib.rs | 15 + ampd/src/stacks/http_client.rs | 262 ++++++++++++++++++ ampd/src/stacks/mod.rs | 2 + ampd/src/stacks/verifier.rs | 11 + ampd/src/tests/config_template.toml | 5 + contracts/multisig-prover/src/contract.rs | 1 + .../multisig-prover/src/contract/execute.rs | 1 + 11 files changed, 469 insertions(+), 1 deletion(-) create mode 100644 ampd/src/handlers/stacks_verify_msg.rs create mode 100644 ampd/src/stacks/http_client.rs create mode 100644 ampd/src/stacks/mod.rs create mode 100644 ampd/src/stacks/verifier.rs diff --git a/ampd/src/config.rs b/ampd/src/config.rs index 2ae2b9efc..487a1114b 100644 --- a/ampd/src/config.rs +++ b/ampd/src/config.rs @@ -132,6 +132,12 @@ mod tests { type = 'StellarVerifierSetVerifier' cosmwasm_contract = '{}' http_url = 'http://localhost:8000' + + + [[handlers]] + type = 'StacksMsgVerifier' + cosmwasm_contract = '{}' + http_url = 'http://localhost:8000' ", TMAddress::random(PREFIX), TMAddress::random(PREFIX), @@ -143,10 +149,11 @@ mod tests { TMAddress::random(PREFIX), TMAddress::random(PREFIX), TMAddress::random(PREFIX), + TMAddress::random(PREFIX), ); let cfg: Config = toml::from_str(config_str.as_str()).unwrap(); - assert_eq!(cfg.handlers.len(), 10); + assert_eq!(cfg.handlers.len(), 11); } #[test] @@ -350,6 +357,12 @@ mod tests { ), http_url: Url::from_str("http://127.0.0.1").unwrap(), }, + HandlerConfig::StacksMsgVerifier { + cosmwasm_contract: TMAddress::from( + AccountId::new("axelar", &[0u8; 32]).unwrap(), + ), + http_url: Url::from_str("http://127.0.0.1").unwrap(), + }, ], ..Config::default() } diff --git a/ampd/src/handlers/config.rs b/ampd/src/handlers/config.rs index 2cd8ed6f1..55214a8fb 100644 --- a/ampd/src/handlers/config.rs +++ b/ampd/src/handlers/config.rs @@ -63,6 +63,10 @@ pub enum Config { cosmwasm_contract: TMAddress, http_url: Url, }, + StacksMsgVerifier { + cosmwasm_contract: TMAddress, + http_url: Url, + }, } fn validate_evm_verifier_set_verifier_configs<'de, D>(configs: &[Config]) -> Result<(), D::Error> @@ -159,6 +163,7 @@ where Config::StellarVerifierSetVerifier, "Stellar verifier set verifier" )?; + ensure_unique_config!(&configs, Config::StacksMsgVerifier, "Stacks message verifier")?; Ok(configs) } @@ -305,5 +310,22 @@ mod tests { Err(e) if e.to_string().contains("only one Stellar verifier set verifier config is allowed") ) ); + + let configs = vec![ + Config::StacksMsgVerifier { + cosmwasm_contract: TMAddress::random(PREFIX), + http_url: "http://localhost:8080/".parse().unwrap(), + }, + Config::StacksMsgVerifier { + cosmwasm_contract: TMAddress::random(PREFIX), + http_url: "http://localhost:8080/".parse().unwrap(), + }, + ]; + + assert!( + matches!(deserialize_handler_configs(to_value(configs).unwrap()), + Err(e) if e.to_string().contains("only one Stacks message verifier config is allowed") + ) + ); } } diff --git a/ampd/src/handlers/mod.rs b/ampd/src/handlers/mod.rs index 1f8868164..0b63520fc 100644 --- a/ampd/src/handlers/mod.rs +++ b/ampd/src/handlers/mod.rs @@ -5,6 +5,7 @@ pub mod evm_verify_verifier_set; pub mod multisig; pub mod mvx_verify_msg; pub mod mvx_verify_verifier_set; +pub mod stacks_verify_msg; pub(crate) mod stellar_verify_msg; pub(crate) mod stellar_verify_verifier_set; pub mod sui_verify_msg; diff --git a/ampd/src/handlers/stacks_verify_msg.rs b/ampd/src/handlers/stacks_verify_msg.rs new file mode 100644 index 000000000..b7412d96d --- /dev/null +++ b/ampd/src/handlers/stacks_verify_msg.rs @@ -0,0 +1,135 @@ +use std::collections::HashSet; +use std::convert::TryInto; + +use async_trait::async_trait; +use axelar_wasm_std::voting::{PollId, Vote}; +use cosmrs::cosmwasm::MsgExecuteContract; +use cosmrs::tx::Msg; +use cosmrs::Any; +use error_stack::ResultExt; +use events::Error::EventTypeMismatch; +use events::Event; +use events_derive::try_from; +use serde::Deserialize; +use tokio::sync::watch::Receiver; +use tracing::info; +use voting_verifier::msg::ExecuteMsg; + +use crate::event_processor::EventHandler; +use crate::handlers::errors::Error; +use crate::stacks::http_client::Client; +use crate::stacks::verifier::verify_message; +use crate::types::{Hash, TMAddress}; + +type Result = error_stack::Result; + +#[derive(Deserialize, Debug)] +pub struct Message { + pub tx_id: Hash, + pub event_index: u32, + pub destination_address: String, + pub destination_chain: router_api::ChainName, + pub source_address: String, // TODO + pub payload_hash: Hash, +} + +#[derive(Deserialize, Debug)] +#[try_from("wasm-messages_poll_started")] +struct PollStartedEvent { + poll_id: PollId, + source_gateway_address: String, // TODO + messages: Vec, + participants: Vec, + expires_at: u64, +} + +pub struct Handler { + verifier: TMAddress, + voting_verifier_contract: TMAddress, + http_client: Client, + latest_block_height: Receiver, +} + +impl Handler { + pub fn new( + verifier: TMAddress, + voting_verifier_contract: TMAddress, + http_client: Client, + latest_block_height: Receiver, + ) -> Self { + Self { + verifier, + voting_verifier_contract, + http_client, + latest_block_height, + } + } + + fn vote_msg(&self, poll_id: PollId, votes: Vec) -> MsgExecuteContract { + MsgExecuteContract { + sender: self.verifier.as_ref().clone(), + contract: self.voting_verifier_contract.as_ref().clone(), + msg: serde_json::to_vec(&ExecuteMsg::Vote { poll_id, votes }) + .expect("vote msg should serialize"), + funds: vec![], + } + } +} + +#[async_trait] +impl EventHandler for Handler { + type Err = Error; + + async fn handle(&self, event: &Event) -> Result> { + if !event.is_from_contract(self.voting_verifier_contract.as_ref()) { + return Ok(vec![]); + } + + let PollStartedEvent { + poll_id, + source_gateway_address, + messages, + participants, + expires_at, + .. + } = match event.try_into() as error_stack::Result<_, _> { + Err(report) if matches!(report.current_context(), EventTypeMismatch(_)) => { + return Ok(vec![]); + } + event => event.change_context(Error::DeserializeEvent)?, + }; + + if !participants.contains(&self.verifier) { + return Ok(vec![]); + } + + let latest_block_height = *self.latest_block_height.borrow(); + if latest_block_height >= expires_at { + info!(poll_id = poll_id.to_string(), "skipping expired poll"); + + return Ok(vec![]); + } + + let tx_hashes: HashSet<_> = messages.iter().map(|message| message.tx_id).collect(); + let transactions_info = self + .http_client + .get_transactions(tx_hashes) + .await; + + let votes: Vec = messages + .iter() + .map(|msg| { + transactions_info + .get(&msg.tx_id) + .map_or(Vote::NotFound, |transaction| { + verify_message(&source_gateway_address, transaction, msg) + }) + }) + .collect(); + + Ok(vec![self + .vote_msg(poll_id, votes) + .into_any() + .expect("vote msg should serialize")]) + } +} diff --git a/ampd/src/lib.rs b/ampd/src/lib.rs index c6b774df9..b2e4afacf 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -40,6 +40,7 @@ mod health_check; mod json_rpc; mod mvx; mod queue; +mod stacks; mod stellar; mod sui; mod tm_client; @@ -51,6 +52,7 @@ pub use grpc::{client, proto}; use crate::asyncutil::future::RetryPolicy; use crate::broadcaster::confirm_tx::TxConfirmer; +use crate::stacks::http_client::Client; const PREFIX: &str = "axelar"; const DEFAULT_RPC_TIMEOUT: Duration = Duration::from_secs(3); @@ -388,6 +390,19 @@ where ), event_processor_config.clone(), ), + handlers::config::Config::StacksMsgVerifier { + cosmwasm_contract, + http_url, + } => self.create_handler_task( + "stacks-msg-verifier", + handlers::stacks_verify_msg::Handler::new( + verifier.clone(), + cosmwasm_contract, + Client::new_http(http_url.to_string().trim_end_matches('/').into()), + self.block_height_monitor.latest_block_height(), + ), + event_processor_config.clone(), + ), }; self.event_processor = self.event_processor.add_task(task); } diff --git a/ampd/src/stacks/http_client.rs b/ampd/src/stacks/http_client.rs new file mode 100644 index 000000000..99f54e3b4 --- /dev/null +++ b/ampd/src/stacks/http_client.rs @@ -0,0 +1,262 @@ +use crate::types::Hash; +use futures::future::join_all; +use serde::Deserialize; +use std::collections::{HashMap, HashSet}; +use std::fmt::Debug; +use thiserror::Error; + +const GET_TRANSACTION: &str = "extended/v1/tx/"; + +const STATUS_SUCCESS: &str = "success"; + +#[derive(Error, Debug)] +pub enum Error { + #[error("failed to create client")] + Client, + #[error("invalid tx hash")] + TxHash, +} + +#[derive(Debug, Deserialize, Default)] +pub struct ContractLogValue { + hex: String, +} + +#[derive(Debug, Deserialize, Default)] +pub struct ContractLog { + contract_id: String, + topic: String, + value: ContractLogValue, +} + +#[derive(Debug, Deserialize, Default)] +pub struct TransactionEvents { + event_index: u64, + tx_id: String, + contract_log: Option, +} + +#[derive(Debug, Deserialize, Default)] +pub struct Transaction { + tx_id: String, + nonce: u64, + sender_address: String, + tx_status: String, // 'success' + events: Vec, +} + +#[cfg_attr(test, faux::create)] +pub struct Client { + api_url: String, + client: reqwest::Client, +} + +#[cfg_attr(test, faux::methods)] +impl Client { + pub fn new_http(api_url: String) -> Self { + Client { + api_url, + client: reqwest::Client::new(), + } + } + + pub async fn get_transactions(&self, tx_hashes: HashSet) -> HashMap { + let tx_hashes = Vec::from_iter(tx_hashes); + + let txs = join_all( + tx_hashes + .iter() + .map(|tx_hash| self.get_valid_transaction(tx_hash)), + ) + .await; + + tx_hashes + .into_iter() + .zip(txs) + .filter_map(|(hash, tx)| { + tx.as_ref()?; + + Some((hash, tx.unwrap())) + }) + .collect() + } + + async fn get_valid_transaction(&self, tx_hash: &Hash) -> Option { + self.get_transaction(tx_hash.to_string().as_str()) + .await + .ok() + .filter(Self::is_valid_transaction) + } + + async fn get_transaction(&self, tx_id: &str) -> Result { + let mut endpoint = GET_TRANSACTION.to_string() + tx_id; + + let endpoint = self.get_endpoint(endpoint.as_str()); + + self.client + .get(endpoint) + .send() + .await + .map_err(|err_str| Error::TxHash)? + .json::() + .await + .map_err(|err_str| Error::Client) + } + + fn get_endpoint(&self, endpoint: &str) -> String { + format!("{}/{}", self.api_url, endpoint) + } + + fn is_valid_transaction(tx: &Transaction) -> bool { + tx.tx_status == *STATUS_SUCCESS + } +} + +#[cfg(test)] +mod tests { + use super::{Client, Transaction}; + + #[test] + fn parse_transaction() { + let data = r#" +{ + "tx_id": "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf", + "nonce": 2, + "fee_rate": "943", + "sender_address": "SP3F7B2PGN7TVMTNBS1HBJBEC6M64DMCY944MXDD0", + "sponsored": false, + "post_condition_mode": "deny", + "post_conditions": [], + "anchor_mode": "any", + "block_hash": "0x9248a412fc98e245820160aba1f89defefe5380af920bff73bc6617207284aa9", + "block_height": 168868, + "block_time": 1728309360, + "block_time_iso": "2024-10-07T13:56:00.000Z", + "burn_block_time": 1728309301, + "burn_block_height": 864594, + "burn_block_time_iso": "2024-10-07T13:55:01.000Z", + "parent_burn_block_time": 1728308843, + "parent_burn_block_time_iso": "2024-10-07T13:47:23.000Z", + "canonical": true, + "tx_index": 85, + "tx_status": "success", + "tx_result": { + "hex": "0x0703", + "repr": "(ok true)" + }, + "event_count": 1, + "parent_block_hash": "0x1cbb43f502524bfa0edbb16b5f2a98350de6d8041c93dd54eab35347a90f6a68", + "is_unanchored": false, + "microblock_hash": "0x", + "microblock_sequence": 2147483647, + "microblock_canonical": true, + "execution_cost_read_count": 6, + "execution_cost_read_length": 13939, + "execution_cost_runtime": 46110, + "execution_cost_write_count": 1, + "execution_cost_write_length": 125, + "events": [ + { + "event_index": 0, + "event_type": "smart_contract_log", + "tx_id": "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf", + "contract_log": { + "contract_id": "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.leo-cats", + "topic": "print", + "value": { + "hex": "0x0c0000000501610d0000000c6c6973742d696e2d757374780a636f6d6d697373696f6e06162bcf9762d5b90bc36dc1b4759b1727690f92ddd31367616d6d612d636f6d6d697373696f6e2d76310269640100000000000000000000000000000d7105707269636501000000000000000000000000004e89b307726f79616c74790100000000000000000000000000000000", + "repr": "(tuple (a \"list-in-ustx\") (commission 'SPNWZ5V2TPWGQGVDR6T7B6RQ4XMGZ4PXTEE0VQ0S.gamma-commission-v1) (id u3441) (price u5147059) (royalty u0))" + } + } + }, + { + "event_index": 1, + "event_type": "fungible_token_asset", + "tx_id": "0xea34df6d263a274ec852b04f3d9bc13b989811f263c58e02293504c3e66164fd", + "asset": { + "asset_event_type": "transfer", + "asset_id": "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.brc20-db20::brc20-db20", + "sender": "SPP2B792YYNWTM5W8N3TBJT51745K8HPSCP9EFTT", + "recipient": "SP38AN2F75Y4AP8ZVA7402XPK77F82TBQX05R8EH6", + "amount": "1548865732" + } + } + ], + "tx_type": "contract_call", + "contract_call": { + "contract_id": "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.leo-cats", + "function_name": "list-in-ustx", + "function_signature": "(define-public (list-in-ustx (id uint) (price uint) (comm-trait trait_reference)))", + "function_args": [ + { + "hex": "0x0100000000000000000000000000000d71", + "repr": "u3441", + "name": "id", + "type": "uint" + } + ] + } +} + "#; + + let transaction = serde_json::from_str::(data).unwrap(); + assert_eq!( + transaction.tx_id, + "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" + ); + assert_eq!(transaction.nonce, 2); + assert_eq!( + transaction.sender_address, + "SP3F7B2PGN7TVMTNBS1HBJBEC6M64DMCY944MXDD0" + ); + assert_eq!(transaction.tx_status, "success"); + assert_eq!(transaction.events.len(), 2); + + let event = transaction.events.get(0).unwrap(); + + assert_eq!(event.event_index, 0); + assert_eq!( + event.tx_id, + "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" + ); + assert!(event.contract_log.is_some()); + + let contract_log = event.contract_log.as_ref().unwrap(); + + assert_eq!( + contract_log.contract_id, + "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.leo-cats" + ); + assert_eq!(contract_log.topic, "print"); + assert_eq!(contract_log.value.hex, "0x0c0000000501610d0000000c6c6973742d696e2d757374780a636f6d6d697373696f6e06162bcf9762d5b90bc36dc1b4759b1727690f92ddd31367616d6d612d636f6d6d697373696f6e2d76310269640100000000000000000000000000000d7105707269636501000000000000000000000000004e89b307726f79616c74790100000000000000000000000000000000"); + + let token_event = transaction.events.get(1).unwrap(); + + assert_eq!(token_event.event_index, 1); + assert_eq!( + token_event.tx_id, + "0xea34df6d263a274ec852b04f3d9bc13b989811f263c58e02293504c3e66164fd" + ); + assert!(token_event.contract_log.is_none()); + } + + #[test] + fn should_not_be_valid_transaction_invalid_status() { + let tx = Transaction { + tx_status: "pending".into(), + ..Transaction::default() + }; + + assert!(!Client::is_valid_transaction(&tx)); + } + + #[test] + fn should_be_valid_transaction() { + let tx = Transaction { + tx_status: "success".into(), + ..Transaction::default() + }; + + assert!(Client::is_valid_transaction(&tx)); + } +} diff --git a/ampd/src/stacks/mod.rs b/ampd/src/stacks/mod.rs new file mode 100644 index 000000000..be0cc859c --- /dev/null +++ b/ampd/src/stacks/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod http_client; +pub(crate) mod verifier; diff --git a/ampd/src/stacks/verifier.rs b/ampd/src/stacks/verifier.rs new file mode 100644 index 000000000..545fdde32 --- /dev/null +++ b/ampd/src/stacks/verifier.rs @@ -0,0 +1,11 @@ +use axelar_wasm_std::voting::Vote; +use crate::handlers::stacks_verify_msg::Message; +use crate::stacks::http_client::Transaction; + +pub fn verify_message( + gateway_address: &String, + transaction: &Transaction, + message: &Message, +) -> Vote { + Vote::NotFound // TODO: +} diff --git a/ampd/src/tests/config_template.toml b/ampd/src/tests/config_template.toml index e6b9c818a..2fdb3bc94 100644 --- a/ampd/src/tests/config_template.toml +++ b/ampd/src/tests/config_template.toml @@ -82,6 +82,11 @@ type = 'StellarVerifierSetVerifier' cosmwasm_contract = 'axelar1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqecnww6' http_url = 'http://127.0.0.1/' +[[handlers]] +type = 'StacksMsgVerifier' +cosmwasm_contract = 'axelar1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqecnww6' +http_url = 'http://127.0.0.1/' + [tofnd_config] url = 'http://localhost:50051/' party_uid = 'ampd' diff --git a/contracts/multisig-prover/src/contract.rs b/contracts/multisig-prover/src/contract.rs index caa7d25b4..31d946274 100644 --- a/contracts/multisig-prover/src/contract.rs +++ b/contracts/multisig-prover/src/contract.rs @@ -71,6 +71,7 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg.ensure_permissions(deps.storage, &info.sender)? { + // TODO: Add ConstructProof (message_id, payload) for ITS Hub only messages? ExecuteMsg::ConstructProof(message_ids) => Ok(execute::construct_proof(deps, message_ids)?), ExecuteMsg::UpdateVerifierSet {} => Ok(execute::update_verifier_set(deps, env)?), ExecuteMsg::ConfirmVerifierSet {} => Ok(execute::confirm_verifier_set(deps, info.sender)?), diff --git a/contracts/multisig-prover/src/contract/execute.rs b/contracts/multisig-prover/src/contract/execute.rs index bc01ff2ff..561dd9b98 100644 --- a/contracts/multisig-prover/src/contract/execute.rs +++ b/contracts/multisig-prover/src/contract/execute.rs @@ -100,6 +100,7 @@ fn messages( "violated invariant: returned gateway messages count mismatch" ); + // TODO: Filter messages that have `destination_address` as the Stacks ITS Contract if let Some(wrong_destination) = messages .iter() .find(|msg| msg.destination_chain != chain_name) From 1c08d962a094ee7191181bc518bf5832049cf5a0 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:09:22 +0300 Subject: [PATCH 02/20] Start work on decoding Stacks events. --- Cargo.lock | 660 +++++++++++++++++++++++-- ampd/Cargo.toml | 1 + ampd/src/handlers/stacks_verify_msg.rs | 4 +- ampd/src/stacks/error.rs | 7 + ampd/src/stacks/http_client.rs | 24 +- ampd/src/stacks/mod.rs | 1 + ampd/src/stacks/verifier.rs | 60 ++- 7 files changed, 694 insertions(+), 63 deletions(-) create mode 100644 ampd/src/stacks/error.rs diff --git a/Cargo.lock b/Cargo.lock index df4220ff7..32c951eba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,7 +45,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -73,13 +73,19 @@ dependencies = [ "subtle", ] +[[package]] +name = "ahash" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0453232ace82dee0dd0b4c87a59bd90f7b53b314f3e0f61fe2ee7c8a16482289" + [[package]] name = "ahash" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom", + "getrandom 0.2.15", "once_cell", "version_check", ] @@ -126,6 +132,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "alloy-primitives" version = "0.7.7" @@ -137,7 +149,7 @@ dependencies = [ "cfg-if", "const-hex", "derive_more", - "getrandom", + "getrandom 0.2.15", "hex-literal", "itoa", "k256", @@ -228,6 +240,7 @@ dependencies = [ "base64 0.21.7", "bcs", "clap", + "clarity", "config", "cosmrs", "cosmwasm-std", @@ -248,7 +261,7 @@ dependencies = [ "evm-gateway", "faux", "futures", - "generic-array", + "generic-array 0.14.7", "hex", "humantime-serde", "itertools 0.11.0", @@ -683,7 +696,7 @@ dependencies = [ "num-traits", "rusticata-macros", "thiserror", - "time", + "time 0.3.36", ] [[package]] @@ -932,7 +945,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sha1", + "sha1 0.10.6", "sync_wrapper 0.1.2", "tokio", "tokio-tungstenite", @@ -1303,7 +1316,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -1312,7 +1325,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -1321,7 +1334,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -1563,6 +1576,30 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +[[package]] +name = "clarity" +version = "0.0.1" +source = "git+https://github.com/stacks-network/stacks-core?tag=2.5.0.0.7#bed29bca57eab8264970188ed6bbb90486578a5b" +dependencies = [ + "hashbrown 0.14.5", + "integer-sqrt", + "lazy_static", + "mutants", + "rand", + "rand_chacha", + "regex", + "rstest", + "rstest_reuse", + "rusqlite", + "serde", + "serde_derive", + "serde_json", + "serde_stacker", + "slog", + "stacks-common", + "time 0.2.27", +] + [[package]] name = "client" version = "1.0.0" @@ -1672,6 +1709,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const_fn" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373e9fafaa20882876db20562275ff58d50e0caa2590077fe7ce7bef90211d0d" + [[package]] name = "constant_time_eq" version = "0.3.0" @@ -1755,7 +1798,7 @@ dependencies = [ "cosmos-sdk-proto", "ecdsa", "eyre", - "getrandom", + "getrandom 0.2.15", "k256", "rand_core 0.6.4", "serde", @@ -1885,7 +1928,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", "subtle", "zeroize", @@ -1897,7 +1940,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", "typenum", ] @@ -1920,6 +1963,20 @@ dependencies = [ "cipher", ] +[[package]] +name = "curve25519-dalek" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26778518a7f6cffa1d25a44b602b62b979bd88adb9e99ffec546998cf3404839" +dependencies = [ + "byteorder", + "digest 0.8.1", + "rand_core 0.5.1", + "serde", + "subtle", + "zeroize", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -2333,13 +2390,22 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -2396,6 +2462,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + [[package]] name = "displaydoc" version = "0.2.4" @@ -2463,6 +2535,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8 0.10.2", + "serde", "signature 2.2.0", ] @@ -2490,6 +2563,7 @@ dependencies = [ "curve25519-dalek 4.1.3", "ed25519 2.2.3", "rand_core 0.6.4", + "serde", "sha2 0.10.8", "subtle", "zeroize", @@ -2526,7 +2600,7 @@ dependencies = [ "crypto-bigint", "digest 0.10.7", "ff", - "generic-array", + "generic-array 0.14.7", "group", "pem-rfc7468 0.7.0", "pkcs8 0.10.2", @@ -2750,7 +2824,7 @@ dependencies = [ "const-hex", "elliptic-curve", "ethabi", - "generic-array", + "generic-array 0.14.7", "k256", "num_enum 0.7.2", "once_cell", @@ -2867,6 +2941,18 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastcrypto" version = "0.1.8" @@ -2894,7 +2980,7 @@ dependencies = [ "ed25519-consensus", "elliptic-curve", "fastcrypto-derive", - "generic-array", + "generic-array 0.14.7", "hex", "hex-literal", "hkdf", @@ -2907,7 +2993,7 @@ dependencies = [ "rfc6979", "rsa", "schemars", - "secp256k1", + "secp256k1 0.27.0", "serde", "serde_json", "serde_with 2.3.3", @@ -3337,6 +3423,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -3349,6 +3444,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -3358,7 +3464,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -3475,6 +3581,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +dependencies = [ + "ahash 0.4.8", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -3498,6 +3613,11 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", + "serde", +] [[package]] name = "hashers" @@ -3508,6 +3628,15 @@ dependencies = [ "fxhash", ] +[[package]] +name = "hashlink" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d99cf782f0dc4372d26846bec3de7804ceb5df083c2d4462c0b8d2330e894fa8" +dependencies = [ + "hashbrown 0.9.1", +] + [[package]] name = "hdrhistogram" version = "7.5.4" @@ -3530,7 +3659,7 @@ dependencies = [ "http 0.2.12", "httpdate", "mime", - "sha1", + "sha1 0.10.6", ] [[package]] @@ -3560,6 +3689,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -4013,7 +4148,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ "block-padding", - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -4040,6 +4175,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + [[package]] name = "integration-tests" version = "1.0.0" @@ -4127,6 +4271,17 @@ dependencies = [ "nom", ] +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.0" @@ -4424,6 +4579,17 @@ dependencies = [ "libc", ] +[[package]] +name = "libsqlite3-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d31059f22935e6c31830db5249ba2b7ecd54fd73a9909286f0a67aa55c2fbd" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -4497,6 +4663,15 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.17" @@ -4535,7 +4710,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] @@ -5005,7 +5180,7 @@ dependencies = [ "ed25519-dalek", "enum-display-derive", "error-stack", - "getrandom", + "getrandom 0.2.15", "goldie", "hex", "itertools 0.11.0", @@ -5044,7 +5219,7 @@ dependencies = [ "evm-gateway", "gateway", "gateway-api", - "generic-array", + "generic-array 0.14.7", "goldie", "hex", "itertools 0.11.0", @@ -5096,6 +5271,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "mutants" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0287524726960e07b119cebd01678f852f147742ae0d925e6a520dca956126" + [[package]] name = "mysten-metrics" version = "0.7.0" @@ -5226,7 +5407,7 @@ dependencies = [ "blstrs", "byteorder", "ff", - "generic-array", + "generic-array 0.14.7", "log", "pasta_curves", "serde", @@ -5261,6 +5442,19 @@ dependencies = [ "voting-verifier", ] +[[package]] +name = "nix" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +dependencies = [ + "bitflags 1.3.2", + "cc", + "cfg-if", + "libc", + "memoffset", +] + [[package]] name = "nom" version = "7.1.3" @@ -5419,7 +5613,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] @@ -5632,6 +5826,28 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "p256k1" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a40a031a559eb38c35a14096f21c366254501a06d41c4b327d2a7515d713a5b7" +dependencies = [ + "bitvec 1.0.1", + "bs58 0.4.0", + "cc", + "hex", + "itertools 0.10.5", + "num-traits", + "primitive-types 0.12.2", + "proc-macro2 1.0.85", + "quote 1.0.36", + "rand_core 0.6.4", + "rustfmt-wrapper", + "serde", + "sha2 0.10.8", + "syn 2.0.68", +] + [[package]] name = "pairing" version = "0.23.0" @@ -6034,6 +6250,16 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "polynomial" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27abb6e4638dcecc65a92b50d7f1d87dd6dea987ba71db987b6bf881f4877e9d" +dependencies = [ + "num-traits", + "serde", +] + [[package]] name = "polyval" version = "0.6.2" @@ -6206,6 +6432,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro2" version = "0.4.30" @@ -6508,6 +6740,9 @@ name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] [[package]] name = "rand_core" @@ -6515,7 +6750,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] @@ -6553,7 +6788,7 @@ checksum = "6413f3de1edee53342e6138e75b56d32e7bc6e332b3bd62d497b1929d4cfbcdd" dependencies = [ "pem 1.1.1", "ring 0.16.20", - "time", + "time 0.3.36", "yasna", ] @@ -6583,7 +6818,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ - "getrandom", + "getrandom 0.2.15", "libredox", "thiserror", ] @@ -6800,7 +7035,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -6930,6 +7165,44 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rstest" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de1bb486a691878cd320c2f0d319ba91eeaa2e894066d8b5f8f117c000e9d962" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version 0.4.0", +] + +[[package]] +name = "rstest_macros" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290ca1a1c8ca7edb7c3283bd44dc35dd54fdec6253a3912e201ba1072018fca8" +dependencies = [ + "cfg-if", + "proc-macro2 1.0.85", + "quote 1.0.36", + "rustc_version 0.4.0", + "syn 1.0.109", + "unicode-ident", +] + +[[package]] +name = "rstest_reuse" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45f80dcc84beab3a327bbe161f77db25f336a1452428176787c8c79ac79d7073" +dependencies = [ + "quote 1.0.36", + "rand", + "rustc_version 0.4.0", + "syn 1.0.109", +] + [[package]] name = "ruint" version = "1.12.3" @@ -6960,6 +7233,23 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rusqlite" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38ee71cbab2c827ec0ac24e76f82eca723cee92c509a65f67dee393c25112" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "memchr", + "serde_json", + "smallvec", +] + [[package]] name = "rust-ini" version = "0.18.0" @@ -6988,6 +7278,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + [[package]] name = "rustc_version" version = "0.3.3" @@ -7006,6 +7305,19 @@ dependencies = [ "semver 1.0.23", ] +[[package]] +name = "rustfmt-wrapper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1adc9dfed5cc999077978cc7163b9282c5751c8d39827c4ea8c8c220ca5a440" +dependencies = [ + "serde", + "tempfile", + "thiserror", + "toml 0.8.14", + "toolchain_find", +] + [[package]] name = "rusticata-macros" version = "4.1.0" @@ -7294,12 +7606,22 @@ checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der 0.7.9", - "generic-array", + "generic-array 0.14.7", "pkcs8 0.10.2", "subtle", "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" +dependencies = [ + "secp256k1-sys 0.6.1", + "serde", +] + [[package]] name = "secp256k1" version = "0.27.0" @@ -7308,7 +7630,16 @@ checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" dependencies = [ "bitcoin_hashes 0.12.0", "rand", - "secp256k1-sys", + "secp256k1-sys 0.8.1", +] + +[[package]] +name = "secp256k1-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" +dependencies = [ + "cc", ] [[package]] @@ -7343,13 +7674,22 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser 0.7.0", +] + [[package]] name = "semver" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" dependencies = [ - "semver-parser", + "semver-parser 0.10.2", ] [[package]] @@ -7361,6 +7701,12 @@ dependencies = [ "serde", ] +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "semver-parser" version = "0.10.2" @@ -7495,6 +7841,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_stacker" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "babfccff5773ff80657f0ecf553c7c516bdc2eb16389c0918b36b73e7015276e" +dependencies = [ + "serde", + "stacker", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -7520,7 +7876,7 @@ dependencies = [ "serde", "serde_json", "serde_with_macros 2.3.3", - "time", + "time 0.3.36", ] [[package]] @@ -7538,7 +7894,7 @@ dependencies = [ "serde_derive", "serde_json", "serde_with_macros 3.8.1", - "time", + "time 0.3.36", ] [[package]] @@ -7634,6 +7990,15 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sha1" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" +dependencies = [ + "sha1_smol", +] + [[package]] name = "sha1" version = "0.10.6" @@ -7645,6 +8010,12 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + [[package]] name = "sha2" version = "0.9.9" @@ -7770,7 +8141,7 @@ dependencies = [ "num-bigint 0.4.5", "num-traits", "thiserror", - "time", + "time 0.3.36", ] [[package]] @@ -7798,6 +8169,25 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slog" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" + +[[package]] +name = "slog-term" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6e022d0b998abfe5c3782c1f03551a596269450ccd677ea51c56f8b214610e8" +dependencies = [ + "is-terminal", + "slog", + "term", + "thread_local", + "time 0.3.36", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -7881,12 +8271,100 @@ dependencies = [ "winapi", ] +[[package]] +name = "stacks-common" +version = "0.0.1" +source = "git+https://github.com/stacks-network/stacks-core?tag=2.5.0.0.7#bed29bca57eab8264970188ed6bbb90486578a5b" +dependencies = [ + "chrono", + "curve25519-dalek 2.0.0", + "ed25519-dalek", + "hashbrown 0.14.5", + "lazy_static", + "libc", + "nix", + "percent-encoding", + "rand", + "ripemd", + "rusqlite", + "secp256k1 0.24.3", + "serde", + "serde_derive", + "serde_json", + "serde_stacker", + "sha2 0.10.8", + "sha3", + "slog", + "slog-term", + "time 0.2.27", + "winapi", + "wsts", +] + +[[package]] +name = "standback" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" +dependencies = [ + "version_check", +] + [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version 0.2.3", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2 1.0.85", + "quote 1.0.36", + "serde", + "serde_derive", + "syn 1.0.109", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2 1.0.85", + "quote 1.0.36", + "serde", + "serde_derive", + "serde_json", + "sha1 0.6.1", + "syn 1.0.109", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + [[package]] name = "stellar" version = "1.0.0" @@ -8475,7 +8953,7 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto 0.32.2", - "time", + "time 0.3.36", "zeroize", ] @@ -8503,7 +8981,7 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto 0.33.0", - "time", + "time 0.3.36", "zeroize", ] @@ -8535,7 +9013,7 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time", + "time 0.3.36", ] [[package]] @@ -8552,7 +9030,7 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time", + "time 0.3.36", ] [[package]] @@ -8564,7 +9042,7 @@ dependencies = [ "bytes", "flex-error", "futures", - "getrandom", + "getrandom 0.2.15", "http 0.2.12", "hyper 0.14.29", "hyper-proxy", @@ -8581,7 +9059,7 @@ dependencies = [ "tendermint-config", "tendermint-proto 0.33.0", "thiserror", - "time", + "time 0.3.36", "tokio", "tracing", "url", @@ -8589,6 +9067,17 @@ dependencies = [ "walkdir", ] +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -8653,6 +9142,21 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "time" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" +dependencies = [ + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros 0.1.1", + "version_check", + "winapi", +] + [[package]] name = "time" version = "0.3.36" @@ -8665,7 +9169,7 @@ dependencies = [ "powerfmt", "serde", "time-core", - "time-macros", + "time-macros 0.2.18", ] [[package]] @@ -8674,6 +9178,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + [[package]] name = "time-macros" version = "0.2.18" @@ -8684,6 +9198,19 @@ dependencies = [ "time-core", ] +[[package]] +name = "time-macros-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" +dependencies = [ + "proc-macro-hack", + "proc-macro2 1.0.85", + "quote 1.0.36", + "standback", + "syn 1.0.109", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -8998,6 +9525,19 @@ dependencies = [ "tonic 0.11.0", ] +[[package]] +name = "toolchain_find" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc8c9a7f0a2966e1acdaf0461023d0b01471eeead645370cf4c3f5cff153f2a" +dependencies = [ + "home", + "once_cell", + "regex", + "semver 1.0.23", + "walkdir", +] + [[package]] name = "tower" version = "0.4.13" @@ -9181,7 +9721,7 @@ dependencies = [ "log", "rand", "rustls 0.21.12", - "sha1", + "sha1 0.10.6", "thiserror", "url", "utf-8", @@ -9368,7 +9908,7 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ - "getrandom", + "getrandom 0.2.15", "rand", ] @@ -9488,6 +10028,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -9862,6 +10408,28 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wsts" +version = "9.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "538ed71c766b41946e7a663e9d9ab317fd45c3c9b61090edc1d202a281ca195c" +dependencies = [ + "aes-gcm", + "bs58 0.5.1", + "hashbrown 0.14.5", + "hex", + "num-traits", + "p256k1", + "polynomial", + "primitive-types 0.12.2", + "rand_core 0.6.4", + "serde", + "sha2 0.10.8", + "thiserror", + "tracing", + "tracing-subscriber", +] + [[package]] name = "wyz" version = "0.2.0" @@ -9892,7 +10460,7 @@ dependencies = [ "oid-registry", "rusticata-macros", "thiserror", - "time", + "time 0.3.36", ] [[package]] @@ -9922,7 +10490,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" dependencies = [ - "time", + "time 0.3.36", ] [[package]] diff --git a/ampd/Cargo.toml b/ampd/Cargo.toml index d8f7659d9..9e4dd779a 100644 --- a/ampd/Cargo.toml +++ b/ampd/Cargo.toml @@ -12,6 +12,7 @@ axum = "0.7.5" base64 = "0.21.2" bcs = { workspace = true } clap = { version = "4.2.7", features = ["derive", "cargo"] } +clarity = { git = "https://github.com/stacks-network/stacks-core", tag = "2.5.0.0.7" } config = "0.13.2" cosmrs = { version = "0.14.0", features = ["cosmwasm", "grpc"] } cosmwasm-std = { workspace = true, features = ["stargate"] } diff --git a/ampd/src/handlers/stacks_verify_msg.rs b/ampd/src/handlers/stacks_verify_msg.rs index b7412d96d..e4f953441 100644 --- a/ampd/src/handlers/stacks_verify_msg.rs +++ b/ampd/src/handlers/stacks_verify_msg.rs @@ -111,7 +111,7 @@ impl EventHandler for Handler { } let tx_hashes: HashSet<_> = messages.iter().map(|message| message.tx_id).collect(); - let transactions_info = self + let transactions = self .http_client .get_transactions(tx_hashes) .await; @@ -119,7 +119,7 @@ impl EventHandler for Handler { let votes: Vec = messages .iter() .map(|msg| { - transactions_info + transactions .get(&msg.tx_id) .map_or(Vote::NotFound, |transaction| { verify_message(&source_gateway_address, transaction, msg) diff --git a/ampd/src/stacks/error.rs b/ampd/src/stacks/error.rs new file mode 100644 index 000000000..8d6510643 --- /dev/null +++ b/ampd/src/stacks/error.rs @@ -0,0 +1,7 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("required property is empty")] + PropertyEmpty, +} diff --git a/ampd/src/stacks/http_client.rs b/ampd/src/stacks/http_client.rs index 99f54e3b4..3c9d0ff39 100644 --- a/ampd/src/stacks/http_client.rs +++ b/ampd/src/stacks/http_client.rs @@ -19,30 +19,30 @@ pub enum Error { #[derive(Debug, Deserialize, Default)] pub struct ContractLogValue { - hex: String, + pub hex: String, } #[derive(Debug, Deserialize, Default)] pub struct ContractLog { - contract_id: String, - topic: String, - value: ContractLogValue, + pub contract_id: String, + pub topic: String, + pub value: ContractLogValue, } #[derive(Debug, Deserialize, Default)] pub struct TransactionEvents { - event_index: u64, - tx_id: String, - contract_log: Option, + pub event_index: u32, + pub tx_id: String, + pub contract_log: Option, } #[derive(Debug, Deserialize, Default)] pub struct Transaction { - tx_id: String, - nonce: u64, - sender_address: String, - tx_status: String, // 'success' - events: Vec, + pub tx_id: String, + pub nonce: u64, + pub sender_address: String, + pub tx_status: String, // 'success' + pub events: Vec, } #[cfg_attr(test, faux::create)] diff --git a/ampd/src/stacks/mod.rs b/ampd/src/stacks/mod.rs index be0cc859c..257035609 100644 --- a/ampd/src/stacks/mod.rs +++ b/ampd/src/stacks/mod.rs @@ -1,2 +1,3 @@ pub(crate) mod http_client; pub(crate) mod verifier; +mod error; diff --git a/ampd/src/stacks/verifier.rs b/ampd/src/stacks/verifier.rs index 545fdde32..25dafcbf2 100644 --- a/ampd/src/stacks/verifier.rs +++ b/ampd/src/stacks/verifier.rs @@ -1,11 +1,65 @@ -use axelar_wasm_std::voting::Vote; +use clarity::vm::ClarityName; use crate::handlers::stacks_verify_msg::Message; -use crate::stacks::http_client::Transaction; +use crate::stacks::error::Error; +use crate::stacks::http_client::{Transaction, TransactionEvents}; +use axelar_wasm_std::voting::Vote; +use clarity::vm::types::{TupleTypeSignature, TypeSignature, Value}; + +const CONTRACT_CALL_TOPIC: &str = "contract-call"; + +impl Message { + fn eq_event(&self, event: &TransactionEvents) -> Result> { + let contract_log = event.contract_log.as_ref().ok_or(Error::PropertyEmpty)?; + + if contract_log.topic != CONTRACT_CALL_TOPIC { + return Ok(false); + } + + let tuple_type_signature = TupleTypeSignature::try_from(vec![( + ClarityName::try_from("sender".to_string())?, + TypeSignature::PrincipalType, + )])?; + + let value = Value::try_deserialize_hex( + contract_log.value.hex.as_str(), + &TypeSignature::TupleType(tuple_type_signature), + false, + )?; + + value.expect_tuple()?; + + Ok(true) + } +} + +fn find_event<'a>( + transaction: &'a Transaction, + gateway_address: &String, + log_index: u32, +) -> Option<&'a TransactionEvents> { + let event = transaction + .events + .iter() + .find(|el| el.event_index == log_index)?; + + if !event.contract_log.as_ref()?.contract_id.eq(gateway_address) { + return None; + } + + Some(event) +} pub fn verify_message( gateway_address: &String, transaction: &Transaction, message: &Message, ) -> Vote { - Vote::NotFound // TODO: + if message.tx_id.to_string() != transaction.tx_id { + return Vote::NotFound; + } + + match find_event(transaction, gateway_address, message.event_index) { + Some(event) if message.eq_event(event).unwrap_or(false) => Vote::SucceededOnChain, + _ => Vote::NotFound, + } } From 87eb02f99301ea7153741d07518eb31d3c611f2d Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Wed, 9 Oct 2024 13:04:11 +0300 Subject: [PATCH 03/20] Working tests for stacks verifier. --- ampd/src/stacks/http_client.rs | 4 +- ampd/src/stacks/verifier.rs | 277 +++++++++++++++++++++++++++++++-- 2 files changed, 263 insertions(+), 18 deletions(-) diff --git a/ampd/src/stacks/http_client.rs b/ampd/src/stacks/http_client.rs index 3c9d0ff39..aeffde941 100644 --- a/ampd/src/stacks/http_client.rs +++ b/ampd/src/stacks/http_client.rs @@ -38,7 +38,7 @@ pub struct TransactionEvents { #[derive(Debug, Deserialize, Default)] pub struct Transaction { - pub tx_id: String, + pub tx_id: Hash, pub nonce: u64, pub sender_address: String, pub tx_status: String, // 'success' @@ -202,7 +202,7 @@ mod tests { let transaction = serde_json::from_str::(data).unwrap(); assert_eq!( transaction.tx_id, - "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" + "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf".parse().unwrap() ); assert_eq!(transaction.nonce, 2); assert_eq!( diff --git a/ampd/src/stacks/verifier.rs b/ampd/src/stacks/verifier.rs index 25dafcbf2..8dd086352 100644 --- a/ampd/src/stacks/verifier.rs +++ b/ampd/src/stacks/verifier.rs @@ -1,34 +1,102 @@ -use clarity::vm::ClarityName; use crate::handlers::stacks_verify_msg::Message; use crate::stacks::error::Error; use crate::stacks::http_client::{Transaction, TransactionEvents}; use axelar_wasm_std::voting::Vote; -use clarity::vm::types::{TupleTypeSignature, TypeSignature, Value}; +use clarity::vm::types::{ + BufferLength, PrincipalData, SequenceSubtype, StringSubtype, TupleTypeSignature, TypeSignature, + Value, +}; +use clarity::vm::ClarityName; +use cosmrs::tx::MessageExt; + +const PRINT_TOPIC: &str = "print"; -const CONTRACT_CALL_TOPIC: &str = "contract-call"; +const CONTRACT_CALL_TYPE: &str = "contract-call"; impl Message { fn eq_event(&self, event: &TransactionEvents) -> Result> { let contract_log = event.contract_log.as_ref().ok_or(Error::PropertyEmpty)?; - if contract_log.topic != CONTRACT_CALL_TOPIC { + if contract_log.topic != PRINT_TOPIC { return Ok(false); } - let tuple_type_signature = TupleTypeSignature::try_from(vec![( - ClarityName::try_from("sender".to_string())?, - TypeSignature::PrincipalType, - )])?; + let tuple_type_signature = TupleTypeSignature::try_from(vec![ + ( + ClarityName::from("type"), + TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( + BufferLength::try_from(13u32)?, + ))), + ), + (ClarityName::from("sender"), TypeSignature::PrincipalType), + ( + ClarityName::from("destination-chain"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 18u32, + )?)), + ), + ( + ClarityName::from("destination-contract-address"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 96u32, + )?)), + ), + ( + ClarityName::from("payload-hash"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 32u32, + )?)), + ), + ])?; + + let hex = contract_log + .value + .hex + .strip_prefix("0x") + .ok_or(Error::PropertyEmpty)?; + + let value = + Value::try_deserialize_hex(hex, &TypeSignature::TupleType(tuple_type_signature), true)?; - let value = Value::try_deserialize_hex( - contract_log.value.hex.as_str(), - &TypeSignature::TupleType(tuple_type_signature), - false, - )?; + if let Value::Tuple(data) = value { + if !data.get("type")?.eq(&Value::string_ascii_from_bytes( + CONTRACT_CALL_TYPE.as_bytes().to_vec(), + )?) { + return Ok(false); + } - value.expect_tuple()?; + if !data.get("sender")?.eq(&Value::from(PrincipalData::parse( + self.source_address.as_str(), + )?)) { + return Ok(false); + } - Ok(true) + if !data.get("destination-chain")?.eq(&Value::buff_from( + self.destination_chain.as_ref().as_bytes().to_vec(), + )?) { + return Ok(false); + } + + if !data + .get("destination-contract-address")? + .eq(&Value::buff_from( + self.destination_address.as_bytes().to_vec(), + )?) + { + return Ok(false); + } + + if !data + .get("payload-hash")? + .eq(&Value::buff_from(self.payload_hash.as_bytes().to_vec())?) + { + return Ok(false); + } + + return Ok(true); + } + + Ok(false) } } @@ -54,7 +122,7 @@ pub fn verify_message( transaction: &Transaction, message: &Message, ) -> Vote { - if message.tx_id.to_string() != transaction.tx_id { + if message.tx_id != transaction.tx_id { return Vote::NotFound; } @@ -63,3 +131,180 @@ pub fn verify_message( _ => Vote::NotFound, } } + +#[cfg(test)] +mod tests { + use crate::handlers::stacks_verify_msg::Message; + use crate::stacks::http_client::{ + ContractLog, ContractLogValue, Transaction, TransactionEvents, + }; + use crate::stacks::verifier::verify_message; + use axelar_wasm_std::voting::Vote; + + // test verify message + #[test] + fn should_not_verify_tx_id_does_not_match() { + let (gateway_address, tx, mut msg) = get_matching_msg_and_tx(); + + msg.tx_id = "ffaf64de66510723f2efbacd7ead3c4f8c856aed1afc2cb30254552aeda47313" + .parse() + .unwrap(); + assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + } + + #[test] + fn should_not_verify_no_log_for_event_index() { + let (gateway_address, tx, mut msg) = get_matching_msg_and_tx(); + + msg.event_index = 2; + + assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + } + + #[test] + fn should_not_verify_event_index_does_not_match() { + let (gateway_address, tx, mut msg) = get_matching_msg_and_tx(); + + msg.event_index = 0; + + assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + } + + #[test] + fn should_not_verify_not_gateway() { + let (gateway_address, mut tx, msg) = get_matching_msg_and_tx(); + + let mut transaction_events = tx.events.get_mut(1).unwrap(); + let mut contract_call = transaction_events.contract_log.as_mut().unwrap(); + + contract_call.contract_id = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM".to_string(); + + assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + } + + #[test] + fn should_not_verify_invalid_topic() { + let (gateway_address, mut tx, msg) = get_matching_msg_and_tx(); + + let mut transaction_events = tx.events.get_mut(1).unwrap(); + let mut contract_call = transaction_events.contract_log.as_mut().unwrap(); + + contract_call.topic = "other".to_string(); + + assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + } + + #[test] + fn should_not_verify_invalid_type() { + let (gateway_address, mut tx, msg) = get_matching_msg_and_tx(); + + let mut transaction_events = tx.events.get_mut(1).unwrap(); + let mut contract_call = transaction_events.contract_log.as_mut().unwrap(); + + // Remove 'call' as hex from `contract-call` data + contract_call.value.hex = contract_call + .value + .hex + .strip_suffix("63616c6c") + .unwrap() + .to_string(); + + assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + } + + #[test] + fn should_not_verify_invalid_sender() { + let (gateway_address, tx, mut msg) = get_matching_msg_and_tx(); + + msg.source_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway".to_string(); + + assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + } + + #[test] + fn should_not_verify_invalid_destination_chain() { + let (gateway_address, tx, mut msg) = get_matching_msg_and_tx(); + + msg.destination_chain = "other".parse().unwrap(); + + assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + } + + #[test] + fn should_not_verify_invalid_destination_address() { + let (gateway_address, tx, mut msg) = get_matching_msg_and_tx(); + + msg.destination_address = "other".parse().unwrap(); + + assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + } + + #[test] + fn should_not_verify_invalid_payload_hash() { + let (gateway_address, tx, mut msg) = get_matching_msg_and_tx(); + + msg.payload_hash = "0xaa38573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f4aa" + .parse() + .unwrap(); + + assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + } + + #[test] + fn should_verify_msg() { + let (gateway_address, tx, msg) = get_matching_msg_and_tx(); + + assert_eq!( + verify_message(&gateway_address, &tx, &msg), + Vote::SucceededOnChain + ); + } + + fn get_matching_msg_and_tx() -> (String, Transaction, Message) { + let gateway_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"; + let tx_id = "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" + .parse() + .unwrap(); + + let msg = Message { + tx_id, + event_index: 1, + source_address: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM".to_string(), + destination_chain: "ethereum".parse().unwrap(), + destination_address: "0x043E105189e15AC72252CFEF898EC3841A4A0561".to_string(), + payload_hash: "0x0338573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f46d" + .parse() + .unwrap(), + }; + + let payload_hash = msg.payload_hash; + + let wrong_event = TransactionEvents { + event_index: 0, + tx_id: tx_id.to_string(), + contract_log: None, + }; + + let event = TransactionEvents { + event_index: 1, + tx_id: tx_id.to_string(), + contract_log: Some(ContractLog { + contract_id: gateway_address.to_string(), + topic: "print".to_string(), + value: ContractLogValue { + hex: "0x0c000000061164657374696e6174696f6e2d636861696e0200000008657468657265756d1c64657374696e6174696f6e2d636f6e74726163742d61646472657373020000002a307830343345313035313839653135414337323235324346454638393845433338343141344130353631077061796c6f616402000000196c6f72656d697073756d20646f6c6f722073697420616d65740c7061796c6f61642d6861736802000000200338573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f46d0673656e646572051a6d78de7b0625dfbfc16c3a8a5735f6dc3dc3f2ce04747970650d0000000d636f6e74726163742d63616c6c".to_string(), + } + }), + }; + + let transaction = Transaction { + tx_id, + nonce: 1, + sender_address: "whatever".to_string(), + tx_status: "success".to_string(), + events: vec![wrong_event, event], + }; + + (gateway_address.to_string(), transaction, msg) + } +} From 3a4d70513983e78d1e06ff56b7bbb85fba45e3a0 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Thu, 10 Oct 2024 13:06:01 +0300 Subject: [PATCH 04/20] Tests for stacks verify message handler. --- ampd/src/config.rs | 1 - ampd/src/handlers/stacks_verify_msg.rs | 197 ++++++++++++++++++++++++- 2 files changed, 191 insertions(+), 7 deletions(-) diff --git a/ampd/src/config.rs b/ampd/src/config.rs index 487a1114b..4a1760b17 100644 --- a/ampd/src/config.rs +++ b/ampd/src/config.rs @@ -133,7 +133,6 @@ mod tests { cosmwasm_contract = '{}' http_url = 'http://localhost:8000' - [[handlers]] type = 'StacksMsgVerifier' cosmwasm_contract = '{}' diff --git a/ampd/src/handlers/stacks_verify_msg.rs b/ampd/src/handlers/stacks_verify_msg.rs index e4f953441..e13b372dd 100644 --- a/ampd/src/handlers/stacks_verify_msg.rs +++ b/ampd/src/handlers/stacks_verify_msg.rs @@ -3,6 +3,7 @@ use std::convert::TryInto; use async_trait::async_trait; use axelar_wasm_std::voting::{PollId, Vote}; +use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; use cosmrs::cosmwasm::MsgExecuteContract; use cosmrs::tx::Msg; use cosmrs::Any; @@ -29,7 +30,7 @@ pub struct Message { pub event_index: u32, pub destination_address: String, pub destination_chain: router_api::ChainName, - pub source_address: String, // TODO + pub source_address: String, pub payload_hash: Hash, } @@ -37,7 +38,7 @@ pub struct Message { #[try_from("wasm-messages_poll_started")] struct PollStartedEvent { poll_id: PollId, - source_gateway_address: String, // TODO + source_gateway_address: String, messages: Vec, participants: Vec, expires_at: u64, @@ -111,10 +112,7 @@ impl EventHandler for Handler { } let tx_hashes: HashSet<_> = messages.iter().map(|message| message.tx_id).collect(); - let transactions = self - .http_client - .get_transactions(tx_hashes) - .await; + let transactions = self.http_client.get_transactions(tx_hashes).await; let votes: Vec = messages .iter() @@ -133,3 +131,190 @@ impl EventHandler for Handler { .expect("vote msg should serialize")]) } } + +#[cfg(test)] +mod tests { + use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; + use cosmrs::cosmwasm::MsgExecuteContract; + use cosmrs::tx::Msg; + use cosmwasm_std; + use error_stack::Result; + use hex::ToHex; + use std::collections::HashMap; + use std::convert::TryInto; + use tokio::sync::watch; + use tokio::test as async_test; + use voting_verifier::events::{PollMetadata, PollStarted, TxEventConfirmation}; + + use super::PollStartedEvent; + use crate::event_processor::EventHandler; + use crate::handlers::tests::into_structured_event; + use crate::stacks::http_client::Client; + use crate::types::{EVMAddress, Hash, TMAddress}; + use crate::PREFIX; + + #[test] + fn should_deserialize_poll_started_event() { + let event: Result = into_structured_event( + poll_started_event(participants(5, None)), + &TMAddress::random(PREFIX), + ) + .try_into(); + + assert!(event.is_ok()); + + let event = event.unwrap(); + + assert!(event.poll_id == 100u64.into()); + assert!( + event.source_gateway_address + == "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway" + ); + + let message = event.messages.first().unwrap(); + + assert!( + message.tx_id + == "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" + .parse() + .unwrap(), + ); + assert!(message.event_index == 1u32); + assert!(message.destination_chain == "ethereum"); + assert!(message.source_address == "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM"); + } + + // Should not handle event if it is not a poll started event + #[async_test] + async fn not_poll_started_event() { + let event = into_structured_event( + cosmwasm_std::Event::new("transfer"), + &TMAddress::random(PREFIX), + ); + + let handler = super::Handler::new( + TMAddress::random(PREFIX), + TMAddress::random(PREFIX), + Client::faux(), + watch::channel(0).1, + ); + + assert!(handler.handle(&event).await.is_ok()); + } + + // Should not handle event if it is not emitted from voting verifier + #[async_test] + async fn contract_is_not_voting_verifier() { + let event = into_structured_event( + poll_started_event(participants(5, None)), + &TMAddress::random(PREFIX), + ); + + let handler = super::Handler::new( + TMAddress::random(PREFIX), + TMAddress::random(PREFIX), + Client::faux(), + watch::channel(0).1, + ); + + assert!(handler.handle(&event).await.is_ok()); + } + + // Should not handle event if worker is not a poll participant + #[async_test] + async fn verifier_is_not_a_participant() { + let voting_verifier = TMAddress::random(PREFIX); + let event = + into_structured_event(poll_started_event(participants(5, None)), &voting_verifier); + + let handler = super::Handler::new( + TMAddress::random(PREFIX), + voting_verifier, + Client::faux(), + watch::channel(0).1, + ); + + assert!(handler.handle(&event).await.is_ok()); + } + + #[async_test] + async fn should_vote_correctly() { + let mut client = Client::faux(); + faux::when!(client.get_transactions).then(|_| HashMap::new()); + + let voting_verifier = TMAddress::random(PREFIX); + let worker = TMAddress::random(PREFIX); + let event = into_structured_event( + poll_started_event(participants(5, Some(worker.clone()))), + &voting_verifier, + ); + + let handler = super::Handler::new(worker, voting_verifier, client, watch::channel(0).1); + + let actual = handler.handle(&event).await.unwrap(); + assert_eq!(actual.len(), 1); + assert!(MsgExecuteContract::from_any(actual.first().unwrap()).is_ok()); + } + + #[async_test] + async fn should_skip_expired_poll() { + let mut client = Client::faux(); + faux::when!(client.get_transactions).then(|_| HashMap::new()); + + let voting_verifier = TMAddress::random(PREFIX); + let worker = TMAddress::random(PREFIX); + let expiration = 100u64; + let event = into_structured_event( + poll_started_event(participants(5, Some(worker.clone()))), + &voting_verifier, + ); + + let (tx, rx) = watch::channel(expiration - 1); + + let handler = super::Handler::new(worker, voting_verifier, client, rx); + + // poll is not expired yet, should hit proxy + let actual = handler.handle(&event).await.unwrap(); + assert_eq!(actual.len(), 1); + + let _ = tx.send(expiration + 1); + + // poll is expired + assert_eq!(handler.handle(&event).await.unwrap(), vec![]); + } + + fn poll_started_event(participants: Vec) -> PollStarted { + PollStarted::Messages { + metadata: PollMetadata { + poll_id: "100".parse().unwrap(), + source_chain: "stacks".parse().unwrap(), + source_gateway_address: "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway" + .parse() + .unwrap(), + confirmation_height: 15, + expires_at: 100, + participants: participants + .into_iter() + .map(|addr| cosmwasm_std::Addr::unchecked(addr.to_string())) + .collect(), + }, + messages: vec![TxEventConfirmation { + tx_id: "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" + .parse() + .unwrap(), + event_index: 1, + source_address: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM".parse().unwrap(), + destination_chain: "ethereum".parse().unwrap(), + destination_address: format!("0x{:x}", EVMAddress::random()).parse().unwrap(), + payload_hash: Hash::random().to_fixed_bytes(), + }], + } + } + + fn participants(n: u8, worker: Option) -> Vec { + (0..n) + .map(|_| TMAddress::random(PREFIX)) + .chain(worker) + .collect() + } +} From 72a41e53997e030876e00e226386f18627917f38 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Fri, 11 Oct 2024 11:20:22 +0300 Subject: [PATCH 05/20] Remove default features from clarity package. --- Cargo.lock | 79 ++++++++----------------------------------------- ampd/Cargo.toml | 2 +- 2 files changed, 14 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 32c951eba..a479c236f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,12 +73,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ahash" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0453232ace82dee0dd0b4c87a59bd90f7b53b314f3e0f61fe2ee7c8a16482289" - [[package]] name = "ahash" version = "0.7.8" @@ -1590,7 +1584,6 @@ dependencies = [ "regex", "rstest", "rstest_reuse", - "rusqlite", "serde", "serde_derive", "serde_json", @@ -2941,18 +2934,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - [[package]] name = "fastcrypto" version = "0.1.8" @@ -3581,15 +3562,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "hashbrown" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" -dependencies = [ - "ahash 0.4.8", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -3628,15 +3600,6 @@ dependencies = [ "fxhash", ] -[[package]] -name = "hashlink" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d99cf782f0dc4372d26846bec3de7804ceb5df083c2d4462c0b8d2330e894fa8" -dependencies = [ - "hashbrown 0.9.1", -] - [[package]] name = "hdrhistogram" version = "7.5.4" @@ -4579,17 +4542,6 @@ dependencies = [ "libc", ] -[[package]] -name = "libsqlite3-sys" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d31059f22935e6c31830db5249ba2b7ecd54fd73a9909286f0a67aa55c2fbd" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - [[package]] name = "linked-hash-map" version = "0.5.6" @@ -7233,23 +7185,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" -[[package]] -name = "rusqlite" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38ee71cbab2c827ec0ac24e76f82eca723cee92c509a65f67dee393c25112" -dependencies = [ - "bitflags 1.3.2", - "byteorder", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "memchr", - "serde_json", - "smallvec", -] - [[package]] name = "rust-ini" version = "0.18.0" @@ -8175,6 +8110,18 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" +[[package]] +name = "slog-json" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1e53f61af1e3c8b852eef0a9dee29008f55d6dd63794f3f12cef786cf0f219" +dependencies = [ + "serde", + "serde_json", + "slog", + "time 0.3.36", +] + [[package]] name = "slog-term" version = "2.9.1" @@ -8286,7 +8233,6 @@ dependencies = [ "percent-encoding", "rand", "ripemd", - "rusqlite", "secp256k1 0.24.3", "serde", "serde_derive", @@ -8295,6 +8241,7 @@ dependencies = [ "sha2 0.10.8", "sha3", "slog", + "slog-json", "slog-term", "time 0.2.27", "winapi", diff --git a/ampd/Cargo.toml b/ampd/Cargo.toml index 9e4dd779a..8eae8e3e2 100644 --- a/ampd/Cargo.toml +++ b/ampd/Cargo.toml @@ -12,7 +12,7 @@ axum = "0.7.5" base64 = "0.21.2" bcs = { workspace = true } clap = { version = "4.2.7", features = ["derive", "cargo"] } -clarity = { git = "https://github.com/stacks-network/stacks-core", tag = "2.5.0.0.7" } +clarity = { git = "https://github.com/stacks-network/stacks-core", tag = "2.5.0.0.7", default-features = false, features = ["slog_json"] } config = "0.13.2" cosmrs = { version = "0.14.0", features = ["cosmwasm", "grpc"] } cosmwasm-std = { workspace = true, features = ["stargate"] } From b2dda219353fe9d5cf66e25d49cf69efe9b13696 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:49:50 +0300 Subject: [PATCH 06/20] Formatting for stacks verify msg handler. --- ampd/src/handlers/config.rs | 6 +++++- ampd/src/handlers/stacks_verify_msg.rs | 8 +++----- ampd/src/stacks/http_client.rs | 18 +++++++++++------- ampd/src/stacks/mod.rs | 2 +- ampd/src/stacks/verifier.rs | 25 ++++++++++++------------- 5 files changed, 32 insertions(+), 27 deletions(-) diff --git a/ampd/src/handlers/config.rs b/ampd/src/handlers/config.rs index 55214a8fb..7094b7129 100644 --- a/ampd/src/handlers/config.rs +++ b/ampd/src/handlers/config.rs @@ -163,7 +163,11 @@ where Config::StellarVerifierSetVerifier, "Stellar verifier set verifier" )?; - ensure_unique_config!(&configs, Config::StacksMsgVerifier, "Stacks message verifier")?; + ensure_unique_config!( + &configs, + Config::StacksMsgVerifier, + "Stacks message verifier" + )?; Ok(configs) } diff --git a/ampd/src/handlers/stacks_verify_msg.rs b/ampd/src/handlers/stacks_verify_msg.rs index e13b372dd..c71a1c7f0 100644 --- a/ampd/src/handlers/stacks_verify_msg.rs +++ b/ampd/src/handlers/stacks_verify_msg.rs @@ -3,7 +3,6 @@ use std::convert::TryInto; use async_trait::async_trait; use axelar_wasm_std::voting::{PollId, Vote}; -use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; use cosmrs::cosmwasm::MsgExecuteContract; use cosmrs::tx::Msg; use cosmrs::Any; @@ -134,14 +133,13 @@ impl EventHandler for Handler { #[cfg(test)] mod tests { - use clarity::vm::types::{PrincipalData, QualifiedContractIdentifier}; + use std::collections::HashMap; + use std::convert::TryInto; + use cosmrs::cosmwasm::MsgExecuteContract; use cosmrs::tx::Msg; use cosmwasm_std; use error_stack::Result; - use hex::ToHex; - use std::collections::HashMap; - use std::convert::TryInto; use tokio::sync::watch; use tokio::test as async_test; use voting_verifier::events::{PollMetadata, PollStarted, TxEventConfirmation}; diff --git a/ampd/src/stacks/http_client.rs b/ampd/src/stacks/http_client.rs index aeffde941..770849b5f 100644 --- a/ampd/src/stacks/http_client.rs +++ b/ampd/src/stacks/http_client.rs @@ -1,10 +1,12 @@ -use crate::types::Hash; -use futures::future::join_all; -use serde::Deserialize; use std::collections::{HashMap, HashSet}; use std::fmt::Debug; + +use futures::future::join_all; +use serde::Deserialize; use thiserror::Error; +use crate::types::Hash; + const GET_TRANSACTION: &str = "extended/v1/tx/"; const STATUS_SUCCESS: &str = "success"; @@ -89,7 +91,7 @@ impl Client { } async fn get_transaction(&self, tx_id: &str) -> Result { - let mut endpoint = GET_TRANSACTION.to_string() + tx_id; + let endpoint = GET_TRANSACTION.to_string() + tx_id; let endpoint = self.get_endpoint(endpoint.as_str()); @@ -97,10 +99,10 @@ impl Client { .get(endpoint) .send() .await - .map_err(|err_str| Error::TxHash)? + .map_err(|_| Error::TxHash)? .json::() .await - .map_err(|err_str| Error::Client) + .map_err(|_| Error::Client) } fn get_endpoint(&self, endpoint: &str) -> String { @@ -202,7 +204,9 @@ mod tests { let transaction = serde_json::from_str::(data).unwrap(); assert_eq!( transaction.tx_id, - "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf".parse().unwrap() + "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" + .parse() + .unwrap() ); assert_eq!(transaction.nonce, 2); assert_eq!( diff --git a/ampd/src/stacks/mod.rs b/ampd/src/stacks/mod.rs index 257035609..4cde61827 100644 --- a/ampd/src/stacks/mod.rs +++ b/ampd/src/stacks/mod.rs @@ -1,3 +1,3 @@ +mod error; pub(crate) mod http_client; pub(crate) mod verifier; -mod error; diff --git a/ampd/src/stacks/verifier.rs b/ampd/src/stacks/verifier.rs index 8dd086352..1d09e6d3c 100644 --- a/ampd/src/stacks/verifier.rs +++ b/ampd/src/stacks/verifier.rs @@ -1,13 +1,13 @@ -use crate::handlers::stacks_verify_msg::Message; -use crate::stacks::error::Error; -use crate::stacks::http_client::{Transaction, TransactionEvents}; use axelar_wasm_std::voting::Vote; use clarity::vm::types::{ BufferLength, PrincipalData, SequenceSubtype, StringSubtype, TupleTypeSignature, TypeSignature, Value, }; use clarity::vm::ClarityName; -use cosmrs::tx::MessageExt; + +use crate::handlers::stacks_verify_msg::Message; +use crate::stacks::error::Error; +use crate::stacks::http_client::{Transaction, TransactionEvents}; const PRINT_TOPIC: &str = "print"; @@ -134,12 +134,13 @@ pub fn verify_message( #[cfg(test)] mod tests { + use axelar_wasm_std::voting::Vote; + use crate::handlers::stacks_verify_msg::Message; use crate::stacks::http_client::{ ContractLog, ContractLogValue, Transaction, TransactionEvents, }; use crate::stacks::verifier::verify_message; - use axelar_wasm_std::voting::Vote; // test verify message #[test] @@ -174,8 +175,8 @@ mod tests { fn should_not_verify_not_gateway() { let (gateway_address, mut tx, msg) = get_matching_msg_and_tx(); - let mut transaction_events = tx.events.get_mut(1).unwrap(); - let mut contract_call = transaction_events.contract_log.as_mut().unwrap(); + let transaction_events = tx.events.get_mut(1).unwrap(); + let contract_call = transaction_events.contract_log.as_mut().unwrap(); contract_call.contract_id = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM".to_string(); @@ -186,8 +187,8 @@ mod tests { fn should_not_verify_invalid_topic() { let (gateway_address, mut tx, msg) = get_matching_msg_and_tx(); - let mut transaction_events = tx.events.get_mut(1).unwrap(); - let mut contract_call = transaction_events.contract_log.as_mut().unwrap(); + let transaction_events = tx.events.get_mut(1).unwrap(); + let contract_call = transaction_events.contract_log.as_mut().unwrap(); contract_call.topic = "other".to_string(); @@ -198,8 +199,8 @@ mod tests { fn should_not_verify_invalid_type() { let (gateway_address, mut tx, msg) = get_matching_msg_and_tx(); - let mut transaction_events = tx.events.get_mut(1).unwrap(); - let mut contract_call = transaction_events.contract_log.as_mut().unwrap(); + let transaction_events = tx.events.get_mut(1).unwrap(); + let contract_call = transaction_events.contract_log.as_mut().unwrap(); // Remove 'call' as hex from `contract-call` data contract_call.value.hex = contract_call @@ -277,8 +278,6 @@ mod tests { .unwrap(), }; - let payload_hash = msg.payload_hash; - let wrong_event = TransactionEvents { event_index: 0, tx_id: tx_id.to_string(), From 2182806763285f0bbfe60768c49f1e637d5ce819 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Wed, 16 Oct 2024 17:12:25 +0300 Subject: [PATCH 07/20] Implement verify verifier set handler for stacks. --- ampd/src/config.rs | 6 + ampd/src/handlers/config.rs | 26 ++ ampd/src/handlers/mod.rs | 1 + .../handlers/stacks_verify_verifier_set.rs | 247 ++++++++++++++++++ ampd/src/lib.rs | 13 + ampd/src/stacks/error.rs | 4 + ampd/src/stacks/http_client.rs | 2 +- ampd/src/stacks/mod.rs | 137 ++++++++++ ampd/src/stacks/verifier.rs | 73 ++++++ ampd/src/tests/config_template.toml | 5 + contracts/multisig-prover/src/contract.rs | 1 - .../multisig-prover/src/contract/execute.rs | 1 - 12 files changed, 513 insertions(+), 3 deletions(-) create mode 100644 ampd/src/handlers/stacks_verify_verifier_set.rs diff --git a/ampd/src/config.rs b/ampd/src/config.rs index 4a1760b17..5ef475b03 100644 --- a/ampd/src/config.rs +++ b/ampd/src/config.rs @@ -362,6 +362,12 @@ mod tests { ), http_url: Url::from_str("http://127.0.0.1").unwrap(), }, + HandlerConfig::StacksVerifierSetVerifier { + cosmwasm_contract: TMAddress::from( + AccountId::new("axelar", &[0u8; 32]).unwrap(), + ), + http_url: Url::from_str("http://127.0.0.1").unwrap(), + }, ], ..Config::default() } diff --git a/ampd/src/handlers/config.rs b/ampd/src/handlers/config.rs index 7094b7129..3c14888eb 100644 --- a/ampd/src/handlers/config.rs +++ b/ampd/src/handlers/config.rs @@ -67,6 +67,10 @@ pub enum Config { cosmwasm_contract: TMAddress, http_url: Url, }, + StacksVerifierSetVerifier { + cosmwasm_contract: TMAddress, + http_url: Url, + }, } fn validate_evm_verifier_set_verifier_configs<'de, D>(configs: &[Config]) -> Result<(), D::Error> @@ -168,6 +172,11 @@ where Config::StacksMsgVerifier, "Stacks message verifier" )?; + ensure_unique_config!( + &configs, + Config::StacksVerifierSetVerifier, + "Stacks verifier set verifier" + )?; Ok(configs) } @@ -331,5 +340,22 @@ mod tests { Err(e) if e.to_string().contains("only one Stacks message verifier config is allowed") ) ); + + let configs = vec![ + Config::StacksVerifierSetVerifier { + cosmwasm_contract: TMAddress::random(PREFIX), + http_url: "http://localhost:8080/".parse().unwrap(), + }, + Config::StacksVerifierSetVerifier { + cosmwasm_contract: TMAddress::random(PREFIX), + http_url: "http://localhost:8080/".parse().unwrap(), + }, + ]; + + assert!( + matches!(deserialize_handler_configs(to_value(configs).unwrap()), + Err(e) if e.to_string().contains("only one Stacks verifier set verifier config is allowed") + ) + ); } } diff --git a/ampd/src/handlers/mod.rs b/ampd/src/handlers/mod.rs index 0b63520fc..48c6f03b5 100644 --- a/ampd/src/handlers/mod.rs +++ b/ampd/src/handlers/mod.rs @@ -6,6 +6,7 @@ pub mod multisig; pub mod mvx_verify_msg; pub mod mvx_verify_verifier_set; pub mod stacks_verify_msg; +pub mod stacks_verify_verifier_set; pub(crate) mod stellar_verify_msg; pub(crate) mod stellar_verify_verifier_set; pub mod sui_verify_msg; diff --git a/ampd/src/handlers/stacks_verify_verifier_set.rs b/ampd/src/handlers/stacks_verify_verifier_set.rs new file mode 100644 index 000000000..17a64b713 --- /dev/null +++ b/ampd/src/handlers/stacks_verify_verifier_set.rs @@ -0,0 +1,247 @@ +use std::convert::TryInto; + +use async_trait::async_trait; +use axelar_wasm_std::voting::{PollId, Vote}; +use cosmrs::cosmwasm::MsgExecuteContract; +use cosmrs::tx::Msg; +use cosmrs::Any; +use error_stack::ResultExt; +use events::Error::EventTypeMismatch; +use events::Event; +use events_derive::try_from; +use multisig::verifier_set::VerifierSet; +use serde::Deserialize; +use tokio::sync::watch::Receiver; +use tracing::{info, info_span}; +use valuable::Valuable; +use voting_verifier::msg::ExecuteMsg; + +use crate::event_processor::EventHandler; +use crate::handlers::errors::Error; +use crate::stacks::http_client::Client; +use crate::stacks::verifier::verify_verifier_set; +use crate::types::{Hash, TMAddress}; + +#[derive(Deserialize, Debug)] +pub struct VerifierSetConfirmation { + pub tx_id: Hash, + pub event_index: u32, + pub verifier_set: VerifierSet, +} + +#[derive(Deserialize, Debug)] +#[try_from("wasm-verifier_set_poll_started")] +struct PollStartedEvent { + poll_id: PollId, + source_gateway_address: String, + verifier_set: VerifierSetConfirmation, + participants: Vec, + expires_at: u64, +} + +pub struct Handler { + verifier: TMAddress, + voting_verifier_contract: TMAddress, + http_client: Client, + latest_block_height: Receiver, +} + +impl Handler { + pub fn new( + verifier: TMAddress, + voting_verifier_contract: TMAddress, + http_client: Client, + latest_block_height: Receiver, + ) -> Self { + Self { + verifier, + voting_verifier_contract, + http_client, + latest_block_height, + } + } + + fn vote_msg(&self, poll_id: PollId, vote: Vote) -> MsgExecuteContract { + MsgExecuteContract { + sender: self.verifier.as_ref().clone(), + contract: self.voting_verifier_contract.as_ref().clone(), + msg: serde_json::to_vec(&ExecuteMsg::Vote { + poll_id, + votes: vec![vote], + }) + .expect("vote msg should serialize"), + funds: vec![], + } + } +} + +#[async_trait] +impl EventHandler for Handler { + type Err = Error; + + async fn handle(&self, event: &Event) -> error_stack::Result, Error> { + if !event.is_from_contract(self.voting_verifier_contract.as_ref()) { + return Ok(vec![]); + } + + let PollStartedEvent { + poll_id, + source_gateway_address, + verifier_set, + participants, + expires_at, + .. + } = match event.try_into() as error_stack::Result<_, _> { + Err(report) if matches!(report.current_context(), EventTypeMismatch(_)) => { + return Ok(vec![]); + } + event => event.change_context(Error::DeserializeEvent)?, + }; + + if !participants.contains(&self.verifier) { + return Ok(vec![]); + } + + let latest_block_height = *self.latest_block_height.borrow(); + if latest_block_height >= expires_at { + info!(poll_id = poll_id.to_string(), "skipping expired poll"); + return Ok(vec![]); + } + + let transaction = self + .http_client + .get_valid_transaction(&verifier_set.tx_id) + .await; + + let vote = info_span!( + "verify a new verifier set for Stacks", + poll_id = poll_id.to_string(), + id = format!("{}_{}", verifier_set.tx_id, verifier_set.event_index) + ) + .in_scope(|| { + info!("ready to verify a new worker set in poll"); + + let vote = transaction.map_or(Vote::NotFound, |transaction| { + verify_verifier_set(&source_gateway_address, &transaction, verifier_set) + }); + info!( + vote = vote.as_value(), + "ready to vote for a new worker set in poll" + ); + + vote + }); + + Ok(vec![self + .vote_msg(poll_id, vote) + .into_any() + .expect("vote msg should serialize")]) + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + use std::convert::TryInto; + + use cosmrs::cosmwasm::MsgExecuteContract; + use cosmrs::tx::Msg; + use cosmwasm_std; + use error_stack::Result; + use multisig::key::KeyType; + use multisig::test::common::{build_verifier_set, ecdsa_test_data}; + use tokio::sync::watch; + use tokio::test as async_test; + use voting_verifier::events::{ + PollMetadata, PollStarted, TxEventConfirmation, VerifierSetConfirmation, + }; + + use super::PollStartedEvent; + use crate::event_processor::EventHandler; + use crate::handlers::tests::into_structured_event; + use crate::stacks::http_client::Client; + use crate::types::{EVMAddress, Hash, TMAddress}; + use crate::PREFIX; + + #[test] + fn should_deserialize_verifier_set_poll_started_event() { + let event: Result = into_structured_event( + verifier_set_poll_started_event(participants(5, None), 100), + &TMAddress::random(PREFIX), + ) + .try_into(); + + assert!(event.is_ok()); + + let event = event.unwrap(); + + assert!(event.poll_id == 100u64.into()); + assert!( + event.source_gateway_address + == "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway" + ); + + let verifier_set = event.verifier_set; + + assert!( + verifier_set.tx_id + == "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" + .parse() + .unwrap() + ); + assert!(verifier_set.event_index == 1u32); + assert!(verifier_set.verifier_set.signers.len() == 3); + assert_eq!(verifier_set.verifier_set.threshold, Uint128::from(2u128)); + + let mut signers = verifier_set.verifier_set.signers.values(); + let signer1 = signers.next().unwrap(); + let signer2 = signers.next().unwrap(); + + assert_eq!(signer1.pub_key.as_ref(), HexBinary::from_hex( + "45e67eaf446e6c26eb3a2b55b64339ecf3a4d1d03180bee20eb5afdd23fa644f", + ) + .unwrap().as_ref()); + assert_eq!(signer1.weight, Uint128::from(1u128)); + + assert_eq!(signer2.pub_key.as_ref(), HexBinary::from_hex( + "dd9822c7fa239dda9913ebee813ecbe69e35d88ff651548d5cc42c033a8a667b", + ) + .unwrap().as_ref()); + assert_eq!(signer2.weight, Uint128::from(1u128)); + } + + fn verifier_set_poll_started_event( + participants: Vec, + expires_at: u64, + ) -> PollStarted { + PollStarted::VerifierSet { + metadata: PollMetadata { + poll_id: "100".parse().unwrap(), + source_chain: "multiversx".parse().unwrap(), + source_gateway_address: "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway" + .parse() + .unwrap(), + confirmation_height: 15, + expires_at, + participants: participants + .into_iter() + .map(|addr| cosmwasm_std::Addr::unchecked(addr.to_string())) + .collect(), + }, + verifier_set: VerifierSetConfirmation { + tx_id: "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" + .parse() + .unwrap(), + event_index: 1, + verifier_set: build_verifier_set(KeyType::Ecdsa, &ecdsa_test_data::signers()), + }, + } + } + + fn participants(n: u8, worker: Option) -> Vec { + (0..n) + .map(|_| TMAddress::random(PREFIX)) + .chain(worker) + .collect() + } +} diff --git a/ampd/src/lib.rs b/ampd/src/lib.rs index b2e4afacf..83803849b 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -403,6 +403,19 @@ where ), event_processor_config.clone(), ), + handlers::config::Config::StacksVerifierSetVerifier { + cosmwasm_contract, + http_url, + } => self.create_handler_task( + "stacks-verifier-set-verifier", + handlers::stacks_verify_verifier_set::Handler::new( + verifier.clone(), + cosmwasm_contract, + Client::new_http(http_url.to_string().trim_end_matches('/').into()), + self.block_height_monitor.latest_block_height(), + ), + event_processor_config.clone(), + ), }; self.event_processor = self.event_processor.add_task(task); } diff --git a/ampd/src/stacks/error.rs b/ampd/src/stacks/error.rs index 8d6510643..e142258d7 100644 --- a/ampd/src/stacks/error.rs +++ b/ampd/src/stacks/error.rs @@ -4,4 +4,8 @@ use thiserror::Error; pub enum Error { #[error("required property is empty")] PropertyEmpty, + #[error("invalid encoding")] + InvalidEncoding, + #[error("provided key is not ecdsa")] + NotEcdsaKey, } diff --git a/ampd/src/stacks/http_client.rs b/ampd/src/stacks/http_client.rs index 770849b5f..470c756b5 100644 --- a/ampd/src/stacks/http_client.rs +++ b/ampd/src/stacks/http_client.rs @@ -83,7 +83,7 @@ impl Client { .collect() } - async fn get_valid_transaction(&self, tx_hash: &Hash) -> Option { + pub async fn get_valid_transaction(&self, tx_hash: &Hash) -> Option { self.get_transaction(tx_hash.to_string().as_str()) .await .ok() diff --git a/ampd/src/stacks/mod.rs b/ampd/src/stacks/mod.rs index 4cde61827..8afe8bfaf 100644 --- a/ampd/src/stacks/mod.rs +++ b/ampd/src/stacks/mod.rs @@ -1,3 +1,140 @@ +use axelar_wasm_std::hash::Hash; +use clarity::types::StacksEpochId; +use clarity::vm::types::{ + BufferLength, ListTypeData, SequenceSubtype, TupleData, TupleTypeSignature, TypeSignature, +}; +use clarity::vm::{ClarityName, Value}; +use cosmwasm_std::Uint256; +use error_stack::{Report, ResultExt}; +use multisig::key::PublicKey; +use multisig::msg::Signer; +use multisig::verifier_set::VerifierSet; +use sha3::{Digest, Keccak256}; + +use crate::stacks::error::Error; + mod error; pub(crate) mod http_client; pub(crate) mod verifier; + +pub struct WeightedSigner { + pub signer: Vec, + pub weight: u128, +} + +pub struct WeightedSigners { + pub signers: Vec, + pub threshold: Value, + pub nonce: Value, +} + +impl TryFrom<&Signer> for WeightedSigner { + type Error = Error; + + fn try_from(signer: &Signer) -> Result { + Ok(WeightedSigner { + signer: ecdsa_key(&signer.pub_key)?, + weight: signer.weight.into(), + }) + } +} + +impl TryFrom<&VerifierSet> for WeightedSigners { + type Error = Report; + + fn try_from(verifier_set: &VerifierSet) -> Result { + let mut signers: Vec = verifier_set + .signers + .values() + .map(WeightedSigner::try_from) + .collect::>()?; + + signers.sort_by(|signer1, signer2| signer1.signer.cmp(&signer2.signer)); + + Ok(WeightedSigners { + signers, + threshold: Value::UInt(verifier_set.threshold.into()), + nonce: Value::buff_from( + Uint256::from(verifier_set.created_at) + .to_be_bytes() + .to_vec(), + ) + .change_context(Error::InvalidEncoding)?, + }) + } +} + +impl WeightedSigner { + fn try_into_value(self) -> Result> { + Ok(Value::from( + TupleData::from_data(vec![ + ( + ClarityName::from("signer"), + Value::buff_from(self.signer).change_context(Error::InvalidEncoding)?, + ), + (ClarityName::from("weight"), Value::UInt(self.weight)), + ]) + .change_context(Error::InvalidEncoding)?, + )) + } +} + +impl WeightedSigners { + pub fn hash(self) -> Result> { + let value = self + .try_into_value() + .change_context(Error::InvalidEncoding)?; + + Ok(Keccak256::digest( + value + .serialize_to_vec() + .map_err(|_| Error::InvalidEncoding)?, + ) + .into()) + } + + pub fn try_into_value(self) -> Result> { + let weighted_signers: Vec = self + .signers + .into_iter() + .map(|weighted_signer| weighted_signer.try_into_value()) + .collect::>() + .change_context(Error::InvalidEncoding)?; + + let signer_type_signature = TupleTypeSignature::try_from(vec![ + ( + ClarityName::from("signer"), + TypeSignature::SequenceType(SequenceSubtype::BufferType( + BufferLength::try_from(33u32).change_context(Error::InvalidEncoding)?, + )), + ), + (ClarityName::from("weight"), TypeSignature::UIntType), + ]) + .change_context(Error::InvalidEncoding)?; + + let tuple_data = TupleData::from_data(vec![ + ( + ClarityName::from("signers"), + Value::list_with_type( + &StacksEpochId::latest(), + weighted_signers, + ListTypeData::new_list(TypeSignature::from(signer_type_signature), 48) + .change_context(Error::InvalidEncoding)?, + ) + .map_err(|_| Error::InvalidEncoding)?, + ), + (ClarityName::from("threshold"), self.threshold), + (ClarityName::from("nonce"), self.nonce), + ]) + .change_context(Error::InvalidEncoding)?; + + Ok(Value::from(tuple_data)) + } +} + +pub fn ecdsa_key(pub_key: &PublicKey) -> Result, Error> { + match pub_key { + PublicKey::Ecdsa(ecdsa_key) => Ok(ecdsa_key.to_vec()), + _ => Err(Error::NotEcdsaKey), + } +} diff --git a/ampd/src/stacks/verifier.rs b/ampd/src/stacks/verifier.rs index 1d09e6d3c..1d2cf2c16 100644 --- a/ampd/src/stacks/verifier.rs +++ b/ampd/src/stacks/verifier.rs @@ -6,12 +6,15 @@ use clarity::vm::types::{ use clarity::vm::ClarityName; use crate::handlers::stacks_verify_msg::Message; +use crate::handlers::stacks_verify_verifier_set::VerifierSetConfirmation; use crate::stacks::error::Error; use crate::stacks::http_client::{Transaction, TransactionEvents}; +use crate::stacks::WeightedSigners; const PRINT_TOPIC: &str = "print"; const CONTRACT_CALL_TYPE: &str = "contract-call"; +const SIGNERS_ROTATED_TYPE: &str = "signers-rotated"; impl Message { fn eq_event(&self, event: &TransactionEvents) -> Result> { @@ -100,6 +103,61 @@ impl Message { } } +impl VerifierSetConfirmation { + fn eq_event(&self, event: &TransactionEvents) -> Result> { + let contract_log = event.contract_log.as_ref().ok_or(Error::PropertyEmpty)?; + + if contract_log.topic != PRINT_TOPIC { + return Ok(false); + } + + let tuple_type_signature = TupleTypeSignature::try_from(vec![ + ( + ClarityName::from("type"), + TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( + BufferLength::try_from(15u32)?, + ))), + ), + ( + ClarityName::from("signers-hash"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 32u32, + )?)), + ), + ])?; + + let hex = contract_log + .value + .hex + .strip_prefix("0x") + .ok_or(Error::PropertyEmpty)?; + + let value = + Value::try_deserialize_hex(hex, &TypeSignature::TupleType(tuple_type_signature), true)?; + + if let Value::Tuple(data) = value { + if !data.get("type")?.eq(&Value::string_ascii_from_bytes( + SIGNERS_ROTATED_TYPE.as_bytes().to_vec(), + )?) { + return Ok(false); + } + + let weighted_signers = WeightedSigners::try_from(&self.verifier_set)?; + + if !data + .get("signers-hash")? + .eq(&Value::buff_from(weighted_signers.hash()?.to_vec())?) + { + return Ok(false); + } + + return Ok(true); + } + + Ok(false) + } +} + fn find_event<'a>( transaction: &'a Transaction, gateway_address: &String, @@ -132,6 +190,21 @@ pub fn verify_message( } } +pub fn verify_verifier_set( + gateway_address: &String, + transaction: &Transaction, + verifier_set: VerifierSetConfirmation, +) -> Vote { + if verifier_set.tx_id != transaction.tx_id { + return Vote::NotFound; + } + + match find_event(transaction, gateway_address, verifier_set.event_index) { + Some(event) if verifier_set.eq_event(event).unwrap_or(false) => Vote::SucceededOnChain, + _ => Vote::NotFound, + } +} + #[cfg(test)] mod tests { use axelar_wasm_std::voting::Vote; diff --git a/ampd/src/tests/config_template.toml b/ampd/src/tests/config_template.toml index 2fdb3bc94..1a2e414ca 100644 --- a/ampd/src/tests/config_template.toml +++ b/ampd/src/tests/config_template.toml @@ -87,6 +87,11 @@ type = 'StacksMsgVerifier' cosmwasm_contract = 'axelar1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqecnww6' http_url = 'http://127.0.0.1/' +[[handlers]] +type = 'StacksVerifierSetVerifier' +cosmwasm_contract = 'axelar1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqecnww6' +http_url = 'http://127.0.0.1/' + [tofnd_config] url = 'http://localhost:50051/' party_uid = 'ampd' diff --git a/contracts/multisig-prover/src/contract.rs b/contracts/multisig-prover/src/contract.rs index 31d946274..caa7d25b4 100644 --- a/contracts/multisig-prover/src/contract.rs +++ b/contracts/multisig-prover/src/contract.rs @@ -71,7 +71,6 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg.ensure_permissions(deps.storage, &info.sender)? { - // TODO: Add ConstructProof (message_id, payload) for ITS Hub only messages? ExecuteMsg::ConstructProof(message_ids) => Ok(execute::construct_proof(deps, message_ids)?), ExecuteMsg::UpdateVerifierSet {} => Ok(execute::update_verifier_set(deps, env)?), ExecuteMsg::ConfirmVerifierSet {} => Ok(execute::confirm_verifier_set(deps, info.sender)?), diff --git a/contracts/multisig-prover/src/contract/execute.rs b/contracts/multisig-prover/src/contract/execute.rs index 561dd9b98..bc01ff2ff 100644 --- a/contracts/multisig-prover/src/contract/execute.rs +++ b/contracts/multisig-prover/src/contract/execute.rs @@ -100,7 +100,6 @@ fn messages( "violated invariant: returned gateway messages count mismatch" ); - // TODO: Filter messages that have `destination_address` as the Stacks ITS Contract if let Some(wrong_destination) = messages .iter() .find(|msg| msg.destination_chain != chain_name) From 757cd46e7577b9ded4e7da78bfff15ab0ccea008 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Thu, 17 Oct 2024 15:29:13 +0300 Subject: [PATCH 08/20] Add tests for stacks verify verifier set. --- .../handlers/stacks_verify_verifier_set.rs | 602 +++++++++++------- ampd/src/stacks/verifier.rs | 186 +++++- 2 files changed, 539 insertions(+), 249 deletions(-) diff --git a/ampd/src/handlers/stacks_verify_verifier_set.rs b/ampd/src/handlers/stacks_verify_verifier_set.rs index 17a64b713..b87319697 100644 --- a/ampd/src/handlers/stacks_verify_verifier_set.rs +++ b/ampd/src/handlers/stacks_verify_verifier_set.rs @@ -1,247 +1,355 @@ -use std::convert::TryInto; - -use async_trait::async_trait; -use axelar_wasm_std::voting::{PollId, Vote}; -use cosmrs::cosmwasm::MsgExecuteContract; -use cosmrs::tx::Msg; -use cosmrs::Any; -use error_stack::ResultExt; -use events::Error::EventTypeMismatch; -use events::Event; -use events_derive::try_from; -use multisig::verifier_set::VerifierSet; -use serde::Deserialize; -use tokio::sync::watch::Receiver; -use tracing::{info, info_span}; -use valuable::Valuable; -use voting_verifier::msg::ExecuteMsg; - -use crate::event_processor::EventHandler; -use crate::handlers::errors::Error; -use crate::stacks::http_client::Client; -use crate::stacks::verifier::verify_verifier_set; -use crate::types::{Hash, TMAddress}; - -#[derive(Deserialize, Debug)] -pub struct VerifierSetConfirmation { - pub tx_id: Hash, - pub event_index: u32, - pub verifier_set: VerifierSet, -} - -#[derive(Deserialize, Debug)] -#[try_from("wasm-verifier_set_poll_started")] -struct PollStartedEvent { - poll_id: PollId, - source_gateway_address: String, - verifier_set: VerifierSetConfirmation, - participants: Vec, - expires_at: u64, -} - -pub struct Handler { - verifier: TMAddress, - voting_verifier_contract: TMAddress, - http_client: Client, - latest_block_height: Receiver, -} - -impl Handler { - pub fn new( - verifier: TMAddress, - voting_verifier_contract: TMAddress, - http_client: Client, - latest_block_height: Receiver, - ) -> Self { - Self { - verifier, - voting_verifier_contract, - http_client, - latest_block_height, - } - } - - fn vote_msg(&self, poll_id: PollId, vote: Vote) -> MsgExecuteContract { - MsgExecuteContract { - sender: self.verifier.as_ref().clone(), - contract: self.voting_verifier_contract.as_ref().clone(), - msg: serde_json::to_vec(&ExecuteMsg::Vote { - poll_id, - votes: vec![vote], - }) - .expect("vote msg should serialize"), - funds: vec![], - } - } -} - -#[async_trait] -impl EventHandler for Handler { - type Err = Error; - - async fn handle(&self, event: &Event) -> error_stack::Result, Error> { - if !event.is_from_contract(self.voting_verifier_contract.as_ref()) { - return Ok(vec![]); - } - - let PollStartedEvent { - poll_id, - source_gateway_address, - verifier_set, - participants, - expires_at, - .. - } = match event.try_into() as error_stack::Result<_, _> { - Err(report) if matches!(report.current_context(), EventTypeMismatch(_)) => { - return Ok(vec![]); - } - event => event.change_context(Error::DeserializeEvent)?, - }; - - if !participants.contains(&self.verifier) { - return Ok(vec![]); - } - - let latest_block_height = *self.latest_block_height.borrow(); - if latest_block_height >= expires_at { - info!(poll_id = poll_id.to_string(), "skipping expired poll"); - return Ok(vec![]); - } - - let transaction = self - .http_client - .get_valid_transaction(&verifier_set.tx_id) - .await; - - let vote = info_span!( - "verify a new verifier set for Stacks", - poll_id = poll_id.to_string(), - id = format!("{}_{}", verifier_set.tx_id, verifier_set.event_index) - ) - .in_scope(|| { - info!("ready to verify a new worker set in poll"); - - let vote = transaction.map_or(Vote::NotFound, |transaction| { - verify_verifier_set(&source_gateway_address, &transaction, verifier_set) - }); - info!( - vote = vote.as_value(), - "ready to vote for a new worker set in poll" - ); - - vote - }); - - Ok(vec![self - .vote_msg(poll_id, vote) - .into_any() - .expect("vote msg should serialize")]) - } -} - -#[cfg(test)] -mod tests { - use std::collections::HashMap; - use std::convert::TryInto; - - use cosmrs::cosmwasm::MsgExecuteContract; - use cosmrs::tx::Msg; - use cosmwasm_std; - use error_stack::Result; - use multisig::key::KeyType; - use multisig::test::common::{build_verifier_set, ecdsa_test_data}; - use tokio::sync::watch; - use tokio::test as async_test; - use voting_verifier::events::{ - PollMetadata, PollStarted, TxEventConfirmation, VerifierSetConfirmation, - }; - - use super::PollStartedEvent; - use crate::event_processor::EventHandler; - use crate::handlers::tests::into_structured_event; - use crate::stacks::http_client::Client; - use crate::types::{EVMAddress, Hash, TMAddress}; - use crate::PREFIX; - - #[test] - fn should_deserialize_verifier_set_poll_started_event() { - let event: Result = into_structured_event( - verifier_set_poll_started_event(participants(5, None), 100), - &TMAddress::random(PREFIX), - ) - .try_into(); - - assert!(event.is_ok()); - - let event = event.unwrap(); - - assert!(event.poll_id == 100u64.into()); - assert!( - event.source_gateway_address - == "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway" - ); - - let verifier_set = event.verifier_set; - - assert!( - verifier_set.tx_id - == "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" - .parse() - .unwrap() - ); - assert!(verifier_set.event_index == 1u32); - assert!(verifier_set.verifier_set.signers.len() == 3); - assert_eq!(verifier_set.verifier_set.threshold, Uint128::from(2u128)); - - let mut signers = verifier_set.verifier_set.signers.values(); - let signer1 = signers.next().unwrap(); - let signer2 = signers.next().unwrap(); - - assert_eq!(signer1.pub_key.as_ref(), HexBinary::from_hex( - "45e67eaf446e6c26eb3a2b55b64339ecf3a4d1d03180bee20eb5afdd23fa644f", - ) - .unwrap().as_ref()); - assert_eq!(signer1.weight, Uint128::from(1u128)); - - assert_eq!(signer2.pub_key.as_ref(), HexBinary::from_hex( - "dd9822c7fa239dda9913ebee813ecbe69e35d88ff651548d5cc42c033a8a667b", - ) - .unwrap().as_ref()); - assert_eq!(signer2.weight, Uint128::from(1u128)); - } - - fn verifier_set_poll_started_event( - participants: Vec, - expires_at: u64, - ) -> PollStarted { - PollStarted::VerifierSet { - metadata: PollMetadata { - poll_id: "100".parse().unwrap(), - source_chain: "multiversx".parse().unwrap(), - source_gateway_address: "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway" - .parse() - .unwrap(), - confirmation_height: 15, - expires_at, - participants: participants - .into_iter() - .map(|addr| cosmwasm_std::Addr::unchecked(addr.to_string())) - .collect(), - }, - verifier_set: VerifierSetConfirmation { - tx_id: "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" - .parse() - .unwrap(), - event_index: 1, - verifier_set: build_verifier_set(KeyType::Ecdsa, &ecdsa_test_data::signers()), - }, - } - } - - fn participants(n: u8, worker: Option) -> Vec { - (0..n) - .map(|_| TMAddress::random(PREFIX)) - .chain(worker) - .collect() - } -} +use std::convert::TryInto; + +use async_trait::async_trait; +use axelar_wasm_std::voting::{PollId, Vote}; +use cosmrs::cosmwasm::MsgExecuteContract; +use cosmrs::tx::Msg; +use cosmrs::Any; +use error_stack::ResultExt; +use events::Error::EventTypeMismatch; +use events::Event; +use events_derive::try_from; +use multisig::verifier_set::VerifierSet; +use serde::Deserialize; +use tokio::sync::watch::Receiver; +use tracing::{info, info_span}; +use valuable::Valuable; +use voting_verifier::msg::ExecuteMsg; + +use crate::event_processor::EventHandler; +use crate::handlers::errors::Error; +use crate::stacks::http_client::Client; +use crate::stacks::verifier::verify_verifier_set; +use crate::types::{Hash, TMAddress}; + +#[derive(Deserialize, Debug)] +pub struct VerifierSetConfirmation { + pub tx_id: Hash, + pub event_index: u32, + pub verifier_set: VerifierSet, +} + +#[derive(Deserialize, Debug)] +#[try_from("wasm-verifier_set_poll_started")] +struct PollStartedEvent { + poll_id: PollId, + source_gateway_address: String, + verifier_set: VerifierSetConfirmation, + participants: Vec, + expires_at: u64, +} + +pub struct Handler { + verifier: TMAddress, + voting_verifier_contract: TMAddress, + http_client: Client, + latest_block_height: Receiver, +} + +impl Handler { + pub fn new( + verifier: TMAddress, + voting_verifier_contract: TMAddress, + http_client: Client, + latest_block_height: Receiver, + ) -> Self { + Self { + verifier, + voting_verifier_contract, + http_client, + latest_block_height, + } + } + + fn vote_msg(&self, poll_id: PollId, vote: Vote) -> MsgExecuteContract { + MsgExecuteContract { + sender: self.verifier.as_ref().clone(), + contract: self.voting_verifier_contract.as_ref().clone(), + msg: serde_json::to_vec(&ExecuteMsg::Vote { + poll_id, + votes: vec![vote], + }) + .expect("vote msg should serialize"), + funds: vec![], + } + } +} + +#[async_trait] +impl EventHandler for Handler { + type Err = Error; + + async fn handle(&self, event: &Event) -> error_stack::Result, Error> { + if !event.is_from_contract(self.voting_verifier_contract.as_ref()) { + return Ok(vec![]); + } + + let PollStartedEvent { + poll_id, + source_gateway_address, + verifier_set, + participants, + expires_at, + .. + } = match event.try_into() as error_stack::Result<_, _> { + Err(report) if matches!(report.current_context(), EventTypeMismatch(_)) => { + return Ok(vec![]); + } + event => event.change_context(Error::DeserializeEvent)?, + }; + + if !participants.contains(&self.verifier) { + return Ok(vec![]); + } + + let latest_block_height = *self.latest_block_height.borrow(); + if latest_block_height >= expires_at { + info!(poll_id = poll_id.to_string(), "skipping expired poll"); + return Ok(vec![]); + } + + let transaction = self + .http_client + .get_valid_transaction(&verifier_set.tx_id) + .await; + + let vote = info_span!( + "verify a new verifier set for Stacks", + poll_id = poll_id.to_string(), + id = format!("{}_{}", verifier_set.tx_id, verifier_set.event_index) + ) + .in_scope(|| { + info!("ready to verify a new worker set in poll"); + + let vote = transaction.map_or(Vote::NotFound, |transaction| { + verify_verifier_set(&source_gateway_address, &transaction, verifier_set) + }); + info!( + vote = vote.as_value(), + "ready to vote for a new worker set in poll" + ); + + vote + }); + + Ok(vec![self + .vote_msg(poll_id, vote) + .into_any() + .expect("vote msg should serialize")]) + } +} + +#[cfg(test)] +mod tests { + use std::convert::TryInto; + + use cosmrs::cosmwasm::MsgExecuteContract; + use cosmrs::tx::Msg; + use cosmwasm_std; + use cosmwasm_std::{HexBinary, Uint128}; + use error_stack::Result; + use multisig::key::KeyType; + use multisig::test::common::{build_verifier_set, ecdsa_test_data}; + use tokio::sync::watch; + use tokio::test as async_test; + use voting_verifier::events::{PollMetadata, PollStarted, VerifierSetConfirmation}; + + use super::PollStartedEvent; + use crate::event_processor::EventHandler; + use crate::handlers::tests::into_structured_event; + use crate::stacks::http_client::Client; + use crate::types::TMAddress; + use crate::PREFIX; + + #[test] + fn should_deserialize_verifier_set_poll_started_event() { + let event: Result = into_structured_event( + verifier_set_poll_started_event(participants(5, None), 100), + &TMAddress::random(PREFIX), + ) + .try_into(); + + assert!(event.is_ok()); + + let event = event.unwrap(); + + assert!(event.poll_id == 100u64.into()); + assert!( + event.source_gateway_address + == "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway" + ); + + let verifier_set = event.verifier_set; + + assert!( + verifier_set.tx_id + == "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" + .parse() + .unwrap() + ); + assert!(verifier_set.event_index == 1u32); + assert!(verifier_set.verifier_set.signers.len() == 3); + assert_eq!(verifier_set.verifier_set.threshold, Uint128::from(2u128)); + + let mut signers = verifier_set.verifier_set.signers.values(); + let signer1 = signers.next().unwrap(); + let signer2 = signers.next().unwrap(); + + assert_eq!( + signer1.pub_key.as_ref(), + HexBinary::from_hex( + "025e0231bfad810e5276e2cf9eb2f3f380ce0bdf6d84c3b6173499d3ddcc008856", + ) + .unwrap() + .as_ref() + ); + assert_eq!(signer1.weight, Uint128::from(1u128)); + + assert_eq!( + signer2.pub_key.as_ref(), + HexBinary::from_hex( + "036ff6f4b2bc5e08aba924bd8fd986608f3685ca651a015b3d9d6a656de14769fe", + ) + .unwrap() + .as_ref() + ); + assert_eq!(signer2.weight, Uint128::from(1u128)); + } + + #[async_test] + async fn not_poll_started_event() { + let event = into_structured_event( + cosmwasm_std::Event::new("transfer"), + &TMAddress::random(PREFIX), + ); + + let handler = super::Handler::new( + TMAddress::random(PREFIX), + TMAddress::random(PREFIX), + Client::faux(), + watch::channel(0).1, + ); + + assert_eq!(handler.handle(&event).await.unwrap(), vec![]); + } + + #[async_test] + async fn contract_is_not_voting_verifier() { + let event = into_structured_event( + verifier_set_poll_started_event(participants(5, None), 100), + &TMAddress::random(PREFIX), + ); + + let handler = super::Handler::new( + TMAddress::random(PREFIX), + TMAddress::random(PREFIX), + Client::faux(), + watch::channel(0).1, + ); + + assert_eq!(handler.handle(&event).await.unwrap(), vec![]); + } + + #[async_test] + async fn verifier_is_not_a_participant() { + let voting_verifier = TMAddress::random(PREFIX); + let event = into_structured_event( + verifier_set_poll_started_event(participants(5, None), 100), + &voting_verifier, + ); + + let handler = super::Handler::new( + TMAddress::random(PREFIX), + voting_verifier, + Client::faux(), + watch::channel(0).1, + ); + + assert_eq!(handler.handle(&event).await.unwrap(), vec![]); + } + + #[async_test] + async fn should_skip_expired_poll() { + let mut client = Client::faux(); + faux::when!(client.get_valid_transaction).then(|_| None); + + let voting_verifier = TMAddress::random(PREFIX); + let verifier = TMAddress::random(PREFIX); + let expiration = 100u64; + let event = into_structured_event( + verifier_set_poll_started_event( + vec![verifier.clone()].into_iter().collect(), + expiration, + ), + &voting_verifier, + ); + + let (tx, rx) = watch::channel(expiration - 1); + + let handler = super::Handler::new(verifier, voting_verifier, client, rx); + + // poll is not expired yet, should hit proxy + let actual = handler.handle(&event).await.unwrap(); + assert_eq!(actual.len(), 1); + + let _ = tx.send(expiration + 1); + + // poll is expired + assert_eq!(handler.handle(&event).await.unwrap(), vec![]); + } + + #[async_test] + async fn should_vote_correctly() { + let mut client = Client::faux(); + faux::when!(client.get_valid_transaction).then(|_| None); + + let voting_verifier = TMAddress::random(PREFIX); + let worker = TMAddress::random(PREFIX); + + let event = into_structured_event( + verifier_set_poll_started_event(participants(5, Some(worker.clone())), 100), + &voting_verifier, + ); + + let handler = super::Handler::new(worker, voting_verifier, client, watch::channel(0).1); + + let actual = handler.handle(&event).await.unwrap(); + assert_eq!(actual.len(), 1); + assert!(MsgExecuteContract::from_any(actual.first().unwrap()).is_ok()); + } + + fn verifier_set_poll_started_event( + participants: Vec, + expires_at: u64, + ) -> PollStarted { + PollStarted::VerifierSet { + metadata: PollMetadata { + poll_id: "100".parse().unwrap(), + source_chain: "multiversx".parse().unwrap(), + source_gateway_address: "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway" + .parse() + .unwrap(), + confirmation_height: 15, + expires_at, + participants: participants + .into_iter() + .map(|addr| cosmwasm_std::Addr::unchecked(addr.to_string())) + .collect(), + }, + verifier_set: VerifierSetConfirmation { + tx_id: "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" + .parse() + .unwrap(), + event_index: 1, + verifier_set: build_verifier_set(KeyType::Ecdsa, &ecdsa_test_data::signers()), + }, + } + } + + fn participants(n: u8, worker: Option) -> Vec { + (0..n) + .map(|_| TMAddress::random(PREFIX)) + .chain(worker) + .collect() + } +} diff --git a/ampd/src/stacks/verifier.rs b/ampd/src/stacks/verifier.rs index 1d2cf2c16..aae7fa560 100644 --- a/ampd/src/stacks/verifier.rs +++ b/ampd/src/stacks/verifier.rs @@ -144,9 +144,11 @@ impl VerifierSetConfirmation { let weighted_signers = WeightedSigners::try_from(&self.verifier_set)?; + let hash = weighted_signers.hash(); + if !data .get("signers-hash")? - .eq(&Value::buff_from(weighted_signers.hash()?.to_vec())?) + .eq(&Value::buff_from(hash?.to_vec())?) { return Ok(false); } @@ -208,12 +210,18 @@ pub fn verify_verifier_set( #[cfg(test)] mod tests { use axelar_wasm_std::voting::Vote; + use clarity::vm::types::TupleData; + use clarity::vm::{ClarityName, Value}; + use cosmwasm_std::{HexBinary, Uint128}; + use multisig::key::KeyType; + use multisig::test::common::{build_verifier_set, ecdsa_test_data}; use crate::handlers::stacks_verify_msg::Message; + use crate::handlers::stacks_verify_verifier_set::VerifierSetConfirmation; use crate::stacks::http_client::{ ContractLog, ContractLogValue, Transaction, TransactionEvents, }; - use crate::stacks::verifier::verify_message; + use crate::stacks::verifier::{verify_message, verify_verifier_set, SIGNERS_ROTATED_TYPE}; // test verify message #[test] @@ -334,6 +342,114 @@ mod tests { ); } + // test verify worker set + #[test] + fn should_not_verify_verifier_set_if_tx_id_does_not_match() { + let (gateway_address, tx, mut verifier_set) = get_matching_verifier_set_and_tx(); + + verifier_set.tx_id = "ffaf64de66510723f2efbacd7ead3c4f8c856aed1afc2cb30254552aeda47313" + .parse() + .unwrap(); + assert_eq!( + verify_verifier_set(&gateway_address, &tx, verifier_set), + Vote::NotFound + ); + } + + #[test] + fn should_not_verify_verifier_set_if_no_log_for_event_index() { + let (gateway_address, tx, mut verifier_set) = get_matching_verifier_set_and_tx(); + + verifier_set.event_index = 2; + assert_eq!( + verify_verifier_set(&gateway_address, &tx, verifier_set), + Vote::NotFound + ); + } + + #[test] + fn should_not_verify_verifier_set_if_event_index_does_not_match() { + let (gateway_address, tx, mut verifier_set) = get_matching_verifier_set_and_tx(); + + verifier_set.event_index = 0; + assert_eq!( + verify_verifier_set(&gateway_address, &tx, verifier_set), + Vote::NotFound + ); + } + + #[test] + fn should_not_verify_verifier_set_if_not_from_gateway() { + let (gateway_address, mut tx, verifier_set) = get_matching_verifier_set_and_tx(); + + let transaction_events = tx.events.get_mut(1).unwrap(); + let contract_call = transaction_events.contract_log.as_mut().unwrap(); + + contract_call.contract_id = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM".to_string(); + + assert_eq!( + verify_verifier_set(&gateway_address, &tx, verifier_set), + Vote::NotFound + ); + } + + #[test] + fn should_not_verify_verifier_set_invalid_topic() { + let (gateway_address, mut tx, verifier_set) = get_matching_verifier_set_and_tx(); + + let transaction_events = tx.events.get_mut(1).unwrap(); + let contract_call = transaction_events.contract_log.as_mut().unwrap(); + + contract_call.topic = "other".to_string(); + + assert_eq!( + verify_verifier_set(&gateway_address, &tx, verifier_set), + Vote::NotFound + ); + } + + #[test] + fn should_not_verify_verifier_set_invalid_type() { + let (gateway_address, mut tx, verifier_set) = get_matching_verifier_set_and_tx(); + + let transaction_events = tx.events.get_mut(1).unwrap(); + let signers_rotated = transaction_events.contract_log.as_mut().unwrap(); + + // Remove 'rotated' as hex from `signers-rotated` data + signers_rotated.value.hex = signers_rotated + .value + .hex + .strip_suffix("726f7461746564") + .unwrap() + .to_string(); + + assert_eq!( + verify_verifier_set(&gateway_address, &tx, verifier_set), + Vote::NotFound + ); + } + + #[test] + fn should_not_verify_worker_set_if_verifier_set_does_not_match() { + let (gateway_address, tx, mut verifier_set) = get_matching_verifier_set_and_tx(); + + verifier_set.verifier_set.threshold = Uint128::from(10u128); + assert_eq!( + verify_verifier_set(&gateway_address, &tx, verifier_set), + Vote::NotFound + ); + } + + #[test] + fn should_verify_verifier_set() { + let (gateway_address, tx, verifier_set) = get_matching_verifier_set_and_tx(); + + assert_eq!( + verify_verifier_set(&gateway_address, &tx, verifier_set), + Vote::SucceededOnChain + ); + } + fn get_matching_msg_and_tx() -> (String, Transaction, Message) { let gateway_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"; let tx_id = "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" @@ -379,4 +495,70 @@ mod tests { (gateway_address.to_string(), transaction, msg) } + + fn get_matching_verifier_set_and_tx() -> (String, Transaction, VerifierSetConfirmation) { + let gateway_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"; + let tx_id = "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" + .parse() + .unwrap(); + + let mut verifier_set_confirmation = VerifierSetConfirmation { + tx_id, + event_index: 1, + verifier_set: build_verifier_set(KeyType::Ecdsa, &ecdsa_test_data::signers()), + }; + verifier_set_confirmation.verifier_set.created_at = 5; + + let wrong_event = TransactionEvents { + event_index: 0, + tx_id: tx_id.to_string(), + contract_log: None, + }; + + let signers_hash = + HexBinary::from_hex("6925aafa48d1c99f0fd9bdd98b00fc319462a3ecbf2bbb8379c975a26a0c0c46") + .unwrap(); + + let value = Value::from( + TupleData::from_data(vec![ + ( + ClarityName::from("signers-hash"), + Value::buff_from(signers_hash.to_vec()).unwrap(), + ), + ( + ClarityName::from("type"), + Value::string_ascii_from_bytes(SIGNERS_ROTATED_TYPE.as_bytes().to_vec()) + .unwrap(), + ), + ]) + .unwrap(), + ); + + // TODO: Add proper hex data for event + let event = TransactionEvents { + event_index: 1, + tx_id: tx_id.to_string(), + contract_log: Some(ContractLog { + contract_id: gateway_address.to_string(), + topic: "print".to_string(), + value: ContractLogValue { + hex: format!("0x{}", value.serialize_to_hex().unwrap()), + }, + }), + }; + + let transaction = Transaction { + tx_id, + nonce: 1, + sender_address: "whatever".to_string(), + tx_status: "success".to_string(), + events: vec![wrong_event, event], + }; + + ( + gateway_address.to_string(), + transaction, + verifier_set_confirmation, + ) + } } From 62637550c86e216c0fd818dfa9e76cfcff1f3f85 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Fri, 18 Oct 2024 17:13:04 +0300 Subject: [PATCH 09/20] Start working on stacks its verifier. --- ampd/src/config.rs | 10 +- ampd/src/handlers/config.rs | 3 + ampd/src/handlers/stacks_verify_msg.rs | 33 ++++- ampd/src/lib.rs | 2 + ampd/src/stacks/its_verifier.rs | 187 +++++++++++++++++++++++++ ampd/src/stacks/mod.rs | 1 + ampd/src/stacks/verifier.rs | 126 +++++++++++++---- ampd/src/tests/config_template.toml | 1 + 8 files changed, 331 insertions(+), 32 deletions(-) create mode 100644 ampd/src/stacks/its_verifier.rs diff --git a/ampd/src/config.rs b/ampd/src/config.rs index 5ef475b03..62f90784d 100644 --- a/ampd/src/config.rs +++ b/ampd/src/config.rs @@ -137,6 +137,12 @@ mod tests { type = 'StacksMsgVerifier' cosmwasm_contract = '{}' http_url = 'http://localhost:8000' + its_address = 'its_address' + + [[handlers]] + type = 'StacksVerifierSetVerifier' + cosmwasm_contract = '{}' + http_url = 'http://localhost:8000' ", TMAddress::random(PREFIX), TMAddress::random(PREFIX), @@ -149,10 +155,11 @@ mod tests { TMAddress::random(PREFIX), TMAddress::random(PREFIX), TMAddress::random(PREFIX), + TMAddress::random(PREFIX), ); let cfg: Config = toml::from_str(config_str.as_str()).unwrap(); - assert_eq!(cfg.handlers.len(), 11); + assert_eq!(cfg.handlers.len(), 12); } #[test] @@ -361,6 +368,7 @@ mod tests { AccountId::new("axelar", &[0u8; 32]).unwrap(), ), http_url: Url::from_str("http://127.0.0.1").unwrap(), + its_address: "its_address".to_string(), }, HandlerConfig::StacksVerifierSetVerifier { cosmwasm_contract: TMAddress::from( diff --git a/ampd/src/handlers/config.rs b/ampd/src/handlers/config.rs index 3c14888eb..1d88c7196 100644 --- a/ampd/src/handlers/config.rs +++ b/ampd/src/handlers/config.rs @@ -66,6 +66,7 @@ pub enum Config { StacksMsgVerifier { cosmwasm_contract: TMAddress, http_url: Url, + its_address: String, }, StacksVerifierSetVerifier { cosmwasm_contract: TMAddress, @@ -328,10 +329,12 @@ mod tests { Config::StacksMsgVerifier { cosmwasm_contract: TMAddress::random(PREFIX), http_url: "http://localhost:8080/".parse().unwrap(), + its_address: "its_address".to_string(), }, Config::StacksMsgVerifier { cosmwasm_contract: TMAddress::random(PREFIX), http_url: "http://localhost:8080/".parse().unwrap(), + its_address: "its_address".to_string(), }, ]; diff --git a/ampd/src/handlers/stacks_verify_msg.rs b/ampd/src/handlers/stacks_verify_msg.rs index c71a1c7f0..07d5f860f 100644 --- a/ampd/src/handlers/stacks_verify_msg.rs +++ b/ampd/src/handlers/stacks_verify_msg.rs @@ -10,6 +10,7 @@ use error_stack::ResultExt; use events::Error::EventTypeMismatch; use events::Event; use events_derive::try_from; +use router_api::ChainName; use serde::Deserialize; use tokio::sync::watch::Receiver; use tracing::info; @@ -37,6 +38,7 @@ pub struct Message { #[try_from("wasm-messages_poll_started")] struct PollStartedEvent { poll_id: PollId, + source_chain: ChainName, source_gateway_address: String, messages: Vec, participants: Vec, @@ -48,6 +50,7 @@ pub struct Handler { voting_verifier_contract: TMAddress, http_client: Client, latest_block_height: Receiver, + its_address: String, } impl Handler { @@ -56,12 +59,14 @@ impl Handler { voting_verifier_contract: TMAddress, http_client: Client, latest_block_height: Receiver, + its_address: String, ) -> Self { Self { verifier, voting_verifier_contract, http_client, latest_block_height, + its_address, } } @@ -87,6 +92,7 @@ impl EventHandler for Handler { let PollStartedEvent { poll_id, + source_chain, source_gateway_address, messages, participants, @@ -119,7 +125,13 @@ impl EventHandler for Handler { transactions .get(&msg.tx_id) .map_or(Vote::NotFound, |transaction| { - verify_message(&source_gateway_address, transaction, msg) + verify_message( + &source_chain, + &source_gateway_address, + &self.its_address, + transaction, + msg, + ) }) }) .collect(); @@ -195,6 +207,7 @@ mod tests { TMAddress::random(PREFIX), Client::faux(), watch::channel(0).1, + "its_address".to_string(), ); assert!(handler.handle(&event).await.is_ok()); @@ -213,6 +226,7 @@ mod tests { TMAddress::random(PREFIX), Client::faux(), watch::channel(0).1, + "its_address".to_string(), ); assert!(handler.handle(&event).await.is_ok()); @@ -230,6 +244,7 @@ mod tests { voting_verifier, Client::faux(), watch::channel(0).1, + "its_address".to_string(), ); assert!(handler.handle(&event).await.is_ok()); @@ -247,7 +262,13 @@ mod tests { &voting_verifier, ); - let handler = super::Handler::new(worker, voting_verifier, client, watch::channel(0).1); + let handler = super::Handler::new( + worker, + voting_verifier, + client, + watch::channel(0).1, + "its_address".to_string(), + ); let actual = handler.handle(&event).await.unwrap(); assert_eq!(actual.len(), 1); @@ -269,7 +290,13 @@ mod tests { let (tx, rx) = watch::channel(expiration - 1); - let handler = super::Handler::new(worker, voting_verifier, client, rx); + let handler = super::Handler::new( + worker, + voting_verifier, + client, + rx, + "its_address".to_string(), + ); // poll is not expired yet, should hit proxy let actual = handler.handle(&event).await.unwrap(); diff --git a/ampd/src/lib.rs b/ampd/src/lib.rs index 83803849b..fd3dca156 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -393,6 +393,7 @@ where handlers::config::Config::StacksMsgVerifier { cosmwasm_contract, http_url, + its_address, } => self.create_handler_task( "stacks-msg-verifier", handlers::stacks_verify_msg::Handler::new( @@ -400,6 +401,7 @@ where cosmwasm_contract, Client::new_http(http_url.to_string().trim_end_matches('/').into()), self.block_height_monitor.latest_block_height(), + its_address, ), event_processor_config.clone(), ), diff --git a/ampd/src/stacks/its_verifier.rs b/ampd/src/stacks/its_verifier.rs new file mode 100644 index 000000000..b6e15c626 --- /dev/null +++ b/ampd/src/stacks/its_verifier.rs @@ -0,0 +1,187 @@ +use clarity::vm::types::{ + BufferLength, PrincipalData, SequenceSubtype, StringSubtype, TupleTypeSignature, TypeSignature, +}; +use clarity::vm::{ClarityName, Value}; + +use crate::handlers::stacks_verify_msg::Message; +use crate::stacks::error::Error; +use crate::stacks::http_client::TransactionEvents; +use crate::stacks::verifier::{CONTRACT_CALL_TYPE, PRINT_TOPIC}; + +impl Message { + pub fn eq_its_hub_event( + &self, + event: &TransactionEvents, + ) -> Result> { + let contract_log = event.contract_log.as_ref().ok_or(Error::PropertyEmpty)?; + + if contract_log.topic != PRINT_TOPIC { + return Ok(false); + } + + let tuple_type_signature = TupleTypeSignature::try_from(vec![ + ( + ClarityName::from("type"), + TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( + BufferLength::try_from(13u32)?, + ))), + ), + (ClarityName::from("sender"), TypeSignature::PrincipalType), + ( + ClarityName::from("destination-chain"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 18u32, + )?)), + ), + ( + ClarityName::from("destination-contract-address"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 96u32, + )?)), + ), + ( + ClarityName::from("payload-hash"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 32u32, + )?)), + ), + ])?; + + let hex = contract_log + .value + .hex + .strip_prefix("0x") + .ok_or(Error::PropertyEmpty)?; + + let value = + Value::try_deserialize_hex(hex, &TypeSignature::TupleType(tuple_type_signature), true)?; + + if let Value::Tuple(data) = value { + if !data.get("type")?.eq(&Value::string_ascii_from_bytes( + CONTRACT_CALL_TYPE.as_bytes().to_vec(), + )?) { + return Ok(false); + } + + if !data.get("sender")?.eq(&Value::from(PrincipalData::parse( + self.source_address.as_str(), + )?)) { + return Ok(false); + } + + if !data.get("destination-chain")?.eq(&Value::buff_from( + self.destination_chain.as_ref().as_bytes().to_vec(), + )?) { + return Ok(false); + } + + if !data + .get("destination-contract-address")? + .eq(&Value::buff_from( + self.destination_address.as_bytes().to_vec(), + )?) + { + return Ok(false); + } + + if !data + .get("payload-hash")? + .eq(&Value::buff_from(self.payload_hash.as_bytes().to_vec())?) + { + return Ok(false); + } + + return Ok(true); + } + + Ok(false) + } + + pub fn eq_its_verify_event( + &self, + event: &TransactionEvents, + ) -> Result> { + let contract_log = event.contract_log.as_ref().ok_or(Error::PropertyEmpty)?; + + if contract_log.topic != PRINT_TOPIC { + return Ok(false); + } + + let tuple_type_signature = TupleTypeSignature::try_from(vec![ + ( + ClarityName::from("type"), + TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( + BufferLength::try_from(13u32)?, + ))), + ), + (ClarityName::from("sender"), TypeSignature::PrincipalType), + ( + ClarityName::from("destination-chain"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 18u32, + )?)), + ), + ( + ClarityName::from("destination-contract-address"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 96u32, + )?)), + ), + ( + ClarityName::from("payload-hash"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 32u32, + )?)), + ), + ])?; + + let hex = contract_log + .value + .hex + .strip_prefix("0x") + .ok_or(Error::PropertyEmpty)?; + + let value = + Value::try_deserialize_hex(hex, &TypeSignature::TupleType(tuple_type_signature), true)?; + + if let Value::Tuple(data) = value { + if !data.get("type")?.eq(&Value::string_ascii_from_bytes( + CONTRACT_CALL_TYPE.as_bytes().to_vec(), + )?) { + return Ok(false); + } + + if !data.get("sender")?.eq(&Value::from(PrincipalData::parse( + self.source_address.as_str(), + )?)) { + return Ok(false); + } + + if !data.get("destination-chain")?.eq(&Value::buff_from( + self.destination_chain.as_ref().as_bytes().to_vec(), + )?) { + return Ok(false); + } + + if !data + .get("destination-contract-address")? + .eq(&Value::buff_from( + self.destination_address.as_bytes().to_vec(), + )?) + { + return Ok(false); + } + + if !data + .get("payload-hash")? + .eq(&Value::buff_from(self.payload_hash.as_bytes().to_vec())?) + { + return Ok(false); + } + + return Ok(true); + } + + Ok(false) + } +} diff --git a/ampd/src/stacks/mod.rs b/ampd/src/stacks/mod.rs index 8afe8bfaf..8a1029ac5 100644 --- a/ampd/src/stacks/mod.rs +++ b/ampd/src/stacks/mod.rs @@ -15,6 +15,7 @@ use crate::stacks::error::Error; mod error; pub(crate) mod http_client; +mod its_verifier; pub(crate) mod verifier; pub struct WeightedSigner { diff --git a/ampd/src/stacks/verifier.rs b/ampd/src/stacks/verifier.rs index aae7fa560..1985c5a06 100644 --- a/ampd/src/stacks/verifier.rs +++ b/ampd/src/stacks/verifier.rs @@ -4,6 +4,7 @@ use clarity::vm::types::{ Value, }; use clarity::vm::ClarityName; +use router_api::ChainName; use crate::handlers::stacks_verify_msg::Message; use crate::handlers::stacks_verify_verifier_set::VerifierSetConfirmation; @@ -11,9 +12,9 @@ use crate::stacks::error::Error; use crate::stacks::http_client::{Transaction, TransactionEvents}; use crate::stacks::WeightedSigners; -const PRINT_TOPIC: &str = "print"; +pub const PRINT_TOPIC: &str = "print"; -const CONTRACT_CALL_TYPE: &str = "contract-call"; +pub const CONTRACT_CALL_TYPE: &str = "contract-call"; const SIGNERS_ROTATED_TYPE: &str = "signers-rotated"; impl Message { @@ -178,7 +179,9 @@ fn find_event<'a>( } pub fn verify_message( + source_chain: &ChainName, gateway_address: &String, + its_address: &String, transaction: &Transaction, message: &Message, ) -> Vote { @@ -187,7 +190,36 @@ pub fn verify_message( } match find_event(transaction, gateway_address, message.event_index) { - Some(event) if message.eq_event(event).unwrap_or(false) => Vote::SucceededOnChain, + Some(event) => { + // In case message is from its + if &message.source_address == its_address { + // In case messages is from Stacks -> Stacks and from ITS -> ITS, use custom logic + if &message.destination_chain == source_chain + && &message.destination_address == its_address + { + if message.eq_its_verify_event(event).unwrap_or(false) { + return Vote::SucceededOnChain; + } + + return Vote::NotFound; + } + + // TODO: Should we check if the message is towards axelar and ITS Hub contract here? + // In other case, abi encode payload + + if message.eq_its_hub_event(event).unwrap_or(false) { + return Vote::SucceededOnChain; + } + + return Vote::NotFound; + } + + if message.eq_event(event).unwrap_or(false) { + return Vote::SucceededOnChain; + } + + Vote::NotFound + } _ => Vote::NotFound, } } @@ -215,6 +247,7 @@ mod tests { use cosmwasm_std::{HexBinary, Uint128}; use multisig::key::KeyType; use multisig::test::common::{build_verifier_set, ecdsa_test_data}; + use router_api::ChainName; use crate::handlers::stacks_verify_msg::Message; use crate::handlers::stacks_verify_verifier_set::VerifierSetConfirmation; @@ -226,59 +259,74 @@ mod tests { // test verify message #[test] fn should_not_verify_tx_id_does_not_match() { - let (gateway_address, tx, mut msg) = get_matching_msg_and_tx(); + let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_msg_and_tx(); msg.tx_id = "ffaf64de66510723f2efbacd7ead3c4f8c856aed1afc2cb30254552aeda47313" .parse() .unwrap(); - assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + assert_eq!( + verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + Vote::NotFound + ); } #[test] fn should_not_verify_no_log_for_event_index() { - let (gateway_address, tx, mut msg) = get_matching_msg_and_tx(); + let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_msg_and_tx(); msg.event_index = 2; - assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + assert_eq!( + verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + Vote::NotFound + ); } #[test] fn should_not_verify_event_index_does_not_match() { - let (gateway_address, tx, mut msg) = get_matching_msg_and_tx(); + let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_msg_and_tx(); msg.event_index = 0; - assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + assert_eq!( + verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + Vote::NotFound + ); } #[test] fn should_not_verify_not_gateway() { - let (gateway_address, mut tx, msg) = get_matching_msg_and_tx(); + let (source_chain, gateway_address, its_address, mut tx, msg) = get_matching_msg_and_tx(); let transaction_events = tx.events.get_mut(1).unwrap(); let contract_call = transaction_events.contract_log.as_mut().unwrap(); contract_call.contract_id = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM".to_string(); - assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + assert_eq!( + verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + Vote::NotFound + ); } #[test] fn should_not_verify_invalid_topic() { - let (gateway_address, mut tx, msg) = get_matching_msg_and_tx(); + let (source_chain, gateway_address, its_address, mut tx, msg) = get_matching_msg_and_tx(); let transaction_events = tx.events.get_mut(1).unwrap(); let contract_call = transaction_events.contract_log.as_mut().unwrap(); contract_call.topic = "other".to_string(); - assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + assert_eq!( + verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + Vote::NotFound + ); } #[test] fn should_not_verify_invalid_type() { - let (gateway_address, mut tx, msg) = get_matching_msg_and_tx(); + let (source_chain, gateway_address, its_address, mut tx, msg) = get_matching_msg_and_tx(); let transaction_events = tx.events.get_mut(1).unwrap(); let contract_call = transaction_events.contract_log.as_mut().unwrap(); @@ -291,53 +339,68 @@ mod tests { .unwrap() .to_string(); - assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + assert_eq!( + verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + Vote::NotFound + ); } #[test] fn should_not_verify_invalid_sender() { - let (gateway_address, tx, mut msg) = get_matching_msg_and_tx(); + let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_msg_and_tx(); msg.source_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway".to_string(); - assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + assert_eq!( + verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + Vote::NotFound + ); } #[test] fn should_not_verify_invalid_destination_chain() { - let (gateway_address, tx, mut msg) = get_matching_msg_and_tx(); + let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_msg_and_tx(); msg.destination_chain = "other".parse().unwrap(); - assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + assert_eq!( + verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + Vote::NotFound + ); } #[test] fn should_not_verify_invalid_destination_address() { - let (gateway_address, tx, mut msg) = get_matching_msg_and_tx(); + let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_msg_and_tx(); msg.destination_address = "other".parse().unwrap(); - assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + assert_eq!( + verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + Vote::NotFound + ); } #[test] fn should_not_verify_invalid_payload_hash() { - let (gateway_address, tx, mut msg) = get_matching_msg_and_tx(); + let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_msg_and_tx(); msg.payload_hash = "0xaa38573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f4aa" .parse() .unwrap(); - assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + assert_eq!( + verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + Vote::NotFound + ); } #[test] fn should_verify_msg() { - let (gateway_address, tx, msg) = get_matching_msg_and_tx(); + let (source_chain, gateway_address, its_address, tx, msg) = get_matching_msg_and_tx(); assert_eq!( - verify_message(&gateway_address, &tx, &msg), + verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), Vote::SucceededOnChain ); } @@ -450,8 +513,10 @@ mod tests { ); } - fn get_matching_msg_and_tx() -> (String, Transaction, Message) { + fn get_matching_msg_and_tx() -> (ChainName, String, String, Transaction, Message) { + let source_chain = "stacks"; let gateway_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"; + let its_address = "ST2TFVBMRPS5SSNP98DQKQ5JNB2B6NZM91C4K3P7B.its"; let tx_id = "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" .parse() .unwrap(); @@ -493,7 +558,13 @@ mod tests { events: vec![wrong_event, event], }; - (gateway_address.to_string(), transaction, msg) + ( + source_chain.parse().unwrap(), + gateway_address.to_string(), + its_address.to_string(), + transaction, + msg, + ) } fn get_matching_verifier_set_and_tx() -> (String, Transaction, VerifierSetConfirmation) { @@ -534,7 +605,6 @@ mod tests { .unwrap(), ); - // TODO: Add proper hex data for event let event = TransactionEvents { event_index: 1, tx_id: tx_id.to_string(), diff --git a/ampd/src/tests/config_template.toml b/ampd/src/tests/config_template.toml index 1a2e414ca..bdb13b33d 100644 --- a/ampd/src/tests/config_template.toml +++ b/ampd/src/tests/config_template.toml @@ -86,6 +86,7 @@ http_url = 'http://127.0.0.1/' type = 'StacksMsgVerifier' cosmwasm_contract = 'axelar1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqecnww6' http_url = 'http://127.0.0.1/' +its_address = 'its_address' [[handlers]] type = 'StacksVerifierSetVerifier' From 78e18a0d58e6521087cdd6680a3733fc8e84fd63 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Fri, 18 Oct 2024 17:44:13 +0300 Subject: [PATCH 10/20] Add basic decode for its hub data. --- ampd/src/stacks/its_verifier.rs | 76 ++++++++++++++------------------- ampd/src/stacks/verifier.rs | 28 ++++++------ 2 files changed, 44 insertions(+), 60 deletions(-) diff --git a/ampd/src/stacks/its_verifier.rs b/ampd/src/stacks/its_verifier.rs index b6e15c626..7dfc4eb1f 100644 --- a/ampd/src/stacks/its_verifier.rs +++ b/ampd/src/stacks/its_verifier.rs @@ -8,6 +8,11 @@ use crate::stacks::error::Error; use crate::stacks::http_client::TransactionEvents; use crate::stacks::verifier::{CONTRACT_CALL_TYPE, PRINT_TOPIC}; +const MESSAGE_TYPE_INTERCHAIN_TRANSFER: u128 = 0; +const MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN: u128 = 1; +const MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER: u128 = 2; +const MESSAGE_TYPE_SEND_TO_HUB: u128 = 3; + impl Message { pub fn eq_its_hub_event( &self, @@ -20,13 +25,7 @@ impl Message { } let tuple_type_signature = TupleTypeSignature::try_from(vec![ - ( - ClarityName::from("type"), - TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( - BufferLength::try_from(13u32)?, - ))), - ), - (ClarityName::from("sender"), TypeSignature::PrincipalType), + (ClarityName::from("type"), TypeSignature::UIntType), ( ClarityName::from("destination-chain"), TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( @@ -34,15 +33,9 @@ impl Message { )?)), ), ( - ClarityName::from("destination-contract-address"), - TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( - 96u32, - )?)), - ), - ( - ClarityName::from("payload-hash"), + ClarityName::from("payload"), TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( - 32u32, + 10240u32, )?)), ), ])?; @@ -57,41 +50,34 @@ impl Message { Value::try_deserialize_hex(hex, &TypeSignature::TupleType(tuple_type_signature), true)?; if let Value::Tuple(data) = value { - if !data.get("type")?.eq(&Value::string_ascii_from_bytes( - CONTRACT_CALL_TYPE.as_bytes().to_vec(), - )?) { - return Ok(false); - } - - if !data.get("sender")?.eq(&Value::from(PrincipalData::parse( - self.source_address.as_str(), - )?)) { - return Ok(false); - } - - if !data.get("destination-chain")?.eq(&Value::buff_from( - self.destination_chain.as_ref().as_bytes().to_vec(), - )?) { - return Ok(false); - } - - if !data - .get("destination-contract-address")? - .eq(&Value::buff_from( - self.destination_address.as_bytes().to_vec(), - )?) - { + // All messages should go through ITS hub + if !data.get("type")?.eq(&Value::UInt(MESSAGE_TYPE_SEND_TO_HUB)) { return Ok(false); } - if !data - .get("payload-hash")? - .eq(&Value::buff_from(self.payload_hash.as_bytes().to_vec())?) - { - return Ok(false); + let subtuple_type_signature = TupleTypeSignature::try_from(vec![( + ClarityName::from("type"), + TypeSignature::UIntType, + )])?; + + let original_value = Value::try_deserialize_hex( + hex, + &TypeSignature::TupleType(subtuple_type_signature), + true, + )?; + + // Unwrapp its payload + if let Value::Tuple(new_data) = original_value { + if new_data.get("type")?.eq(&Value::UInt(MESSAGE_TYPE_INTERCHAIN_TRANSFER)) { + // TODO: Decode and ABI encode this payload + } else if new_data.get("type")?.eq(&Value::UInt(MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN)) { + // TODO: Decode and ABI encode this payload + } else if new_data.get("type")?.eq(&Value::UInt(MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER)) { + // TODO: Decode and ABI encode this payload + } } - return Ok(true); + return Ok(false); } Ok(false) diff --git a/ampd/src/stacks/verifier.rs b/ampd/src/stacks/verifier.rs index 1985c5a06..d04e16be7 100644 --- a/ampd/src/stacks/verifier.rs +++ b/ampd/src/stacks/verifier.rs @@ -191,30 +191,28 @@ pub fn verify_message( match find_event(transaction, gateway_address, message.event_index) { Some(event) => { - // In case message is from its - if &message.source_address == its_address { - // In case messages is from Stacks -> Stacks and from ITS -> ITS, use custom logic - if &message.destination_chain == source_chain - && &message.destination_address == its_address - { - if message.eq_its_verify_event(event).unwrap_or(false) { - return Vote::SucceededOnChain; - } - - return Vote::NotFound; + // In case message is not from ITS + if &message.source_address != its_address { + if message.eq_event(event).unwrap_or(false) { + return Vote::SucceededOnChain; } - // TODO: Should we check if the message is towards axelar and ITS Hub contract here? - // In other case, abi encode payload + return Vote::NotFound; + } - if message.eq_its_hub_event(event).unwrap_or(false) { + // In case messages is from Stacks -> Stacks and from ITS -> ITS, use custom logic + if &message.destination_chain == source_chain + && &message.destination_address == its_address + { + if message.eq_its_verify_event(event).unwrap_or(false) { return Vote::SucceededOnChain; } return Vote::NotFound; } - if message.eq_event(event).unwrap_or(false) { + // In other case, abi encode payload coming from Stacks ITS + if message.eq_its_hub_event(event).unwrap_or(false) { return Vote::SucceededOnChain; } From 03e3ed35bc3dab19a58ec24c8bc329e0821aa43c Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Mon, 21 Oct 2024 14:01:26 +0300 Subject: [PATCH 11/20] Implement conversion of its hub interchain transfer payload for stacks. --- ampd/src/stacks/error.rs | 2 + ampd/src/stacks/its_verifier.rs | 431 +++++++++++++++++++++++++++----- ampd/src/stacks/verifier.rs | 17 +- 3 files changed, 390 insertions(+), 60 deletions(-) diff --git a/ampd/src/stacks/error.rs b/ampd/src/stacks/error.rs index e142258d7..af270acab 100644 --- a/ampd/src/stacks/error.rs +++ b/ampd/src/stacks/error.rs @@ -8,4 +8,6 @@ pub enum Error { InvalidEncoding, #[error("provided key is not ecdsa")] NotEcdsaKey, + #[error("contract call is invalid")] + InvalidCall, } diff --git a/ampd/src/stacks/its_verifier.rs b/ampd/src/stacks/its_verifier.rs index 7dfc4eb1f..35cb33738 100644 --- a/ampd/src/stacks/its_verifier.rs +++ b/ampd/src/stacks/its_verifier.rs @@ -1,88 +1,229 @@ -use clarity::vm::types::{ - BufferLength, PrincipalData, SequenceSubtype, StringSubtype, TupleTypeSignature, TypeSignature, -}; -use clarity::vm::{ClarityName, Value}; - use crate::handlers::stacks_verify_msg::Message; use crate::stacks::error::Error; use crate::stacks::http_client::TransactionEvents; use crate::stacks::verifier::{CONTRACT_CALL_TYPE, PRINT_TOPIC}; +use axelar_wasm_std::hash::Hash; +use clarity::codec::StacksMessageCodec; +use clarity::vm::types::{ + BufferLength, PrincipalData, SequenceSubtype, StringSubtype, TupleData, TupleTypeSignature, + TypeSignature, +}; +use clarity::vm::{ClarityName, Value}; +use ethers_core::abi::{encode, Token}; +use sha3::{Digest, Keccak256}; const MESSAGE_TYPE_INTERCHAIN_TRANSFER: u128 = 0; const MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN: u128 = 1; const MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER: u128 = 2; const MESSAGE_TYPE_SEND_TO_HUB: u128 = 3; +fn get_payload_from_contract_call_event( + event: &TransactionEvents, +) -> Result, Box> { + let contract_log = event.contract_log.as_ref().ok_or(Error::PropertyEmpty)?; + + let contract_call_signature = TupleTypeSignature::try_from(vec![( + ClarityName::from("payload"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 10240u32, + )?)), + )])?; + + let hex = contract_log + .value + .hex + .strip_prefix("0x") + .ok_or(Error::PropertyEmpty)?; + + let contract_call_value = Value::try_deserialize_hex( + hex, + &TypeSignature::TupleType(contract_call_signature), + true, + )?; + + let payload = contract_call_value + .expect_tuple()? + .get_owned("payload")? + .expect_buff(10240)?; + + Ok(payload) +} + +fn get_its_hub_call_params( + event: &TransactionEvents, +) -> Result> { + let payload = get_payload_from_contract_call_event(event)?; + + let its_send_to_hub_signature = TupleTypeSignature::try_from(vec![ + (ClarityName::from("type"), TypeSignature::UIntType), + ( + ClarityName::from("destination-chain"), + TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( + BufferLength::try_from(18u32)?, + ))), + ), + ( + ClarityName::from("payload"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 10240u32, + )?)), + ), + ])?; + + let its_hub_value = Value::try_deserialize_bytes( + &payload, + &TypeSignature::TupleType(its_send_to_hub_signature), + true, + )? + .expect_tuple()?; + + Ok(its_hub_value) +} + +fn get_its_interchain_transfer_abi_payload( + payload: Vec, +) -> Result, Box> { + let tuple_type_signature = TupleTypeSignature::try_from(vec![ + ( + ClarityName::from("token-id"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 32u32, + )?)), + ), + ( + ClarityName::from("source-address"), + TypeSignature::PrincipalType, + ), + ( + ClarityName::from("destination-chain"), + TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( + BufferLength::try_from(18u32)?, + ))), + ), + ( + ClarityName::from("destination-address"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 100u32, + )?)), + ), + (ClarityName::from("amount"), TypeSignature::UIntType), + ( + ClarityName::from("data"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 1024u32, + )?)), + ), + ])?; + + let mut original_value = Value::try_deserialize_bytes( + &payload, + &TypeSignature::TupleType(tuple_type_signature), + true, + )? + .expect_tuple()?; + + let abi_payload = encode(&[ + Token::Uint(MESSAGE_TYPE_INTERCHAIN_TRANSFER.into()), + Token::FixedBytes( + original_value + .data_map + .remove("token-id") + .ok_or(Error::InvalidCall)? + .expect_buff(32)?, + ), + Token::Bytes( + original_value + .data_map + .remove("source-address") + .ok_or(Error::InvalidCall)? + .expect_principal()? + .serialize_to_vec(), + ), + Token::Bytes( + original_value + .data_map + .remove("destination-address") + .ok_or(Error::InvalidCall)? + .expect_buff(100)?, + ), + Token::Uint( + original_value + .data_map + .remove("amount") + .ok_or(Error::InvalidCall)? + .expect_u128()? + .into(), + ), + Token::Bytes( + original_value + .data_map + .remove("data") + .ok_or(Error::InvalidCall)? + .expect_buff(1024)?, + ), + ]); + + Ok(abi_payload) +} + impl Message { pub fn eq_its_hub_event( &self, event: &TransactionEvents, ) -> Result> { - let contract_log = event.contract_log.as_ref().ok_or(Error::PropertyEmpty)?; + let tuple_data = get_its_hub_call_params(event)?; - if contract_log.topic != PRINT_TOPIC { + // All messages should go through ITS hub + if !tuple_data + .get("type")? + .eq(&Value::UInt(MESSAGE_TYPE_SEND_TO_HUB)) + { return Ok(false); } - let tuple_type_signature = TupleTypeSignature::try_from(vec![ - (ClarityName::from("type"), TypeSignature::UIntType), - ( - ClarityName::from("destination-chain"), - TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( - 18u32, - )?)), - ), - ( - ClarityName::from("payload"), - TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( - 10240u32, - )?)), - ), - ])?; + let destination_chain = tuple_data + .get("destination-chain")? + .clone() + .expect_ascii()?; + let payload = tuple_data.get_owned("payload")?.expect_buff(10240)?; - let hex = contract_log - .value - .hex - .strip_prefix("0x") - .ok_or(Error::PropertyEmpty)?; + let subtuple_type_signature = TupleTypeSignature::try_from(vec![( + ClarityName::from("type"), + TypeSignature::UIntType, + )])?; - let value = - Value::try_deserialize_hex(hex, &TypeSignature::TupleType(tuple_type_signature), true)?; + let original_its_call = Value::try_deserialize_bytes( + &payload, + &TypeSignature::TupleType(subtuple_type_signature), + true, + )? + .expect_tuple()?; - if let Value::Tuple(data) = value { - // All messages should go through ITS hub - if !data.get("type")?.eq(&Value::UInt(MESSAGE_TYPE_SEND_TO_HUB)) { - return Ok(false); - } + let its_type = original_its_call.get_owned("type")?.expect_u128()?; - let subtuple_type_signature = TupleTypeSignature::try_from(vec![( - ClarityName::from("type"), - TypeSignature::UIntType, - )])?; - - let original_value = Value::try_deserialize_hex( - hex, - &TypeSignature::TupleType(subtuple_type_signature), - true, - )?; - - // Unwrapp its payload - if let Value::Tuple(new_data) = original_value { - if new_data.get("type")?.eq(&Value::UInt(MESSAGE_TYPE_INTERCHAIN_TRANSFER)) { - // TODO: Decode and ABI encode this payload - } else if new_data.get("type")?.eq(&Value::UInt(MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN)) { - // TODO: Decode and ABI encode this payload - } else if new_data.get("type")?.eq(&Value::UInt(MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER)) { - // TODO: Decode and ABI encode this payload - } + let abi_payload = match its_type { + MESSAGE_TYPE_INTERCHAIN_TRANSFER => get_its_interchain_transfer_abi_payload(payload), + // TODO: Handle other cases + // MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN => {} + // MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER => {} + _ => { + return Err(Error::InvalidCall.into()); } + }?; - return Ok(false); - } + // Convert to ITS payload and use its hash to verify the message + let abi_payload = encode(&[ + Token::Uint(MESSAGE_TYPE_SEND_TO_HUB.into()), + Token::String(destination_chain), + Token::Bytes(abi_payload), + ]); - Ok(false) + let payload_hash: Hash = Keccak256::digest(abi_payload).into(); + + self.eq_event(event, Some(payload_hash.into())) } + // TODO: pub fn eq_its_verify_event( &self, event: &TransactionEvents, @@ -171,3 +312,179 @@ impl Message { Ok(false) } } + +#[cfg(test)] +mod tests { + use axelar_wasm_std::voting::Vote; + use router_api::ChainName; + + use crate::handlers::stacks_verify_msg::Message; + use crate::stacks::http_client::{ + ContractLog, ContractLogValue, Transaction, TransactionEvents, + }; + use crate::stacks::verifier::verify_message; + + // test verify message its hub + #[test] + fn should_not_verify_its_hub_invalid_payload_hash() { + let (source_chain, gateway_address, its_address, tx, mut msg) = + get_matching_its_hub_msg_and_tx(); + + msg.payload_hash = "0xaa38573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f4aa" + .parse() + .unwrap(); + + assert_eq!( + verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + Vote::NotFound + ); + } + + // // TODO: + // #[test] + // fn should_verify_msg_its_hub_interchain_transfer() { + // let (source_chain, gateway_address, its_address, tx, msg) = + // get_matching_its_hub_msg_and_tx(); + // + // assert_eq!( + // verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + // Vote::SucceededOnChain + // ); + // } + + // test verify message its + #[test] + fn should_not_verify_its_invalid_payload_hash() { + let (source_chain, gateway_address, its_address, tx, mut msg) = + get_matching_its_verify_msg_and_tx(); + + msg.payload_hash = "0xaa38573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f4aa" + .parse() + .unwrap(); + + assert_eq!( + verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + Vote::NotFound + ); + } + + // TODO: + // #[test] + // fn should_verify_msg_its_interchain_transfer() { + // let (source_chain, gateway_address, its_address, tx, msg) = + // get_matching_its_verify_msg_and_tx(); + // + // assert_eq!( + // verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + // Vote::SucceededOnChain + // ); + // } + + fn get_matching_its_hub_msg_and_tx() -> (ChainName, String, String, Transaction, Message) { + let source_chain = "stacks"; + let gateway_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"; + let its_address = "ST2TFVBMRPS5SSNP98DQKQ5JNB2B6NZM91C4K3P7B.its"; + let tx_id = "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" + .parse() + .unwrap(); + + let msg = Message { + tx_id, + event_index: 1, + source_address: its_address.to_string(), + destination_chain: "axelar".parse().unwrap(), + destination_address: "axelartest".to_string(), + payload_hash: "0x0338573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f46d" + .parse() + .unwrap(), + }; + + let wrong_event = TransactionEvents { + event_index: 0, + tx_id: tx_id.to_string(), + contract_log: None, + }; + + let event = TransactionEvents { + event_index: 1, + tx_id: tx_id.to_string(), + contract_log: Some(ContractLog { + contract_id: gateway_address.to_string(), + topic: "print".to_string(), + value: ContractLogValue { + hex: "0x0c000000061164657374696e6174696f6e2d636861696e0200000008657468657265756d1c64657374696e6174696f6e2d636f6e74726163742d61646472657373020000002a307830343345313035313839653135414337323235324346454638393845433338343141344130353631077061796c6f616402000000196c6f72656d697073756d20646f6c6f722073697420616d65740c7061796c6f61642d6861736802000000200338573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f46d0673656e646572051a6d78de7b0625dfbfc16c3a8a5735f6dc3dc3f2ce04747970650d0000000d636f6e74726163742d63616c6c".to_string(), + } + }), + }; + + let transaction = Transaction { + tx_id, + nonce: 1, + sender_address: "whatever".to_string(), + tx_status: "success".to_string(), + events: vec![wrong_event, event], + }; + + ( + source_chain.parse().unwrap(), + gateway_address.to_string(), + its_address.to_string(), + transaction, + msg, + ) + } + + fn get_matching_its_verify_msg_and_tx() -> (ChainName, String, String, Transaction, Message) { + let source_chain = "stacks"; + let gateway_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"; + let its_address = "ST2TFVBMRPS5SSNP98DQKQ5JNB2B6NZM91C4K3P7B.its"; + let tx_id = "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" + .parse() + .unwrap(); + + let msg = Message { + tx_id, + event_index: 1, + source_address: its_address.to_string(), + destination_chain: source_chain.parse().unwrap(), + destination_address: its_address.to_string(), + payload_hash: "0x0338573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f46d" + .parse() + .unwrap(), + }; + + let wrong_event = TransactionEvents { + event_index: 0, + tx_id: tx_id.to_string(), + contract_log: None, + }; + + let event = TransactionEvents { + event_index: 1, + tx_id: tx_id.to_string(), + contract_log: Some(ContractLog { + contract_id: gateway_address.to_string(), + topic: "print".to_string(), + value: ContractLogValue { + hex: "0x0c000000061164657374696e6174696f6e2d636861696e0200000008657468657265756d1c64657374696e6174696f6e2d636f6e74726163742d61646472657373020000002a307830343345313035313839653135414337323235324346454638393845433338343141344130353631077061796c6f616402000000196c6f72656d697073756d20646f6c6f722073697420616d65740c7061796c6f61642d6861736802000000200338573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f46d0673656e646572051a6d78de7b0625dfbfc16c3a8a5735f6dc3dc3f2ce04747970650d0000000d636f6e74726163742d63616c6c".to_string(), + } + }), + }; + + let transaction = Transaction { + tx_id, + nonce: 1, + sender_address: "whatever".to_string(), + tx_status: "success".to_string(), + events: vec![wrong_event, event], + }; + + ( + source_chain.parse().unwrap(), + gateway_address.to_string(), + its_address.to_string(), + transaction, + msg, + ) + } +} diff --git a/ampd/src/stacks/verifier.rs b/ampd/src/stacks/verifier.rs index d04e16be7..e2e8edc1f 100644 --- a/ampd/src/stacks/verifier.rs +++ b/ampd/src/stacks/verifier.rs @@ -11,6 +11,7 @@ use crate::handlers::stacks_verify_verifier_set::VerifierSetConfirmation; use crate::stacks::error::Error; use crate::stacks::http_client::{Transaction, TransactionEvents}; use crate::stacks::WeightedSigners; +use crate::types::Hash; pub const PRINT_TOPIC: &str = "print"; @@ -18,7 +19,11 @@ pub const CONTRACT_CALL_TYPE: &str = "contract-call"; const SIGNERS_ROTATED_TYPE: &str = "signers-rotated"; impl Message { - fn eq_event(&self, event: &TransactionEvents) -> Result> { + pub fn eq_event( + &self, + event: &TransactionEvents, + new_payload_hash: Option, + ) -> Result> { let contract_log = event.contract_log.as_ref().ok_or(Error::PropertyEmpty)?; if contract_log.topic != PRINT_TOPIC { @@ -90,7 +95,11 @@ impl Message { return Ok(false); } - if !data + if let Some(new_payload_hash) = new_payload_hash { + if new_payload_hash != self.payload_hash { + return Ok(false); + } + } else if !data .get("payload-hash")? .eq(&Value::buff_from(self.payload_hash.as_bytes().to_vec())?) { @@ -193,14 +202,16 @@ pub fn verify_message( Some(event) => { // In case message is not from ITS if &message.source_address != its_address { - if message.eq_event(event).unwrap_or(false) { + if message.eq_event(event, None).unwrap_or(false) { return Vote::SucceededOnChain; } return Vote::NotFound; } + // TODO: // In case messages is from Stacks -> Stacks and from ITS -> ITS, use custom logic + // for confirming contract deployments if &message.destination_chain == source_chain && &message.destination_address == its_address { From 3faa95ee4fb745cf9ddf9b09c5337e4dbe834bcf Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Mon, 21 Oct 2024 15:54:40 +0300 Subject: [PATCH 12/20] Add abi encoding for all message types. --- ampd/src/stacks/its_verifier.rs | 489 ++++++++++++++++++++++++-------- ampd/src/stacks/verifier.rs | 31 +- 2 files changed, 392 insertions(+), 128 deletions(-) diff --git a/ampd/src/stacks/its_verifier.rs b/ampd/src/stacks/its_verifier.rs index 35cb33738..31fef5f4c 100644 --- a/ampd/src/stacks/its_verifier.rs +++ b/ampd/src/stacks/its_verifier.rs @@ -2,7 +2,7 @@ use crate::handlers::stacks_verify_msg::Message; use crate::stacks::error::Error; use crate::stacks::http_client::TransactionEvents; use crate::stacks::verifier::{CONTRACT_CALL_TYPE, PRINT_TOPIC}; -use axelar_wasm_std::hash::Hash; +use crate::types::Hash; use clarity::codec::StacksMessageCodec; use clarity::vm::types::{ BufferLength, PrincipalData, SequenceSubtype, StringSubtype, TupleData, TupleTypeSignature, @@ -17,36 +17,58 @@ const MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN: u128 = 1; const MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER: u128 = 2; const MESSAGE_TYPE_SEND_TO_HUB: u128 = 3; -fn get_payload_from_contract_call_event( +pub fn get_its_hub_payload_hash( event: &TransactionEvents, -) -> Result, Box> { - let contract_log = event.contract_log.as_ref().ok_or(Error::PropertyEmpty)?; +) -> Result> { + let tuple_data = get_its_hub_call_params(event)?; + + // All messages should go through ITS hub + if !tuple_data + .get("type")? + .eq(&Value::UInt(MESSAGE_TYPE_SEND_TO_HUB)) + { + return Err(Error::InvalidCall.into()); + } - let contract_call_signature = TupleTypeSignature::try_from(vec![( - ClarityName::from("payload"), - TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( - 10240u32, - )?)), - )])?; + let destination_chain = tuple_data + .get("destination-chain")? + .clone() + .expect_ascii()?; + let payload = tuple_data.get_owned("payload")?.expect_buff(10240)?; - let hex = contract_log - .value - .hex - .strip_prefix("0x") - .ok_or(Error::PropertyEmpty)?; + let subtuple_type_signature = + TupleTypeSignature::try_from(vec![(ClarityName::from("type"), TypeSignature::UIntType)])?; - let contract_call_value = Value::try_deserialize_hex( - hex, - &TypeSignature::TupleType(contract_call_signature), + let original_its_call = Value::try_deserialize_bytes( + &payload, + &TypeSignature::TupleType(subtuple_type_signature), true, - )?; + )? + .expect_tuple()?; - let payload = contract_call_value - .expect_tuple()? - .get_owned("payload")? - .expect_buff(10240)?; + let its_type = original_its_call.get_owned("type")?.expect_u128()?; - Ok(payload) + let abi_payload = match its_type { + MESSAGE_TYPE_INTERCHAIN_TRANSFER => get_its_interchain_transfer_abi_payload(payload), + MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN => { + get_its_deploy_interchain_token_abi_payload(payload) + } + MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER => get_its_deploy_token_manager_payload(payload), + _ => { + return Err(Error::InvalidCall.into()); + } + }?; + + // Convert to ITS payload and use its hash to verify the message + let abi_payload = encode(&[ + Token::Uint(MESSAGE_TYPE_SEND_TO_HUB.into()), + Token::String(destination_chain), + Token::Bytes(abi_payload), + ]); + + let payload_hash: [u8; 32] = Keccak256::digest(abi_payload).into(); + + Ok(payload_hash.into()) } fn get_its_hub_call_params( @@ -80,6 +102,38 @@ fn get_its_hub_call_params( Ok(its_hub_value) } +fn get_payload_from_contract_call_event( + event: &TransactionEvents, +) -> Result, Box> { + let contract_log = event.contract_log.as_ref().ok_or(Error::PropertyEmpty)?; + + let contract_call_signature = TupleTypeSignature::try_from(vec![( + ClarityName::from("payload"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 10240u32, + )?)), + )])?; + + let hex = contract_log + .value + .hex + .strip_prefix("0x") + .ok_or(Error::PropertyEmpty)?; + + let contract_call_value = Value::try_deserialize_hex( + hex, + &TypeSignature::TupleType(contract_call_signature), + true, + )?; + + let payload = contract_call_value + .expect_tuple()? + .get_owned("payload")? + .expect_buff(10240)?; + + Ok(payload) +} + fn get_its_interchain_transfer_abi_payload( payload: Vec, ) -> Result, Box> { @@ -94,12 +148,6 @@ fn get_its_interchain_transfer_abi_payload( ClarityName::from("source-address"), TypeSignature::PrincipalType, ), - ( - ClarityName::from("destination-chain"), - TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( - BufferLength::try_from(18u32)?, - ))), - ), ( ClarityName::from("destination-address"), TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( @@ -166,63 +214,146 @@ fn get_its_interchain_transfer_abi_payload( Ok(abi_payload) } -impl Message { - pub fn eq_its_hub_event( - &self, - event: &TransactionEvents, - ) -> Result> { - let tuple_data = get_its_hub_call_params(event)?; +fn get_its_deploy_interchain_token_abi_payload( + payload: Vec, +) -> Result, Box> { + let tuple_type_signature = TupleTypeSignature::try_from(vec![ + ( + ClarityName::from("token-id"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 32u32, + )?)), + ), + ( + ClarityName::from("name"), + TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( + BufferLength::try_from(32u32)?, + ))), + ), + ( + ClarityName::from("symbol"), + TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( + BufferLength::try_from(32u32)?, + ))), + ), + (ClarityName::from("decimals"), TypeSignature::UIntType), + ( + ClarityName::from("minter"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 200u32, + )?)), + ), + ])?; - // All messages should go through ITS hub - if !tuple_data - .get("type")? - .eq(&Value::UInt(MESSAGE_TYPE_SEND_TO_HUB)) - { - return Ok(false); - } + let mut original_value = Value::try_deserialize_bytes( + &payload, + &TypeSignature::TupleType(tuple_type_signature), + true, + )? + .expect_tuple()?; - let destination_chain = tuple_data - .get("destination-chain")? - .clone() - .expect_ascii()?; - let payload = tuple_data.get_owned("payload")?.expect_buff(10240)?; + let abi_payload = encode(&[ + Token::Uint(MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN.into()), + Token::FixedBytes( + original_value + .data_map + .remove("token-id") + .ok_or(Error::InvalidCall)? + .expect_buff(32)?, + ), + Token::String( + original_value + .data_map + .remove("name") + .ok_or(Error::InvalidCall)? + .expect_ascii()?, + ), + Token::String( + original_value + .data_map + .remove("symbol") + .ok_or(Error::InvalidCall)? + .expect_ascii()?, + ), + Token::Uint( + original_value + .data_map + .remove("decimals") + .ok_or(Error::InvalidCall)? + .expect_u128()? + .into(), + ), + Token::Bytes( + original_value + .data_map + .remove("minter") + .ok_or(Error::InvalidCall)? + .expect_buff(200)?, + ), + ]); + + Ok(abi_payload) +} - let subtuple_type_signature = TupleTypeSignature::try_from(vec![( - ClarityName::from("type"), +fn get_its_deploy_token_manager_payload( + payload: Vec, +) -> Result, Box> { + let tuple_type_signature = TupleTypeSignature::try_from(vec![ + ( + ClarityName::from("token-id"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 32u32, + )?)), + ), + ( + ClarityName::from("token-manager-type"), TypeSignature::UIntType, - )])?; - - let original_its_call = Value::try_deserialize_bytes( - &payload, - &TypeSignature::TupleType(subtuple_type_signature), - true, - )? - .expect_tuple()?; - - let its_type = original_its_call.get_owned("type")?.expect_u128()?; - - let abi_payload = match its_type { - MESSAGE_TYPE_INTERCHAIN_TRANSFER => get_its_interchain_transfer_abi_payload(payload), - // TODO: Handle other cases - // MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN => {} - // MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER => {} - _ => { - return Err(Error::InvalidCall.into()); - } - }?; + ), + ( + ClarityName::from("params"), + TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( + 1024u32, + )?)), + ), + ])?; - // Convert to ITS payload and use its hash to verify the message - let abi_payload = encode(&[ - Token::Uint(MESSAGE_TYPE_SEND_TO_HUB.into()), - Token::String(destination_chain), - Token::Bytes(abi_payload), - ]); + let mut original_value = Value::try_deserialize_bytes( + &payload, + &TypeSignature::TupleType(tuple_type_signature), + true, + )? + .expect_tuple()?; - let payload_hash: Hash = Keccak256::digest(abi_payload).into(); + let abi_payload = encode(&[ + Token::Uint(MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER.into()), + Token::FixedBytes( + original_value + .data_map + .remove("token-id") + .ok_or(Error::InvalidCall)? + .expect_buff(32)?, + ), + Token::Uint( + original_value + .data_map + .remove("token-manager-type") + .ok_or(Error::InvalidCall)? + .expect_u128()? + .into(), + ), + Token::Bytes( + original_value + .data_map + .remove("params") + .ok_or(Error::InvalidCall)? + .expect_buff(1024)?, + ), + ]); - self.eq_event(event, Some(payload_hash.into())) - } + Ok(abi_payload) +} +impl Message { // TODO: pub fn eq_its_verify_event( &self, @@ -328,7 +459,7 @@ mod tests { #[test] fn should_not_verify_its_hub_invalid_payload_hash() { let (source_chain, gateway_address, its_address, tx, mut msg) = - get_matching_its_hub_msg_and_tx(); + get_matching_its_hub_interchain_transfer_msg_and_tx(); msg.payload_hash = "0xaa38573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f4aa" .parse() @@ -340,23 +471,21 @@ mod tests { ); } - // // TODO: - // #[test] - // fn should_verify_msg_its_hub_interchain_transfer() { - // let (source_chain, gateway_address, its_address, tx, msg) = - // get_matching_its_hub_msg_and_tx(); - // - // assert_eq!( - // verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), - // Vote::SucceededOnChain - // ); - // } + #[test] + fn should_verify_msg_its_hub_interchain_transfer() { + let (source_chain, gateway_address, its_address, tx, msg) = + get_matching_its_hub_interchain_transfer_msg_and_tx(); + + assert_eq!( + verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + Vote::SucceededOnChain + ); + } - // test verify message its #[test] - fn should_not_verify_its_invalid_payload_hash() { + fn should_not_verify_its_hub_deploy_interchain_token_invalid_payload_hash() { let (source_chain, gateway_address, its_address, tx, mut msg) = - get_matching_its_verify_msg_and_tx(); + get_matching_its_hub_deploy_interchain_token_msg_and_tx(); msg.payload_hash = "0xaa38573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f4aa" .parse() @@ -368,22 +497,121 @@ mod tests { ); } + #[test] + fn should_verify_msg_its_hub_deploy_interchain_token() { + let (source_chain, gateway_address, its_address, tx, msg) = + get_matching_its_hub_deploy_interchain_token_msg_and_tx(); + + assert_eq!( + verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + Vote::SucceededOnChain + ); + } + + #[test] + fn should_not_verify_its_hub_deploy_token_manager_invalid_payload_hash() { + let (source_chain, gateway_address, its_address, tx, mut msg) = + get_matching_its_hub_deploy_token_manager_msg_and_tx(); + + msg.payload_hash = "0xaa38573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f4aa" + .parse() + .unwrap(); + + assert_eq!( + verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + Vote::NotFound + ); + } + + #[test] + fn should_verify_msg_its_hub_deploy_token_manager() { + let (source_chain, gateway_address, its_address, tx, msg) = + get_matching_its_hub_deploy_token_manager_msg_and_tx(); + + assert_eq!( + verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + Vote::SucceededOnChain + ); + } + + // test verify message its // TODO: - // #[test] - // fn should_verify_msg_its_interchain_transfer() { - // let (source_chain, gateway_address, its_address, tx, msg) = - // get_matching_its_verify_msg_and_tx(); - // - // assert_eq!( - // verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), - // Vote::SucceededOnChain - // ); - // } - - fn get_matching_its_hub_msg_and_tx() -> (ChainName, String, String, Transaction, Message) { + + fn get_matching_its_hub_interchain_transfer_msg_and_tx( + ) -> (ChainName, String, String, Transaction, Message) { + let source_chain = "stacks"; + let gateway_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"; + let its_address = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.interchain-token-service"; + let tx_id = "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" + .parse() + .unwrap(); + + let msg = Message { + tx_id, + event_index: 1, + source_address: its_address.to_string(), + destination_chain: "axelar".parse().unwrap(), + destination_address: "cosmwasm".to_string(), + payload_hash: "0x99cdb5935274c6a59d3ce9cd6c47b58acc0ef461d6b3cab7162c2842c182b94a" + .parse() + .unwrap(), + }; + + let wrong_event = TransactionEvents { + event_index: 0, + tx_id: tx_id.to_string(), + contract_log: None, + }; + + /* + payload is: + { + type: u3, + destination-chain: "ethereum", + payload: { + type: u0, + token-id: 0x753306c46380848b5189cd9db90107b15d25decccd93dcb175c0098958f18b6f, + source-address: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM, + destination-address: 0x00, + amount: u100000, + data: 0x00 + } + } + */ + let event = TransactionEvents { + event_index: 1, + tx_id: tx_id.to_string(), + contract_log: Some(ContractLog { + contract_id: gateway_address.to_string(), + topic: "print".to_string(), + value: ContractLogValue { + hex: "0x0c000000061164657374696e6174696f6e2d636861696e0d000000066178656c61721c64657374696e6174696f6e2d636f6e74726163742d616464726573730d00000008636f736d7761736d077061796c6f616402000000f20c000000031164657374696e6174696f6e2d636861696e0d00000008657468657265756d077061796c6f616402000000ab0c0000000606616d6f756e7401000000000000000000000000000186a004646174610200000001001364657374696e6174696f6e2d616464726573730200000001000e736f757263652d61646472657373051a6d78de7b0625dfbfc16c3a8a5735f6dc3dc3f2ce08746f6b656e2d69640200000020753306c46380848b5189cd9db90107b15d25decccd93dcb175c0098958f18b6f04747970650100000000000000000000000000000000047479706501000000000000000000000000000000030c7061796c6f61642d6861736802000000203dc0763c57c9c7912d2c072718e6ef2ae2d595ce2da31d8b248205d67ad7c3ab0673656e646572061a6d78de7b0625dfbfc16c3a8a5735f6dc3dc3f2ce18696e746572636861696e2d746f6b656e2d7365727669636504747970650d0000000d636f6e74726163742d63616c6c".to_string(), + } + }), + }; + + let transaction = Transaction { + tx_id, + nonce: 1, + sender_address: "whatever".to_string(), + tx_status: "success".to_string(), + events: vec![wrong_event, event], + }; + + ( + source_chain.parse().unwrap(), + gateway_address.to_string(), + its_address.to_string(), + transaction, + msg, + ) + } + + fn get_matching_its_hub_deploy_interchain_token_msg_and_tx( + ) -> (ChainName, String, String, Transaction, Message) { let source_chain = "stacks"; let gateway_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"; - let its_address = "ST2TFVBMRPS5SSNP98DQKQ5JNB2B6NZM91C4K3P7B.its"; + let its_address = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.interchain-token-service"; let tx_id = "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" .parse() .unwrap(); @@ -393,8 +621,8 @@ mod tests { event_index: 1, source_address: its_address.to_string(), destination_chain: "axelar".parse().unwrap(), - destination_address: "axelartest".to_string(), - payload_hash: "0x0338573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f46d" + destination_address: "0x00".to_string(), + payload_hash: "0x63b56229fc520914aa0f690e136517fceae159a49082f5f18f866a9ba5e3ce15" .parse() .unwrap(), }; @@ -405,6 +633,21 @@ mod tests { contract_log: None, }; + /* + payload is: + { + type: u3, + destination-chain: "ethereum", + payload: { + type: u1, + token-id: 0x42fad3435446674f88b47510fe7d2d144c8867c405d4933007705db85f37ded5, + name: "sample", + symbol: "sample", + decimals: u6, + minter: 0x00 + } + } + */ let event = TransactionEvents { event_index: 1, tx_id: tx_id.to_string(), @@ -412,7 +655,7 @@ mod tests { contract_id: gateway_address.to_string(), topic: "print".to_string(), value: ContractLogValue { - hex: "0x0c000000061164657374696e6174696f6e2d636861696e0200000008657468657265756d1c64657374696e6174696f6e2d636f6e74726163742d61646472657373020000002a307830343345313035313839653135414337323235324346454638393845433338343141344130353631077061796c6f616402000000196c6f72656d697073756d20646f6c6f722073697420616d65740c7061796c6f61642d6861736802000000200338573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f46d0673656e646572051a6d78de7b0625dfbfc16c3a8a5735f6dc3dc3f2ce04747970650d0000000d636f6e74726163742d63616c6c".to_string(), + hex: "0x0c000000061164657374696e6174696f6e2d636861696e0d000000066178656c61721c64657374696e6174696f6e2d636f6e74726163742d616464726573730d0000000430783030077061796c6f616402000000d90c000000031164657374696e6174696f6e2d636861696e0d00000008657468657265756d077061796c6f616402000000920c0000000608646563696d616c730100000000000000000000000000000006066d696e746572020000000100046e616d650d0000000673616d706c650673796d626f6c0d0000000673616d706c6508746f6b656e2d69640200000020563dc3698c0f2c5adf375ff350bb54ecf86d2be109e3aacaf38111cdf171df7804747970650100000000000000000000000000000001047479706501000000000000000000000000000000030c7061796c6f61642d6861736802000000207bcf62a3e8aed07d1eb704a1c4b142de9c1f429d2a6cf835c3347763ae8e05ab0673656e646572061a6d78de7b0625dfbfc16c3a8a5735f6dc3dc3f2ce18696e746572636861696e2d746f6b656e2d7365727669636504747970650d0000000d636f6e74726163742d63616c6c".to_string(), } }), }; @@ -434,10 +677,11 @@ mod tests { ) } - fn get_matching_its_verify_msg_and_tx() -> (ChainName, String, String, Transaction, Message) { + fn get_matching_its_hub_deploy_token_manager_msg_and_tx( + ) -> (ChainName, String, String, Transaction, Message) { let source_chain = "stacks"; let gateway_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"; - let its_address = "ST2TFVBMRPS5SSNP98DQKQ5JNB2B6NZM91C4K3P7B.its"; + let its_address = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.interchain-token-service"; let tx_id = "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" .parse() .unwrap(); @@ -446,9 +690,9 @@ mod tests { tx_id, event_index: 1, source_address: its_address.to_string(), - destination_chain: source_chain.parse().unwrap(), - destination_address: its_address.to_string(), - payload_hash: "0x0338573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f46d" + destination_chain: "axelar".parse().unwrap(), + destination_address: "cosmwasm".to_string(), + payload_hash: "0x617076bb0067f463de653c1d16e4037f2cfb59c383820351e5b8bd2ca9d50948" .parse() .unwrap(), }; @@ -459,6 +703,19 @@ mod tests { contract_log: None, }; + /* + payload is: + { + type: u3, + destination-chain: "ethereum", + payload: { + type: u2, + token-id: 0xc99a1f0a4b46456129d86b37f580af16fea20eeaf7e73628547c10f6799b90b0, + token-manager-type: u2, + params: 0x00 + } + } + */ let event = TransactionEvents { event_index: 1, tx_id: tx_id.to_string(), @@ -466,7 +723,7 @@ mod tests { contract_id: gateway_address.to_string(), topic: "print".to_string(), value: ContractLogValue { - hex: "0x0c000000061164657374696e6174696f6e2d636861696e0200000008657468657265756d1c64657374696e6174696f6e2d636f6e74726163742d61646472657373020000002a307830343345313035313839653135414337323235324346454638393845433338343141344130353631077061796c6f616402000000196c6f72656d697073756d20646f6c6f722073697420616d65740c7061796c6f61642d6861736802000000200338573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f46d0673656e646572051a6d78de7b0625dfbfc16c3a8a5735f6dc3dc3f2ce04747970650d0000000d636f6e74726163742d63616c6c".to_string(), + hex: "0x0c000000061164657374696e6174696f6e2d636861696e0d000000066178656c61721c64657374696e6174696f6e2d636f6e74726163742d616464726573730d00000008636f736d7761736d077061796c6f616402000000c10c000000031164657374696e6174696f6e2d636861696e0d00000008657468657265756d077061796c6f6164020000007a0c0000000406706172616d7302000000010008746f6b656e2d69640200000020c99a1f0a4b46456129d86b37f580af16fea20eeaf7e73628547c10f6799b90b012746f6b656e2d6d616e616765722d74797065010000000000000000000000000000000204747970650100000000000000000000000000000002047479706501000000000000000000000000000000030c7061796c6f61642d6861736802000000209ce89d392d43333d269dd9f234e765ded79db1ba895e8b2e3d6d8f936cae57320673656e646572061a6d78de7b0625dfbfc16c3a8a5735f6dc3dc3f2ce18696e746572636861696e2d746f6b656e2d7365727669636504747970650d0000000d636f6e74726163742d63616c6c".to_string(), } }), }; diff --git a/ampd/src/stacks/verifier.rs b/ampd/src/stacks/verifier.rs index e2e8edc1f..4622c201d 100644 --- a/ampd/src/stacks/verifier.rs +++ b/ampd/src/stacks/verifier.rs @@ -10,6 +10,7 @@ use crate::handlers::stacks_verify_msg::Message; use crate::handlers::stacks_verify_verifier_set::VerifierSetConfirmation; use crate::stacks::error::Error; use crate::stacks::http_client::{Transaction, TransactionEvents}; +use crate::stacks::its_verifier::get_its_hub_payload_hash; use crate::stacks::WeightedSigners; use crate::types::Hash; @@ -40,15 +41,15 @@ impl Message { (ClarityName::from("sender"), TypeSignature::PrincipalType), ( ClarityName::from("destination-chain"), - TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( - 18u32, - )?)), + TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( + BufferLength::try_from(32u32)?, + ))), ), ( ClarityName::from("destination-contract-address"), - TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( - 96u32, - )?)), + TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( + BufferLength::try_from(48u32)?, + ))), ), ( ClarityName::from("payload-hash"), @@ -80,15 +81,18 @@ impl Message { return Ok(false); } - if !data.get("destination-chain")?.eq(&Value::buff_from( - self.destination_chain.as_ref().as_bytes().to_vec(), - )?) { + if !data + .get("destination-chain")? + .eq(&Value::string_ascii_from_bytes( + self.destination_chain.as_ref().as_bytes().to_vec(), + )?) + { return Ok(false); } if !data .get("destination-contract-address")? - .eq(&Value::buff_from( + .eq(&Value::string_ascii_from_bytes( self.destination_address.as_bytes().to_vec(), )?) { @@ -223,8 +227,11 @@ pub fn verify_message( } // In other case, abi encode payload coming from Stacks ITS - if message.eq_its_hub_event(event).unwrap_or(false) { - return Vote::SucceededOnChain; + if let Ok(payload_hash) = get_its_hub_payload_hash(event) + { + if message.eq_event(event, Some(payload_hash)).unwrap_or(false) { + return Vote::SucceededOnChain; + } } Vote::NotFound From b0b361cc6ad3942070df413c1d66fd2c89c72b27 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Mon, 21 Oct 2024 18:05:49 +0300 Subject: [PATCH 13/20] Start working on verifying its contract deployments. --- ampd/src/config.rs | 2 + ampd/src/handlers/config.rs | 6 + ampd/src/handlers/stacks_verify_msg.rs | 62 +++++-- ampd/src/lib.rs | 8 +- ampd/src/stacks/http_client.rs | 23 +++ ampd/src/stacks/its_verifier.rs | 248 +++++++++++++++---------- ampd/src/stacks/verifier.rs | 202 ++++++++++++++++---- ampd/src/tests/config_template.toml | 2 + 8 files changed, 396 insertions(+), 157 deletions(-) diff --git a/ampd/src/config.rs b/ampd/src/config.rs index 62f90784d..2699a14e8 100644 --- a/ampd/src/config.rs +++ b/ampd/src/config.rs @@ -369,6 +369,8 @@ mod tests { ), http_url: Url::from_str("http://127.0.0.1").unwrap(), its_address: "its_address".to_string(), + reference_native_interchain_token_address: "interchain_token_address".to_string(), + reference_token_manager_address: "token_manager_address".to_string(), }, HandlerConfig::StacksVerifierSetVerifier { cosmwasm_contract: TMAddress::from( diff --git a/ampd/src/handlers/config.rs b/ampd/src/handlers/config.rs index 1d88c7196..b7311b92e 100644 --- a/ampd/src/handlers/config.rs +++ b/ampd/src/handlers/config.rs @@ -67,6 +67,8 @@ pub enum Config { cosmwasm_contract: TMAddress, http_url: Url, its_address: String, + reference_native_interchain_token_address: String, + reference_token_manager_address: String, }, StacksVerifierSetVerifier { cosmwasm_contract: TMAddress, @@ -330,11 +332,15 @@ mod tests { cosmwasm_contract: TMAddress::random(PREFIX), http_url: "http://localhost:8080/".parse().unwrap(), its_address: "its_address".to_string(), + reference_native_interchain_token_address: "interchain_token_address".to_string(), + reference_token_manager_address: "token_manager_address".to_string(), }, Config::StacksMsgVerifier { cosmwasm_contract: TMAddress::random(PREFIX), http_url: "http://localhost:8080/".parse().unwrap(), its_address: "its_address".to_string(), + reference_native_interchain_token_address: "interchain_token_address".to_string(), + reference_token_manager_address: "token_manager_address".to_string(), }, ]; diff --git a/ampd/src/handlers/stacks_verify_msg.rs b/ampd/src/handlers/stacks_verify_msg.rs index 07d5f860f..3e771fe55 100644 --- a/ampd/src/handlers/stacks_verify_msg.rs +++ b/ampd/src/handlers/stacks_verify_msg.rs @@ -7,6 +7,7 @@ use cosmrs::cosmwasm::MsgExecuteContract; use cosmrs::tx::Msg; use cosmrs::Any; use error_stack::ResultExt; +use futures::future; use events::Error::EventTypeMismatch; use events::Event; use events_derive::try_from; @@ -51,23 +52,38 @@ pub struct Handler { http_client: Client, latest_block_height: Receiver, its_address: String, + reference_native_interchain_token_code: String, + reference_token_manager_code: String, } impl Handler { - pub fn new( + pub async fn new( verifier: TMAddress, voting_verifier_contract: TMAddress, http_client: Client, latest_block_height: Receiver, its_address: String, - ) -> Self { - Self { + reference_native_interchain_token_address: String, + reference_token_manager_address: String, + ) -> error_stack::Result { + let reference_native_interchain_token_info = http_client + .get_contract_info(reference_native_interchain_token_address.as_str()) + .await?; + + let reference_token_manager_info = http_client + .get_contract_info(reference_token_manager_address.as_str()) + .await?; + + Ok(Self { verifier, voting_verifier_contract, http_client, latest_block_height, its_address, - } + reference_native_interchain_token_code: reference_native_interchain_token_info + .source_code, + reference_token_manager_code: reference_token_manager_info.source_code, + }) } fn vote_msg(&self, poll_id: PollId, votes: Vec) -> MsgExecuteContract { @@ -119,7 +135,8 @@ impl EventHandler for Handler { let tx_hashes: HashSet<_> = messages.iter().map(|message| message.tx_id).collect(); let transactions = self.http_client.get_transactions(tx_hashes).await; - let votes: Vec = messages + // TODO: See how to handle async function in map properly + let votes: Vec = future::try_join_all(messages .iter() .map(|msg| { transactions @@ -131,10 +148,13 @@ impl EventHandler for Handler { &self.its_address, transaction, msg, + &self.http_client, + &self.reference_native_interchain_token_code, + &self.reference_token_manager_code, ) }) - }) - .collect(); + })) + .await?; Ok(vec![self .vote_msg(poll_id, votes) @@ -208,9 +228,11 @@ mod tests { Client::faux(), watch::channel(0).1, "its_address".to_string(), - ); + "native_interchain_token_code".to_string(), + "token_manager_code".to_string() + ).await; - assert!(handler.handle(&event).await.is_ok()); + assert!(handler.unwrap().handle(&event).await.is_ok()); } // Should not handle event if it is not emitted from voting verifier @@ -227,9 +249,11 @@ mod tests { Client::faux(), watch::channel(0).1, "its_address".to_string(), - ); + "native_interchain_token_code".to_string(), + "token_manager_code".to_string() + ).await; - assert!(handler.handle(&event).await.is_ok()); + assert!(handler.unwrap().handle(&event).await.is_ok()); } // Should not handle event if worker is not a poll participant @@ -245,9 +269,11 @@ mod tests { Client::faux(), watch::channel(0).1, "its_address".to_string(), - ); + "native_interchain_token_code".to_string(), + "token_manager_code".to_string() + ).await; - assert!(handler.handle(&event).await.is_ok()); + assert!(handler.unwrap().handle(&event).await.is_ok()); } #[async_test] @@ -268,9 +294,11 @@ mod tests { client, watch::channel(0).1, "its_address".to_string(), - ); + "native_interchain_token_code".to_string(), + "token_manager_code".to_string() + ).await; - let actual = handler.handle(&event).await.unwrap(); + let actual = handler.unwrap().handle(&event).await.unwrap(); assert_eq!(actual.len(), 1); assert!(MsgExecuteContract::from_any(actual.first().unwrap()).is_ok()); } @@ -296,7 +324,9 @@ mod tests { client, rx, "its_address".to_string(), - ); + "native_interchain_token_code".to_string(), + "token_manager_code".to_string() + ).await.unwrap(); // poll is not expired yet, should hit proxy let actual = handler.handle(&event).await.unwrap(); diff --git a/ampd/src/lib.rs b/ampd/src/lib.rs index fd3dca156..7171eb717 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -394,6 +394,8 @@ where cosmwasm_contract, http_url, its_address, + reference_native_interchain_token_address, + reference_token_manager_address, } => self.create_handler_task( "stacks-msg-verifier", handlers::stacks_verify_msg::Handler::new( @@ -402,7 +404,11 @@ where Client::new_http(http_url.to_string().trim_end_matches('/').into()), self.block_height_monitor.latest_block_height(), its_address, - ), + reference_native_interchain_token_address, + reference_token_manager_address, + ) + .await + .change_context(Error::Connection)?, event_processor_config.clone(), ), handlers::config::Config::StacksVerifierSetVerifier { diff --git a/ampd/src/stacks/http_client.rs b/ampd/src/stacks/http_client.rs index 470c756b5..63bef6b42 100644 --- a/ampd/src/stacks/http_client.rs +++ b/ampd/src/stacks/http_client.rs @@ -8,6 +8,7 @@ use thiserror::Error; use crate::types::Hash; const GET_TRANSACTION: &str = "extended/v1/tx/"; +const GET_CONTRACT_INFO: &str = "extended/v1/contract/"; const STATUS_SUCCESS: &str = "success"; @@ -17,6 +18,8 @@ pub enum Error { Client, #[error("invalid tx hash")] TxHash, + #[error("invalid contract")] + Contract, } #[derive(Debug, Deserialize, Default)] @@ -47,6 +50,11 @@ pub struct Transaction { pub events: Vec, } +#[derive(Debug, Deserialize, Default)] +pub struct ContractInfo { + pub source_code: String, +} + #[cfg_attr(test, faux::create)] pub struct Client { api_url: String, @@ -105,6 +113,21 @@ impl Client { .map_err(|_| Error::Client) } + pub async fn get_contract_info(&self, contract_id: &str) -> Result { + let endpoint = GET_CONTRACT_INFO.to_string() + contract_id; + + let endpoint = self.get_endpoint(endpoint.as_str()); + + self.client + .get(endpoint) + .send() + .await + .map_err(|_| Error::Contract)? + .json::() + .await + .map_err(|_| Error::Client) + } + fn get_endpoint(&self, endpoint: &str) -> String { format!("{}/{}", self.api_url, endpoint) } diff --git a/ampd/src/stacks/its_verifier.rs b/ampd/src/stacks/its_verifier.rs index 31fef5f4c..c523a20b5 100644 --- a/ampd/src/stacks/its_verifier.rs +++ b/ampd/src/stacks/its_verifier.rs @@ -1,6 +1,6 @@ use crate::handlers::stacks_verify_msg::Message; use crate::stacks::error::Error; -use crate::stacks::http_client::TransactionEvents; +use crate::stacks::http_client::{Client, TransactionEvents}; use crate::stacks::verifier::{CONTRACT_CALL_TYPE, PRINT_TOPIC}; use crate::types::Hash; use clarity::codec::StacksMessageCodec; @@ -17,6 +17,9 @@ const MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN: u128 = 1; const MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER: u128 = 2; const MESSAGE_TYPE_SEND_TO_HUB: u128 = 3; +const VERIFY_INTERCHAIN_TOKEN: &str = "verify-interchain-token"; +const VERIFY_TOKEN_MANAGER: &str = "verify-token-manager"; + pub fn get_its_hub_payload_hash( event: &TransactionEvents, ) -> Result> { @@ -353,111 +356,96 @@ fn get_its_deploy_token_manager_payload( Ok(abi_payload) } -impl Message { - // TODO: - pub fn eq_its_verify_event( - &self, - event: &TransactionEvents, - ) -> Result> { - let contract_log = event.contract_log.as_ref().ok_or(Error::PropertyEmpty)?; - - if contract_log.topic != PRINT_TOPIC { - return Ok(false); +pub async fn its_verify_contract_code( + event: &TransactionEvents, + http_client: &Client, + reference_native_interchain_token_code: &String, + reference_token_manager_code: &String, +) -> Result> { + let (payload, verify_type) = get_its_verify_call_params(event)?; + + match verify_type.as_str() { + VERIFY_INTERCHAIN_TOKEN => {} + VERIFY_TOKEN_MANAGER => { + return its_verify_token_manager(payload, http_client, reference_token_manager_code) + .await; } + _ => {} + } - let tuple_type_signature = TupleTypeSignature::try_from(vec![ - ( - ClarityName::from("type"), - TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( - BufferLength::try_from(13u32)?, - ))), - ), - (ClarityName::from("sender"), TypeSignature::PrincipalType), - ( - ClarityName::from("destination-chain"), - TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( - 18u32, - )?)), - ), - ( - ClarityName::from("destination-contract-address"), - TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( - 96u32, - )?)), - ), - ( - ClarityName::from("payload-hash"), - TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( - 32u32, - )?)), - ), - ])?; - - let hex = contract_log - .value - .hex - .strip_prefix("0x") - .ok_or(Error::PropertyEmpty)?; - - let value = - Value::try_deserialize_hex(hex, &TypeSignature::TupleType(tuple_type_signature), true)?; - - if let Value::Tuple(data) = value { - if !data.get("type")?.eq(&Value::string_ascii_from_bytes( - CONTRACT_CALL_TYPE.as_bytes().to_vec(), - )?) { - return Ok(false); - } + Ok(false) +} - if !data.get("sender")?.eq(&Value::from(PrincipalData::parse( - self.source_address.as_str(), - )?)) { - return Ok(false); - } +fn get_its_verify_call_params( + event: &TransactionEvents, +) -> Result<(Vec, String), Box> { + let payload = get_payload_from_contract_call_event(event)?; - if !data.get("destination-chain")?.eq(&Value::buff_from( - self.destination_chain.as_ref().as_bytes().to_vec(), - )?) { - return Ok(false); - } + let verify_type_signature = TupleTypeSignature::try_from(vec![( + ClarityName::from("type"), + TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( + BufferLength::try_from(23u32)?, + ))), + )])?; - if !data - .get("destination-contract-address")? - .eq(&Value::buff_from( - self.destination_address.as_bytes().to_vec(), - )?) - { - return Ok(false); - } + let verify_type = Value::try_deserialize_bytes( + &payload, + &TypeSignature::TupleType(verify_type_signature), + true, + )? + .expect_tuple()? + .get_owned("type")? + .expect_ascii()?; - if !data - .get("payload-hash")? - .eq(&Value::buff_from(self.payload_hash.as_bytes().to_vec())?) - { - return Ok(false); - } + Ok((payload, verify_type)) +} - return Ok(true); - } +async fn its_verify_token_manager( + payload: Vec, + http_client: &Client, + reference_token_manager_code: &String, +) -> Result> { + let tuple_type_signature = TupleTypeSignature::try_from(vec![( + ClarityName::from("token-manager-address"), + TypeSignature::PrincipalType, + )])?; - Ok(false) - } + let mut value = Value::try_deserialize_bytes( + &payload, + &TypeSignature::TupleType(tuple_type_signature), + true, + )? + .expect_tuple()?; + + let token_manager_address = value + .data_map + .remove("token-manager-address") + .ok_or(Error::InvalidCall)? + .expect_principal()?; + + let token_manager_info = http_client + .get_contract_info(format!("{}", token_manager_address).as_str()) + .await?; + + Ok(&token_manager_info.source_code == reference_token_manager_code) } #[cfg(test)] mod tests { use axelar_wasm_std::voting::Vote; use router_api::ChainName; + use tokio::test as async_test; use crate::handlers::stacks_verify_msg::Message; + use crate::stacks::http_client::Client; use crate::stacks::http_client::{ ContractLog, ContractLogValue, Transaction, TransactionEvents, }; use crate::stacks::verifier::verify_message; // test verify message its hub - #[test] - fn should_not_verify_its_hub_invalid_payload_hash() { + #[async_test] + async fn should_not_verify_its_hub_interchain_transfer_invalid_payload_hash() { let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_its_hub_interchain_transfer_msg_and_tx(); @@ -466,24 +454,44 @@ mod tests { .unwrap(); assert_eq!( - verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &Client::faux(), + &"native_interchain_token_code".to_string(), + &"token_manager_code".to_string() + ) + .await, Vote::NotFound ); } - #[test] - fn should_verify_msg_its_hub_interchain_transfer() { + #[async_test] + async fn should_verify_msg_its_hub_interchain_transfer() { let (source_chain, gateway_address, its_address, tx, msg) = get_matching_its_hub_interchain_transfer_msg_and_tx(); assert_eq!( - verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &Client::faux(), + &"native_interchain_token_code".to_string(), + &"token_manager_code".to_string() + ) + .await, Vote::SucceededOnChain ); } - #[test] - fn should_not_verify_its_hub_deploy_interchain_token_invalid_payload_hash() { + #[async_test] + async fn should_not_verify_its_hub_deploy_interchain_token_invalid_payload_hash() { let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_its_hub_deploy_interchain_token_msg_and_tx(); @@ -492,24 +500,44 @@ mod tests { .unwrap(); assert_eq!( - verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &Client::faux(), + &"native_interchain_token_code".to_string(), + &"token_manager_code".to_string() + ) + .await, Vote::NotFound ); } - #[test] - fn should_verify_msg_its_hub_deploy_interchain_token() { + #[async_test] + async fn should_verify_msg_its_hub_deploy_interchain_token() { let (source_chain, gateway_address, its_address, tx, msg) = get_matching_its_hub_deploy_interchain_token_msg_and_tx(); assert_eq!( - verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &Client::faux(), + &"native_interchain_token_code".to_string(), + &"token_manager_code".to_string() + ) + .await, Vote::SucceededOnChain ); } - #[test] - fn should_not_verify_its_hub_deploy_token_manager_invalid_payload_hash() { + #[async_test] + async fn should_not_verify_its_hub_deploy_token_manager_invalid_payload_hash() { let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_its_hub_deploy_token_manager_msg_and_tx(); @@ -518,18 +546,38 @@ mod tests { .unwrap(); assert_eq!( - verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &Client::faux(), + &"native_interchain_token_code".to_string(), + &"token_manager_code".to_string() + ) + .await, Vote::NotFound ); } - #[test] - fn should_verify_msg_its_hub_deploy_token_manager() { + #[async_test] + async fn should_verify_msg_its_hub_deploy_token_manager() { let (source_chain, gateway_address, its_address, tx, msg) = get_matching_its_hub_deploy_token_manager_msg_and_tx(); assert_eq!( - verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &Client::faux(), + &"native_interchain_token_code".to_string(), + &"token_manager_code".to_string() + ) + .await, Vote::SucceededOnChain ); } diff --git a/ampd/src/stacks/verifier.rs b/ampd/src/stacks/verifier.rs index 4622c201d..f594170fd 100644 --- a/ampd/src/stacks/verifier.rs +++ b/ampd/src/stacks/verifier.rs @@ -9,8 +9,8 @@ use router_api::ChainName; use crate::handlers::stacks_verify_msg::Message; use crate::handlers::stacks_verify_verifier_set::VerifierSetConfirmation; use crate::stacks::error::Error; -use crate::stacks::http_client::{Transaction, TransactionEvents}; -use crate::stacks::its_verifier::get_its_hub_payload_hash; +use crate::stacks::http_client::{Client, Transaction, TransactionEvents}; +use crate::stacks::its_verifier::{get_its_hub_payload_hash, its_verify_contract_code}; use crate::stacks::WeightedSigners; use crate::types::Hash; @@ -191,12 +191,15 @@ fn find_event<'a>( Some(event) } -pub fn verify_message( +pub async fn verify_message( source_chain: &ChainName, gateway_address: &String, its_address: &String, transaction: &Transaction, message: &Message, + http_client: &Client, + reference_native_interchain_token_code: &String, + reference_token_manager_code: &String, ) -> Vote { if message.tx_id != transaction.tx_id { return Vote::NotFound; @@ -213,13 +216,21 @@ pub fn verify_message( return Vote::NotFound; } - // TODO: // In case messages is from Stacks -> Stacks and from ITS -> ITS, use custom logic // for confirming contract deployments if &message.destination_chain == source_chain && &message.destination_address == its_address { - if message.eq_its_verify_event(event).unwrap_or(false) { + if message.eq_event(event, None).unwrap_or(false) + && its_verify_contract_code( + event, + http_client, + reference_native_interchain_token_code, + reference_token_manager_code, + ) + .await + .unwrap_or(false) + { return Vote::SucceededOnChain; } @@ -227,8 +238,7 @@ pub fn verify_message( } // In other case, abi encode payload coming from Stacks ITS - if let Ok(payload_hash) = get_its_hub_payload_hash(event) - { + if let Ok(payload_hash) = get_its_hub_payload_hash(event) { if message.eq_event(event, Some(payload_hash)).unwrap_or(false) { return Vote::SucceededOnChain; } @@ -264,54 +274,86 @@ mod tests { use multisig::key::KeyType; use multisig::test::common::{build_verifier_set, ecdsa_test_data}; use router_api::ChainName; + use tokio::test as async_test; use crate::handlers::stacks_verify_msg::Message; use crate::handlers::stacks_verify_verifier_set::VerifierSetConfirmation; + use crate::stacks::http_client::Client; use crate::stacks::http_client::{ ContractLog, ContractLogValue, Transaction, TransactionEvents, }; use crate::stacks::verifier::{verify_message, verify_verifier_set, SIGNERS_ROTATED_TYPE}; // test verify message - #[test] - fn should_not_verify_tx_id_does_not_match() { + #[async_test] + async fn should_not_verify_tx_id_does_not_match() { let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_msg_and_tx(); msg.tx_id = "ffaf64de66510723f2efbacd7ead3c4f8c856aed1afc2cb30254552aeda47313" .parse() .unwrap(); assert_eq!( - verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &Client::faux(), + &"native_interchain_token_code".to_string(), + &"token_manager_code".to_string() + ) + .await, Vote::NotFound ); } - #[test] - fn should_not_verify_no_log_for_event_index() { + #[async_test] + async fn should_not_verify_no_log_for_event_index() { let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_msg_and_tx(); msg.event_index = 2; assert_eq!( - verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &Client::faux(), + &"native_interchain_token_code".to_string(), + &"token_manager_code".to_string() + ) + .await, Vote::NotFound ); } - #[test] - fn should_not_verify_event_index_does_not_match() { + #[async_test] + async fn should_not_verify_event_index_does_not_match() { let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_msg_and_tx(); msg.event_index = 0; assert_eq!( - verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &Client::faux(), + &"native_interchain_token_code".to_string(), + &"token_manager_code".to_string() + ) + .await, Vote::NotFound ); } - #[test] - fn should_not_verify_not_gateway() { + #[async_test] + async fn should_not_verify_not_gateway() { let (source_chain, gateway_address, its_address, mut tx, msg) = get_matching_msg_and_tx(); let transaction_events = tx.events.get_mut(1).unwrap(); @@ -320,13 +362,23 @@ mod tests { contract_call.contract_id = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM".to_string(); assert_eq!( - verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &Client::faux(), + &"native_interchain_token_code".to_string(), + &"token_manager_code".to_string() + ) + .await, Vote::NotFound ); } - #[test] - fn should_not_verify_invalid_topic() { + #[async_test] + async fn should_not_verify_invalid_topic() { let (source_chain, gateway_address, its_address, mut tx, msg) = get_matching_msg_and_tx(); let transaction_events = tx.events.get_mut(1).unwrap(); @@ -335,13 +387,23 @@ mod tests { contract_call.topic = "other".to_string(); assert_eq!( - verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &Client::faux(), + &"native_interchain_token_code".to_string(), + &"token_manager_code".to_string() + ) + .await, Vote::NotFound ); } - #[test] - fn should_not_verify_invalid_type() { + #[async_test] + async fn should_not_verify_invalid_type() { let (source_chain, gateway_address, its_address, mut tx, msg) = get_matching_msg_and_tx(); let transaction_events = tx.events.get_mut(1).unwrap(); @@ -356,49 +418,89 @@ mod tests { .to_string(); assert_eq!( - verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &Client::faux(), + &"native_interchain_token_code".to_string(), + &"token_manager_code".to_string() + ) + .await, Vote::NotFound ); } - #[test] - fn should_not_verify_invalid_sender() { + #[async_test] + async fn should_not_verify_invalid_sender() { let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_msg_and_tx(); msg.source_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway".to_string(); assert_eq!( - verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &Client::faux(), + &"native_interchain_token_code".to_string(), + &"token_manager_code".to_string() + ) + .await, Vote::NotFound ); } - #[test] - fn should_not_verify_invalid_destination_chain() { + #[async_test] + async fn should_not_verify_invalid_destination_chain() { let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_msg_and_tx(); msg.destination_chain = "other".parse().unwrap(); assert_eq!( - verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &Client::faux(), + &"native_interchain_token_code".to_string(), + &"token_manager_code".to_string() + ) + .await, Vote::NotFound ); } - #[test] - fn should_not_verify_invalid_destination_address() { + #[async_test] + async fn should_not_verify_invalid_destination_address() { let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_msg_and_tx(); msg.destination_address = "other".parse().unwrap(); assert_eq!( - verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &Client::faux(), + &"native_interchain_token_code".to_string(), + &"token_manager_code".to_string() + ) + .await, Vote::NotFound ); } - #[test] - fn should_not_verify_invalid_payload_hash() { + #[async_test] + async fn should_not_verify_invalid_payload_hash() { let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_msg_and_tx(); msg.payload_hash = "0xaa38573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f4aa" @@ -406,17 +508,37 @@ mod tests { .unwrap(); assert_eq!( - verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &Client::faux(), + &"native_interchain_token_code".to_string(), + &"token_manager_code".to_string() + ) + .await, Vote::NotFound ); } - #[test] - fn should_verify_msg() { + #[async_test] + async fn should_verify_msg() { let (source_chain, gateway_address, its_address, tx, msg) = get_matching_msg_and_tx(); assert_eq!( - verify_message(&source_chain, &gateway_address, &its_address, &tx, &msg), + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &Client::faux(), + &"native_interchain_token_code".to_string(), + &"token_manager_code".to_string() + ) + .await, Vote::SucceededOnChain ); } diff --git a/ampd/src/tests/config_template.toml b/ampd/src/tests/config_template.toml index bdb13b33d..9eea65d51 100644 --- a/ampd/src/tests/config_template.toml +++ b/ampd/src/tests/config_template.toml @@ -87,6 +87,8 @@ type = 'StacksMsgVerifier' cosmwasm_contract = 'axelar1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqecnww6' http_url = 'http://127.0.0.1/' its_address = 'its_address' +reference_native_interchain_token_address = 'interchain_token_address' +reference_token_manager_address = 'token_manager_address' [[handlers]] type = 'StacksVerifierSetVerifier' From 32f339d236f079e13ce73c6db49cd97d989eccd3 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:09:43 +0300 Subject: [PATCH 14/20] Implement its verify contract for stacks. --- ampd/src/config.rs | 2 + ampd/src/handlers/stacks_verify_msg.rs | 137 +++++++++++++++---------- ampd/src/stacks/its_verifier.rs | 41 +++++++- ampd/src/stacks/verifier.rs | 1 + 4 files changed, 126 insertions(+), 55 deletions(-) diff --git a/ampd/src/config.rs b/ampd/src/config.rs index 2699a14e8..f2019d2f4 100644 --- a/ampd/src/config.rs +++ b/ampd/src/config.rs @@ -138,6 +138,8 @@ mod tests { cosmwasm_contract = '{}' http_url = 'http://localhost:8000' its_address = 'its_address' + reference_native_interchain_token_address = 'interchain_token_address' + reference_token_manager_address = 'token_manager_address' [[handlers]] type = 'StacksVerifierSetVerifier' diff --git a/ampd/src/handlers/stacks_verify_msg.rs b/ampd/src/handlers/stacks_verify_msg.rs index 3e771fe55..820dcb345 100644 --- a/ampd/src/handlers/stacks_verify_msg.rs +++ b/ampd/src/handlers/stacks_verify_msg.rs @@ -7,10 +7,10 @@ use cosmrs::cosmwasm::MsgExecuteContract; use cosmrs::tx::Msg; use cosmrs::Any; use error_stack::ResultExt; -use futures::future; use events::Error::EventTypeMismatch; use events::Event; use events_derive::try_from; +use futures::future; use router_api::ChainName; use serde::Deserialize; use tokio::sync::watch::Receiver; @@ -19,7 +19,7 @@ use voting_verifier::msg::ExecuteMsg; use crate::event_processor::EventHandler; use crate::handlers::errors::Error; -use crate::stacks::http_client::Client; +use crate::stacks::http_client::{Client, Transaction}; use crate::stacks::verifier::verify_message; use crate::types::{Hash, TMAddress}; @@ -135,26 +135,26 @@ impl EventHandler for Handler { let tx_hashes: HashSet<_> = messages.iter().map(|message| message.tx_id).collect(); let transactions = self.http_client.get_transactions(tx_hashes).await; - // TODO: See how to handle async function in map properly - let votes: Vec = future::try_join_all(messages - .iter() - .map(|msg| { - transactions - .get(&msg.tx_id) - .map_or(Vote::NotFound, |transaction| { - verify_message( - &source_chain, - &source_gateway_address, - &self.its_address, - transaction, - msg, - &self.http_client, - &self.reference_native_interchain_token_code, - &self.reference_token_manager_code, - ) - }) - })) - .await?; + let futures = messages.iter().map(|msg| async { + match transactions.get(&msg.tx_id) { + Some(transaction) => { + verify_message( + &source_chain, + &source_gateway_address, + &self.its_address, + transaction, + msg, + &self.http_client, + &self.reference_native_interchain_token_code, + &self.reference_token_manager_code, + ) + .await + } + None => Vote::NotFound, + } + }); + + let votes: Vec = future::join_all(futures).await; Ok(vec![self .vote_msg(poll_id, votes) @@ -176,10 +176,10 @@ mod tests { use tokio::test as async_test; use voting_verifier::events::{PollMetadata, PollStarted, TxEventConfirmation}; - use super::PollStartedEvent; + use super::{Handler, PollStartedEvent}; use crate::event_processor::EventHandler; use crate::handlers::tests::into_structured_event; - use crate::stacks::http_client::Client; + use crate::stacks::http_client::{Client, ContractInfo}; use crate::types::{EVMAddress, Hash, TMAddress}; use crate::PREFIX; @@ -222,17 +222,9 @@ mod tests { &TMAddress::random(PREFIX), ); - let handler = super::Handler::new( - TMAddress::random(PREFIX), - TMAddress::random(PREFIX), - Client::faux(), - watch::channel(0).1, - "its_address".to_string(), - "native_interchain_token_code".to_string(), - "token_manager_code".to_string() - ).await; + let handler = get_handler().await; - assert!(handler.unwrap().handle(&event).await.is_ok()); + assert!(handler.handle(&event).await.is_ok()); } // Should not handle event if it is not emitted from voting verifier @@ -243,22 +235,21 @@ mod tests { &TMAddress::random(PREFIX), ); - let handler = super::Handler::new( - TMAddress::random(PREFIX), - TMAddress::random(PREFIX), - Client::faux(), - watch::channel(0).1, - "its_address".to_string(), - "native_interchain_token_code".to_string(), - "token_manager_code".to_string() - ).await; + let handler = get_handler().await; - assert!(handler.unwrap().handle(&event).await.is_ok()); + assert!(handler.handle(&event).await.is_ok()); } // Should not handle event if worker is not a poll participant #[async_test] async fn verifier_is_not_a_participant() { + let mut client = Client::faux(); + faux::when!(client.get_contract_info).then(|_| { + Ok(ContractInfo { + source_code: "()".to_string(), + }) + }); + let voting_verifier = TMAddress::random(PREFIX); let event = into_structured_event(poll_started_event(participants(5, None)), &voting_verifier); @@ -266,19 +257,26 @@ mod tests { let handler = super::Handler::new( TMAddress::random(PREFIX), voting_verifier, - Client::faux(), + client, watch::channel(0).1, "its_address".to_string(), "native_interchain_token_code".to_string(), - "token_manager_code".to_string() - ).await; + "token_manager_code".to_string(), + ) + .await + .unwrap(); - assert!(handler.unwrap().handle(&event).await.is_ok()); + assert!(handler.handle(&event).await.is_ok()); } #[async_test] async fn should_vote_correctly() { let mut client = Client::faux(); + faux::when!(client.get_contract_info).then(|_| { + Ok(ContractInfo { + source_code: "()".to_string(), + }) + }); faux::when!(client.get_transactions).then(|_| HashMap::new()); let voting_verifier = TMAddress::random(PREFIX); @@ -295,10 +293,12 @@ mod tests { watch::channel(0).1, "its_address".to_string(), "native_interchain_token_code".to_string(), - "token_manager_code".to_string() - ).await; + "token_manager_code".to_string(), + ) + .await + .unwrap(); - let actual = handler.unwrap().handle(&event).await.unwrap(); + let actual = handler.handle(&event).await.unwrap(); assert_eq!(actual.len(), 1); assert!(MsgExecuteContract::from_any(actual.first().unwrap()).is_ok()); } @@ -306,6 +306,11 @@ mod tests { #[async_test] async fn should_skip_expired_poll() { let mut client = Client::faux(); + faux::when!(client.get_contract_info).then(|_| { + Ok(ContractInfo { + source_code: "()".to_string(), + }) + }); faux::when!(client.get_transactions).then(|_| HashMap::new()); let voting_verifier = TMAddress::random(PREFIX); @@ -325,8 +330,10 @@ mod tests { rx, "its_address".to_string(), "native_interchain_token_code".to_string(), - "token_manager_code".to_string() - ).await.unwrap(); + "token_manager_code".to_string(), + ) + .await + .unwrap(); // poll is not expired yet, should hit proxy let actual = handler.handle(&event).await.unwrap(); @@ -338,6 +345,30 @@ mod tests { assert_eq!(handler.handle(&event).await.unwrap(), vec![]); } + async fn get_handler() -> Handler { + let mut client = Client::faux(); + faux::when!(client.get_contract_info).then(|_| { + Ok(ContractInfo { + source_code: "()".to_string(), + }) + }); + + let handler = Handler::new( + TMAddress::random(PREFIX), + TMAddress::random(PREFIX), + client, + watch::channel(0).1, + "its_address".to_string(), + "native_interchain_token_code".to_string(), + "token_manager_code".to_string(), + ) + .await + .unwrap(); + + handler + } + + fn poll_started_event(participants: Vec) -> PollStarted { PollStarted::Messages { metadata: PollMetadata { diff --git a/ampd/src/stacks/its_verifier.rs b/ampd/src/stacks/its_verifier.rs index c523a20b5..4838e04c6 100644 --- a/ampd/src/stacks/its_verifier.rs +++ b/ampd/src/stacks/its_verifier.rs @@ -365,7 +365,14 @@ pub async fn its_verify_contract_code( let (payload, verify_type) = get_its_verify_call_params(event)?; match verify_type.as_str() { - VERIFY_INTERCHAIN_TOKEN => {} + VERIFY_INTERCHAIN_TOKEN => { + return its_verify_interchain_token( + payload, + http_client, + reference_native_interchain_token_code, + ) + .await; + } VERIFY_TOKEN_MANAGER => { return its_verify_token_manager(payload, http_client, reference_token_manager_code) .await; @@ -400,6 +407,36 @@ fn get_its_verify_call_params( Ok((payload, verify_type)) } +async fn its_verify_interchain_token( + payload: Vec, + http_client: &Client, + reference_native_interchain_token: &String, +) -> Result> { + let tuple_type_signature = TupleTypeSignature::try_from(vec![( + ClarityName::from("token-address"), + TypeSignature::PrincipalType, + )])?; + + let mut value = Value::try_deserialize_bytes( + &payload, + &TypeSignature::TupleType(tuple_type_signature), + true, + )? + .expect_tuple()?; + + let token_address = value + .data_map + .remove("token-address") + .ok_or(Error::InvalidCall)? + .expect_principal()?; + + let token_info = http_client + .get_contract_info(format!("{}", token_address).as_str()) + .await?; + + Ok(&token_info.source_code == reference_native_interchain_token) +} + async fn its_verify_token_manager( payload: Vec, http_client: &Client, @@ -582,7 +619,7 @@ mod tests { ); } - // test verify message its + // test its_verify_contract_code // TODO: fn get_matching_its_hub_interchain_transfer_msg_and_tx( diff --git a/ampd/src/stacks/verifier.rs b/ampd/src/stacks/verifier.rs index f594170fd..c07bbb92c 100644 --- a/ampd/src/stacks/verifier.rs +++ b/ampd/src/stacks/verifier.rs @@ -676,6 +676,7 @@ mod tests { contract_log: None, }; + // TODO: Update hex value to correct one let event = TransactionEvents { event_index: 1, tx_id: tx_id.to_string(), From 07eaadc0a8c0efad208dbcd80faa99d0a957a3f7 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Tue, 22 Oct 2024 12:42:20 +0300 Subject: [PATCH 15/20] Implement all its logic for stacks ampd module. --- ampd/src/config.rs | 3 +- ampd/src/handlers/stacks_verify_msg.rs | 5 +- ampd/src/stacks/its_verifier.rs | 256 +++++++++++++++++++++++-- ampd/src/stacks/verifier.rs | 16 +- 4 files changed, 256 insertions(+), 24 deletions(-) diff --git a/ampd/src/config.rs b/ampd/src/config.rs index f2019d2f4..8e945ce70 100644 --- a/ampd/src/config.rs +++ b/ampd/src/config.rs @@ -371,7 +371,8 @@ mod tests { ), http_url: Url::from_str("http://127.0.0.1").unwrap(), its_address: "its_address".to_string(), - reference_native_interchain_token_address: "interchain_token_address".to_string(), + reference_native_interchain_token_address: "interchain_token_address" + .to_string(), reference_token_manager_address: "token_manager_address".to_string(), }, HandlerConfig::StacksVerifierSetVerifier { diff --git a/ampd/src/handlers/stacks_verify_msg.rs b/ampd/src/handlers/stacks_verify_msg.rs index 820dcb345..d66153dcf 100644 --- a/ampd/src/handlers/stacks_verify_msg.rs +++ b/ampd/src/handlers/stacks_verify_msg.rs @@ -362,13 +362,12 @@ mod tests { "native_interchain_token_code".to_string(), "token_manager_code".to_string(), ) - .await - .unwrap(); + .await + .unwrap(); handler } - fn poll_started_event(participants: Vec) -> PollStarted { PollStarted::Messages { metadata: PollMetadata { diff --git a/ampd/src/stacks/its_verifier.rs b/ampd/src/stacks/its_verifier.rs index 4838e04c6..afdf6ec5a 100644 --- a/ampd/src/stacks/its_verifier.rs +++ b/ampd/src/stacks/its_verifier.rs @@ -1,17 +1,15 @@ -use crate::handlers::stacks_verify_msg::Message; -use crate::stacks::error::Error; -use crate::stacks::http_client::{Client, TransactionEvents}; -use crate::stacks::verifier::{CONTRACT_CALL_TYPE, PRINT_TOPIC}; -use crate::types::Hash; use clarity::codec::StacksMessageCodec; use clarity::vm::types::{ - BufferLength, PrincipalData, SequenceSubtype, StringSubtype, TupleData, TupleTypeSignature, - TypeSignature, + BufferLength, SequenceSubtype, StringSubtype, TupleData, TupleTypeSignature, TypeSignature, }; use clarity::vm::{ClarityName, Value}; use ethers_core::abi::{encode, Token}; use sha3::{Digest, Keccak256}; +use crate::stacks::error::Error; +use crate::stacks::http_client::{Client, TransactionEvents}; +use crate::types::Hash; + const MESSAGE_TYPE_INTERCHAIN_TRANSFER: u128 = 0; const MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN: u128 = 1; const MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER: u128 = 2; @@ -474,9 +472,8 @@ mod tests { use tokio::test as async_test; use crate::handlers::stacks_verify_msg::Message; - use crate::stacks::http_client::Client; use crate::stacks::http_client::{ - ContractLog, ContractLogValue, Transaction, TransactionEvents, + Client, ContractInfo, ContractLog, ContractLogValue, Transaction, TransactionEvents, }; use crate::stacks::verifier::verify_message; @@ -619,8 +616,121 @@ mod tests { ); } - // test its_verify_contract_code - // TODO: + #[async_test] + async fn should_not_verify_msg_its_verify_interchain_token_invalid_contract_code() { + let mut client = Client::faux(); + faux::when!(client.get_contract_info).then(|_| { + Ok(ContractInfo { + source_code: "()".to_string(), + }) + }); + + let (source_chain, gateway_address, its_address, tx, mut msg) = + get_matching_its_verify_interchain_token_msg_and_tx(); + + assert_eq!( + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &client, + &"native_interchain_token_code".to_string(), + &"token_manager_code".to_string() + ) + .await, + Vote::NotFound + ); + } + + #[async_test] + async fn should_verify_msg_its_verify_interchain_token() { + let source_code = "native_interchain_token_code"; + + let mut client = Client::faux(); + faux::when!(client.get_contract_info).then(|_| { + Ok(ContractInfo { + source_code: source_code.to_string(), + }) + }); + + let (source_chain, gateway_address, its_address, tx, mut msg) = + get_matching_its_verify_interchain_token_msg_and_tx(); + + assert_eq!( + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &client, + &source_code.to_string(), + &"token_manager_code".to_string() + ) + .await, + Vote::SucceededOnChain + ); + } + + #[async_test] + async fn should_not_verify_msg_its_verify_token_manager_invalid_contract_code() { + let mut client = Client::faux(); + faux::when!(client.get_contract_info).then(|_| { + Ok(ContractInfo { + source_code: "()".to_string(), + }) + }); + + let (source_chain, gateway_address, its_address, tx, mut msg) = + get_matching_its_verify_token_manager_msg_and_tx(); + + assert_eq!( + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &client, + &"native_interchain_token_code".to_string(), + &"token_manager_code".to_string() + ) + .await, + Vote::NotFound + ); + } + + #[async_test] + async fn should_verify_msg_its_verify_token_manager() { + let source_code = "token_manager_code"; + + let mut client = Client::faux(); + faux::when!(client.get_contract_info).then(|_| { + Ok(ContractInfo { + source_code: source_code.to_string(), + }) + }); + + let (source_chain, gateway_address, its_address, tx, mut msg) = + get_matching_its_verify_token_manager_msg_and_tx(); + + assert_eq!( + verify_message( + &source_chain, + &gateway_address, + &its_address, + &tx, + &msg, + &client, + &"native_interchain_token_code".to_string(), + &source_code.to_string(), + ) + .await, + Vote::SucceededOnChain + ); + } fn get_matching_its_hub_interchain_transfer_msg_and_tx( ) -> (ChainName, String, String, Transaction, Message) { @@ -829,4 +939,128 @@ mod tests { msg, ) } + + fn get_matching_its_verify_interchain_token_msg_and_tx( + ) -> (ChainName, String, String, Transaction, Message) { + let source_chain = "stacks"; + let gateway_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"; + let its_address = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.interchain-token-service"; + let tx_id = "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" + .parse() + .unwrap(); + + let msg = Message { + tx_id, + event_index: 1, + source_address: its_address.to_string(), + destination_chain: "stacks".parse().unwrap(), + destination_address: its_address.to_string(), + payload_hash: "0xe0a3c74b09fa9fc9ce46ab8b6984ffb079f49fc08862a949a14a6eb6ad063c75" + .parse() + .unwrap(), + }; + + let wrong_event = TransactionEvents { + event_index: 0, + tx_id: tx_id.to_string(), + contract_log: None, + }; + + /* + payload is: + { + type: 'verify-interchain-token', + token-address: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.sample-sip-010 + } + */ + let event = TransactionEvents { + event_index: 1, + tx_id: tx_id.to_string(), + contract_log: Some(ContractLog { + contract_id: gateway_address.to_string(), + topic: "print".to_string(), + value: ContractLogValue { + hex: "0x0c000000061164657374696e6174696f6e2d636861696e0d00000006737461636b731c64657374696e6174696f6e2d636f6e74726163742d616464726573730d00000042535431505148514b5630524a585a465931444758384d4e534e5956453356475a4a53525450475a474d2e696e746572636861696e2d746f6b656e2d73657276696365077061796c6f616402000001c40c000000080a6d6573736167652d69640d0000002c617070726f7665642d696e746572636861696e2d746f6b656e2d6465706c6f796d656e742d6d657373616765077061796c6f616402000000a60c0000000608646563696d616c7301000000000000000000000000000000120c6d696e7465722d6279746573020000000100046e616d650d000000176e61746976652d696e746572636861696e2d746f6b656e0673796d626f6c0d0000000349545408746f6b656e2d696402000000206c96e90b60cd71d0b948ae26be1046377a10f46441d595a6d5dd4f4a6a850372047479706501000000000000000000000000000000010e736f757263652d616464726573730d00000004307830300c736f757263652d636861696e0d00000008657468657265756d0d746f6b656e2d61646472657373061a6d78de7b0625dfbfc16c3a8a5735f6dc3dc3f2ce0e73616d706c652d7369702d30313008746f6b656e2d696402000000206c96e90b60cd71d0b948ae26be1046377a10f46441d595a6d5dd4f4a6a8503720a746f6b656e2d74797065010000000000000000000000000000000004747970650d000000177665726966792d696e746572636861696e2d746f6b656e0c7061796c6f61642d686173680200000020e0a3c74b09fa9fc9ce46ab8b6984ffb079f49fc08862a949a14a6eb6ad063c750673656e646572061a6d78de7b0625dfbfc16c3a8a5735f6dc3dc3f2ce18696e746572636861696e2d746f6b656e2d7365727669636504747970650d0000000d636f6e74726163742d63616c6c".to_string(), + } + }), + }; + + let transaction = Transaction { + tx_id, + nonce: 1, + sender_address: "whatever".to_string(), + tx_status: "success".to_string(), + events: vec![wrong_event, event], + }; + + ( + source_chain.parse().unwrap(), + gateway_address.to_string(), + its_address.to_string(), + transaction, + msg, + ) + } + + fn get_matching_its_verify_token_manager_msg_and_tx( + ) -> (ChainName, String, String, Transaction, Message) { + let source_chain = "stacks"; + let gateway_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"; + let its_address = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.interchain-token-service"; + let tx_id = "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" + .parse() + .unwrap(); + + let msg = Message { + tx_id, + event_index: 1, + source_address: its_address.to_string(), + destination_chain: "stacks".parse().unwrap(), + destination_address: its_address.to_string(), + payload_hash: "0x8488259c3537e21e92750cc757a4b99377c5149ea986e2eff7716fdaf8c4ace8" + .parse() + .unwrap(), + }; + + let wrong_event = TransactionEvents { + event_index: 0, + tx_id: tx_id.to_string(), + contract_log: None, + }; + + /* + payload is: + { + type: 'verify-token-manager', + token-manager-address: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.token-manager + } + */ + let event = TransactionEvents { + event_index: 1, + tx_id: tx_id.to_string(), + contract_log: Some(ContractLog { + contract_id: gateway_address.to_string(), + topic: "print".to_string(), + value: ContractLogValue { + hex: "0x0c000000061164657374696e6174696f6e2d636861696e0d00000006737461636b731c64657374696e6174696f6e2d636f6e74726163742d616464726573730d00000042535431505148514b5630524a585a465931444758384d4e534e5956453356475a4a53525450475a474d2e696e746572636861696e2d746f6b656e2d73657276696365077061796c6f616402000000da0c000000050d746f6b656e2d61646472657373061a6d78de7b0625dfbfc16c3a8a5735f6dc3dc3f2ce0e73616d706c652d7369702d30313008746f6b656e2d69640200000020289df9e77347122b6306bc2db1fa9387bb8b851d685ff3ee92d18335abd1c10c15746f6b656e2d6d616e616765722d61646472657373061a6d78de7b0625dfbfc16c3a8a5735f6dc3dc3f2ce0d746f6b656e2d6d616e616765720a746f6b656e2d74797065010000000000000000000000000000000204747970650d000000147665726966792d746f6b656e2d6d616e616765720c7061796c6f61642d6861736802000000208488259c3537e21e92750cc757a4b99377c5149ea986e2eff7716fdaf8c4ace80673656e646572061a6d78de7b0625dfbfc16c3a8a5735f6dc3dc3f2ce18696e746572636861696e2d746f6b656e2d7365727669636504747970650d0000000d636f6e74726163742d63616c6c".to_string(), + } + }), + }; + + let transaction = Transaction { + tx_id, + nonce: 1, + sender_address: "whatever".to_string(), + tx_status: "success".to_string(), + events: vec![wrong_event, event], + }; + + ( + source_chain.parse().unwrap(), + gateway_address.to_string(), + its_address.to_string(), + transaction, + msg, + ) + } } diff --git a/ampd/src/stacks/verifier.rs b/ampd/src/stacks/verifier.rs index c07bbb92c..62a0e796e 100644 --- a/ampd/src/stacks/verifier.rs +++ b/ampd/src/stacks/verifier.rs @@ -48,7 +48,7 @@ impl Message { ( ClarityName::from("destination-contract-address"), TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( - BufferLength::try_from(48u32)?, + BufferLength::try_from(128u32)?, ))), ), ( @@ -278,9 +278,8 @@ mod tests { use crate::handlers::stacks_verify_msg::Message; use crate::handlers::stacks_verify_verifier_set::VerifierSetConfirmation; - use crate::stacks::http_client::Client; use crate::stacks::http_client::{ - ContractLog, ContractLogValue, Transaction, TransactionEvents, + Client, ContractLog, ContractLogValue, Transaction, TransactionEvents, }; use crate::stacks::verifier::{verify_message, verify_verifier_set, SIGNERS_ROTATED_TYPE}; @@ -662,10 +661,10 @@ mod tests { let msg = Message { tx_id, event_index: 1, - source_address: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM".to_string(), - destination_chain: "ethereum".parse().unwrap(), - destination_address: "0x043E105189e15AC72252CFEF898EC3841A4A0561".to_string(), - payload_hash: "0x0338573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f46d" + source_address: "ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG".to_string(), + destination_chain: "Destination".parse().unwrap(), + destination_address: "0x123abc".to_string(), + payload_hash: "0x9ed02951dbf029855b46b102cc960362732569e83d00a49a7575d7aed229890e" .parse() .unwrap(), }; @@ -676,7 +675,6 @@ mod tests { contract_log: None, }; - // TODO: Update hex value to correct one let event = TransactionEvents { event_index: 1, tx_id: tx_id.to_string(), @@ -684,7 +682,7 @@ mod tests { contract_id: gateway_address.to_string(), topic: "print".to_string(), value: ContractLogValue { - hex: "0x0c000000061164657374696e6174696f6e2d636861696e0200000008657468657265756d1c64657374696e6174696f6e2d636f6e74726163742d61646472657373020000002a307830343345313035313839653135414337323235324346454638393845433338343141344130353631077061796c6f616402000000196c6f72656d697073756d20646f6c6f722073697420616d65740c7061796c6f61642d6861736802000000200338573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f46d0673656e646572051a6d78de7b0625dfbfc16c3a8a5735f6dc3dc3f2ce04747970650d0000000d636f6e74726163742d63616c6c".to_string(), + hex: "0x0c000000061164657374696e6174696f6e2d636861696e0d0000000b64657374696e6174696f6e1c64657374696e6174696f6e2d636f6e74726163742d616464726573730d000000083078313233616263077061796c6f61640200000029535431534a3344544535444e375835345944483544363452334243423641324147325a5138595044350c7061796c6f61642d6861736802000000209ed02951dbf029855b46b102cc960362732569e83d00a49a7575d7aed229890e0673656e646572051a99e2ec69ac5b6e67b4e26edd0e2c1c1a6b9bbd2304747970650d0000000d636f6e74726163742d63616c6c".to_string(), } }), }; From 74f95d56025713d16912cea993f75aa614203af3 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Mon, 28 Oct 2024 09:40:08 +0200 Subject: [PATCH 16/20] Fix wrong tx hash for stacks. --- ampd/src/stacks/http_client.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ampd/src/stacks/http_client.rs b/ampd/src/stacks/http_client.rs index 63bef6b42..721534b96 100644 --- a/ampd/src/stacks/http_client.rs +++ b/ampd/src/stacks/http_client.rs @@ -2,12 +2,13 @@ use std::collections::{HashMap, HashSet}; use std::fmt::Debug; use futures::future::join_all; +use hex::ToHex; use serde::Deserialize; use thiserror::Error; use crate::types::Hash; -const GET_TRANSACTION: &str = "extended/v1/tx/"; +const GET_TRANSACTION: &str = "extended/v1/tx/0x"; const GET_CONTRACT_INFO: &str = "extended/v1/contract/"; const STATUS_SUCCESS: &str = "success"; @@ -92,7 +93,7 @@ impl Client { } pub async fn get_valid_transaction(&self, tx_hash: &Hash) -> Option { - self.get_transaction(tx_hash.to_string().as_str()) + self.get_transaction(tx_hash.encode_hex::().as_str()) .await .ok() .filter(Self::is_valid_transaction) From 4fe3c69d15d06199954481619c40b17f6b7825c5 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:11:43 +0200 Subject: [PATCH 17/20] Updates to stacks type sizes. --- ampd/src/stacks/its_verifier.rs | 30 +++++++++++++++--------------- ampd/src/stacks/mod.rs | 2 +- ampd/src/stacks/verifier.rs | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/ampd/src/stacks/its_verifier.rs b/ampd/src/stacks/its_verifier.rs index afdf6ec5a..e03af7bfe 100644 --- a/ampd/src/stacks/its_verifier.rs +++ b/ampd/src/stacks/its_verifier.rs @@ -35,7 +35,7 @@ pub fn get_its_hub_payload_hash( .get("destination-chain")? .clone() .expect_ascii()?; - let payload = tuple_data.get_owned("payload")?.expect_buff(10240)?; + let payload = tuple_data.get_owned("payload")?.expect_buff(63_000)?; let subtuple_type_signature = TupleTypeSignature::try_from(vec![(ClarityName::from("type"), TypeSignature::UIntType)])?; @@ -82,13 +82,13 @@ fn get_its_hub_call_params( ( ClarityName::from("destination-chain"), TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( - BufferLength::try_from(18u32)?, + BufferLength::try_from(20u32)?, ))), ), ( ClarityName::from("payload"), TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( - 10240u32, + 63_000u32, )?)), ), ])?; @@ -111,7 +111,7 @@ fn get_payload_from_contract_call_event( let contract_call_signature = TupleTypeSignature::try_from(vec![( ClarityName::from("payload"), TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( - 10240u32, + 64_000u32, )?)), )])?; @@ -130,7 +130,7 @@ fn get_payload_from_contract_call_event( let payload = contract_call_value .expect_tuple()? .get_owned("payload")? - .expect_buff(10240)?; + .expect_buff(64_000)?; Ok(payload) } @@ -152,14 +152,14 @@ fn get_its_interchain_transfer_abi_payload( ( ClarityName::from("destination-address"), TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( - 100u32, + 128u32, )?)), ), (ClarityName::from("amount"), TypeSignature::UIntType), ( ClarityName::from("data"), TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( - 1024u32, + 62_000u32, )?)), ), ])?; @@ -193,7 +193,7 @@ fn get_its_interchain_transfer_abi_payload( .data_map .remove("destination-address") .ok_or(Error::InvalidCall)? - .expect_buff(100)?, + .expect_buff(128)?, ), Token::Uint( original_value @@ -208,7 +208,7 @@ fn get_its_interchain_transfer_abi_payload( .data_map .remove("data") .ok_or(Error::InvalidCall)? - .expect_buff(1024)?, + .expect_buff(62_000)?, ), ]); @@ -228,20 +228,20 @@ fn get_its_deploy_interchain_token_abi_payload( ( ClarityName::from("name"), TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( - BufferLength::try_from(32u32)?, + BufferLength::try_from(64u32)?, ))), ), ( ClarityName::from("symbol"), TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( - BufferLength::try_from(32u32)?, + BufferLength::try_from(16u32)?, ))), ), (ClarityName::from("decimals"), TypeSignature::UIntType), ( ClarityName::from("minter"), TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( - 200u32, + 128u32, )?)), ), ])?; @@ -289,7 +289,7 @@ fn get_its_deploy_interchain_token_abi_payload( .data_map .remove("minter") .ok_or(Error::InvalidCall)? - .expect_buff(200)?, + .expect_buff(128)?, ), ]); @@ -313,7 +313,7 @@ fn get_its_deploy_token_manager_payload( ( ClarityName::from("params"), TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( - 1024u32, + 62_000u32, )?)), ), ])?; @@ -347,7 +347,7 @@ fn get_its_deploy_token_manager_payload( .data_map .remove("params") .ok_or(Error::InvalidCall)? - .expect_buff(1024)?, + .expect_buff(62_000)?, ), ]); diff --git a/ampd/src/stacks/mod.rs b/ampd/src/stacks/mod.rs index 8a1029ac5..cfc89248c 100644 --- a/ampd/src/stacks/mod.rs +++ b/ampd/src/stacks/mod.rs @@ -119,7 +119,7 @@ impl WeightedSigners { Value::list_with_type( &StacksEpochId::latest(), weighted_signers, - ListTypeData::new_list(TypeSignature::from(signer_type_signature), 48) + ListTypeData::new_list(TypeSignature::from(signer_type_signature), 100) .change_context(Error::InvalidEncoding)?, ) .map_err(|_| Error::InvalidEncoding)?, diff --git a/ampd/src/stacks/verifier.rs b/ampd/src/stacks/verifier.rs index 62a0e796e..7ed2f4f9f 100644 --- a/ampd/src/stacks/verifier.rs +++ b/ampd/src/stacks/verifier.rs @@ -42,7 +42,7 @@ impl Message { ( ClarityName::from("destination-chain"), TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( - BufferLength::try_from(32u32)?, + BufferLength::try_from(20u32)?, ))), ), ( From ff5896163eb530885db83c805bc293061f0cd8a9 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Mon, 28 Oct 2024 17:12:20 +0200 Subject: [PATCH 18/20] Update token limits. --- ampd/src/stacks/its_verifier.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ampd/src/stacks/its_verifier.rs b/ampd/src/stacks/its_verifier.rs index e03af7bfe..a75aef6d7 100644 --- a/ampd/src/stacks/its_verifier.rs +++ b/ampd/src/stacks/its_verifier.rs @@ -228,13 +228,13 @@ fn get_its_deploy_interchain_token_abi_payload( ( ClarityName::from("name"), TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( - BufferLength::try_from(64u32)?, + BufferLength::try_from(32u32)?, ))), ), ( ClarityName::from("symbol"), TypeSignature::SequenceType(SequenceSubtype::StringType(StringSubtype::ASCII( - BufferLength::try_from(16u32)?, + BufferLength::try_from(32u32)?, ))), ), (ClarityName::from("decimals"), TypeSignature::UIntType), From ba5d24bb388e514ed2374f0bda584c46a44d8113 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Fri, 8 Nov 2024 13:11:30 +0200 Subject: [PATCH 19/20] Update stacks integration to use the new message_id from events. --- ampd/src/handlers/stacks_verify_msg.rs | 102 +++++++++++------- .../handlers/stacks_verify_verifier_set.rs | 35 +++--- ampd/src/stacks/http_client.rs | 2 +- ampd/src/stacks/its_verifier.rs | 80 +++++++------- ampd/src/stacks/verifier.rs | 63 +++++------ 5 files changed, 144 insertions(+), 138 deletions(-) diff --git a/ampd/src/handlers/stacks_verify_msg.rs b/ampd/src/handlers/stacks_verify_msg.rs index e672bc69a..63990fc94 100644 --- a/ampd/src/handlers/stacks_verify_msg.rs +++ b/ampd/src/handlers/stacks_verify_msg.rs @@ -2,6 +2,7 @@ use std::collections::HashSet; use std::convert::TryInto; use async_trait::async_trait; +use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; use axelar_wasm_std::voting::{PollId, Vote}; use cosmrs::cosmwasm::MsgExecuteContract; use cosmrs::tx::Msg; @@ -14,12 +15,13 @@ use futures::future; use router_api::ChainName; use serde::Deserialize; use tokio::sync::watch::Receiver; -use tracing::info; +use tracing::{info, info_span}; +use valuable::Valuable; use voting_verifier::msg::ExecuteMsg; use crate::event_processor::EventHandler; use crate::handlers::errors::Error; -use crate::stacks::http_client::{Client, Transaction}; +use crate::stacks::http_client::Client; use crate::stacks::verifier::verify_message; use crate::types::{Hash, TMAddress}; @@ -27,10 +29,9 @@ type Result = error_stack::Result; #[derive(Deserialize, Debug)] pub struct Message { - pub tx_id: Hash, - pub event_index: u32, + pub message_id: HexTxHashAndEventIndex, pub destination_address: String, - pub destination_chain: router_api::ChainName, + pub destination_chain: ChainName, pub source_address: String, pub payload_hash: Hash, } @@ -132,29 +133,55 @@ impl EventHandler for Handler { return Ok(vec![]); } - let tx_hashes: HashSet<_> = messages.iter().map(|message| message.tx_id).collect(); + let tx_hashes: HashSet = messages + .iter() + .map(|message| message.message_id.tx_hash.into()) + .collect(); let transactions = self.http_client.get_transactions(tx_hashes).await; - let futures = messages.iter().map(|msg| async { - match transactions.get(&msg.tx_id) { - Some(transaction) => { - verify_message( - &source_chain, - &source_gateway_address, - &self.its_address, - transaction, - msg, - &self.http_client, - &self.reference_native_interchain_token_code, - &self.reference_token_manager_code, - ) - .await + let message_ids = messages + .iter() + .map(|message| message.message_id.to_string()) + .collect::>(); + + let votes = info_span!( + "verify messages in poll", + poll_id = poll_id.to_string(), + source_chain = source_chain.to_string(), + message_ids = message_ids.as_value() + ) + .in_scope(|| async { + info!("ready to verify messages in poll",); + + let futures = messages.iter().map(|msg| async { + match transactions.get(&msg.message_id.tx_hash.into()) { + Some(transaction) => { + verify_message( + &source_chain, + &source_gateway_address, + &self.its_address, + transaction, + msg, + &self.http_client, + &self.reference_native_interchain_token_code, + &self.reference_token_manager_code, + ) + .await + } + None => Vote::NotFound, } - None => Vote::NotFound, - } - }); + }); - let votes: Vec = future::join_all(futures).await; + let votes: Vec = future::join_all(futures).await; + + info!( + votes = votes.as_value(), + "ready to vote for messages in poll" + ); + + votes + }) + .await; Ok(vec![self .vote_msg(poll_id, votes) @@ -168,6 +195,7 @@ mod tests { use std::collections::HashMap; use std::convert::TryInto; + use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; use cosmrs::cosmwasm::MsgExecuteContract; use cosmrs::tx::Msg; use cosmwasm_std; @@ -176,7 +204,7 @@ mod tests { use tokio::test as async_test; use voting_verifier::events::{PollMetadata, PollStarted, TxEventConfirmation}; - use super::{Handler, PollStartedEvent}; + use super::{Handler, Message, PollStartedEvent}; use crate::event_processor::EventHandler; use crate::handlers::tests::into_structured_event; use crate::stacks::http_client::{Client, ContractInfo}; @@ -201,15 +229,9 @@ mod tests { == "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway" ); - let message = event.messages.first().unwrap(); + let message: &Message = event.messages.first().unwrap(); - assert!( - message.tx_id - == "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" - .parse() - .unwrap(), - ); - assert!(message.event_index == 1u32); + assert!(message.message_id.event_index == 1u64); assert!(message.destination_chain == "ethereum"); assert!(message.source_address == "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM"); } @@ -369,6 +391,8 @@ mod tests { } fn poll_started_event(participants: Vec) -> PollStarted { + let msg_id = HexTxHashAndEventIndex::new(Hash::random(), 1u64); + PollStarted::Messages { metadata: PollMetadata { poll_id: "100".parse().unwrap(), @@ -383,15 +407,11 @@ mod tests { .map(|addr| cosmwasm_std::Addr::unchecked(addr.to_string())) .collect(), }, + #[allow(deprecated)] // TODO: The below events use the deprecated tx_id and event_index fields. Remove this attribute when those fields are removed messages: vec![TxEventConfirmation { - tx_id: "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" - .parse() - .unwrap(), - event_index: 1, - message_id: "0xdfaf64de66510723f2efbacd7ead3c4f8c856aed1afc2cb30254552aeda47312-1" - .to_string() - .parse() - .unwrap(), + tx_id: msg_id.tx_hash_as_hex(), + event_index: u32::try_from(msg_id.event_index).unwrap(), + message_id: msg_id.to_string().parse().unwrap(), source_address: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM".parse().unwrap(), destination_chain: "ethereum".parse().unwrap(), destination_address: format!("0x{:x}", EVMAddress::random()).parse().unwrap(), diff --git a/ampd/src/handlers/stacks_verify_verifier_set.rs b/ampd/src/handlers/stacks_verify_verifier_set.rs index 666051000..051b6444d 100644 --- a/ampd/src/handlers/stacks_verify_verifier_set.rs +++ b/ampd/src/handlers/stacks_verify_verifier_set.rs @@ -1,6 +1,7 @@ use std::convert::TryInto; use async_trait::async_trait; +use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; use axelar_wasm_std::voting::{PollId, Vote}; use cosmrs::cosmwasm::MsgExecuteContract; use cosmrs::tx::Msg; @@ -20,12 +21,11 @@ use crate::event_processor::EventHandler; use crate::handlers::errors::Error; use crate::stacks::http_client::Client; use crate::stacks::verifier::verify_verifier_set; -use crate::types::{Hash, TMAddress}; +use crate::types::TMAddress; #[derive(Deserialize, Debug)] pub struct VerifierSetConfirmation { - pub tx_id: Hash, - pub event_index: u32, + pub message_id: HexTxHashAndEventIndex, pub verifier_set: VerifierSet, } @@ -110,13 +110,13 @@ impl EventHandler for Handler { let transaction = self .http_client - .get_valid_transaction(&verifier_set.tx_id) + .get_valid_transaction(&verifier_set.message_id.tx_hash.into()) .await; let vote = info_span!( "verify a new verifier set for Stacks", poll_id = poll_id.to_string(), - id = format!("{}_{}", verifier_set.tx_id, verifier_set.event_index) + id = verifier_set.message_id.to_string(), ) .in_scope(|| { info!("ready to verify a new worker set in poll"); @@ -143,6 +143,7 @@ impl EventHandler for Handler { mod tests { use std::convert::TryInto; + use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; use cosmrs::cosmwasm::MsgExecuteContract; use cosmrs::tx::Msg; use cosmwasm_std; @@ -158,7 +159,7 @@ mod tests { use crate::event_processor::EventHandler; use crate::handlers::tests::into_structured_event; use crate::stacks::http_client::Client; - use crate::types::TMAddress; + use crate::types::{Hash, TMAddress}; use crate::PREFIX; #[test] @@ -181,13 +182,7 @@ mod tests { let verifier_set = event.verifier_set; - assert!( - verifier_set.tx_id - == "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" - .parse() - .unwrap() - ); - assert!(verifier_set.event_index == 1u32); + assert!(verifier_set.message_id.event_index == 1u64); assert!(verifier_set.verifier_set.signers.len() == 3); assert_eq!(verifier_set.verifier_set.threshold, Uint128::from(2u128)); @@ -322,6 +317,8 @@ mod tests { participants: Vec, expires_at: u64, ) -> PollStarted { + let msg_id = HexTxHashAndEventIndex::new(Hash::random(), 1u64); + PollStarted::VerifierSet { metadata: PollMetadata { poll_id: "100".parse().unwrap(), @@ -336,15 +333,11 @@ mod tests { .map(|addr| cosmwasm_std::Addr::unchecked(addr.to_string())) .collect(), }, + #[allow(deprecated)] // TODO: The below events use the deprecated tx_id and event_index fields. Remove this attribute when those fields are removed verifier_set: VerifierSetConfirmation { - tx_id: "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" - .parse() - .unwrap(), - event_index: 1, - message_id: "0xdfaf64de66510723f2efbacd7ead3c4f8c856aed1afc2cb30254552aeda47312-1" - .to_string() - .parse() - .unwrap(), + tx_id: msg_id.tx_hash_as_hex(), + event_index: u32::try_from(msg_id.event_index).unwrap(), + message_id: msg_id.to_string().parse().unwrap(), verifier_set: build_verifier_set(KeyType::Ecdsa, &ecdsa_test_data::signers()), }, } diff --git a/ampd/src/stacks/http_client.rs b/ampd/src/stacks/http_client.rs index 721534b96..0d8856b0f 100644 --- a/ampd/src/stacks/http_client.rs +++ b/ampd/src/stacks/http_client.rs @@ -37,7 +37,7 @@ pub struct ContractLog { #[derive(Debug, Deserialize, Default)] pub struct TransactionEvents { - pub event_index: u32, + pub event_index: u64, pub tx_id: String, pub contract_log: Option, } diff --git a/ampd/src/stacks/its_verifier.rs b/ampd/src/stacks/its_verifier.rs index a75aef6d7..01a282ecc 100644 --- a/ampd/src/stacks/its_verifier.rs +++ b/ampd/src/stacks/its_verifier.rs @@ -467,6 +467,7 @@ async fn its_verify_token_manager( #[cfg(test)] mod tests { + use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; use axelar_wasm_std::voting::Vote; use router_api::ChainName; use tokio::test as async_test; @@ -476,6 +477,7 @@ mod tests { Client, ContractInfo, ContractLog, ContractLogValue, Transaction, TransactionEvents, }; use crate::stacks::verifier::verify_message; + use crate::types::Hash; // test verify message its hub #[async_test] @@ -625,7 +627,7 @@ mod tests { }) }); - let (source_chain, gateway_address, its_address, tx, mut msg) = + let (source_chain, gateway_address, its_address, tx, msg) = get_matching_its_verify_interchain_token_msg_and_tx(); assert_eq!( @@ -655,7 +657,7 @@ mod tests { }) }); - let (source_chain, gateway_address, its_address, tx, mut msg) = + let (source_chain, gateway_address, its_address, tx, msg) = get_matching_its_verify_interchain_token_msg_and_tx(); assert_eq!( @@ -683,7 +685,7 @@ mod tests { }) }); - let (source_chain, gateway_address, its_address, tx, mut msg) = + let (source_chain, gateway_address, its_address, tx, msg) = get_matching_its_verify_token_manager_msg_and_tx(); assert_eq!( @@ -713,7 +715,7 @@ mod tests { }) }); - let (source_chain, gateway_address, its_address, tx, mut msg) = + let (source_chain, gateway_address, its_address, tx, msg) = get_matching_its_verify_token_manager_msg_and_tx(); assert_eq!( @@ -737,13 +739,11 @@ mod tests { let source_chain = "stacks"; let gateway_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"; let its_address = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.interchain-token-service"; - let tx_id = "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" - .parse() - .unwrap(); + + let message_id = HexTxHashAndEventIndex::new(Hash::random(), 1u64); let msg = Message { - tx_id, - event_index: 1, + message_id: message_id.clone(), source_address: its_address.to_string(), destination_chain: "axelar".parse().unwrap(), destination_address: "cosmwasm".to_string(), @@ -754,7 +754,7 @@ mod tests { let wrong_event = TransactionEvents { event_index: 0, - tx_id: tx_id.to_string(), + tx_id: message_id.tx_hash_as_hex().to_string(), contract_log: None, }; @@ -775,7 +775,7 @@ mod tests { */ let event = TransactionEvents { event_index: 1, - tx_id: tx_id.to_string(), + tx_id: message_id.tx_hash_as_hex().to_string(), contract_log: Some(ContractLog { contract_id: gateway_address.to_string(), topic: "print".to_string(), @@ -786,7 +786,7 @@ mod tests { }; let transaction = Transaction { - tx_id, + tx_id: message_id.tx_hash.into(), nonce: 1, sender_address: "whatever".to_string(), tx_status: "success".to_string(), @@ -807,13 +807,11 @@ mod tests { let source_chain = "stacks"; let gateway_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"; let its_address = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.interchain-token-service"; - let tx_id = "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" - .parse() - .unwrap(); + + let message_id = HexTxHashAndEventIndex::new(Hash::random(), 1u64); let msg = Message { - tx_id, - event_index: 1, + message_id: message_id.clone(), source_address: its_address.to_string(), destination_chain: "axelar".parse().unwrap(), destination_address: "0x00".to_string(), @@ -824,7 +822,7 @@ mod tests { let wrong_event = TransactionEvents { event_index: 0, - tx_id: tx_id.to_string(), + tx_id: message_id.tx_hash_as_hex().to_string(), contract_log: None, }; @@ -845,7 +843,7 @@ mod tests { */ let event = TransactionEvents { event_index: 1, - tx_id: tx_id.to_string(), + tx_id: message_id.tx_hash_as_hex().to_string(), contract_log: Some(ContractLog { contract_id: gateway_address.to_string(), topic: "print".to_string(), @@ -856,7 +854,7 @@ mod tests { }; let transaction = Transaction { - tx_id, + tx_id: message_id.tx_hash.into(), nonce: 1, sender_address: "whatever".to_string(), tx_status: "success".to_string(), @@ -877,13 +875,11 @@ mod tests { let source_chain = "stacks"; let gateway_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"; let its_address = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.interchain-token-service"; - let tx_id = "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" - .parse() - .unwrap(); + + let message_id = HexTxHashAndEventIndex::new(Hash::random(), 1u64); let msg = Message { - tx_id, - event_index: 1, + message_id: message_id.clone(), source_address: its_address.to_string(), destination_chain: "axelar".parse().unwrap(), destination_address: "cosmwasm".to_string(), @@ -894,7 +890,7 @@ mod tests { let wrong_event = TransactionEvents { event_index: 0, - tx_id: tx_id.to_string(), + tx_id: message_id.tx_hash_as_hex().to_string(), contract_log: None, }; @@ -913,7 +909,7 @@ mod tests { */ let event = TransactionEvents { event_index: 1, - tx_id: tx_id.to_string(), + tx_id: message_id.tx_hash_as_hex().to_string(), contract_log: Some(ContractLog { contract_id: gateway_address.to_string(), topic: "print".to_string(), @@ -924,7 +920,7 @@ mod tests { }; let transaction = Transaction { - tx_id, + tx_id: message_id.tx_hash.into(), nonce: 1, sender_address: "whatever".to_string(), tx_status: "success".to_string(), @@ -945,13 +941,11 @@ mod tests { let source_chain = "stacks"; let gateway_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"; let its_address = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.interchain-token-service"; - let tx_id = "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" - .parse() - .unwrap(); + + let message_id = HexTxHashAndEventIndex::new(Hash::random(), 1u64); let msg = Message { - tx_id, - event_index: 1, + message_id: message_id.clone(), source_address: its_address.to_string(), destination_chain: "stacks".parse().unwrap(), destination_address: its_address.to_string(), @@ -962,7 +956,7 @@ mod tests { let wrong_event = TransactionEvents { event_index: 0, - tx_id: tx_id.to_string(), + tx_id: message_id.tx_hash_as_hex().to_string(), contract_log: None, }; @@ -975,7 +969,7 @@ mod tests { */ let event = TransactionEvents { event_index: 1, - tx_id: tx_id.to_string(), + tx_id: message_id.tx_hash_as_hex().to_string(), contract_log: Some(ContractLog { contract_id: gateway_address.to_string(), topic: "print".to_string(), @@ -986,7 +980,7 @@ mod tests { }; let transaction = Transaction { - tx_id, + tx_id: message_id.tx_hash.into(), nonce: 1, sender_address: "whatever".to_string(), tx_status: "success".to_string(), @@ -1007,13 +1001,11 @@ mod tests { let source_chain = "stacks"; let gateway_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"; let its_address = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.interchain-token-service"; - let tx_id = "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" - .parse() - .unwrap(); + + let message_id = HexTxHashAndEventIndex::new(Hash::random(), 1u64); let msg = Message { - tx_id, - event_index: 1, + message_id: message_id.clone(), source_address: its_address.to_string(), destination_chain: "stacks".parse().unwrap(), destination_address: its_address.to_string(), @@ -1024,7 +1016,7 @@ mod tests { let wrong_event = TransactionEvents { event_index: 0, - tx_id: tx_id.to_string(), + tx_id: message_id.tx_hash_as_hex().to_string(), contract_log: None, }; @@ -1037,7 +1029,7 @@ mod tests { */ let event = TransactionEvents { event_index: 1, - tx_id: tx_id.to_string(), + tx_id: message_id.tx_hash_as_hex().to_string(), contract_log: Some(ContractLog { contract_id: gateway_address.to_string(), topic: "print".to_string(), @@ -1048,7 +1040,7 @@ mod tests { }; let transaction = Transaction { - tx_id, + tx_id: message_id.tx_hash.into(), nonce: 1, sender_address: "whatever".to_string(), tx_status: "success".to_string(), diff --git a/ampd/src/stacks/verifier.rs b/ampd/src/stacks/verifier.rs index 7ed2f4f9f..851fe35c3 100644 --- a/ampd/src/stacks/verifier.rs +++ b/ampd/src/stacks/verifier.rs @@ -177,7 +177,7 @@ impl VerifierSetConfirmation { fn find_event<'a>( transaction: &'a Transaction, gateway_address: &String, - log_index: u32, + log_index: u64, ) -> Option<&'a TransactionEvents> { let event = transaction .events @@ -201,11 +201,11 @@ pub async fn verify_message( reference_native_interchain_token_code: &String, reference_token_manager_code: &String, ) -> Vote { - if message.tx_id != transaction.tx_id { + if message.message_id.tx_hash != transaction.tx_id.as_bytes() { return Vote::NotFound; } - match find_event(transaction, gateway_address, message.event_index) { + match find_event(transaction, gateway_address, message.message_id.event_index) { Some(event) => { // In case message is not from ITS if &message.source_address != its_address { @@ -255,11 +255,15 @@ pub fn verify_verifier_set( transaction: &Transaction, verifier_set: VerifierSetConfirmation, ) -> Vote { - if verifier_set.tx_id != transaction.tx_id { + if verifier_set.message_id.tx_hash != transaction.tx_id.as_bytes() { return Vote::NotFound; } - match find_event(transaction, gateway_address, verifier_set.event_index) { + match find_event( + transaction, + gateway_address, + verifier_set.message_id.event_index, + ) { Some(event) if verifier_set.eq_event(event).unwrap_or(false) => Vote::SucceededOnChain, _ => Vote::NotFound, } @@ -267,6 +271,7 @@ pub fn verify_verifier_set( #[cfg(test)] mod tests { + use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; use axelar_wasm_std::voting::Vote; use clarity::vm::types::TupleData; use clarity::vm::{ClarityName, Value}; @@ -282,15 +287,15 @@ mod tests { Client, ContractLog, ContractLogValue, Transaction, TransactionEvents, }; use crate::stacks::verifier::{verify_message, verify_verifier_set, SIGNERS_ROTATED_TYPE}; + use crate::types::Hash; // test verify message #[async_test] async fn should_not_verify_tx_id_does_not_match() { let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_msg_and_tx(); - msg.tx_id = "ffaf64de66510723f2efbacd7ead3c4f8c856aed1afc2cb30254552aeda47313" - .parse() - .unwrap(); + msg.message_id.tx_hash = Hash::random().into(); + assert_eq!( verify_message( &source_chain, @@ -311,7 +316,7 @@ mod tests { async fn should_not_verify_no_log_for_event_index() { let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_msg_and_tx(); - msg.event_index = 2; + msg.message_id.event_index = 2; assert_eq!( verify_message( @@ -333,7 +338,7 @@ mod tests { async fn should_not_verify_event_index_does_not_match() { let (source_chain, gateway_address, its_address, tx, mut msg) = get_matching_msg_and_tx(); - msg.event_index = 0; + msg.message_id.event_index = 0; assert_eq!( verify_message( @@ -547,9 +552,8 @@ mod tests { fn should_not_verify_verifier_set_if_tx_id_does_not_match() { let (gateway_address, tx, mut verifier_set) = get_matching_verifier_set_and_tx(); - verifier_set.tx_id = "ffaf64de66510723f2efbacd7ead3c4f8c856aed1afc2cb30254552aeda47313" - .parse() - .unwrap(); + verifier_set.message_id.tx_hash = Hash::random().into(); + assert_eq!( verify_verifier_set(&gateway_address, &tx, verifier_set), Vote::NotFound @@ -560,7 +564,8 @@ mod tests { fn should_not_verify_verifier_set_if_no_log_for_event_index() { let (gateway_address, tx, mut verifier_set) = get_matching_verifier_set_and_tx(); - verifier_set.event_index = 2; + verifier_set.message_id.event_index = 2; + assert_eq!( verify_verifier_set(&gateway_address, &tx, verifier_set), Vote::NotFound @@ -571,7 +576,8 @@ mod tests { fn should_not_verify_verifier_set_if_event_index_does_not_match() { let (gateway_address, tx, mut verifier_set) = get_matching_verifier_set_and_tx(); - verifier_set.event_index = 0; + verifier_set.message_id.event_index = 0; + assert_eq!( verify_verifier_set(&gateway_address, &tx, verifier_set), Vote::NotFound @@ -654,13 +660,11 @@ mod tests { let source_chain = "stacks"; let gateway_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"; let its_address = "ST2TFVBMRPS5SSNP98DQKQ5JNB2B6NZM91C4K3P7B.its"; - let tx_id = "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" - .parse() - .unwrap(); + + let message_id = HexTxHashAndEventIndex::new(Hash::random(), 1u64); let msg = Message { - tx_id, - event_index: 1, + message_id: message_id.clone(), source_address: "ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG".to_string(), destination_chain: "Destination".parse().unwrap(), destination_address: "0x123abc".to_string(), @@ -671,13 +675,13 @@ mod tests { let wrong_event = TransactionEvents { event_index: 0, - tx_id: tx_id.to_string(), + tx_id: message_id.tx_hash_as_hex().to_string(), contract_log: None, }; let event = TransactionEvents { event_index: 1, - tx_id: tx_id.to_string(), + tx_id: message_id.tx_hash_as_hex().to_string(), contract_log: Some(ContractLog { contract_id: gateway_address.to_string(), topic: "print".to_string(), @@ -688,7 +692,7 @@ mod tests { }; let transaction = Transaction { - tx_id, + tx_id: message_id.tx_hash.into(), nonce: 1, sender_address: "whatever".to_string(), tx_status: "success".to_string(), @@ -706,20 +710,17 @@ mod tests { fn get_matching_verifier_set_and_tx() -> (String, Transaction, VerifierSetConfirmation) { let gateway_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"; - let tx_id = "0xee0049faf8dde5507418140ed72bd64f73cc001b08de98e0c16a3a8d9f2c38cf" - .parse() - .unwrap(); + let message_id = HexTxHashAndEventIndex::new(Hash::random(), 1u64); let mut verifier_set_confirmation = VerifierSetConfirmation { - tx_id, - event_index: 1, + message_id: message_id.clone(), verifier_set: build_verifier_set(KeyType::Ecdsa, &ecdsa_test_data::signers()), }; verifier_set_confirmation.verifier_set.created_at = 5; let wrong_event = TransactionEvents { event_index: 0, - tx_id: tx_id.to_string(), + tx_id: message_id.tx_hash_as_hex().to_string(), contract_log: None, }; @@ -744,7 +745,7 @@ mod tests { let event = TransactionEvents { event_index: 1, - tx_id: tx_id.to_string(), + tx_id: message_id.tx_hash_as_hex().to_string(), contract_log: Some(ContractLog { contract_id: gateway_address.to_string(), topic: "print".to_string(), @@ -755,7 +756,7 @@ mod tests { }; let transaction = Transaction { - tx_id, + tx_id: message_id.tx_hash.into(), nonce: 1, sender_address: "whatever".to_string(), tx_status: "success".to_string(), From 8755655a0e200fca061e3a4cdc5b678b54da3cb7 Mon Sep 17 00:00:00 2001 From: Rares <6453351+raress96@users.noreply.github.com> Date: Wed, 20 Nov 2024 16:27:42 +0200 Subject: [PATCH 20/20] Remove deploy token manager support for its hub for stacks. --- ampd/src/stacks/its_verifier.rs | 172 -------------------------------- 1 file changed, 172 deletions(-) diff --git a/ampd/src/stacks/its_verifier.rs b/ampd/src/stacks/its_verifier.rs index 01a282ecc..90b7fea77 100644 --- a/ampd/src/stacks/its_verifier.rs +++ b/ampd/src/stacks/its_verifier.rs @@ -12,7 +12,6 @@ use crate::types::Hash; const MESSAGE_TYPE_INTERCHAIN_TRANSFER: u128 = 0; const MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN: u128 = 1; -const MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER: u128 = 2; const MESSAGE_TYPE_SEND_TO_HUB: u128 = 3; const VERIFY_INTERCHAIN_TOKEN: &str = "verify-interchain-token"; @@ -54,7 +53,6 @@ pub fn get_its_hub_payload_hash( MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN => { get_its_deploy_interchain_token_abi_payload(payload) } - MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER => get_its_deploy_token_manager_payload(payload), _ => { return Err(Error::InvalidCall.into()); } @@ -296,64 +294,6 @@ fn get_its_deploy_interchain_token_abi_payload( Ok(abi_payload) } -fn get_its_deploy_token_manager_payload( - payload: Vec, -) -> Result, Box> { - let tuple_type_signature = TupleTypeSignature::try_from(vec![ - ( - ClarityName::from("token-id"), - TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( - 32u32, - )?)), - ), - ( - ClarityName::from("token-manager-type"), - TypeSignature::UIntType, - ), - ( - ClarityName::from("params"), - TypeSignature::SequenceType(SequenceSubtype::BufferType(BufferLength::try_from( - 62_000u32, - )?)), - ), - ])?; - - let mut original_value = Value::try_deserialize_bytes( - &payload, - &TypeSignature::TupleType(tuple_type_signature), - true, - )? - .expect_tuple()?; - - let abi_payload = encode(&[ - Token::Uint(MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER.into()), - Token::FixedBytes( - original_value - .data_map - .remove("token-id") - .ok_or(Error::InvalidCall)? - .expect_buff(32)?, - ), - Token::Uint( - original_value - .data_map - .remove("token-manager-type") - .ok_or(Error::InvalidCall)? - .expect_u128()? - .into(), - ), - Token::Bytes( - original_value - .data_map - .remove("params") - .ok_or(Error::InvalidCall)? - .expect_buff(62_000)?, - ), - ]); - - Ok(abi_payload) -} - pub async fn its_verify_contract_code( event: &TransactionEvents, http_client: &Client, @@ -572,52 +512,6 @@ mod tests { ); } - #[async_test] - async fn should_not_verify_its_hub_deploy_token_manager_invalid_payload_hash() { - let (source_chain, gateway_address, its_address, tx, mut msg) = - get_matching_its_hub_deploy_token_manager_msg_and_tx(); - - msg.payload_hash = "0xaa38573718f5cd6d7e5a90adcdebd28b097f99574ad6febffea9a40adb17f4aa" - .parse() - .unwrap(); - - assert_eq!( - verify_message( - &source_chain, - &gateway_address, - &its_address, - &tx, - &msg, - &Client::faux(), - &"native_interchain_token_code".to_string(), - &"token_manager_code".to_string() - ) - .await, - Vote::NotFound - ); - } - - #[async_test] - async fn should_verify_msg_its_hub_deploy_token_manager() { - let (source_chain, gateway_address, its_address, tx, msg) = - get_matching_its_hub_deploy_token_manager_msg_and_tx(); - - assert_eq!( - verify_message( - &source_chain, - &gateway_address, - &its_address, - &tx, - &msg, - &Client::faux(), - &"native_interchain_token_code".to_string(), - &"token_manager_code".to_string() - ) - .await, - Vote::SucceededOnChain - ); - } - #[async_test] async fn should_not_verify_msg_its_verify_interchain_token_invalid_contract_code() { let mut client = Client::faux(); @@ -870,72 +764,6 @@ mod tests { ) } - fn get_matching_its_hub_deploy_token_manager_msg_and_tx( - ) -> (ChainName, String, String, Transaction, Message) { - let source_chain = "stacks"; - let gateway_address = "SP2N959SER36FZ5QT1CX9BR63W3E8X35WQCMBYYWC.axelar-gateway"; - let its_address = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.interchain-token-service"; - - let message_id = HexTxHashAndEventIndex::new(Hash::random(), 1u64); - - let msg = Message { - message_id: message_id.clone(), - source_address: its_address.to_string(), - destination_chain: "axelar".parse().unwrap(), - destination_address: "cosmwasm".to_string(), - payload_hash: "0x617076bb0067f463de653c1d16e4037f2cfb59c383820351e5b8bd2ca9d50948" - .parse() - .unwrap(), - }; - - let wrong_event = TransactionEvents { - event_index: 0, - tx_id: message_id.tx_hash_as_hex().to_string(), - contract_log: None, - }; - - /* - payload is: - { - type: u3, - destination-chain: "ethereum", - payload: { - type: u2, - token-id: 0xc99a1f0a4b46456129d86b37f580af16fea20eeaf7e73628547c10f6799b90b0, - token-manager-type: u2, - params: 0x00 - } - } - */ - let event = TransactionEvents { - event_index: 1, - tx_id: message_id.tx_hash_as_hex().to_string(), - contract_log: Some(ContractLog { - contract_id: gateway_address.to_string(), - topic: "print".to_string(), - value: ContractLogValue { - hex: "0x0c000000061164657374696e6174696f6e2d636861696e0d000000066178656c61721c64657374696e6174696f6e2d636f6e74726163742d616464726573730d00000008636f736d7761736d077061796c6f616402000000c10c000000031164657374696e6174696f6e2d636861696e0d00000008657468657265756d077061796c6f6164020000007a0c0000000406706172616d7302000000010008746f6b656e2d69640200000020c99a1f0a4b46456129d86b37f580af16fea20eeaf7e73628547c10f6799b90b012746f6b656e2d6d616e616765722d74797065010000000000000000000000000000000204747970650100000000000000000000000000000002047479706501000000000000000000000000000000030c7061796c6f61642d6861736802000000209ce89d392d43333d269dd9f234e765ded79db1ba895e8b2e3d6d8f936cae57320673656e646572061a6d78de7b0625dfbfc16c3a8a5735f6dc3dc3f2ce18696e746572636861696e2d746f6b656e2d7365727669636504747970650d0000000d636f6e74726163742d63616c6c".to_string(), - } - }), - }; - - let transaction = Transaction { - tx_id: message_id.tx_hash.into(), - nonce: 1, - sender_address: "whatever".to_string(), - tx_status: "success".to_string(), - events: vec![wrong_event, event], - }; - - ( - source_chain.parse().unwrap(), - gateway_address.to_string(), - its_address.to_string(), - transaction, - msg, - ) - } - fn get_matching_its_verify_interchain_token_msg_and_tx( ) -> (ChainName, String, String, Transaction, Message) { let source_chain = "stacks";